From bbeb0e2a66c7860a83d02adcfedc3c9390356dfc Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 16:58:39 -0700 Subject: [PATCH 01/11] Update all npm dependencies (2026-05-15) (#1032) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ⬆️ Update all npm dependencies (2026-05-15) * fix(tests): update Dark mode toggle queries from role="checkbox" to role="switch" for MUI upgrade updated snapshots * fix(lint): remove unnecessary type assertions in useSubtestRegressionCount and ThemeSlice --------- Co-authored-by: depfu[bot] <23717796+depfu[bot]@users.noreply.github.com> Co-authored-by: Carla Severe --- package-lock.json | 2772 +++++++++-------- package.json | 36 +- src/__tests__/App.test.tsx | 2 +- .../OverTimeResultsView.test.tsx.snap | 18 +- .../__snapshots__/ResultsTable.test.tsx.snap | 80 +- .../__snapshots__/ResultsView.test.tsx.snap | 22 +- .../__snapshots__/Retrigger.test.tsx.snap | 4 +- .../SubtestsResultsView.test.tsx.snap | 133 +- .../Search/SearchResultsList.test.tsx | 2 +- .../CompareOverTime.test.tsx.snap | 40 +- .../CompareWithBase.test.tsx.snap | 75 +- .../SearchContainer.test.tsx.snap | 4 +- .../SearchResultsList.test.tsx.snap | 35 +- .../__snapshots__/SearchView.test.tsx.snap | 23 +- src/__tests__/__snapshots__/App.test.tsx.snap | 3 + .../CompareResults/TableContent.tsx | 2 +- src/components/Search/SearchContainer.tsx | 3 +- .../Shared/ToggleDarkModeButton.tsx | 3 +- src/hooks/useSubtestRegressionCount.ts | 8 +- src/reducers/ThemeSlice.ts | 6 +- 20 files changed, 1766 insertions(+), 1505 deletions(-) diff --git a/package-lock.json b/package-lock.json index 61a23c948..2a4d34f5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "0.9.0", "dependencies": { "@emotion/styled": "^11.14.1", - "@mui/icons-material": "^7.3.10", - "@mui/material": "^7.3.10", - "@mui/system": "^7.3.10", + "@mui/icons-material": "^7.3.11", + "@mui/material": "^7.3.11", + "@mui/system": "^7.3.11", "@reduxjs/toolkit": "^2.11.2", "assert": "^2.1.0", "buffer": "^6.0.3", @@ -26,12 +26,12 @@ "moize": "^6.1.7", "notistack": "^3.0.2", "process": "^0.11.10", - "react": "^19.2.5", + "react": "^19.2.6", "react-chartjs-2": "^5.3.1", - "react-dom": "^19.2.5", + "react-dom": "^19.2.6", "react-redux": "^9.2.0", - "react-router": "^7.14.1", - "react-virtuoso": "^4.18.4", + "react-router": "^7.15.0", + "react-virtuoso": "^4.18.6", "stream-browserify": "^3.0.0", "taskcluster-client-web": "^87.1.3", "taskcluster-lib-scopes": "^11.0.0", @@ -41,14 +41,14 @@ "devDependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-runtime": "^7.29.0", - "@babel/preset-env": "^7.29.2", + "@babel/preset-env": "^7.29.5", "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", "@babel/runtime": "^7.29.2", "@eslint/js": "^9.39.4", "@fetch-mock/jest": "^0.2.20", - "@jest/types": "^30.3.0", - "@swc/core": "^1.15.21", + "@jest/types": "^30.4.1", + "@swc/core": "^1.15.30", "@swc/jest": "^0.2.39", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", @@ -61,7 +61,7 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "babel-loader": "^10.1.1", - "babel-preset-jest": "^30.3.0", + "babel-preset-jest": "^30.4.0", "case-sensitive-paths-webpack-plugin": "^2.4.0", "copy-webpack-plugin": "^13.0.1", "core-js": "^3.49.0", @@ -70,17 +70,17 @@ "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-jest": "^29.15.1", + "eslint-plugin-jest": "^29.15.2", "eslint-plugin-jest-dom": "^5.5.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-testing-library": "^7.16.2", - "globals": "^17.5.0", + "globals": "^17.6.0", "history": "^5.3.0", - "html-webpack-plugin": "^5.6.6", - "jest": "^30.3.0", + "html-webpack-plugin": "^5.6.7", + "jest": "^30.4.2", "jest-axe": "^10.0.0", - "jest-environment-jsdom": "^30.3.0", - "jest-resolve": "^30.3.0", + "jest-environment-jsdom": "^30.4.1", + "jest-resolve": "^30.4.1", "npm-run-all2": "^8.0.4", "prettier": "3.7.4", "react-app-polyfill": "^3.0.0", @@ -91,7 +91,7 @@ "ts-loader": "^9.5.7", "ts-node": "^10.9.2", "typescript": "^5.9.3", - "typescript-eslint": "^8.57.2", + "typescript-eslint": "^8.59.1", "webpack": "^5.106.2", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.3", @@ -141,9 +141,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -557,6 +557,22 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz", + "integrity": "sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", @@ -1267,9 +1283,9 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", - "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", "dev": true, "dependencies": { "@babel/helper-module-transforms": "^7.28.6", @@ -1803,18 +1819,19 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", - "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", + "version": "7.29.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.5.tgz", + "integrity": "sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.29.0", + "@babel/compat-data": "^7.29.3", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", @@ -1846,7 +1863,7 @@ "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-systemjs": "^7.29.4", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", @@ -2739,16 +2756,16 @@ } }, "node_modules/@jest/console": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", - "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", + "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", "dev": true, "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -2756,37 +2773,38 @@ } }, "node_modules/@jest/core": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", - "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", + "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", "dev": true, "dependencies": { - "@jest/console": "30.3.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/pattern": "30.4.0", + "@jest/reporters": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.3.0", - "jest-config": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-resolve-dependencies": "30.3.0", - "jest-runner": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "jest-watcher": "30.3.0", - "pretty-format": "30.3.0", + "jest-changed-files": "30.4.1", + "jest-config": "30.4.2", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-resolve-dependencies": "30.4.2", + "jest-runner": "30.4.2", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "jest-watcher": "30.4.1", + "pretty-format": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -2801,10 +2819,23 @@ } } }, + "node_modules/@jest/core/node_modules/@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/core/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -2814,9 +2845,9 @@ } }, "node_modules/@jest/core/node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "node_modules/@jest/core/node_modules/ansi-styles": { @@ -2831,15 +2862,25 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@jest/core/node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/core/node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -2894,42 +2935,42 @@ "dev": true }, "node_modules/@jest/diff-sequences": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", - "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", "dev": true, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/environment": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", - "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", + "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", "dev": true, "dependencies": { - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "30.3.0" + "jest-mock": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/environment-jsdom-abstract": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.3.0.tgz", - "integrity": "sha512-0hNFs5N6We3DMCwobzI0ydhkY10sT1tZSC0AAiy+0g2Dt/qEWgrcV5BrMxPczhe41cxW4qm6X+jqZaUdpZIajA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.4.1.tgz", + "integrity": "sha512-dSlKrqug3siYNHVnjwIldShY12wAH3spwRltO/+8VOjg0X+xEq7vOs3DbBs4LRKsu7OH+NUb9kuZUNBF9Ho3TA==", "dev": true, "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/jsdom": "^21.1.7", "@types/node": "*", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "jest-mock": "30.4.1", + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -2945,22 +2986,22 @@ } }, "node_modules/@jest/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", "dev": true, "dependencies": { - "expect": "30.3.0", - "jest-snapshot": "30.3.0" + "expect": "30.4.1", + "jest-snapshot": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", - "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", "dev": true, "dependencies": { "@jest/get-type": "30.1.0" @@ -2970,17 +3011,17 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", - "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", + "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", "dev": true, "dependencies": { - "@jest/types": "30.3.0", - "@sinonjs/fake-timers": "^15.0.0", + "@jest/types": "30.4.1", + "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -2996,15 +3037,15 @@ } }, "node_modules/@jest/globals": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", - "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", + "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", "dev": true, "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/types": "30.3.0", - "jest-mock": "30.3.0" + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/types": "30.4.1", + "jest-mock": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -3025,16 +3066,16 @@ } }, "node_modules/@jest/reporters": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", - "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", + "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -3047,9 +3088,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -3067,9 +3108,9 @@ } }, "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "dependencies": { "balanced-match": "^1.0.0" @@ -3125,12 +3166,12 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", - "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", + "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", "dev": true, "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -3154,13 +3195,13 @@ } }, "node_modules/@jest/test-result": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", - "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", + "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", "dev": true, "dependencies": { - "@jest/console": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/types": "30.4.1", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -3169,14 +3210,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", - "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", + "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", "dev": true, "dependencies": { - "@jest/test-result": "30.3.0", + "@jest/test-result": "30.4.1", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", + "jest-haste-map": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -3184,22 +3225,22 @@ } }, "node_modules/@jest/transform": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", - "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", + "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", "dev": true, "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", + "jest-haste-map": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -3215,14 +3256,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@jest/transform/node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/types": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", - "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", "dev": true, "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -3233,10 +3283,23 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/types/node_modules/@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/types/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -3246,11 +3309,20 @@ } }, "node_modules/@jest/types/node_modules/@sinclair/typebox": { - "version": "0.34.38", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", - "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, + "node_modules/@jest/types/node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -3372,18 +3444,18 @@ "license": "MIT" }, "node_modules/@mui/core-downloads-tracker": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.10.tgz", - "integrity": "sha512-vrOpWRmPJSuwLo23J62wggEm/jvGdzqctej+UOCtgDUz6nZJQuj3ByPccVyaa7eQmwAzUwKN56FQPMKkqbj1GA==", + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.11.tgz", + "integrity": "sha512-a7I/b/nBTdXYz2cOSlEmkQ9WWE1x8FHpqMhFPp+Y1VPFxcOw91G5ELOHARQAGSPy5V+UCgJua6K/1x70bAtQPw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui-org" } }, "node_modules/@mui/icons-material": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.10.tgz", - "integrity": "sha512-Au0ma4NSKGKNiimukj8UT/W1x2Qx6Qwn2RvFGykiSqVLYBNlIOPbjnIMvrwLGLu89EEpTVdu/ys/OduZR+tWqw==", + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.11.tgz", + "integrity": "sha512-+hz5ilwHZ3djd5es3sCErLioqe/NhZcYTsV/TNXZAMdJdb23F4xzJjqnnZdnurc3S1+ietcssRNqieOhPQLZ7Q==", "dependencies": { "@babel/runtime": "^7.28.6" }, @@ -3395,7 +3467,7 @@ "url": "https://opencollective.com/mui-org" }, "peerDependencies": { - "@mui/material": "^7.3.10", + "@mui/material": "^7.3.11", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0" }, @@ -3406,15 +3478,15 @@ } }, "node_modules/@mui/material": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.10.tgz", - "integrity": "sha512-cHvGOk2ZEfbQt3LnGe0ZKd/ETs9gsUpkW66DCO+GSjMZhpdKU4XsuIr7zJ/B/2XaN8ihxuzHfYAR4zPtCN4RYg==", + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.11.tgz", + "integrity": "sha512-yq8bPc3LxOwKRWpcjRgDkYFmpM6aKlARfESTmOQcvLYFeJwtHte2tw6hJDrb8sk8wcvpDprHEHVaoUU0MslIkw==", "dependencies": { "@babel/runtime": "^7.28.6", - "@mui/core-downloads-tracker": "^7.3.10", - "@mui/system": "^7.3.10", + "@mui/core-downloads-tracker": "^7.3.11", + "@mui/system": "^7.3.11", "@mui/types": "^7.4.12", - "@mui/utils": "^7.3.10", + "@mui/utils": "^7.3.11", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", @@ -3433,7 +3505,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^7.3.10", + "@mui/material-pigment-css": "^7.3.11", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3468,12 +3540,12 @@ "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==" }, "node_modules/@mui/private-theming": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.10.tgz", - "integrity": "sha512-j3EZN+zOctxUISvJSmsEPo5o2F8zse4l5vRkBY+ps6UtnL6J7o14kUaI4w7gwo73id9e3cDNMVQK/9BVaMHVBw==", + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.11.tgz", + "integrity": "sha512-9B+YKms0fRHbNrqp9tOT/DNbNnU5gyvJ1o3qAGXfq8GmZcbJnE3At9x07Zr/o0pkhzg4aDdwXVqe4+AcgtOCPA==", "dependencies": { "@babel/runtime": "^7.28.6", - "@mui/utils": "^7.3.10", + "@mui/utils": "^7.3.11", "prop-types": "^15.8.1" }, "engines": { @@ -3527,15 +3599,15 @@ } }, "node_modules/@mui/system": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.10.tgz", - "integrity": "sha512-/sfPpdpJaQn7BSF+avjIdHSYmxHp0UOBYNxSG9QGKfMOD6sLANCpRPCnanq1Pe0lFf0NHkO2iUk0TNzdWC1USQ==", + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.11.tgz", + "integrity": "sha512-7izwGWdNawAKpBKcRlx7f2gFnAAjmASBWvMcyX4YYEeLOFsbfGRbUYGInvnAcUeql3rPxI7F9Ft4oY2OLRz44g==", "dependencies": { "@babel/runtime": "^7.28.6", - "@mui/private-theming": "^7.3.10", + "@mui/private-theming": "^7.3.11", "@mui/styled-engine": "^7.3.10", "@mui/types": "^7.4.12", - "@mui/utils": "^7.3.10", + "@mui/utils": "^7.3.11", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1" @@ -3590,9 +3662,9 @@ } }, "node_modules/@mui/utils": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.10.tgz", - "integrity": "sha512-7y2eIfy0h7JPz+Yy4pS+wgV68d46PuuxDqKBN4Q8VlPQSsCAGwroMCV6xWyc7g9dvEp8ZNFsknc59GHWO+r6Ow==", + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.11.tgz", + "integrity": "sha512-XTjGnifwteg71/ij+0e7Y7d+hwyntMYP5wPoA/g2drdGH+Flkvjwy0OfrVpKBbaOvofq4zU/LIyUZyKgmWu18g==", "dependencies": { "@babel/runtime": "^7.28.6", "@mui/types": "^7.4.12", @@ -3627,9 +3699,9 @@ } }, "node_modules/@mui/utils/node_modules/react-is": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", - "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==" + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==" }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.11", @@ -3885,9 +3957,9 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", - "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.1" @@ -3906,14 +3978,14 @@ "license": "MIT" }, "node_modules/@swc/core": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.21.tgz", - "integrity": "sha512-fkk7NJcBscrR3/F8jiqlMptRHP650NxqDnspBMrRe5d8xOoCy9MLL5kOBLFXjFLfMo3KQQHhk+/jUULOMlR1uQ==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.30.tgz", + "integrity": "sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==", "dev": true, "hasInstallScript": true, "dependencies": { "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.25" + "@swc/types": "^0.1.26" }, "engines": { "node": ">=10" @@ -3923,18 +3995,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.21", - "@swc/core-darwin-x64": "1.15.21", - "@swc/core-linux-arm-gnueabihf": "1.15.21", - "@swc/core-linux-arm64-gnu": "1.15.21", - "@swc/core-linux-arm64-musl": "1.15.21", - "@swc/core-linux-ppc64-gnu": "1.15.21", - "@swc/core-linux-s390x-gnu": "1.15.21", - "@swc/core-linux-x64-gnu": "1.15.21", - "@swc/core-linux-x64-musl": "1.15.21", - "@swc/core-win32-arm64-msvc": "1.15.21", - "@swc/core-win32-ia32-msvc": "1.15.21", - "@swc/core-win32-x64-msvc": "1.15.21" + "@swc/core-darwin-arm64": "1.15.30", + "@swc/core-darwin-x64": "1.15.30", + "@swc/core-linux-arm-gnueabihf": "1.15.30", + "@swc/core-linux-arm64-gnu": "1.15.30", + "@swc/core-linux-arm64-musl": "1.15.30", + "@swc/core-linux-ppc64-gnu": "1.15.30", + "@swc/core-linux-s390x-gnu": "1.15.30", + "@swc/core-linux-x64-gnu": "1.15.30", + "@swc/core-linux-x64-musl": "1.15.30", + "@swc/core-win32-arm64-msvc": "1.15.30", + "@swc/core-win32-ia32-msvc": "1.15.30", + "@swc/core-win32-x64-msvc": "1.15.30" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -3946,9 +4018,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.21.tgz", - "integrity": "sha512-SA8SFg9dp0qKRH8goWsax6bptFE2EdmPf2YRAQW9WoHGf3XKM1bX0nd5UdwxmC5hXsBUZAYf7xSciCler6/oyA==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.30.tgz", + "integrity": "sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==", "cpu": [ "arm64" ], @@ -3962,9 +4034,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.21.tgz", - "integrity": "sha512-//fOVntgowz9+V90lVsNCtyyrtbHp3jWH6Rch7MXHXbcvbLmbCTmssl5DeedUWLLGiAAW1wksBdqdGYOTjaNLw==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.30.tgz", + "integrity": "sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==", "cpu": [ "x64" ], @@ -3978,9 +4050,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.21.tgz", - "integrity": "sha512-meNI4Sh6h9h8DvIfEc0l5URabYMSuNvyisLmG6vnoYAS43s8ON3NJR8sDHvdP7NJTrLe0q/x2XCn6yL/BeHcZg==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.30.tgz", + "integrity": "sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==", "cpu": [ "arm" ], @@ -3994,9 +4066,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.21.tgz", - "integrity": "sha512-QrXlNQnHeXqU2EzLlnsPoWEh8/GtNJLvfMiPsDhk+ht6Xv8+vhvZ5YZ/BokNWSIZiWPKLAqR0M7T92YF5tmD3g==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.30.tgz", + "integrity": "sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==", "cpu": [ "arm64" ], @@ -4010,9 +4082,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.21.tgz", - "integrity": "sha512-8/yGCMO333ultDaMQivE5CjO6oXDPeeg1IV4sphojPkb0Pv0i6zvcRIkgp60xDB+UxLr6VgHgt+BBgqS959E9g==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.30.tgz", + "integrity": "sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==", "cpu": [ "arm64" ], @@ -4026,9 +4098,9 @@ } }, "node_modules/@swc/core-linux-ppc64-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.21.tgz", - "integrity": "sha512-ucW0HzPx0s1dgRvcvuLSPSA/2Kk/VYTv9st8qe1Kc22Gu0Q0rH9+6TcBTmMuNIp0Xs4BPr1uBttmbO1wEGI49Q==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.30.tgz", + "integrity": "sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==", "cpu": [ "ppc64" ], @@ -4042,9 +4114,9 @@ } }, "node_modules/@swc/core-linux-s390x-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.21.tgz", - "integrity": "sha512-ulTnOGc5I7YRObE/9NreAhQg94QkiR5qNhhcUZ1iFAYjzg/JGAi1ch+s/Ixe61pMIr8bfVrF0NOaB0f8wjaAfA==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.30.tgz", + "integrity": "sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==", "cpu": [ "s390x" ], @@ -4058,9 +4130,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.21.tgz", - "integrity": "sha512-D0RokxtM+cPvSqJIKR6uja4hbD+scI9ezo95mBhfSyLUs9wnPPl26sLp1ZPR/EXRdYm3F3S6RUtVi+8QXhT24Q==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.30.tgz", + "integrity": "sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==", "cpu": [ "x64" ], @@ -4074,9 +4146,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.21.tgz", - "integrity": "sha512-nER8u7VeRfmU6fMDzl1NQAbbB/G7O2avmvCOwIul1uGkZ2/acbPH+DCL9h5+0yd/coNcxMBTL6NGepIew+7C2w==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.30.tgz", + "integrity": "sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==", "cpu": [ "x64" ], @@ -4090,9 +4162,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.21.tgz", - "integrity": "sha512-+/AgNBnjYugUA8C0Do4YzymgvnGbztv7j8HKSQLvR/DQgZPoXQ2B3PqB2mTtGh/X5DhlJWiqnunN35JUgWcAeQ==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.30.tgz", + "integrity": "sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==", "cpu": [ "arm64" ], @@ -4106,9 +4178,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.21.tgz", - "integrity": "sha512-IkSZj8PX/N4HcaFhMQtzmkV8YSnuNoJ0E6OvMwFiOfejPhiKXvl7CdDsn1f4/emYEIDO3fpgZW9DTaCRMDxaDA==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.30.tgz", + "integrity": "sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==", "cpu": [ "ia32" ], @@ -4122,9 +4194,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.21.tgz", - "integrity": "sha512-zUyWso7OOENB6e1N1hNuNn8vbvLsTdKQ5WKLgt/JcBNfJhKy/6jmBmqI3GXk/MyvQKd5SLvP7A0F36p7TeDqvw==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.30.tgz", + "integrity": "sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==", "cpu": [ "x64" ], @@ -4162,9 +4234,9 @@ } }, "node_modules/@swc/types": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", - "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", "dev": true, "dependencies": { "@swc/counter": "^0.1.3" @@ -4814,19 +4886,19 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", - "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/type-utils": "8.57.2", - "@typescript-eslint/utils": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4836,9 +4908,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.57.2", + "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -4852,15 +4924,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", - "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" }, "engines": { @@ -4872,17 +4944,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", - "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", "dev": true, "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.2", - "@typescript-eslint/types": "^8.57.2", + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", "debug": "^4.4.3" }, "engines": { @@ -4893,17 +4965,17 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", - "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2" + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4914,9 +4986,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", - "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4926,20 +4998,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", - "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/utils": "8.57.2", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4950,13 +5022,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", - "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4967,20 +5039,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", - "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", "dev": true, "dependencies": { - "@typescript-eslint/project-service": "8.57.2", - "@typescript-eslint/tsconfig-utils": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4990,7 +5062,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { @@ -5003,9 +5075,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "dependencies": { "balanced-match": "^4.0.2" @@ -5015,12 +5087,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -5030,15 +5102,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", - "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2" + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5049,16 +5121,16 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", - "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -6020,15 +6092,15 @@ } }, "node_modules/babel-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", - "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", + "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", "dev": true, "dependencies": { - "@jest/transform": "30.3.0", + "@jest/transform": "30.4.1", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.3.0", + "babel-preset-jest": "30.4.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -6131,9 +6203,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", - "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", + "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", "dev": true, "dependencies": { "@types/babel__core": "^7.20.5" @@ -6231,12 +6303,12 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", - "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", + "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", "dev": true, "dependencies": { - "babel-plugin-jest-hoist": "30.3.0", + "babel-plugin-jest-hoist": "30.4.0", "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { @@ -8336,9 +8408,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "29.15.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.1.tgz", - "integrity": "sha512-6BjyErCQauz3zfJvzLw/kAez2lf4LEpbHLvWBfEcG4EI0ZiRSwjoH2uZulMouU8kRkBH+S0rhqn11IhTvxKgKw==", + "version": "29.15.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.2.tgz", + "integrity": "sha512-kEN4r9RZl1xcsb4arGq89LrcVdOUFII/JSCwtTPJyv16mDwmPrcuEQwpxqZHeINvcsd7oK5O/rhdGlxFRaZwvQ==", "dev": true, "dependencies": { "@typescript-eslint/utils": "^8.0.0" @@ -8736,17 +8808,17 @@ } }, "node_modules/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", "dev": true, "dependencies": { - "@jest/expect-utils": "30.3.0", + "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -9332,9 +9404,9 @@ "dev": true }, "node_modules/globals": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", - "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", "dev": true, "engines": { "node": ">=18" @@ -9664,9 +9736,9 @@ } }, "node_modules/html-webpack-plugin": { - "version": "5.6.6", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz", - "integrity": "sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw==", + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.7.tgz", + "integrity": "sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw==", "dev": true, "dependencies": { "@types/html-minifier-terser": "^6.0.0", @@ -10694,15 +10766,15 @@ } }, "node_modules/jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", - "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", + "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", "dev": true, "dependencies": { - "@jest/core": "30.3.0", - "@jest/types": "30.3.0", + "@jest/core": "30.4.2", + "@jest/types": "30.4.1", "import-local": "^3.2.0", - "jest-cli": "30.3.0" + "jest-cli": "30.4.2" }, "bin": { "jest": "bin/jest.js" @@ -10762,13 +10834,13 @@ } }, "node_modules/jest-changed-files": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", - "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", + "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", "dev": true, "dependencies": { "execa": "^5.1.1", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "p-limit": "^3.1.0" }, "engines": { @@ -10776,28 +10848,28 @@ } }, "node_modules/jest-circus": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", - "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", + "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", "dev": true, "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", + "jest-each": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "p-limit": "^3.1.0", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -10807,9 +10879,9 @@ } }, "node_modules/jest-circus/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -10819,9 +10891,9 @@ } }, "node_modules/jest-circus/node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "node_modules/jest-circus/node_modules/ansi-styles": { @@ -10837,34 +10909,35 @@ } }, "node_modules/jest-circus/node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-cli": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", - "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", + "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", "dev": true, "dependencies": { - "@jest/core": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/core": "30.4.2", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-config": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "yargs": "^17.7.2" }, "bin": { @@ -10883,32 +10956,32 @@ } }, "node_modules/jest-config": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", - "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", + "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", "dev": true, "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.3.0", - "@jest/types": "30.3.0", - "babel-jest": "30.3.0", + "@jest/pattern": "30.4.0", + "@jest/test-sequencer": "30.4.1", + "@jest/types": "30.4.1", + "babel-jest": "30.4.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-circus": "30.3.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-runner": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-circus": "30.4.2", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-runner": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "parse-json": "^5.2.0", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -10932,10 +11005,23 @@ } } }, + "node_modules/jest-config/node_modules/@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-config/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -10945,9 +11031,9 @@ } }, "node_modules/jest-config/node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "node_modules/jest-config/node_modules/ansi-styles": { @@ -10963,9 +11049,9 @@ } }, "node_modules/jest-config/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "dependencies": { "balanced-match": "^1.0.0" @@ -10992,6 +11078,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jest-config/node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-config/node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -11008,14 +11103,15 @@ } }, "node_modules/jest-config/node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -11038,9 +11134,9 @@ } }, "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", + "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", "dev": true, "dependencies": { "detect-newline": "^3.1.0" @@ -11050,25 +11146,25 @@ } }, "node_modules/jest-each": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", - "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", + "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", "dev": true, "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "chalk": "^4.1.2", - "jest-util": "30.3.0", - "pretty-format": "30.3.0" + "jest-util": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-each/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -11078,9 +11174,9 @@ } }, "node_modules/jest-each/node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "node_modules/jest-each/node_modules/ansi-styles": { @@ -11096,27 +11192,28 @@ } }, "node_modules/jest-each/node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-jsdom": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.3.0.tgz", - "integrity": "sha512-RLEOJy6ip1lpw0yqJ8tB3i88FC7VBz7i00Zvl2qF71IdxjS98gC9/0SPWYIBVXHm5hgCYK0PAlSlnHGGy9RoMg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.4.1.tgz", + "integrity": "sha512-o3nfaN4zej7qgk2X0j8Jhq/S9nAVKs2xK3QeQxeHVvpkEPxaA1yxDGydR+iVI7zPy7Cp62Aq2h3Ja46QvfWHGA==", "dev": true, "dependencies": { - "@jest/environment": "30.3.0", - "@jest/environment-jsdom-abstract": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/environment-jsdom-abstract": "30.4.1", "jsdom": "^26.1.0" }, "engines": { @@ -11132,18 +11229,18 @@ } }, "node_modules/jest-environment-node": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", - "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", + "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", "dev": true, "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0" + "jest-mock": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -11160,19 +11257,19 @@ } }, "node_modules/jest-haste-map": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", - "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", + "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", "dev": true, "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", "picomatch": "^4.0.3", "walker": "^1.0.8" }, @@ -11183,6 +11280,15 @@ "fsevents": "^2.3.3" } }, + "node_modules/jest-haste-map/node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-haste-map/node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", @@ -11196,22 +11302,22 @@ } }, "node_modules/jest-leak-detector": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", - "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", + "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", "dev": true, "dependencies": { "@jest/get-type": "30.1.0", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-leak-detector/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -11221,9 +11327,9 @@ } }, "node_modules/jest-leak-detector/node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "node_modules/jest-leak-detector/node_modules/ansi-styles": { @@ -11239,38 +11345,39 @@ } }, "node_modules/jest-leak-detector/node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", - "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", "dev": true, "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.3.0", - "pretty-format": "30.3.0" + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -11280,9 +11387,9 @@ } }, "node_modules/jest-matcher-utils/node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "node_modules/jest-matcher-utils/node_modules/ansi-styles": { @@ -11298,47 +11405,49 @@ } }, "node_modules/jest-matcher-utils/node_modules/jest-diff": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", - "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "dev": true, "dependencies": { - "@jest/diff-sequences": "30.3.0", + "@jest/diff-sequences": "30.4.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils/node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", - "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", "picomatch": "^4.0.3", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -11347,9 +11456,9 @@ } }, "node_modules/jest-message-util/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -11359,9 +11468,9 @@ } }, "node_modules/jest-message-util/node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "node_modules/jest-message-util/node_modules/ansi-styles": { @@ -11389,28 +11498,29 @@ } }, "node_modules/jest-message-util/node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-mock": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", - "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", "dev": true, "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-util": "30.3.0" + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -11444,17 +11554,17 @@ } }, "node_modules/jest-resolve": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", - "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", + "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", "dev": true, "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", + "jest-haste-map": "30.4.1", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -11463,44 +11573,53 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", - "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", + "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", "dev": true, "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.3.0" + "jest-regex-util": "30.4.0", + "jest-snapshot": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/jest-resolve-dependencies/node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-runner": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", - "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", + "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", "dev": true, "dependencies": { - "@jest/console": "30.3.0", - "@jest/environment": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/environment": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-leak-detector": "30.3.0", - "jest-message-util": "30.3.0", - "jest-resolve": "30.3.0", - "jest-runtime": "30.3.0", - "jest-util": "30.3.0", - "jest-watcher": "30.3.0", - "jest-worker": "30.3.0", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-haste-map": "30.4.1", + "jest-leak-detector": "30.4.1", + "jest-message-util": "30.4.1", + "jest-resolve": "30.4.1", + "jest-runtime": "30.4.2", + "jest-util": "30.4.1", + "jest-watcher": "30.4.1", + "jest-worker": "30.4.1", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -11509,31 +11628,31 @@ } }, "node_modules/jest-runtime": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", - "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", + "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", "dev": true, "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/globals": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/globals": "30.4.1", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -11542,9 +11661,9 @@ } }, "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "dependencies": { "balanced-match": "^1.0.0" @@ -11571,6 +11690,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jest-runtime/node_modules/jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/jest-runtime/node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -11587,9 +11715,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", - "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", + "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", "dev": true, "dependencies": { "@babel/core": "^7.27.4", @@ -11597,20 +11725,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.3.0", + "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/snapshot-utils": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.3.0", + "expect": "30.4.1", "graceful-fs": "^4.2.11", - "jest-diff": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "pretty-format": "30.3.0", + "jest-diff": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "pretty-format": "30.4.1", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -11619,9 +11747,9 @@ } }, "node_modules/jest-snapshot/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -11631,9 +11759,9 @@ } }, "node_modules/jest-snapshot/node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "node_modules/jest-snapshot/node_modules/ansi-styles": { @@ -11649,41 +11777,42 @@ } }, "node_modules/jest-snapshot/node_modules/jest-diff": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", - "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "dev": true, "dependencies": { - "@jest/diff-sequences": "30.3.0", + "@jest/diff-sequences": "30.4.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-snapshot/node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", - "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", "dev": true, "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -11707,26 +11836,26 @@ } }, "node_modules/jest-validate": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", - "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", + "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", "dev": true, "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate/node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "dependencies": { "@sinclair/typebox": "^0.34.0" @@ -11736,9 +11865,9 @@ } }, "node_modules/jest-validate/node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "node_modules/jest-validate/node_modules/ansi-styles": { @@ -11766,32 +11895,33 @@ } }, "node_modules/jest-validate/node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-watcher": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", - "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", + "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", "dev": true, "dependencies": { - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "string-length": "^4.0.2" }, "engines": { @@ -11799,14 +11929,14 @@ } }, "node_modules/jest-worker": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", - "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", + "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", "dev": true, "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -13665,9 +13795,9 @@ } }, "node_modules/react": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", - "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", "engines": { "node": ">=0.10.0" } @@ -13706,14 +13836,14 @@ } }, "node_modules/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.5" + "react": "^19.2.6" } }, "node_modules/react-is": { @@ -13723,6 +13853,20 @@ "dev": true, "license": "MIT" }, + "node_modules/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", + "dev": true + }, "node_modules/react-redux": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", @@ -13756,9 +13900,9 @@ } }, "node_modules/react-router": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.1.tgz", - "integrity": "sha512-5BCvFskyAAVumqhEKh/iPhLOIkfxcEUz8WqFIARCkMg8hZZzDYX9CtwxXA0e+qT8zAxmMC0x3Ckb9iMONwc5jg==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.0.tgz", + "integrity": "sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==", "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -13802,9 +13946,9 @@ } }, "node_modules/react-virtuoso": { - "version": "4.18.4", - "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.18.4.tgz", - "integrity": "sha512-DNM4Wy2tMA/J6ejMaDdqecOug31rOwgSRg4C/Dw6Iox4dJe9qwcx32M8HdhkE5uHEVVZh7h0koYwAsCSNdxGfQ==", + "version": "4.18.6", + "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.18.6.tgz", + "integrity": "sha512-CrT3P6HyjJMHZVWSste2bG2q5aWGlHfW2QuySZjiFwB2Qok/xsvgy+k8Z2jeDP8PP5KsBip7zNrl/F0QoxeyKw==", "peerDependencies": { "react": ">=16 || >=17 || >= 18 || >= 19", "react-dom": ">=16 || >=17 || >= 18 || >=19" @@ -15531,9 +15675,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "engines": { "node": ">=18.12" @@ -15913,15 +16057,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz", - "integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.1.tgz", + "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.57.2", - "@typescript-eslint/parser": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/utils": "8.57.2" + "@typescript-eslint/eslint-plugin": "8.59.1", + "@typescript-eslint/parser": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -15932,7 +16076,7 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/typestyle": { @@ -17144,9 +17288,9 @@ } }, "@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", "dev": true }, "@babel/core": { @@ -17438,6 +17582,16 @@ "@babel/helper-plugin-utils": "^7.27.1" } }, + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz", + "integrity": "sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + } + }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", @@ -17885,9 +18039,9 @@ } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", - "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", "dev": true, "requires": { "@babel/helper-module-transforms": "^7.28.6", @@ -18214,18 +18368,19 @@ } }, "@babel/preset-env": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", - "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", + "version": "7.29.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.5.tgz", + "integrity": "sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==", "dev": true, "requires": { - "@babel/compat-data": "^7.29.0", + "@babel/compat-data": "^7.29.3", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", @@ -18257,7 +18412,7 @@ "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.29.0", + "@babel/plugin-transform-modules-systemjs": "^7.29.4", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", @@ -18873,67 +19028,78 @@ "dev": true }, "@jest/console": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", - "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", + "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", "dev": true, "requires": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0" } }, "@jest/core": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", - "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", + "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", "dev": true, "requires": { - "@jest/console": "30.3.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/pattern": "30.4.0", + "@jest/reporters": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.3.0", - "jest-config": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-resolve-dependencies": "30.3.0", - "jest-runner": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "jest-watcher": "30.3.0", - "pretty-format": "30.3.0", + "jest-changed-files": "30.4.1", + "jest-config": "30.4.2", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-resolve-dependencies": "30.4.2", + "jest-runner": "30.4.2", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "jest-watcher": "30.4.1", + "pretty-format": "30.4.1", "slash": "^3.0.0" }, "dependencies": { + "@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + } + }, "@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "requires": { "@sinclair/typebox": "^0.34.0" } }, "@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "ansi-styles": { @@ -18942,15 +19108,22 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true }, + "jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true + }, "pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "requires": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" } } } @@ -18997,69 +19170,69 @@ } }, "@jest/diff-sequences": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", - "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", "dev": true }, "@jest/environment": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", - "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", + "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", "dev": true, "requires": { - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "30.3.0" + "jest-mock": "30.4.1" } }, "@jest/environment-jsdom-abstract": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.3.0.tgz", - "integrity": "sha512-0hNFs5N6We3DMCwobzI0ydhkY10sT1tZSC0AAiy+0g2Dt/qEWgrcV5BrMxPczhe41cxW4qm6X+jqZaUdpZIajA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.4.1.tgz", + "integrity": "sha512-dSlKrqug3siYNHVnjwIldShY12wAH3spwRltO/+8VOjg0X+xEq7vOs3DbBs4LRKsu7OH+NUb9kuZUNBF9Ho3TA==", "dev": true, "requires": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/jsdom": "^21.1.7", "@types/node": "*", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "jest-mock": "30.4.1", + "jest-util": "30.4.1" } }, "@jest/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", "dev": true, "requires": { - "expect": "30.3.0", - "jest-snapshot": "30.3.0" + "expect": "30.4.1", + "jest-snapshot": "30.4.1" } }, "@jest/expect-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", - "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", "dev": true, "requires": { "@jest/get-type": "30.1.0" } }, "@jest/fake-timers": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", - "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", + "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", "dev": true, "requires": { - "@jest/types": "30.3.0", - "@sinonjs/fake-timers": "^15.0.0", + "@jest/types": "30.4.1", + "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" } }, "@jest/get-type": { @@ -19069,15 +19242,15 @@ "dev": true }, "@jest/globals": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", - "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", + "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", "dev": true, "requires": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/types": "30.3.0", - "jest-mock": "30.3.0" + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/types": "30.4.1", + "jest-mock": "30.4.1" } }, "@jest/pattern": { @@ -19091,16 +19264,16 @@ } }, "@jest/reporters": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", - "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", + "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", "dev": true, "requires": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -19113,18 +19286,18 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "dependencies": { "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "requires": { "balanced-match": "^1.0.0" @@ -19165,12 +19338,12 @@ } }, "@jest/snapshot-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", - "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", + "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", "dev": true, "requires": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -19188,46 +19361,46 @@ } }, "@jest/test-result": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", - "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", + "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", "dev": true, "requires": { - "@jest/console": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/types": "30.4.1", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" } }, "@jest/test-sequencer": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", - "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", + "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", "dev": true, "requires": { - "@jest/test-result": "30.3.0", + "@jest/test-result": "30.4.1", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", + "jest-haste-map": "30.4.1", "slash": "^3.0.0" } }, "@jest/transform": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", - "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", + "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", "dev": true, "requires": { "@babel/core": "^7.27.4", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", + "jest-haste-map": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -19238,17 +19411,23 @@ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true + }, + "jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true } } }, "@jest/types": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", - "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", "dev": true, "requires": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -19256,19 +19435,35 @@ "chalk": "^4.1.2" }, "dependencies": { + "@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + } + }, "@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "requires": { "@sinclair/typebox": "^0.34.0" } }, "@sinclair/typebox": { - "version": "0.34.38", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", - "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "dev": true + }, + "jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", "dev": true } } @@ -19359,28 +19554,28 @@ "dev": true }, "@mui/core-downloads-tracker": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.10.tgz", - "integrity": "sha512-vrOpWRmPJSuwLo23J62wggEm/jvGdzqctej+UOCtgDUz6nZJQuj3ByPccVyaa7eQmwAzUwKN56FQPMKkqbj1GA==" + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.11.tgz", + "integrity": "sha512-a7I/b/nBTdXYz2cOSlEmkQ9WWE1x8FHpqMhFPp+Y1VPFxcOw91G5ELOHARQAGSPy5V+UCgJua6K/1x70bAtQPw==" }, "@mui/icons-material": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.10.tgz", - "integrity": "sha512-Au0ma4NSKGKNiimukj8UT/W1x2Qx6Qwn2RvFGykiSqVLYBNlIOPbjnIMvrwLGLu89EEpTVdu/ys/OduZR+tWqw==", + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.11.tgz", + "integrity": "sha512-+hz5ilwHZ3djd5es3sCErLioqe/NhZcYTsV/TNXZAMdJdb23F4xzJjqnnZdnurc3S1+ietcssRNqieOhPQLZ7Q==", "requires": { "@babel/runtime": "^7.28.6" } }, "@mui/material": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.10.tgz", - "integrity": "sha512-cHvGOk2ZEfbQt3LnGe0ZKd/ETs9gsUpkW66DCO+GSjMZhpdKU4XsuIr7zJ/B/2XaN8ihxuzHfYAR4zPtCN4RYg==", + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.11.tgz", + "integrity": "sha512-yq8bPc3LxOwKRWpcjRgDkYFmpM6aKlARfESTmOQcvLYFeJwtHte2tw6hJDrb8sk8wcvpDprHEHVaoUU0MslIkw==", "requires": { "@babel/runtime": "^7.28.6", - "@mui/core-downloads-tracker": "^7.3.10", - "@mui/system": "^7.3.10", + "@mui/core-downloads-tracker": "^7.3.11", + "@mui/system": "^7.3.11", "@mui/types": "^7.4.12", - "@mui/utils": "^7.3.10", + "@mui/utils": "^7.3.11", "@popperjs/core": "^2.11.8", "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", @@ -19403,12 +19598,12 @@ } }, "@mui/private-theming": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.10.tgz", - "integrity": "sha512-j3EZN+zOctxUISvJSmsEPo5o2F8zse4l5vRkBY+ps6UtnL6J7o14kUaI4w7gwo73id9e3cDNMVQK/9BVaMHVBw==", + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.11.tgz", + "integrity": "sha512-9B+YKms0fRHbNrqp9tOT/DNbNnU5gyvJ1o3qAGXfq8GmZcbJnE3At9x07Zr/o0pkhzg4aDdwXVqe4+AcgtOCPA==", "requires": { "@babel/runtime": "^7.28.6", - "@mui/utils": "^7.3.10", + "@mui/utils": "^7.3.11", "prop-types": "^15.8.1" } }, @@ -19426,15 +19621,15 @@ } }, "@mui/system": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.10.tgz", - "integrity": "sha512-/sfPpdpJaQn7BSF+avjIdHSYmxHp0UOBYNxSG9QGKfMOD6sLANCpRPCnanq1Pe0lFf0NHkO2iUk0TNzdWC1USQ==", + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.11.tgz", + "integrity": "sha512-7izwGWdNawAKpBKcRlx7f2gFnAAjmASBWvMcyX4YYEeLOFsbfGRbUYGInvnAcUeql3rPxI7F9Ft4oY2OLRz44g==", "requires": { "@babel/runtime": "^7.28.6", - "@mui/private-theming": "^7.3.10", + "@mui/private-theming": "^7.3.11", "@mui/styled-engine": "^7.3.10", "@mui/types": "^7.4.12", - "@mui/utils": "^7.3.10", + "@mui/utils": "^7.3.11", "clsx": "^2.1.1", "csstype": "^3.2.3", "prop-types": "^15.8.1" @@ -19456,9 +19651,9 @@ } }, "@mui/utils": { - "version": "7.3.10", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.10.tgz", - "integrity": "sha512-7y2eIfy0h7JPz+Yy4pS+wgV68d46PuuxDqKBN4Q8VlPQSsCAGwroMCV6xWyc7g9dvEp8ZNFsknc59GHWO+r6Ow==", + "version": "7.3.11", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.11.tgz", + "integrity": "sha512-XTjGnifwteg71/ij+0e7Y7d+hwyntMYP5wPoA/g2drdGH+Flkvjwy0OfrVpKBbaOvofq4zU/LIyUZyKgmWu18g==", "requires": { "@babel/runtime": "^7.28.6", "@mui/types": "^7.4.12", @@ -19474,9 +19669,9 @@ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" }, "react-is": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", - "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==" + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==" } } }, @@ -19696,9 +19891,9 @@ } }, "@sinonjs/fake-timers": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", - "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.1" @@ -19715,108 +19910,108 @@ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==" }, "@swc/core": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.21.tgz", - "integrity": "sha512-fkk7NJcBscrR3/F8jiqlMptRHP650NxqDnspBMrRe5d8xOoCy9MLL5kOBLFXjFLfMo3KQQHhk+/jUULOMlR1uQ==", - "dev": true, - "requires": { - "@swc/core-darwin-arm64": "1.15.21", - "@swc/core-darwin-x64": "1.15.21", - "@swc/core-linux-arm-gnueabihf": "1.15.21", - "@swc/core-linux-arm64-gnu": "1.15.21", - "@swc/core-linux-arm64-musl": "1.15.21", - "@swc/core-linux-ppc64-gnu": "1.15.21", - "@swc/core-linux-s390x-gnu": "1.15.21", - "@swc/core-linux-x64-gnu": "1.15.21", - "@swc/core-linux-x64-musl": "1.15.21", - "@swc/core-win32-arm64-msvc": "1.15.21", - "@swc/core-win32-ia32-msvc": "1.15.21", - "@swc/core-win32-x64-msvc": "1.15.21", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.30.tgz", + "integrity": "sha512-R8VQbQY1BZcbIF2p3gjlTCwAQzx1A194ugWfwld5y+WgVVWqVKm7eURGGOVbQVubgKWzidP2agomBbg96rZilQ==", + "dev": true, + "requires": { + "@swc/core-darwin-arm64": "1.15.30", + "@swc/core-darwin-x64": "1.15.30", + "@swc/core-linux-arm-gnueabihf": "1.15.30", + "@swc/core-linux-arm64-gnu": "1.15.30", + "@swc/core-linux-arm64-musl": "1.15.30", + "@swc/core-linux-ppc64-gnu": "1.15.30", + "@swc/core-linux-s390x-gnu": "1.15.30", + "@swc/core-linux-x64-gnu": "1.15.30", + "@swc/core-linux-x64-musl": "1.15.30", + "@swc/core-win32-arm64-msvc": "1.15.30", + "@swc/core-win32-ia32-msvc": "1.15.30", + "@swc/core-win32-x64-msvc": "1.15.30", "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.25" + "@swc/types": "^0.1.26" } }, "@swc/core-darwin-arm64": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.21.tgz", - "integrity": "sha512-SA8SFg9dp0qKRH8goWsax6bptFE2EdmPf2YRAQW9WoHGf3XKM1bX0nd5UdwxmC5hXsBUZAYf7xSciCler6/oyA==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.30.tgz", + "integrity": "sha512-VvpP+vq08HmGYewMWvrdsxh9s2lthz/808zXm8Yu5kaqeR8Yia2b0eYXleHQ3VAjoStUDk6LzTheBW9KXYQdMA==", "dev": true, "optional": true }, "@swc/core-darwin-x64": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.21.tgz", - "integrity": "sha512-//fOVntgowz9+V90lVsNCtyyrtbHp3jWH6Rch7MXHXbcvbLmbCTmssl5DeedUWLLGiAAW1wksBdqdGYOTjaNLw==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.30.tgz", + "integrity": "sha512-WiJA0hiZI3nwQAO6mu5RqigtWGDtth4Hiq6rbZxAaQyhIcqKIg5IoMRc1Y071lrNJn29eEDMC86Rq58xgUxlDg==", "dev": true, "optional": true }, "@swc/core-linux-arm-gnueabihf": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.21.tgz", - "integrity": "sha512-meNI4Sh6h9h8DvIfEc0l5URabYMSuNvyisLmG6vnoYAS43s8ON3NJR8sDHvdP7NJTrLe0q/x2XCn6yL/BeHcZg==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.30.tgz", + "integrity": "sha512-YANuFUo48kIT6plJgCD0keae9HFXfjxsbvsgevqc0hr/07X/p7sAWTFOGYEc2SXcASaK7UvuQqzlbW8pr7R79g==", "dev": true, "optional": true }, "@swc/core-linux-arm64-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.21.tgz", - "integrity": "sha512-QrXlNQnHeXqU2EzLlnsPoWEh8/GtNJLvfMiPsDhk+ht6Xv8+vhvZ5YZ/BokNWSIZiWPKLAqR0M7T92YF5tmD3g==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.30.tgz", + "integrity": "sha512-VndG8jaR4ugY6u+iVOT0Q+d2fZd7sLgjPgN8W/Le+3EbZKl+cRfFxV7Eoz4gfLqhmneZPdcIzf9T3LkgkmqNLg==", "dev": true, "optional": true }, "@swc/core-linux-arm64-musl": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.21.tgz", - "integrity": "sha512-8/yGCMO333ultDaMQivE5CjO6oXDPeeg1IV4sphojPkb0Pv0i6zvcRIkgp60xDB+UxLr6VgHgt+BBgqS959E9g==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.30.tgz", + "integrity": "sha512-1SYGs2l0Yyyi0pR/P/NKz/x0kqxkoiw+BXeJjLUdecSk/KasncWlJrc6hOvFSgKHOBrzgM5jwuluKtlT8dnrcA==", "dev": true, "optional": true }, "@swc/core-linux-ppc64-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.21.tgz", - "integrity": "sha512-ucW0HzPx0s1dgRvcvuLSPSA/2Kk/VYTv9st8qe1Kc22Gu0Q0rH9+6TcBTmMuNIp0Xs4BPr1uBttmbO1wEGI49Q==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.30.tgz", + "integrity": "sha512-TXREtiXeRhbfDFbmhnkIsXpKfzbfT73YkV2ZF6w0sfxgjC5zI2ZAbaCOq25qxvegofj2K93DtOpm9RLaBgqR2g==", "dev": true, "optional": true }, "@swc/core-linux-s390x-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.21.tgz", - "integrity": "sha512-ulTnOGc5I7YRObE/9NreAhQg94QkiR5qNhhcUZ1iFAYjzg/JGAi1ch+s/Ixe61pMIr8bfVrF0NOaB0f8wjaAfA==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.30.tgz", + "integrity": "sha512-DCR2YYeyd6DQE4OuDhImouuNcjXEiEdnn1Y0DyGteugPEDvVuvYk8Xddi+4o2SgWH6jiW8/I+3emZvbep1NC+g==", "dev": true, "optional": true }, "@swc/core-linux-x64-gnu": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.21.tgz", - "integrity": "sha512-D0RokxtM+cPvSqJIKR6uja4hbD+scI9ezo95mBhfSyLUs9wnPPl26sLp1ZPR/EXRdYm3F3S6RUtVi+8QXhT24Q==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.30.tgz", + "integrity": "sha512-5Pizw3NgfOJ5BJOBK8TIRa59xFW2avESTOBDPTAYwZYa1JNDs+KMF9lUfjJiJLM5HiMs/wPheA9eiT0q9m2AoA==", "dev": true, "optional": true }, "@swc/core-linux-x64-musl": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.21.tgz", - "integrity": "sha512-nER8u7VeRfmU6fMDzl1NQAbbB/G7O2avmvCOwIul1uGkZ2/acbPH+DCL9h5+0yd/coNcxMBTL6NGepIew+7C2w==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.30.tgz", + "integrity": "sha512-qyqydP/wyH8alcIP4a2hnGSjHLJjm9H7yDFup+CPy9oTahFgLLwnNcv5UHXqO2Qs3AIND+cls5f/Bb6hqpxdgA==", "dev": true, "optional": true }, "@swc/core-win32-arm64-msvc": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.21.tgz", - "integrity": "sha512-+/AgNBnjYugUA8C0Do4YzymgvnGbztv7j8HKSQLvR/DQgZPoXQ2B3PqB2mTtGh/X5DhlJWiqnunN35JUgWcAeQ==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.30.tgz", + "integrity": "sha512-CaQENgDHVGOg1mSF5sQVgvfFHG9kjMor2rkLMLeLOkfZYNj13ppnJ9+lfaBZLZUMMbnlGQnavCJb8PVBUOso7Q==", "dev": true, "optional": true }, "@swc/core-win32-ia32-msvc": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.21.tgz", - "integrity": "sha512-IkSZj8PX/N4HcaFhMQtzmkV8YSnuNoJ0E6OvMwFiOfejPhiKXvl7CdDsn1f4/emYEIDO3fpgZW9DTaCRMDxaDA==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.30.tgz", + "integrity": "sha512-30VdLeGk6fugiUs/kUdJ/pAg7z/zpvVbR11RH60jZ0Z42WIeIniYx0rLEWN7h/pKJ3CopqsQ3RsogCAkRKiA2g==", "dev": true, "optional": true }, "@swc/core-win32-x64-msvc": { - "version": "1.15.21", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.21.tgz", - "integrity": "sha512-zUyWso7OOENB6e1N1hNuNn8vbvLsTdKQ5WKLgt/JcBNfJhKy/6jmBmqI3GXk/MyvQKd5SLvP7A0F36p7TeDqvw==", + "version": "1.15.30", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.30.tgz", + "integrity": "sha512-4iObHPR+Q4oDY110EF5SF5eIaaVJNpMdG9C0q3Q92BsJ5y467uHz7sYQhP60WYlLFsLQ1el2YrIPUItUAQGOKg==", "dev": true, "optional": true }, @@ -19838,9 +20033,9 @@ } }, "@swc/types": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", - "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", "dev": true, "requires": { "@swc/counter": "^0.1.3" @@ -20408,19 +20603,19 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", - "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/type-utils": "8.57.2", - "@typescript-eslint/utils": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "dependencies": { "ignore": { @@ -20432,80 +20627,80 @@ } }, "@typescript-eslint/parser": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", - "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" } }, "@typescript-eslint/project-service": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", - "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", "dev": true, "requires": { - "@typescript-eslint/tsconfig-utils": "^8.57.2", - "@typescript-eslint/types": "^8.57.2", + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", "debug": "^4.4.3" } }, "@typescript-eslint/scope-manager": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", - "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2" + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" } }, "@typescript-eslint/tsconfig-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", - "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", "dev": true, "requires": {} }, "@typescript-eslint/type-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", - "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", "dev": true, "requires": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/utils": "8.57.2", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" } }, "@typescript-eslint/types": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", - "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", - "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", "dev": true, "requires": { - "@typescript-eslint/project-service": "8.57.2", - "@typescript-eslint/tsconfig-utils": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "dependencies": { "balanced-match": { @@ -20515,44 +20710,44 @@ "dev": true }, "brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "requires": { "balanced-match": "^4.0.2" } }, "minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "requires": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" } } } }, "@typescript-eslint/utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", - "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2" + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" } }, "@typescript-eslint/visitor-keys": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", - "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", "dev": true, "requires": { - "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" }, "dependencies": { @@ -21200,15 +21395,15 @@ "dev": true }, "babel-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", - "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", + "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", "dev": true, "requires": { - "@jest/transform": "30.3.0", + "@jest/transform": "30.4.1", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.3.0", + "babel-preset-jest": "30.4.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -21267,9 +21462,9 @@ } }, "babel-plugin-jest-hoist": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", - "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", + "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", "dev": true, "requires": { "@types/babel__core": "^7.20.5" @@ -21347,12 +21542,12 @@ } }, "babel-preset-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", - "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", + "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "30.3.0", + "babel-plugin-jest-hoist": "30.4.0", "babel-preset-current-node-syntax": "^1.2.0" } }, @@ -22899,9 +23094,9 @@ } }, "eslint-plugin-jest": { - "version": "29.15.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.1.tgz", - "integrity": "sha512-6BjyErCQauz3zfJvzLw/kAez2lf4LEpbHLvWBfEcG4EI0ZiRSwjoH2uZulMouU8kRkBH+S0rhqn11IhTvxKgKw==", + "version": "29.15.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.15.2.tgz", + "integrity": "sha512-kEN4r9RZl1xcsb4arGq89LrcVdOUFII/JSCwtTPJyv16mDwmPrcuEQwpxqZHeINvcsd7oK5O/rhdGlxFRaZwvQ==", "dev": true, "requires": { "@typescript-eslint/utils": "^8.0.0" @@ -23110,17 +23305,17 @@ "dev": true }, "expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", "dev": true, "requires": { - "@jest/expect-utils": "30.3.0", + "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" } }, "express": { @@ -23522,9 +23717,9 @@ "dev": true }, "globals": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", - "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", "dev": true }, "globalthis": { @@ -23779,9 +23974,9 @@ } }, "html-webpack-plugin": { - "version": "5.6.6", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz", - "integrity": "sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw==", + "version": "5.6.7", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.7.tgz", + "integrity": "sha512-md+vXtdCAe60s1k6AU3dUyMJnDxUyQAwfwPKoLisvgUF1IXjtlLsk2se54+qfL9Mdm26bbwvjJybpNx48NKRLw==", "dev": true, "requires": { "@types/html-minifier-terser": "^6.0.0", @@ -24417,15 +24612,15 @@ } }, "jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", - "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", + "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", "dev": true, "requires": { - "@jest/core": "30.3.0", - "@jest/types": "30.3.0", + "@jest/core": "30.4.2", + "@jest/types": "30.4.1", "import-local": "^3.2.0", - "jest-cli": "30.3.0" + "jest-cli": "30.4.2" } }, "jest-axe": { @@ -24461,57 +24656,57 @@ } }, "jest-changed-files": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", - "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", + "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", "dev": true, "requires": { "execa": "^5.1.1", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "p-limit": "^3.1.0" } }, "jest-circus": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", - "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", + "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", "dev": true, "requires": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", + "jest-each": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "p-limit": "^3.1.0", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, "dependencies": { "@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "requires": { "@sinclair/typebox": "^0.34.0" } }, "@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "ansi-styles": { @@ -24521,80 +24716,91 @@ "dev": true }, "pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "requires": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" } } } }, "jest-cli": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", - "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", + "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", "dev": true, "requires": { - "@jest/core": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/core": "30.4.2", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-config": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "yargs": "^17.7.2" } }, "jest-config": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", - "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", + "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", "dev": true, "requires": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.3.0", - "@jest/types": "30.3.0", - "babel-jest": "30.3.0", + "@jest/pattern": "30.4.0", + "@jest/test-sequencer": "30.4.1", + "@jest/types": "30.4.1", + "babel-jest": "30.4.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-circus": "30.3.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-runner": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-circus": "30.4.2", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-runner": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "parse-json": "^5.2.0", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "dependencies": { + "@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + } + }, "@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "requires": { "@sinclair/typebox": "^0.34.0" } }, "@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "ansi-styles": { @@ -24604,9 +24810,9 @@ "dev": true }, "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "requires": { "balanced-match": "^1.0.0" @@ -24626,6 +24832,12 @@ "path-scurry": "^1.11.1" } }, + "jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true + }, "minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -24636,14 +24848,15 @@ } }, "pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "requires": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" } } } @@ -24661,40 +24874,40 @@ } }, "jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", + "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", "dev": true, "requires": { "detect-newline": "^3.1.0" } }, "jest-each": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", - "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", + "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", "dev": true, "requires": { "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "chalk": "^4.1.2", - "jest-util": "30.3.0", - "pretty-format": "30.3.0" + "jest-util": "30.4.1", + "pretty-format": "30.4.1" }, "dependencies": { "@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "requires": { "@sinclair/typebox": "^0.34.0" } }, "@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "ansi-styles": { @@ -24704,42 +24917,43 @@ "dev": true }, "pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "requires": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" } } } }, "jest-environment-jsdom": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.3.0.tgz", - "integrity": "sha512-RLEOJy6ip1lpw0yqJ8tB3i88FC7VBz7i00Zvl2qF71IdxjS98gC9/0SPWYIBVXHm5hgCYK0PAlSlnHGGy9RoMg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.4.1.tgz", + "integrity": "sha512-o3nfaN4zej7qgk2X0j8Jhq/S9nAVKs2xK3QeQxeHVvpkEPxaA1yxDGydR+iVI7zPy7Cp62Aq2h3Ja46QvfWHGA==", "dev": true, "requires": { - "@jest/environment": "30.3.0", - "@jest/environment-jsdom-abstract": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/environment-jsdom-abstract": "30.4.1", "jsdom": "^26.1.0" } }, "jest-environment-node": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", - "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", + "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", "dev": true, "requires": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0" + "jest-mock": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1" } }, "jest-get-type": { @@ -24749,24 +24963,30 @@ "dev": true }, "jest-haste-map": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", - "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", + "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", "dev": true, "requires": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "fsevents": "^2.3.3", "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", "picomatch": "^4.0.3", "walker": "^1.0.8" }, "dependencies": { + "jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true + }, "picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", @@ -24776,28 +24996,28 @@ } }, "jest-leak-detector": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", - "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", + "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", "dev": true, "requires": { "@jest/get-type": "30.1.0", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "dependencies": { "@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "requires": { "@sinclair/typebox": "^0.34.0" } }, "@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "ansi-styles": { @@ -24807,43 +25027,44 @@ "dev": true }, "pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "requires": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" } } } }, "jest-matcher-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", - "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", "dev": true, "requires": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.3.0", - "pretty-format": "30.3.0" + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" }, "dependencies": { "@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "requires": { "@sinclair/typebox": "^0.34.0" } }, "@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "ansi-styles": { @@ -24853,60 +25074,62 @@ "dev": true }, "jest-diff": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", - "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "dev": true, "requires": { - "@jest/diff-sequences": "30.3.0", + "@jest/diff-sequences": "30.4.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" } }, "pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "requires": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" } } } }, "jest-message-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", - "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", "dev": true, "requires": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", "picomatch": "^4.0.3", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, "dependencies": { "@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "requires": { "@sinclair/typebox": "^0.34.0" } }, "@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "ansi-styles": { @@ -24922,27 +25145,28 @@ "dev": true }, "pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "requires": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" } } } }, "jest-mock": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", - "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", "dev": true, "requires": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-util": "30.3.0" + "jest-util": "30.4.1" } }, "jest-pnp-resolver": { @@ -24959,95 +25183,103 @@ "dev": true }, "jest-resolve": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", - "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", + "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", "dev": true, "requires": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", + "jest-haste-map": "30.4.1", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" } }, "jest-resolve-dependencies": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", - "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", + "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", "dev": true, "requires": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.3.0" + "jest-regex-util": "30.4.0", + "jest-snapshot": "30.4.1" + }, + "dependencies": { + "jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true + } } }, "jest-runner": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", - "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", + "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", "dev": true, "requires": { - "@jest/console": "30.3.0", - "@jest/environment": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/environment": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-leak-detector": "30.3.0", - "jest-message-util": "30.3.0", - "jest-resolve": "30.3.0", - "jest-runtime": "30.3.0", - "jest-util": "30.3.0", - "jest-watcher": "30.3.0", - "jest-worker": "30.3.0", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-haste-map": "30.4.1", + "jest-leak-detector": "30.4.1", + "jest-message-util": "30.4.1", + "jest-resolve": "30.4.1", + "jest-runtime": "30.4.2", + "jest-util": "30.4.1", + "jest-watcher": "30.4.1", + "jest-worker": "30.4.1", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "jest-runtime": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", - "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", + "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", "dev": true, "requires": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/globals": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/globals": "30.4.1", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "dependencies": { "brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "requires": { "balanced-match": "^1.0.0" @@ -25067,6 +25299,12 @@ "path-scurry": "^1.11.1" } }, + "jest-regex-util": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "dev": true + }, "minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -25079,9 +25317,9 @@ } }, "jest-snapshot": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", - "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", + "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", "dev": true, "requires": { "@babel/core": "^7.27.4", @@ -25089,37 +25327,37 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.3.0", + "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/snapshot-utils": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.3.0", + "expect": "30.4.1", "graceful-fs": "^4.2.11", - "jest-diff": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "pretty-format": "30.3.0", + "jest-diff": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "pretty-format": "30.4.1", "semver": "^7.7.2", "synckit": "^0.11.8" }, "dependencies": { "@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "requires": { "@sinclair/typebox": "^0.34.0" } }, "@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "ansi-styles": { @@ -25129,37 +25367,38 @@ "dev": true }, "jest-diff": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", - "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "dev": true, "requires": { - "@jest/diff-sequences": "30.3.0", + "@jest/diff-sequences": "30.4.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" } }, "pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "requires": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" } } } }, "jest-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", - "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", "dev": true, "requires": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -25176,32 +25415,32 @@ } }, "jest-validate": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", - "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", + "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", "dev": true, "requires": { "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "dependencies": { "@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "requires": { "@sinclair/typebox": "^0.34.0" } }, "@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true }, "ansi-styles": { @@ -25217,43 +25456,44 @@ "dev": true }, "pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "requires": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" } } } }, "jest-watcher": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", - "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", + "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", "dev": true, "requires": { - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "string-length": "^4.0.2" } }, "jest-worker": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", - "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", + "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", "dev": true, "requires": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -26560,9 +26800,9 @@ } }, "react": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", - "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==" + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==" }, "react-app-polyfill": { "version": "3.0.0", @@ -26593,9 +26833,9 @@ "requires": {} }, "react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", "requires": { "scheduler": "^0.27.0" } @@ -26606,6 +26846,18 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, + "react-is-18": { + "version": "npm:react-is@18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "react-is-19": { + "version": "npm:react-is@19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", + "dev": true + }, "react-redux": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", @@ -26622,9 +26874,9 @@ "dev": true }, "react-router": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.1.tgz", - "integrity": "sha512-5BCvFskyAAVumqhEKh/iPhLOIkfxcEUz8WqFIARCkMg8hZZzDYX9CtwxXA0e+qT8zAxmMC0x3Ckb9iMONwc5jg==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.15.0.tgz", + "integrity": "sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==", "requires": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -26649,9 +26901,9 @@ } }, "react-virtuoso": { - "version": "4.18.4", - "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.18.4.tgz", - "integrity": "sha512-DNM4Wy2tMA/J6ejMaDdqecOug31rOwgSRg4C/Dw6Iox4dJe9qwcx32M8HdhkE5uHEVVZh7h0koYwAsCSNdxGfQ==", + "version": "4.18.6", + "resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.18.6.tgz", + "integrity": "sha512-CrT3P6HyjJMHZVWSste2bG2q5aWGlHfW2QuySZjiFwB2Qok/xsvgy+k8Z2jeDP8PP5KsBip7zNrl/F0QoxeyKw==", "requires": {} }, "read-package-json-fast": { @@ -27895,9 +28147,9 @@ "requires": {} }, "ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "requires": {} }, @@ -28139,15 +28391,15 @@ "dev": true }, "typescript-eslint": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz", - "integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.1.tgz", + "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", "dev": true, "requires": { - "@typescript-eslint/eslint-plugin": "8.57.2", - "@typescript-eslint/parser": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/utils": "8.57.2" + "@typescript-eslint/eslint-plugin": "8.59.1", + "@typescript-eslint/parser": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1" } }, "typestyle": { diff --git a/package.json b/package.json index a31c0e837..7dc1a4261 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,9 @@ }, "dependencies": { "@emotion/styled": "^11.14.1", - "@mui/icons-material": "^7.3.10", - "@mui/material": "^7.3.10", - "@mui/system": "^7.3.10", + "@mui/icons-material": "^7.3.11", + "@mui/material": "^7.3.11", + "@mui/system": "^7.3.11", "@reduxjs/toolkit": "^2.11.2", "assert": "^2.1.0", "buffer": "^6.0.3", @@ -33,12 +33,12 @@ "moize": "^6.1.7", "notistack": "^3.0.2", "process": "^0.11.10", - "react": "^19.2.5", + "react": "^19.2.6", "react-chartjs-2": "^5.3.1", - "react-dom": "^19.2.5", + "react-dom": "^19.2.6", "react-redux": "^9.2.0", - "react-router": "^7.14.1", - "react-virtuoso": "^4.18.4", + "react-router": "^7.15.0", + "react-virtuoso": "^4.18.6", "stream-browserify": "^3.0.0", "taskcluster-client-web": "^87.1.3", "taskcluster-lib-scopes": "^11.0.0", @@ -83,14 +83,14 @@ "devDependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-runtime": "^7.29.0", - "@babel/preset-env": "^7.29.2", + "@babel/preset-env": "^7.29.5", "@babel/preset-react": "^7.28.5", "@babel/preset-typescript": "^7.28.5", "@babel/runtime": "^7.29.2", "@eslint/js": "^9.39.4", "@fetch-mock/jest": "^0.2.20", - "@jest/types": "^30.3.0", - "@swc/core": "^1.15.21", + "@jest/types": "^30.4.1", + "@swc/core": "^1.15.30", "@swc/jest": "^0.2.39", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", @@ -103,7 +103,7 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "babel-loader": "^10.1.1", - "babel-preset-jest": "^30.3.0", + "babel-preset-jest": "^30.4.0", "case-sensitive-paths-webpack-plugin": "^2.4.0", "copy-webpack-plugin": "^13.0.1", "core-js": "^3.49.0", @@ -112,17 +112,17 @@ "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-jest": "^29.15.1", + "eslint-plugin-jest": "^29.15.2", "eslint-plugin-jest-dom": "^5.5.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-testing-library": "^7.16.2", - "globals": "^17.5.0", + "globals": "^17.6.0", "history": "^5.3.0", - "html-webpack-plugin": "^5.6.6", - "jest": "^30.3.0", + "html-webpack-plugin": "^5.6.7", + "jest": "^30.4.2", "jest-axe": "^10.0.0", - "jest-environment-jsdom": "^30.3.0", - "jest-resolve": "^30.3.0", + "jest-environment-jsdom": "^30.4.1", + "jest-resolve": "^30.4.1", "npm-run-all2": "^8.0.4", "prettier": "3.7.4", "react-app-polyfill": "^3.0.0", @@ -133,7 +133,7 @@ "ts-loader": "^9.5.7", "ts-node": "^10.9.2", "typescript": "^5.9.3", - "typescript-eslint": "^8.57.2", + "typescript-eslint": "^8.59.1", "webpack": "^5.106.2", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.3", diff --git a/src/__tests__/App.test.tsx b/src/__tests__/App.test.tsx index d0697ce41..a418178d9 100644 --- a/src/__tests__/App.test.tsx +++ b/src/__tests__/App.test.tsx @@ -91,7 +91,7 @@ describe('App', () => { render(); - const darkModeButton = screen.getByRole('checkbox', { + const darkModeButton = screen.getByRole('switch', { name: /Dark mode switch/, }); diff --git a/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap b/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap index 470b689ae..c8147e2f3 100644 --- a/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap +++ b/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap @@ -301,7 +301,7 @@ exports[`Results View The table should match snapshot and other elements should class="f6hg2jo" > - - ) : null} - + {isHome && ( + + + {strings.title} + + + {strings.tagline} + + + + )} ); } diff --git a/src/styles/Header.ts b/src/styles/Header.ts index 37b58e236..3fbc85645 100644 --- a/src/styles/Header.ts +++ b/src/styles/Header.ts @@ -15,7 +15,7 @@ export const HeaderStyles = (mode: string, isHome: boolean) => { container: { padding: 0, width: '100%', - minHeight: isHome ? '357px' : '130px', + minHeight: isHome ? '357px' : 'auto', backgroundColor: isTrueLight ? lightBg : darkBg, backgroundImage: `url(${headerImage.toString()})`, backgroundPosition: 'center', diff --git a/src/styles/SearchContainerStyles.ts b/src/styles/SearchContainerStyles.ts index a7279f9b8..7f911b426 100644 --- a/src/styles/SearchContainerStyles.ts +++ b/src/styles/SearchContainerStyles.ts @@ -10,7 +10,7 @@ export const SearchContainerStyles = (isHome: boolean) => { maxWidth: '973px', marginTop: isHome ? `${Spacing.layoutLarge + 20}px` : '0px', margin: '0 auto', - marginBottom: isHome ? '0px' : `${Spacing.layoutXLarge + 4}px`, + marginBottom: isHome ? '0px' : `${Spacing.xxLarge}px`, display: 'flex', justifyContent: 'center', flexDirection: 'column', diff --git a/src/theme/components.js b/src/theme/components.js index 32ffc9ee1..049549536 100644 --- a/src/theme/components.js +++ b/src/theme/components.js @@ -162,7 +162,7 @@ const components = { backgroundSize: '290px', content: '""', display: 'block', - height: '0.3em', + height: '0.5em', marginTop: '-5px', }, }, From 1135204ebc02e6339672d8db84af4f056d5a55fb Mon Sep 17 00:00:00 2001 From: Kala Severe Date: Mon, 18 May 2026 17:22:34 -0700 Subject: [PATCH 04/11] Bug 2032246: Add cles statement to expanded row (#1033) add cles statement to expanded row --- .../__snapshots__/ResultsView.test.tsx.snap | 58 +++++++++++++++++++ src/common/testVersions/mannWhitney.tsx | 7 ++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap b/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap index 85251035b..4ceb02d73 100644 --- a/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap +++ b/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap @@ -141,6 +141,35 @@ exports[`Results View Should display Base, New and Common graphs with replicates + @@ -533,6 +562,35 @@ exports[`Results View Should display Base, New and Common graphs with tooltips 1 + diff --git a/src/common/testVersions/mannWhitney.tsx b/src/common/testVersions/mannWhitney.tsx index 5392828c4..ca01c09e6 100644 --- a/src/common/testVersions/mannWhitney.tsx +++ b/src/common/testVersions/mannWhitney.tsx @@ -2,6 +2,7 @@ import KeyboardDoubleArrowUpIcon from '@mui/icons-material/KeyboardDoubleArrowUp import ThumbDownIcon from '@mui/icons-material/ThumbDown'; import ThumbUpIcon from '@mui/icons-material/ThumbUp'; import WarningIcon from '@mui/icons-material/Warning'; +import Alert from '@mui/material/Alert'; import Box from '@mui/material/Box'; import { MannWhitneyCompareMetrics } from '../../components/CompareResults/MannWhitneyCompareMetrics'; @@ -396,9 +397,10 @@ export const mannWhitneyStrategy = { renderExpandedRight(result: CombinedResultsItemType) { const mwResult = result as MannWhitneyResultsItem; - const { cles, cles_direction } = mwResult.cles ?? { + const { cles, cles_direction, mann_whitney_u_cles } = mwResult.cles ?? { cles: '', cles_direction: '', + mann_whitney_u_cles: '', }; const { cliffs_delta, cliffs_interpretation } = mwResult; const pValue = mwResult.mann_whitney_test?.pvalue; @@ -416,6 +418,9 @@ export const mannWhitneyStrategy = { cles={cles} cles_direction={cles_direction} /> + + Effect Size: {mann_whitney_u_cles} + ); From c38aace5e3fae8084909e32fa69116566a35cef7 Mon Sep 17 00:00:00 2001 From: Kala Severe Date: Mon, 18 May 2026 17:35:36 -0700 Subject: [PATCH 05/11] Bug 1931291: Created expanded all rows functionality (#1037) * created expanded all rows functionality * added test coverage * created expanded all rows functionality * added test coverage * updated snapshots --- .../CompareResults/ResultsView.test.tsx | 24 +++++ .../CompareResults/RevisionRow.test.tsx | 80 ++++++++++++++ .../OverTimeResultsView.test.tsx.snap | 45 +++++++- .../__snapshots__/ResultsTable.test.tsx.snap | 100 +++++++++++++++--- .../__snapshots__/ResultsView.test.tsx.snap | 49 +++++++-- .../CompareOverTime.test.tsx.snap | 2 +- .../CompareWithBase.test.tsx.snap | 4 +- .../CompareResults/ResultsControls.tsx | 23 +++- .../CompareResults/ResultsTable.tsx | 4 + src/components/CompareResults/RevisionRow.tsx | 16 ++- .../CompareResults/TableContent.tsx | 3 + .../CompareResults/TableRevisionContent.tsx | 12 ++- 12 files changed, 327 insertions(+), 35 deletions(-) diff --git a/src/__tests__/CompareResults/ResultsView.test.tsx b/src/__tests__/CompareResults/ResultsView.test.tsx index b647ad7b0..216585bdf 100644 --- a/src/__tests__/CompareResults/ResultsView.test.tsx +++ b/src/__tests__/CompareResults/ResultsView.test.tsx @@ -591,4 +591,28 @@ describe('Results View', () => { expect(screen.queryByText('Results')).not.toBeInTheDocument(); expect(screen.getByText(titleName)).toBeInTheDocument(); }); + + it('toggles all rows when the Expand all checkbox is clicked', async () => { + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + renderWithRoute(); + + await screen.findByRole('table'); + + const expandAllCheckbox = screen.getByRole('checkbox', { + name: /Expand all/i, + }); + expect(expandAllCheckbox).not.toBeChecked(); + expect(screen.queryAllByTestId(/ExpandLessIcon/)).toHaveLength(0); + + await user.click(expandAllCheckbox); + expect(expandAllCheckbox).toBeChecked(); + const expandLessIcons = await screen.findAllByTestId(/ExpandLessIcon/); + expect(expandLessIcons.length).toBeGreaterThan(0); + + await user.click(expandAllCheckbox); + expect(expandAllCheckbox).not.toBeChecked(); + await waitFor(() => { + expect(screen.queryAllByTestId(/ExpandLessIcon/)).toHaveLength(0); + }); + }); }); diff --git a/src/__tests__/CompareResults/RevisionRow.test.tsx b/src/__tests__/CompareResults/RevisionRow.test.tsx index 1ad9eccc3..8b4c2be4f 100644 --- a/src/__tests__/CompareResults/RevisionRow.test.tsx +++ b/src/__tests__/CompareResults/RevisionRow.test.tsx @@ -85,6 +85,7 @@ describe('', () => { gridTemplateColumns='none' replicates={false} testVersion='student-t' + expandAll={false} />, ); const shortNameNode = await screen.findByText(shortName); @@ -123,6 +124,7 @@ describe('Subtest count pills', () => { gridTemplateColumns='none' replicates={false} testVersion='mann-whitney-u' + expandAll={false} />, ); @@ -151,6 +153,7 @@ describe('Subtest count pills', () => { gridTemplateColumns='none' replicates={false} testVersion='mann-whitney-u' + expandAll={false} />, ); @@ -172,6 +175,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='student-t' + expandAll={false} />, ); @@ -199,6 +203,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='mann-whitney-u' + expandAll={false} />, ); @@ -220,6 +225,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='student-t' + expandAll={false} />, ); @@ -236,6 +242,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='student-t' + expandAll={false} />, ); const expandRow = await screen.findByTestId(/ExpandMoreIcon/); @@ -256,6 +263,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='mann-whitney-u' + expandAll={false} />, ); @@ -284,6 +292,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='mann-whitney-u' + expandAll={false} />, ); @@ -305,6 +314,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='mann-whitney-u' + expandAll={false} />, ); @@ -323,6 +333,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='mann-whitney-u' + expandAll={false} />, ); @@ -350,6 +361,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='mann-whitney-u' + expandAll={false} />, ); @@ -369,6 +381,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='student-t' + expandAll={false} />, ); @@ -397,6 +410,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='student-t' + expandAll={false} />, ); @@ -435,6 +449,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='mann-whitney-u' + expandAll={false} />, ); const roles = await screen.findAllByRole('cell'); @@ -451,6 +466,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='mann-whitney-u' + expandAll={false} />, ); const roles = await screen.findAllByRole('cell'); @@ -468,6 +484,7 @@ describe('Expanded row', () => { gridTemplateColumns='none' replicates={false} testVersion='mann-whitney-u' + expandAll={false} />, ); const roles = await screen.findAllByRole('cell'); @@ -476,3 +493,66 @@ describe('Expanded row', () => { }); }); }); + +describe('expandAll prop', () => { + it('starts expanded when expandAll is true', async () => { + const { + testCompareData: [rowData], + } = getTestData(); + + renderWithRoute( + , + ); + + expect(await screen.findByTestId(/ExpandLessIcon/)).toBeInTheDocument(); + }); + + it('starts collapsed when expandAll is false', async () => { + const { + testCompareData: [rowData], + } = getTestData(); + + renderWithRoute( + , + ); + + expect(await screen.findByTestId(/ExpandMoreIcon/)).toBeInTheDocument(); + }); + + it('lets the user individually collapse a row that was force-expanded', async () => { + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + const { + testCompareData: [rowData], + } = getTestData(); + + renderWithRoute( + , + ); + + const collapseButton = await screen.findByTestId(/ExpandLessIcon/); + await user.click(collapseButton); + + expect(await screen.findByTestId(/ExpandMoreIcon/)).toBeInTheDocument(); + }); +}); diff --git a/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap b/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap index ca8dde4a2..067598ccd 100644 --- a/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap +++ b/src/__tests__/CompareResults/__snapshots__/OverTimeResultsView.test.tsx.snap @@ -13,7 +13,7 @@ exports[`Results View The table should match snapshot and other elements should class="MuiGrid-root MuiGrid-container MuiGrid-direction-xs-row MuiGrid-spacing-xs-2 f1wpas00 css-13620ud-MuiGrid-root" >
+
+ +
+
+ +
+
+ +
@@ -82,6 +89,7 @@ interface Props { view: typeof compareView | typeof compareOverTimeView; replicates: boolean; testVersion: TestVersion; + expandAll: boolean; } export default TableRevisionContent; From bb77ec5fb588fde17d72e4252535360cb029df95 Mon Sep 17 00:00:00 2001 From: Kala Severe Date: Thu, 21 May 2026 10:34:38 -0700 Subject: [PATCH 06/11] Bug 2036968: Replaced fast-kde with fftkde and used bootstrap-ci to get CI summary (#1034) * use padenots kde for commongraph and get the confidence interval from bootstrap-ci * replaced chartjs with echarts * update package.json * update lockfile * add test coverage; fix lockfile * fall back to Silverman when ISJ bandwidth fails to converge * don't display CommonGraph if either baseValues or newValues are missing * extract 1024 as global and remove scatter series and related tests * resample base and new KDEs onto a shared x-grid so the tooltip can show both densities at a single x position Updated tests to reflect changes * update test merge conflict * add CommonGraph.test.tsx with focused coverage for resampling, Silverman fallback, ref bail-out, and tooltip formatter; also improved x-axis label * round x-axis tick labels to 2 dp, leaving whole numbers as bare integers * delete bootstrap-ci.js * revert package-lock.json to original form * Revert "revert package-lock.json to original form" This reverts commit d19a6217762fd9a48410d68c5056daf424da09ab. --- jest.config.ts | 11 +- package-lock.json | 125 +-- package.json | 3 +- .../CompareResults/CommonGraph.test.tsx | 297 +++++++ .../CompareResults/ResultsView.test.tsx | 220 ++--- .../CompareResults/RevisionRow.test.tsx | 102 +++ .../__snapshots__/ResultsView.test.tsx.snap | 257 +++--- src/__tests__/utils/setupTests.ts | 28 +- src/common/testVersions/mannWhitney.tsx | 27 + src/components/CompareResults/CommonGraph.tsx | 521 ++++++------ .../CompareResults/RevisionRowExpandable.tsx | 12 +- src/utils/bootstrap-ci.ts | 93 +++ src/utils/kde.d.ts | 61 ++ src/utils/kde.js | 755 ++++++++++++++++++ 14 files changed, 1892 insertions(+), 620 deletions(-) create mode 100644 src/__tests__/CompareResults/CommonGraph.test.tsx create mode 100644 src/utils/bootstrap-ci.ts create mode 100644 src/utils/kde.d.ts create mode 100644 src/utils/kde.js diff --git a/jest.config.ts b/jest.config.ts index b0f99801b..303137c53 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -3,7 +3,16 @@ import type { Config } from '@jest/types'; const config: Config.InitialOptions = { roots: ['/src'], collectCoverageFrom: ['src/**/*.{js,ts,tsx}', '!src/**/*.d.ts'], - coveragePathIgnorePatterns: ['__tests__', 'index', 'resources', 'styles'], + coveragePathIgnorePatterns: [ + '__tests__', + 'index', + 'resources', + 'styles', + // Pure-math helpers exercised indirectly by the chart code; not worth + // the ceremony of unit-testing the numerical routines themselves. + 'src/utils/bootstrap-ci\\.[jt]s', + 'src/utils/kde\\.js', + ], setupFiles: ['react-app-polyfill/jsdom'], setupFilesAfterEnv: ['/src/__tests__/utils/setupTests.ts'], testPathIgnorePatterns: ['/node_modules/', '/src/__tests__/utils/'], diff --git a/package-lock.json b/package-lock.json index d91d58125..4f871c83c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,9 +15,9 @@ "@reduxjs/toolkit": "^2.11.2", "assert": "^2.1.0", "buffer": "^6.0.3", - "chart.js": "^4.5.1", "crypto-browserify": "^3.12.1", "dayjs": "^1.11.20", + "echarts": "^6.0.0", "express": "^5.2.1", "fast-kde": "^0.2.2", "format": "^0.2.2", @@ -27,7 +27,6 @@ "notistack": "^3.0.2", "process": "^0.11.10", "react": "^19.2.6", - "react-chartjs-2": "^5.3.1", "react-dom": "^19.2.6", "react-redux": "^9.2.0", "react-router": "^7.15.0", @@ -3431,11 +3430,6 @@ "tslib": "2" } }, - "node_modules/@kurkle/color": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" - }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -4242,14 +4236,6 @@ "@swc/counter": "^0.1.3" } }, - "node_modules/@swc/wasm": { - "version": "1.2.122", - "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.2.122.tgz", - "integrity": "sha512-sM1VCWQxmNhFtdxME+8UXNyPNhxNu7zdb6ikWpz0YKAQQFRGT5ThZgJrubEpah335SUToNg8pkdDF7ibVCjxbQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -6824,17 +6810,6 @@ "node": ">=10" } }, - "node_modules/chart.js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", - "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -7858,6 +7833,22 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.0.0" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -13826,15 +13817,6 @@ "dev": true, "license": "MIT" }, - "node_modules/react-chartjs-2": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz", - "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==", - "peerDependencies": { - "chart.js": "^4.1.1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/react-dom": { "version": "19.2.6", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", @@ -17247,6 +17229,21 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" } }, "dependencies": { @@ -19542,11 +19539,6 @@ "dev": true, "requires": {} }, - "@kurkle/color": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" - }, "@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -20041,14 +20033,6 @@ "@swc/counter": "^0.1.3" } }, - "@swc/wasm": { - "version": "1.2.122", - "resolved": "https://registry.npmjs.org/@swc/wasm/-/wasm-1.2.122.tgz", - "integrity": "sha512-sM1VCWQxmNhFtdxME+8UXNyPNhxNu7zdb6ikWpz0YKAQQFRGT5ThZgJrubEpah335SUToNg8pkdDF7ibVCjxbQ==", - "dev": true, - "optional": true, - "peer": true - }, "@testing-library/dom": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", @@ -21903,14 +21887,6 @@ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true }, - "chart.js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", - "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", - "requires": { - "@kurkle/color": "^0.3.0" - } - }, "chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -22641,6 +22617,22 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "echarts": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz", + "integrity": "sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==", + "requires": { + "tslib": "2.3.0", + "zrender": "6.0.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + } + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -26826,12 +26818,6 @@ } } }, - "react-chartjs-2": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.1.tgz", - "integrity": "sha512-h5IPXKg9EXpjoBzUfyWJvllMjG2mQ4EiuHQFhms/AjUm0XSZHhyRy2xVmLXHKrtcdrPO4mnGqRtYoD0vp95A0A==", - "requires": {} - }, "react-dom": { "version": "19.2.6", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", @@ -29241,6 +29227,21 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zrender": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.0.0.tgz", + "integrity": "sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==", + "requires": { + "tslib": "2.3.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + } + } } } } diff --git a/package.json b/package.json index 6739b0d40..af8a2e261 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,9 @@ "@reduxjs/toolkit": "^2.11.2", "assert": "^2.1.0", "buffer": "^6.0.3", - "chart.js": "^4.5.1", "crypto-browserify": "^3.12.1", "dayjs": "^1.11.20", + "echarts": "^6.0.0", "express": "^5.2.1", "fast-kde": "^0.2.2", "format": "^0.2.2", @@ -34,7 +34,6 @@ "notistack": "^3.0.2", "process": "^0.11.10", "react": "^19.2.6", - "react-chartjs-2": "^5.3.1", "react-dom": "^19.2.6", "react-redux": "^9.2.0", "react-router": "^7.15.0", diff --git a/src/__tests__/CompareResults/CommonGraph.test.tsx b/src/__tests__/CompareResults/CommonGraph.test.tsx new file mode 100644 index 000000000..6eb37df38 --- /dev/null +++ b/src/__tests__/CompareResults/CommonGraph.test.tsx @@ -0,0 +1,297 @@ +import { init as echartsInit } from 'echarts'; +import type { EChartsOption, LineSeriesOption } from 'echarts'; + +import CommonGraph from '../../components/CompareResults/CommonGraph'; +import { fftkde } from '../../utils/kde.js'; +import { render } from '../utils/test-utils'; + +// Wrap React.useRef so a single test can substitute a stubbed ref whose +// `.current` stays null — used to exercise the "no DOM element attached" +// early-return inside CommonGraph's init useEffect. The wrapper delegates to +// the real implementation when the override queue is empty, so other tests +// in this file are unaffected. +const mockUseRefOverrides: Array<{ current: unknown }> = []; + +jest.mock('react', () => { + const actualReact = jest.requireActual('react'); + // Spread loses non-enumerable React exports (Component, createElement, …) + // which downstream code relies on; a Proxy lets us replace `useRef` while + // forwarding every other property to the real module. + return new Proxy(actualReact, { + get(target, prop, receiver) { + if (prop === 'useRef') { + return function wrappedUseRef(initialValue: T) { + const override = mockUseRefOverrides.shift(); + if (override !== undefined) { + return override; + } + return target.useRef(initialValue); + }; + } + return Reflect.get(target, prop, receiver) as unknown; + }, + }); +}); + +// echarts hands the tooltip formatter a pre-built marker HTML string per +// point (a small coloured dot/square). The formatter prepends it to each +// line of the tooltip alongside the seriesName. +type FormatterParam = { + seriesType: 'line'; + seriesName: string; + value: [number, number]; + marker: string; + axisValue?: number; +}; + +const FAKE_BASE_MARKER = ''; +const FAKE_NEW_MARKER = ''; + +function getTooltipFormatter(option: EChartsOption) { + return ( + option.tooltip as unknown as { + formatter: (p: FormatterParam | FormatterParam[]) => string; + } + ).formatter; +} + +// Pull the latest EChartsOption that the chart component pushed via +// `instance.setOption(option)`. Each call to `init()` in the mock returns a +// fresh stub, so we walk through the init mock results to find the most +// recently-rendered chart's options. +function getLatestEChartsOption(): EChartsOption { + const initMock = echartsInit as jest.Mock; + for (let i = initMock.mock.results.length - 1; i >= 0; i--) { + const instance = initMock.mock.results[i].value as { + setOption: jest.Mock; + }; + const lastSetOption = instance.setOption.mock.calls.at(-1); + if (lastSetOption) { + return lastSetOption[0]; + } + } + throw new Error('No echarts setOption call captured'); +} + +describe('CommonGraph', () => { + it('resamples both KDEs onto a shared 1024-point x-grid', () => { + // Both series mock to the same x range so the shared grid spans [10, 30]. + (fftkde as jest.Mock).mockImplementation(() => ({ + x: [10, 20, 30], + y: [0.1, 0.2, 0.3], + bandwidth: 1, + })); + + render(); + + const option = getLatestEChartsOption(); + const series = option.series as LineSeriesOption[]; + const baseData = series[0].data as Array<[number, number]>; + const newData = series[1].data as Array<[number, number]>; + + expect(baseData).toHaveLength(1024); + expect(newData).toHaveLength(1024); + + // Endpoints land on the source boundary values. + expect(baseData[0]).toEqual([10, 0.1]); + expect(baseData[1023]).toEqual([30, 0.3]); + + // Both series share identical x positions — the property the tooltip relies on. + expect(newData.map(([x]) => x)).toEqual(baseData.map(([x]) => x)); + + // Spot-check linear interpolation near the midpoint x = 20. + expect(baseData[511][1]).toBeCloseTo(0.2, 2); + expect(baseData[512][1]).toBeCloseTo(0.2, 2); + }); + + it('zeroes out density outside each source KDE range', () => { + // Disjoint ranges: base spans [10..30], new spans [50..70]. The shared grid + // covers the union [10..70], so each curve is 0 where it has no support. + (fftkde as jest.Mock) + .mockImplementationOnce(() => ({ + x: [10, 20, 30], + y: [0.1, 0.2, 0.3], + bandwidth: 1, + })) + .mockImplementationOnce(() => ({ + x: [50, 60, 70], + y: [0.5, 0.6, 0.7], + bandwidth: 1, + })); + + render(); + + const option = getLatestEChartsOption(); + const series = option.series as LineSeriesOption[]; + const baseData = series[0].data as Array<[number, number]>; + const newData = series[1].data as Array<[number, number]>; + + // Around x = 50, base is past its upper bound (30) → 0. + const baseAtFifty = baseData.find(([x]) => x >= 50); + expect(baseAtFifty?.[1]).toBe(0); + + // Around x = 20, new is below its lower bound (50) → 0. + const newAtTwenty = newData.find(([x]) => x <= 20 && x >= 15); + expect(newAtTwenty?.[1]).toBe(0); + }); + + it("falls back to Silverman's rule when ISJ bandwidth selection throws", () => { + // safeKde tries ISJ first; if that throws, it retries with 'silverman'. + (fftkde as jest.Mock).mockImplementation((_data: number[], bw: string) => { + if (bw === 'ISJ') { + throw new Error('ISJ failed to converge'); + } + return { x: [10, 20, 30], y: [0.1, 0.2, 0.3], bandwidth: 1 }; + }); + + render(); + + const bandwidthArgs = (fftkde as jest.Mock).mock.calls.map( + (call) => call[1] as string, + ); + expect(bandwidthArgs).toContain('ISJ'); + expect(bandwidthArgs).toContain('silverman'); + + // The fallback output still drives a populated series. + const option = getLatestEChartsOption(); + const series = option.series as LineSeriesOption[]; + expect(series[0].data as unknown[]).toHaveLength(1024); + }); + + it('renders both series when one side is empty', () => { + // newValues=[] hits the computeStatisticsForRuns early-return guard and + // skips the KDE for that side entirely. + (fftkde as jest.Mock).mockImplementation(() => ({ + x: [10, 20, 30], + y: [0.1, 0.2, 0.3], + bandwidth: 1, + })); + + render(); + + const option = getLatestEChartsOption(); + const series = option.series as LineSeriesOption[]; + expect(series).toHaveLength(2); + // Base side has a resampled density curve. + expect(series[0].data as unknown[]).toHaveLength(1024); + // New side has no KDE — its data array stays empty. + expect(series[1].data).toEqual([]); + }); + + it('skips chart init when the container ref has no element attached', () => { + // Stub the chartContainerRef so its `.current` stays null — the init + // useEffect should early-return without calling echarts' init. + const stubContainerRef = {} as { current: unknown }; + Object.defineProperty(stubContainerRef, 'current', { + get: () => null, + set: () => { + /* swallow ref assignments so current stays null */ + }, + enumerable: true, + configurable: true, + }); + // CommonGraph's first useRef call is for chartContainerRef; only override + // that one. The second (chartInstanceRef) falls through to real React. + mockUseRefOverrides.push(stubContainerRef); + + const initMock = echartsInit as jest.Mock; + initMock.mockClear(); + + render(); + + expect(initMock).not.toHaveBeenCalled(); + + // Drain any leftover override so it can't leak into later tests. + mockUseRefOverrides.length = 0; + }); + + it('renders both series in a single tooltip block with the unit suffix', () => { + (fftkde as jest.Mock).mockImplementation(() => ({ + x: [10, 20, 30], + y: [0.1, 0.2, 0.3], + bandwidth: 1, + })); + + render(); + + const formatter = getTooltipFormatter(getLatestEChartsOption()); + + const kdeBaseParam: FormatterParam = { + seriesType: 'line', + seriesName: 'Base', + value: [5, 0.1], + marker: FAKE_BASE_MARKER, + }; + const kdeNewParam: FormatterParam = { + seriesType: 'line', + seriesName: 'New', + value: [5, 0.2], + marker: FAKE_NEW_MARKER, + }; + + // Combined (the trigger: 'axis' path): one header, both series' densities. + expect(formatter([kdeBaseParam, kdeNewParam])).toBe( + `Value: 5.00 (ms)
${FAKE_BASE_MARKER}Base: 0.1000
${FAKE_NEW_MARKER}New: 0.2000`, + ); + + // Single-param fallback (defensive normalisation when only one series is at x). + expect(formatter(kdeBaseParam)).toBe( + `Value: 5.00 (ms)
${FAKE_BASE_MARKER}Base: 0.1000`, + ); + }); + + it('formats x-axis tick labels: 2 dp for fractions, bare integer for whole numbers', () => { + (fftkde as jest.Mock).mockImplementation(() => ({ + x: [10, 20, 30], + y: [0.1, 0.2, 0.3], + bandwidth: 1, + })); + + render(); + + const option = getLatestEChartsOption(); + const xAxis = option.xAxis as { + axisLabel: { formatter: (value: number) => string }; + }; + const format = xAxis.axisLabel.formatter; + + // Whole numbers render without a trailing ".00". + expect(format(14)).toBe('14'); + expect(format(0)).toBe('0'); + expect(format(-7)).toBe('-7'); + + // Floats near integers (round-off) collapse to the bare integer. + expect(format(14 + 1e-12)).toBe('14'); + expect(format(14 - 1e-12)).toBe('14'); + + // Fractional values render with 2 decimal places. + expect(format(48.541)).toBe('48.54'); + expect(format(48.545)).toBe('48.55'); + expect(format(0.1)).toBe('0.10'); + expect(format(-3.14159)).toBe('-3.14'); + }); + + it('omits the unit suffix in the tooltip header when unit is null', () => { + (fftkde as jest.Mock).mockImplementation(() => ({ + x: [10, 20, 30], + y: [0.1, 0.2, 0.3], + bandwidth: 1, + })); + + render(); + + const formatter = getTooltipFormatter(getLatestEChartsOption()); + + const rendered = formatter([ + { + seriesType: 'line', + seriesName: 'Base', + value: [5, 0.1], + marker: '', + axisValue: 5, + }, + ]); + // No "(unit)" suffix after the value when unit is null. + expect(rendered).toBe('Value: 5.00
Base: 0.1000'); + }); +}); diff --git a/src/__tests__/CompareResults/ResultsView.test.tsx b/src/__tests__/CompareResults/ResultsView.test.tsx index 216585bdf..f77041cf5 100644 --- a/src/__tests__/CompareResults/ResultsView.test.tsx +++ b/src/__tests__/CompareResults/ResultsView.test.tsx @@ -2,8 +2,8 @@ import type { ReactElement } from 'react'; import fetchMock from '@fetch-mock/jest'; import userEvent from '@testing-library/user-event'; -import type { ScriptableContext } from 'chart.js'; -import { ChartProps, Line } from 'react-chartjs-2'; +import { init as echartsInit } from 'echarts'; +import type { EChartsOption, LineSeriesOption } from 'echarts'; import { loader } from '../../components/CompareResults/loader'; import ResultsView from '../../components/CompareResults/ResultsView'; @@ -12,6 +12,7 @@ import { Strings } from '../../resources/Strings'; import { Colors } from '../../styles/Colors'; import type { Repository } from '../../types/state'; import type { Framework } from '../../types/types'; +import { fftkde } from '../../utils/kde.js'; import { getLocationOrigin } from '../../utils/location'; import getTestData from '../utils/fixtures'; import { renderWithRouter, screen, waitFor } from '../utils/test-utils'; @@ -37,6 +38,50 @@ function renderWithRoute(component: ReactElement) { jest.mock('../../utils/location'); const mockedGetLocationOrigin = getLocationOrigin as jest.Mock; +// Wrap React.useRef so individual tests can substitute a stubbed ref for the +// next useRef call. The wrapper delegates to the real implementation when the +// override queue is empty, so other tests in this file are unaffected. +const mockUseRefOverrides: Array<{ current: unknown }> = []; + +jest.mock('react', () => { + const actualReact = jest.requireActual('react'); + // Spread loses non-enumerable React exports (Component, createElement, …) + // which react-router relies on; a Proxy lets us replace `useRef` while + // forwarding every other property to the real module. + return new Proxy(actualReact, { + get(target, prop, receiver) { + if (prop === 'useRef') { + return function wrappedUseRef(initialValue: T) { + const override = mockUseRefOverrides.shift(); + if (override !== undefined) { + return override; + } + return target.useRef(initialValue); + }; + } + return Reflect.get(target, prop, receiver) as unknown; + }, + }); +}); + +// Pull the latest EChartsOption that the chart component pushed via +// `instance.setOption(option)`. Each call to `init()` in the mock returns a +// fresh stub, so we walk through the init mock results to find the most +// recently-rendered chart's options. +function getLatestEChartsOption(): EChartsOption { + const initMock = echartsInit as jest.Mock; + for (let i = initMock.mock.results.length - 1; i >= 0; i--) { + const instance = initMock.mock.results[i].value as { + setOption: jest.Mock; + }; + const lastSetOption = instance.setOption.mock.calls.at(-1); + if (lastSetOption) { + return lastSetOption[0]; + } + } + throw new Error('No echarts setOption call captured'); +} + describe('Results View', () => { it('The table should match snapshot and other elements should be present in the page', async () => { renderWithRoute(); @@ -171,150 +216,23 @@ describe('Results View', () => { await screen.findByRole('region', { name: 'Revision Row Details' }), ).toMatchSnapshot(); - // 1. Test that the chart library is called with various datasets. - const MockedLine = Line as jest.Mock; - const chartProps = MockedLine.mock.calls[0][0] as ChartProps; - const datasets = chartProps.data.datasets; - expect(datasets).toHaveLength(3); - // The KDE dataset is too long to test here, but let's test the other - // elements. - const datasetsForKde = datasets.filter( - (dataset) => 'yAxisID' in dataset && dataset.yAxisID === 'yKde', - ); - expect(datasetsForKde).toMatchObject([ + // The expanded row renders the chart with the two KDE line series + // (Base, New). Formatter behaviour is covered in CommonGraph.test.tsx. + const option = getLatestEChartsOption(); + const series = option.series as LineSeriesOption[]; + expect(series).toHaveLength(2); + expect(series).toMatchObject([ { - yAxisID: 'yKde', - label: 'Base', - fill: false, - borderColor: Colors.ChartBase, + type: 'line', + name: 'Base', + lineStyle: { color: Colors.ChartBase }, }, { - yAxisID: 'yKde', - label: 'New', - fill: false, - borderColor: Colors.ChartNew, + type: 'line', + name: 'New', + lineStyle: { color: Colors.ChartNew }, }, ]); - - const datasetForScatter = datasets.find( - (dataset) => dataset.type === 'scatter', - ); - expect(datasetForScatter).toMatchSnapshot('Dataset for scatter'); - - // 2. Test the more complex tooltip functions with various use cases. - const labelFunction = - chartProps.options?.plugins?.tooltip?.callbacks?.label; - expect(labelFunction).toBeDefined(); - - const tooltipItemKdeBase = { - dataset: datasetsForKde[0], - parsed: { x: 5, y: 5 }, - }; - const tooltipItemKdeNew = { - dataset: datasetsForKde[1], - parsed: { x: 5, y: 5 }, - }; - const tooltipItemValueBase = { - dataset: datasetForScatter, - raw: { - x: '1.234', - y: 'Base', - }, - }; - const tooltipItemValueNew = { - dataset: datasetForScatter, - raw: { - x: '2.345', - y: 'New', - }, - }; - - expect( - labelFunction!.call( - // @ts-expect-error This object doesn't obey fully to the type - // description, but it's good enough to test our code. - { dataPoints: [tooltipItemKdeBase] }, - tooltipItemKdeBase, - ), - ).toBe('@ 5.00'); - expect( - labelFunction!.call( - // @ts-expect-error This object doesn't obey fully to the type - // description, but it's good enough to test our code. - { dataPoints: [tooltipItemValueBase] }, - tooltipItemValueBase, - ), - ).toBe('Base: 1.234'); - expect( - labelFunction!.call( - // @ts-expect-error This object doesn't obey fully to the type - // description, but it's good enough to test our code. - { dataPoints: [tooltipItemValueNew] }, - tooltipItemValueNew, - ), - ).toBe('New: 2.345'); - - // Also test the cases where there are 2 values at the same x point. - // The first item shows a summary of both values. - expect( - labelFunction!.call( - // @ts-expect-error This object doesn't obey fully to the type - // description, but it's good enough to test our code. - { dataPoints: [tooltipItemValueBase, { ...tooltipItemValueBase }] }, - tooltipItemValueBase, - ), - ).toBe('Base: 1.234 (×2)'); - // But the second item isn't displayed at all. - expect( - labelFunction!.call( - // @ts-expect-error This object doesn't obey fully to the type - // description, but it's good enough to test our code. - { dataPoints: [{ ...tooltipItemValueBase }, tooltipItemValueBase] }, - tooltipItemValueBase, - ), - ).toBe(''); - - // 3. Also test the complex color function - const labelColorFunction = - chartProps.options?.plugins?.tooltip?.callbacks?.labelColor; - expect(labelColorFunction).toBeDefined(); - - // @ts-expect-error This object doesn't obey fully to the type - // description, but it's good enough to test our code. - expect(labelColorFunction!(tooltipItemKdeBase)).toEqual({ - backgroundColor: Colors.ChartBase, - }); - // @ts-expect-error This object doesn't obey fully to the type - // description, but it's good enough to test our code. - expect(labelColorFunction!(tooltipItemKdeNew)).toEqual({ - backgroundColor: Colors.ChartNew, - }); - // @ts-expect-error This object doesn't obey fully to the type - // description, but it's good enough to test our code. - expect(labelColorFunction!(tooltipItemValueBase)).toEqual({ - backgroundColor: Colors.ChartBase, - }); - // @ts-expect-error This object doesn't obey fully to the type - // description, but it's good enough to test our code. - expect(labelColorFunction!(tooltipItemValueNew)).toEqual({ - backgroundColor: Colors.ChartNew, - }); - - // 4. Also test the background color function for the scatter graph - const backgroundColorFunction = datasetForScatter?.backgroundColor as ( - ctx: ScriptableContext<'line'>, - ) => string | undefined; - expect(backgroundColorFunction).toBeInstanceOf(Function); - // @ts-expect-error This object doesn't obey fully to the type - // description, but it's good enough to test our code. - expect(backgroundColorFunction({ raw: { x: 5, y: 'Base' } })).toBe( - Colors.ChartBase + '99', - ); - // @ts-expect-error This object doesn't obey fully to the type - // description, but it's good enough to test our code. - expect(backgroundColorFunction({ raw: { x: 5, y: 'New' } })).toBe( - Colors.ChartNew + '99', - ); }); it('Should display Base, New and Common graphs with replicates', async () => { @@ -350,18 +268,18 @@ describe('Results View', () => { await screen.findByRole('region', { name: 'Revision Row Details' }), ).toMatchSnapshot(); - // Test that this time all replicates are displayed - const MockedLine = Line as jest.Mock; - const chartProps = MockedLine.mock.calls[0][0] as ChartProps; - const datasets = chartProps.data.datasets; - const datasetForScatter = datasets.find( - (dataset) => dataset.type === 'scatter', + // The KDE for each side should be built off the replicates, not the + // single-point base_runs/new_runs. Assert fftkde was called with each + // replicate array. + const fftkdeCalls = (fftkde as jest.Mock).mock.calls.map( + (call) => call[0] as number[], + ); + expect(fftkdeCalls).toContainEqual( + testCompareDataWithReplicates[0].base_runs_replicates, ); - expect(datasetForScatter!.data).toHaveLength( - testCompareDataWithReplicates[0].base_runs_replicates.length + - testCompareDataWithReplicates[0].new_runs_replicates.length, + expect(fftkdeCalls).toContainEqual( + testCompareDataWithReplicates[0].new_runs_replicates, ); - expect(datasetForScatter).toMatchSnapshot('Dataset for scatter'); }); it('should make blobUrl available when "Download JSON" button is clicked', async () => { diff --git a/src/__tests__/CompareResults/RevisionRow.test.tsx b/src/__tests__/CompareResults/RevisionRow.test.tsx index 8b4c2be4f..c1134f514 100644 --- a/src/__tests__/CompareResults/RevisionRow.test.tsx +++ b/src/__tests__/CompareResults/RevisionRow.test.tsx @@ -324,6 +324,108 @@ describe('Expanded row', () => { expect(emptySignificant[0]).toBeInTheDocument(); }); + it('should display median diff and 95% CI alerts when base/new runs are present', async () => { + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + const { testCompareMannWhitneyData: rowData } = getTestData(); + + renderWithRoute( + , + ); + + const expandRowButton = await screen.findByTestId(/ExpandMoreIcon/); + await user.click(expandRowButton); + + // Wait for the expanded panel before reading alert text content. + await screen.findByText(/Cliff's Delta/); + + // The summary alert is "Δ median = +7.6 ms 95% CI [..., ...]". The + // median-of-new minus median-of-base is 712.44 - 704.84 = +7.6. + const alerts = screen.getAllByRole('alert'); + const summaryAlert = alerts.find((alert) => + alert.textContent?.includes('Δ median'), + ); + expect(summaryAlert).toBeDefined(); + expect(summaryAlert?.textContent).toContain('+7.6'); + expect(summaryAlert?.textContent).toContain('ms 95% CI ['); + + // The confidence-interval alert is "Confidence Interval: We are 95% ...". + const ciAlert = alerts.find((alert) => + alert.textContent?.includes('Confidence Interval'), + ); + expect(ciAlert).toBeDefined(); + expect(ciAlert?.textContent).toContain( + 'We are 95% confident the median difference is between', + ); + }); + + it('should not render the CI alerts when base/new runs are empty', async () => { + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + const { testCompareMannWhitneyData: rowData } = getTestData(); + + // rowData[3] has empty base_runs / new_runs, so bootstrapMedianDiffCI is + // skipped and neither alert should render. + renderWithRoute( + , + ); + + const expandRowButton = await screen.findByTestId(/ExpandMoreIcon/); + await user.click(expandRowButton); + + // Other content from renderExpandedRight is present, so the panel is + // expanded — but there should be no Δ median or Confidence Interval alert. + await screen.findByText(/Cliff's Delta/); + expect(screen.queryByText(/Δ median/)).not.toBeInTheDocument(); + expect(screen.queryByText(/Confidence Interval/)).not.toBeInTheDocument(); + }); + + it('should mark the median diff alert as not significant when CI straddles zero', async () => { + const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime }); + const { testCompareMannWhitneyData: rowData } = getTestData(); + + // Identical base and new arrays produce a bootstrap CI symmetric around + // zero, so the difference is not statistically significant. + const overlapping: MannWhitneyResultsItem = { + ...rowData[0], + base_runs: [100, 110, 120], + new_runs: [100, 110, 120], + }; + + renderWithRoute( + , + ); + + const expandRowButton = await screen.findByTestId(/ExpandMoreIcon/); + await user.click(expandRowButton); + + await screen.findByText(/Cliff's Delta/); + const alerts = screen.getAllByRole('alert'); + const summaryAlert = alerts.find((alert) => + alert.textContent?.includes('Δ median'), + ); + expect(summaryAlert?.textContent).toContain('(not significant)'); + }); + it('should display mean for base or new in row headers for mann-whitney-u testVersion', async () => { const { testCompareMannWhitneyData: rowData } = getTestData(); renderWithRoute( diff --git a/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap b/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap index 7f03d434b..8f397bd6d 100644 --- a/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap +++ b/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap @@ -33,7 +33,9 @@ exports[`Results View Should display Base, New and Common graphs with replicates
- chartjs-line +
@@ -170,6 +172,83 @@ exports[`Results View Should display Base, New and Common graphs with replicates + + @@ -373,54 +452,6 @@ exports[`Results View Should display Base, New and Common graphs with replicates `; -exports[`Results View Should display Base, New and Common graphs with replicates: Dataset for scatter 1`] = ` -{ - "backgroundColor": [Function], - "data": [ - { - "x": 587.15, - "y": "Base", - }, - { - "x": 593.04, - "y": "Base", - }, - { - "x": 600.7, - "y": "Base", - }, - { - "x": 602.04, - "y": "Base", - }, - { - "x": 605.16, - "y": "New", - }, - { - "x": 605.31, - "y": "New", - }, - { - "x": 605.61, - "y": "New", - }, - { - "x": 605.81, - "y": "New", - }, - { - "x": 607.27, - "y": "New", - }, - ], - "pointRadius": 7, - "pointStyle": "triangle", - "type": "scatter", - "yAxisID": "yValues", -} -`; - exports[`Results View Should display Base, New and Common graphs with tooltips 1`] = `
- chartjs-line +
@@ -591,6 +624,82 @@ exports[`Results View Should display Base, New and Common graphs with tooltips 1 + + @@ -794,54 +903,6 @@ exports[`Results View Should display Base, New and Common graphs with tooltips 1
`; -exports[`Results View Should display Base, New and Common graphs with tooltips: Dataset for scatter 1`] = ` -{ - "backgroundColor": [Function], - "data": [ - { - "x": 587.15, - "y": "Base", - }, - { - "x": 593.04, - "y": "Base", - }, - { - "x": 600.7, - "y": "Base", - }, - { - "x": 602.04, - "y": "Base", - }, - { - "x": 605.16, - "y": "New", - }, - { - "x": 605.31, - "y": "New", - }, - { - "x": 605.61, - "y": "New", - }, - { - "x": 605.81, - "y": "New", - }, - { - "x": 607.27, - "y": "New", - }, - ], - "pointRadius": 7, - "pointStyle": "triangle", - "type": "scatter", - "yAxisID": "yValues", -} -`; - exports[`Results View Should update url with new title and the table with the new title: After clicking the Save button 1`] = ` { }); }); -jest.mock('react-chartjs-2', () => ({ - Line: jest.fn(), +// Mock echarts so that jsdom-based tests don't try to render to a real canvas. +// `init` returns a stub instance whose `setOption` calls can be inspected. +jest.mock('echarts', () => ({ + init: jest.fn(), })); -const MockedLine = Line as jest.Mock; +const MockedEchartsInit = echartsInit as jest.Mock; -jest.mock('fast-kde', () => ({ - density1d: jest.fn(), +jest.mock('../../utils/kde.js', () => ({ + fftkde: jest.fn(), })); -const MockedDensity1d = density1d as jest.Mock; +const MockedFftkde = fftkde as jest.Mock; Object.defineProperty(window, 'crypto', { value: webcrypto }); beforeEach(() => { // After every test jest resets the mock implementation, so we need to define // it again for each test. - MockedLine.mockImplementation(() => 'chartjs-line'); - MockedDensity1d.mockImplementation(() => 'fast-kde'); + MockedEchartsInit.mockImplementation(() => ({ + setOption: jest.fn(), + resize: jest.fn(), + dispose: jest.fn(), + on: jest.fn(), + off: jest.fn(), + })); + MockedFftkde.mockImplementation(() => ({ x: [], y: [], bandwidth: 1 })); }); // Install the fetch mock globally diff --git a/src/common/testVersions/mannWhitney.tsx b/src/common/testVersions/mannWhitney.tsx index ca01c09e6..b858ebb5d 100644 --- a/src/common/testVersions/mannWhitney.tsx +++ b/src/common/testVersions/mannWhitney.tsx @@ -15,6 +15,7 @@ import { MannWhitneyResultsItem, } from '../../types/state'; import { TableConfig } from '../../types/types'; +import { bootstrapMedianDiffCI } from '../../utils/bootstrap-ci'; import { formatNumber } from '../../utils/format'; import { capitalize } from '../../utils/helpers'; import { getBrowserDisplay, getPlatformShortName } from '../../utils/platform'; @@ -408,6 +409,28 @@ export const mannWhitneyStrategy = { ? capitalize(mwResult.mann_whitney_test.interpretation) : ''; + const baseRuns = mwResult.base_runs ?? []; + const newRuns = mwResult.new_runs ?? []; + const ci = + baseRuns.length > 0 && newRuns.length > 0 + ? bootstrapMedianDiffCI(baseRuns, newRuns) + : null; + const fmt = (n: number) => (n >= 0 ? '+' : '') + n.toFixed(1); + const summary = ci ? ( + + Δ median = {fmt(ci.medianDiff)} ms 95% CI [ + {fmt(ci.ciLow)}, {fmt(ci.ciHigh)}] + {ci.significant ? '' : ' (not significant)'} + + ) : null; + const confidenceInterval = ci && ( + + Confidence Interval: We are 95% confident the median + difference is between {fmt(ci.ciLow)} and{' '} + {fmt(ci.ciHigh)} + + ); + return ( <> Effect Size: {mann_whitney_u_cles} + {summary && {summary}} + {confidenceInterval && ( + {confidenceInterval} + )} ); diff --git a/src/components/CompareResults/CommonGraph.tsx b/src/components/CompareResults/CommonGraph.tsx index a43215c8c..661885ccb 100644 --- a/src/components/CompareResults/CommonGraph.tsx +++ b/src/components/CompareResults/CommonGraph.tsx @@ -1,22 +1,13 @@ +import { useEffect, useMemo, useRef } from 'react'; + import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; -import { - Chart as ChartJS, - LineElement, - LinearScale, - ScriptableContext, - type TooltipItem, - type TooltipModel, -} from 'chart.js'; -import 'chart.js/auto'; -import * as kde from 'fast-kde'; -import { Line } from 'react-chartjs-2'; +import { init, type ECharts, type EChartsOption } from 'echarts'; import { Colors } from '../../styles/Colors'; +import { fftkde } from '../../utils/kde.js'; -ChartJS.register(LinearScale, LineElement); - -// This computes the min, max and the KDE bandwidth from a list of numbers. +// This computes the min, max from a list of numbers. function computeStatisticsForRuns(data: number[]) { if (!data.length) { return null; @@ -25,47 +16,11 @@ function computeStatisticsForRuns(data: number[]) { const sorted = [...data].sort((a, b) => a - b); return { - min: quantileSorted(sorted, 0), - max: quantileSorted(sorted, 1), - bandwidth: approximateSJBandwidth(sorted), + min: sorted[0], + max: sorted[sorted.length - 1], }; } -// This logic approximates the Sheather and Jones algorithm according to ChatGPT. -// In the future we might want to compute a better value, see -// https://bugzilla.mozilla.org/show_bug.cgi?id=1901248 for some ideas. -function approximateSJBandwidth(sorted: number[]): number { - const n = sorted.length; - if (n < 2) return sorted[0] * 0.0015; - - const q25 = quantileSorted(sorted, 0.25); - const q75 = quantileSorted(sorted, 0.75); - const iqr = q75 - q25; - - const mean = sorted.reduce((a, b) => a + b, 0) / n; - const std = Math.sqrt( - sorted.reduce((sum, x) => sum + Math.pow(x - mean, 2), 0) / n, - ); - - const sigma = Math.min(std, iqr / 1.34); // Robust estimate - const h = 0.9 * sigma * Math.pow(n, -1 / 5); - - return h; -} - -// This function returns a quantile from a sorted array of numbers. -function quantileSorted(sorted: number[], q: number): number { - const pos = (sorted.length - 1) * q; - const base = Math.floor(pos); - const rest = pos - base; - - if (sorted[base + 1] !== undefined) { - return sorted[base] + rest * (sorted[base + 1] - sorted[base]); - } else { - return sorted[base]; - } -} - // A simple wrapper to Math.min, resilient when one of the numbers is undefined or null. function computeMin(a?: number, b?: number) { a ??= Infinity; @@ -80,255 +35,237 @@ function computeMax(a?: number, b?: number) { return Math.max(a, b); } -function CommonGraph({ baseValues, newValues, unit }: CommonGraphProps) { - const statsForBase = computeStatisticsForRuns(baseValues); - const statsForNew = computeStatisticsForRuns(newValues); - - // Compute the global min and max with some grace value. - const min = computeMin(statsForBase?.min, statsForNew?.min) * 0.95; - const max = computeMax(statsForBase?.max, statsForNew?.max) * 1.05; - - // The KDE line chart and categorical bubble chart share an x-axis but use - // entirely different y-scales, making the composition flexible but - // non-trivial. - const options = { - // Make the chart responsive to container size - responsive: true, - // Allow the chart to stretch freely, not keeping a fixed aspect ratio. This - // needs the container's size to be well defined. - maintainAspectRatio: false, - plugins: { - legend: { - // Hide the default legend (labels for datasets) - display: false, - }, - - tooltip: { - // Allow tooltips to appear even when not directly intersecting a point - intersect: false, - callbacks: { - // Suppress the tooltip title (normally shows the x-value) - title: () => '', - - // Customize tooltip labels depending on the dataset type - label( - this: TooltipModel<'line'> | TooltipModel<'scatter'>, - tooltipItem: TooltipItem<'line'> | TooltipItem<'scatter'>, - ) { - switch (tooltipItem.dataset.yAxisID) { - case 'yKde': { - // KDE line: show only the x-value with optional unit - if (tooltipItem.parsed.x === null) { - return ''; - } - const x = tooltipItem.parsed.x.toFixed(2); - return `@ ${x}` + (unit ? ` (${unit})` : ''); - } - case 'yValues': { - // For the bubble chart: display only one summary line, even if - // multiple points overlap - if ( - this.dataPoints.length > 1 && - this.dataPoints[0] !== tooltipItem - ) { - return ''; - } - - const point = tooltipItem.raw as { - x: number; - y: 'Base' | 'New'; - }; - // Example: "Base: 42 (ms) (×3)" - const labelString = `${point.y}: ${point.x}`; - const unitString = unit ? ` (${unit})` : ''; - const summaryString = - this.dataPoints.length > 1 - ? ` (×${this.dataPoints.length})` - : ''; - return labelString + unitString + summaryString; - } - default: - return ''; - } - }, - - // Explicitly set the color of the square shown next to each tooltip label - labelColor: ( - tooltipItem: TooltipItem<'line'> | TooltipItem<'scatter'>, - ) => { - const { dataset, raw } = tooltipItem; - - let source: 'Base' | 'New' | undefined; - - if (dataset.yAxisID === 'yKde') { - // KDE lines distinguish between Base and New by label - source = dataset.label === 'Base' ? 'Base' : 'New'; - } else if (dataset.yAxisID === 'yValues') { - // Scatter chart: use the y-value ("Base" or "New") stored in the raw data - source = (raw as { y: 'Base' | 'New' }).y; - } +const CHART_HEIGHT = 300; +const KDE_GRID_POINTS = 1024; + +// ISJ bandwidth selection can fail to converge on tiny or degenerate samples +// (few unique values, near-identical numbers). Fall back to Silverman's rule +// in that case — coarser, but it never fails. +function safeKde(values: number[]) { + if (values.length < 2) return null; + try { + return fftkde(values, 'ISJ', undefined, KDE_GRID_POINTS); + } catch { + return fftkde(values, 'silverman', undefined, KDE_GRID_POINTS); + } +} - if (source) { - return { - backgroundColor: - source === 'Base' ? Colors.ChartBase : Colors.ChartNew, - }; - } +// Linearly resample a uniform-grid KDE curve onto an arbitrary target x array. +// Outside the source range we return 0: each KDE's grid is padded so its +// density has already tapered to ≈0 at the edges. +function resampleOnto( + srcX: ArrayLike, + srcY: ArrayLike, + targetX: number[], +): number[] { + const n = srcX.length; + const lo = srcX[0]; + const hi = srcX[n - 1]; + const step = (hi - lo) / (n - 1); + const out = new Array(targetX.length); + for (let i = 0; i < targetX.length; i++) { + const x = targetX[i]; + if (x < lo || x > hi) { + out[i] = 0; + continue; + } + // Clamp the lower index so x === hi lands on j = n-2 with frac = 1. + const t = (x - lo) / step; + const j = Math.min(Math.floor(t), n - 2); + const frac = t - j; + out[i] = srcY[j] * (1 - frac) + srcY[j + 1] * frac; + } + return out; +} - // Fallback color if the dataset is not recognized - return { - backgroundColor: 'rgba(0,0,0,0)', - }; - }, - }, - // Show color boxes (one per label, unless suppressed in labelColor) - displayColors: true, - padding: 10, - boxPadding: 4, - }, - }, - scales: { - x: { - type: 'linear' as const, - suggestedMin: min, - suggestedMax: max, - grid: { - display: false, // Hide vertical grid lines - offset: false, +function CommonGraph({ baseValues, newValues, unit }: CommonGraphProps) { + const chartContainerRef = useRef(null); + const chartInstanceRef = useRef(null); + + const option: EChartsOption = useMemo(() => { + const statsForBase = computeStatisticsForRuns(baseValues); + const statsForNew = computeStatisticsForRuns(newValues); + + // Compute the global min and max with some grace value. + const min = computeMin(statsForBase?.min, statsForNew?.min) * 0.95; + const max = computeMax(statsForBase?.max, statsForNew?.max) * 1.05; + + // ISJ auto-selects the bandwidth per dataset, so each KDE tunes itself. + const bKde = safeKde(baseValues); + const nKde = safeKde(newValues); + + // Build a shared x-grid covering both KDEs' ranges. Resampling both + // curves onto identical x positions is what lets the axis-trigger tooltip + // pick up Base AND New at the cursor's x position, + // instead of just one series or the other. + const xStart = computeMin(bKde?.x[0], nKde?.x[0]); + const xEnd = computeMax( + bKde?.x[bKde.x.length - 1], + nKde?.x[nKde.x.length - 1], + ); + const sharedX: number[] = []; + if (Number.isFinite(xStart) && Number.isFinite(xEnd) && xEnd > xStart) { + for (let i = 0; i < KDE_GRID_POINTS; i++) { + sharedX.push(xStart + ((xEnd - xStart) * i) / (KDE_GRID_POINTS - 1)); + } + } + + const baseY = bKde ? resampleOnto(bKde.x, bKde.y, sharedX) : []; + const newY = nKde ? resampleOnto(nKde.x, nKde.y, sharedX) : []; + + const baseRunsDensity: [number, number][] = bKde + ? sharedX.map((xCoord, i) => [xCoord, baseY[i]]) + : []; + const newRunsDensity: [number, number][] = nKde + ? sharedX.map((xCoord, i) => [xCoord, newY[i]]) + : []; + + const unitSuffix = unit ? ` (${unit})` : ''; + + return { + animation: false, + grid: { left: 70, right: 70, top: 28, height: 200 }, + xAxis: { + type: 'value', + min, + max, + name: unit ?? '', + nameLocation: 'middle', + nameGap: 30, + nameTextStyle: { + fontSize: 13, + fontWeight: 'bold', + color: '#000', }, - title: { - align: 'end' as const, - display: true, - text: `${unit} →`, // Example: "ms →" + // Tick labels show 2 dp for fractional values, drop ".00" for whole + // numbers. Floats near integers (e.g. 14 + 1e-15) collapse to "14". + axisLabel: { + formatter: (value: number) => { + const rounded = Math.round(value); + if (Math.abs(value - rounded) < 1e-9) return String(rounded); + return value.toFixed(2); + }, }, + splitLine: { show: true, lineStyle: { color: '#eee' } }, + axisLine: { show: true, lineStyle: { color: '#999' } }, }, - yKde: { - type: 'linear', // Linear scale - stack: 'y', // yKde and yValues are part of the same stack - stackWeight: 3, // Larger stack weight means more vertical space - weight: 3, // Larger weight ensures it's on top - beginAtZero: true, - grace: '3%', // Add margin at the top of the axis range - grid: { - drawBorder: false, - display: false, // No horizontal grid lines for KDE - offset: false, - }, - ticks: { - beginAtZero: true, - display: true, - }, + yAxis: { + type: 'value', + min: 0, + splitLine: { show: true, lineStyle: { color: '#eee' } }, + axisLine: { show: true, lineStyle: { color: '#999' } }, + axisTick: { show: false }, + axisLabel: { show: true, color: '#000', fontSize: 12 }, }, - - // Spacer axis to visually separate KDE and scatter plots - // This doesn't display anything. - ySpacer: { - type: 'linear', - stack: 'y', - stackWeight: 0.5, // Takes less space than yKde - weight: 2, // Appears between yKde and yValues - display: false, // Invisible axis (No ticks or grids) - grid: { - display: false, + // Wheel to zoom on the x-axis; shift+drag pans. + // filterMode: 'none' keeps every data point in place — the zoom only + // changes the visible window, so KDE curves still extend to the edges. + dataZoom: [ + { + type: 'inside', + xAxisIndex: 0, + filterMode: 'none', + zoomOnMouseWheel: true, + moveOnMouseMove: 'shift', + moveOnMouseWheel: false, }, - ticks: { - display: false, + { + type: 'slider', + xAxisIndex: 0, + filterMode: 'none', + height: 16, + bottom: 4, + showDetail: false, + brushSelect: false, }, - }, - yValues: { - type: 'category', - stack: 'y', - stackWeight: 1, // Smaller stack weight means it takes less space - weight: 1, // Appears at the bottom - labels: ['Base', 'New'], - offset: true, // Adds extra padding for visual separation - ticks: { - autoSkip: false, // Show both labels even if close together + ], + tooltip: { + trigger: 'axis', + // Vertical guide that snaps to data points, so the tooltip locks onto + // a single x position with both series' densities side by side. + axisPointer: { type: 'line', snap: true, lineStyle: { color: '#999' } }, + padding: 10, + formatter: (params) => { + const items = Array.isArray(params) ? params : [params]; + if (items.length === 0) return ''; + // axisValue is the snapped x position shared by all series; fall back + // to the first item's x when it's absent (e.g. tooltip invoked + // outside the axis-trigger path). + const axisX = + (items[0] as { axisValue?: number }).axisValue ?? + (items[0].value as [number, number])[0]; + const header = `Value: ${Number(axisX).toFixed(2)}${unitSuffix}`; + const lines = items.map((pts) => { + const marker = typeof pts.marker === 'string' ? pts.marker : ''; + const seriesName = pts.seriesName ?? ''; + const y = (pts.value as [number, number])[1]; + return `${marker}${seriesName}: ${y.toFixed(4)}`; + }); + return [header, ...lines].join('
'); }, }, - }, - elements: { - // These ones will be used for the 2 KDE datasets. - // When needed, they will be overridden in the "scatter" dataset. - line: { - borderWidth: 3, // Thickness of KDE curves - }, - point: { - pointRadius: 0, // Points on line chart are invisible - pointHoverRadius: 5, // But they respond to hover + toolbox: { + feature: { restore: {}, saveAsImage: {} }, + right: 8, + top: 4, + itemSize: 12, }, - }, - interaction: { - // Show tooltip for the closest point (across all datasets) - mode: 'nearest', - // Only show tooltip if the mouse intersects the actual shape - intersect: true, - }, - }; - - ///////////////// START SHOW VALUES //////////////////////// - const baseValuesData = baseValues.map((v) => { - return { x: v, y: 'Base' }; - }); - const newValuesData = newValues.map((v) => { - return { x: v, y: 'New' }; - }); - - const allValuesData = [...baseValuesData, ...newValuesData]; - - //////////////////// START FAST KDE //////////////////////// - // So that the 2 KDE graphs are visually comparable, it's important to use the - // same bandwidth for both. - const bandwidth = computeMin(statsForBase?.bandwidth, statsForNew?.bandwidth); - - const baseRunsDensity = Array.from( - kde.density1d(baseValues, { - bandwidth, - extent: [min, max], - }), - ); - const newRunsDensity = Array.from( - kde.density1d(newValues, { - bandwidth, - extent: [min, max], - }), - ); - //////////////////// END FAST KDE //////////////////////// - - const data = { - datasets: [ - { - // First KDE line: density of the "Base" distribution - yAxisID: 'yKde', - label: 'Base', - data: baseRunsDensity, - fill: false, - borderColor: Colors.ChartBase, - }, - { - // Second KDE line: density of the "New" distribution - yAxisID: 'yKde', - label: 'New', - data: newRunsDensity, - fill: false, - borderColor: Colors.ChartNew, - }, - { - // Bubble chart layer: raw values from both distributions (shown as points) - yAxisID: 'yValues', - type: 'scatter', - pointStyle: 'triangle', - // Adjust point size based on dataset size (smaller points if there's a lot of data) - pointRadius: allValuesData.length < 20 ? 7 : 5, - data: allValuesData, - // Color code points by category using dynamic function - backgroundColor: (context: ScriptableContext<'scatter'>) => - ((context.raw as { y: 'Base' | 'New' }).y === 'Base' - ? Colors.ChartBase - : Colors.ChartNew) + '99', // Add 60% transparency to the hexadecimal color + legend: { + data: ['Base', 'New'], + top: 4, + left: 'center', + itemHeight: 10, + itemWidth: 30, }, - ], - }; + series: [ + { + name: 'Base', + type: 'line', + triggerLineEvent: true, + xAxisIndex: 0, + yAxisIndex: 0, + data: baseRunsDensity, + showSymbol: false, + lineStyle: { width: 3, color: Colors.ChartBase }, + itemStyle: { color: Colors.ChartBase }, + emphasis: { focus: 'none' }, + }, + { + name: 'New', + type: 'line', + triggerLineEvent: true, + xAxisIndex: 0, + yAxisIndex: 0, + data: newRunsDensity, + showSymbol: false, + lineStyle: { width: 3, color: Colors.ChartNew }, + itemStyle: { color: Colors.ChartNew }, + emphasis: { focus: 'none' }, + }, + ], + }; + }, [baseValues, newValues, unit]); + + useEffect(() => { + if (!chartContainerRef.current) { + return; + } + const instance = init(chartContainerRef.current); + chartInstanceRef.current = instance; + + const handleResize = () => instance.resize(); + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + instance.dispose(); + chartInstanceRef.current = null; + }; + }, []); + + useEffect(() => { + chartInstanceRef.current?.setOption(option, true); + }, [option]); return ( <> @@ -336,8 +273,10 @@ function CommonGraph({ baseValues, newValues, unit }: CommonGraphProps) { Runs Density Distribution - {/* @ts-expect-error the types for chart.js do not seem great and do not support all options. */} - +
); diff --git a/src/components/CompareResults/RevisionRowExpandable.tsx b/src/components/CompareResults/RevisionRowExpandable.tsx index 0a8697abd..33b6d9ba6 100644 --- a/src/components/CompareResults/RevisionRowExpandable.tsx +++ b/src/components/CompareResults/RevisionRowExpandable.tsx @@ -65,11 +65,13 @@ function RevisionRowExpandable(props: RevisionRowExpandableProps) { - + {(baseValues.length > 0 || newValues.length > 0) && ( + + )} {strategy.renderExpandedLeft(result)} diff --git a/src/utils/bootstrap-ci.ts b/src/utils/bootstrap-ci.ts new file mode 100644 index 000000000..f6e92eac5 --- /dev/null +++ b/src/utils/bootstrap-ci.ts @@ -0,0 +1,93 @@ +/** + * Percentile bootstrap confidence interval for the difference of medians. + * + * No external dependencies. + */ + +function median(arr: ArrayLike): number { + const s = new Float64Array(arr).sort(); + const m = s.length >> 1; + return s.length % 2 === 0 ? (s[m - 1] + s[m]) / 2 : s[m]; +} + +function resample(arr: Float64Array, rng: () => number): Float64Array { + const out = new Float64Array(arr.length); + for (let i = 0; i < arr.length; i++) { + out[i] = arr[Math.floor(rng() * arr.length)]; + } + return out; +} + +// Mulberry32 — fast seedable PRNG so results are reproducible. +function mulberry32(seed: number): () => number { + let s = seed >>> 0; + return () => { + s += 0x6d2b79f5; + let t = Math.imul(s ^ (s >>> 15), 1 | s); + t ^= t + Math.imul(t ^ (t >>> 7), 61 | t); + return ((t ^ (t >>> 14)) >>> 0) / 0x100000000; + }; +} + +export type BootstrapCI = { + medianDiff: number; + ciLow: number; + ciHigh: number; + significant: boolean; // CI does not contain 0 +}; + +/** + * Percentile bootstrap confidence interval for (median(newData) - median(base)). + * Matches scipy.stats.bootstrap(..., method="percentile", paired=False). + * + * How it works + * ------------ + * A confidence interval answers: "given the samples we observed, what is the + * plausible range for the true difference?" The bootstrap approach avoids + * assumptions about the underlying distribution (normality, etc.) by + * simulating the sampling process directly: + * + * 1. Draw nIter synthetic datasets by sampling WITH replacement from each + * input array (a "resample" — same size, but some values repeat and some + * are absent). + * 2. Compute (median(resampledNew) - median(resampledBase)) for each pair. + * 3. Sort the resulting nIter differences. + * 4. The CI is the [alpha/2, 1-alpha/2] percentile range of that distribution. + * + * If the CI does not straddle zero, the difference is statistically significant + * at the chosen alpha level. + * + * @param base - baseline sample values (e.g. before-patch timings) + * @param newData - new/comparison sample values (e.g. after-patch timings) + * @param nIter - number of bootstrap resamples; 1000 is sufficient for most + * uses, increase to 10 000 for publication-quality intervals + * @param alpha - two-tailed significance level: the CI covers (1-alpha) of the + * bootstrap distribution, e.g. 0.05 → 95% CI, 0.01 → 99% CI + * @param seed - PRNG seed; fix this to get reproducible results across runs + */ +export function bootstrapMedianDiffCI( + base: number[], + newData: number[], + nIter: number = 1000, + alpha: number = 0.05, + seed: number = 42, +): BootstrapCI { + const rng = mulberry32(seed); + const baseArr = new Float64Array(base); + const newArr = new Float64Array(newData); + const diffs = new Float64Array(nIter); + for (let i = 0; i < nIter; i++) { + diffs[i] = median(resample(newArr, rng)) - median(resample(baseArr, rng)); + } + diffs.sort(); + const loIdx = Math.floor((alpha / 2) * nIter); + const hiIdx = Math.min(Math.floor((1 - alpha / 2) * nIter), nIter - 1); + const ciLow = diffs[loIdx]; + const ciHigh = diffs[hiIdx]; + return { + medianDiff: median(newData) - median(base), + ciLow, + ciHigh, + significant: ciLow > 0 || ciHigh < 0, + }; +} diff --git a/src/utils/kde.d.ts b/src/utils/kde.d.ts new file mode 100644 index 000000000..63e12dc6a --- /dev/null +++ b/src/utils/kde.d.ts @@ -0,0 +1,61 @@ +/** + * Type declarations for kde.js. + * + * Depends on: nothing (self-contained) + * Consumed by: example.mjs, kde-widget.js (mode-fitting inlined there) + */ + +/** + * Faithful 1D port of KDEpy's FFTKDE with ISJ bandwidth for a Gaussian kernel. + * + * References: + * Botev, Z. I., Grotowski, J. F. and Kroese, D. P. (2010): + * Kernel density estimation via diffusion. Ann. Stat. 38(5), 2916-2957. + * Fan, J. and Marron, J. S. (1994): Fast implementations of nonparametric + * curve estimators. J. Comput. Graph. Stat. 3(1), 35-56. + * KDEpy: https://github.com/tommyod/KDEpy (MIT licence) + */ +export declare function dct2(x: number[]): number[]; +export declare function linearBinning1D( + data: number[], + gridPoints: number[], + weights?: number[], +): Float64Array; +export declare function autogrid1D( + data: number[], + boundaryAbs?: number, + numPoints?: number, + boundaryRel?: number, +): Float64Array; +export declare function improvedSheatherJones( + data: number[], + weights?: number[], +): number; +export declare function silvermansRule(data: number[]): number; +export type FFTKDEResult = { + x: number[]; + y: number[]; + bandwidth: number; +}; +export declare function fftkde( + data: number[], + bw?: number | 'ISJ' | 'silverman', + weights?: number[], + numGridPoints?: number, + boundary?: 'none' | 'reflection', +): FFTKDEResult; +export declare function argrelmax(y: number[], order?: number): number[]; +export type KDEModeResult = { + nModes: number; + peakLocs: number[]; + boundaries: number[]; + x: number[]; + y: number[]; + bandwidth: number; +}; +export declare function fitKdeModes( + data: number[], + valleyThreshold?: number, + minPeakFraction?: number, + minDataFraction?: number, +): KDEModeResult; diff --git a/src/utils/kde.js b/src/utils/kde.js new file mode 100644 index 000000000..26a4aadd4 --- /dev/null +++ b/src/utils/kde.js @@ -0,0 +1,755 @@ +/** + * Faithful 1D port of KDEpy's FFTKDE with ISJ bandwidth for a Gaussian kernel. + * + * References: + * Botev, Z. I., Grotowski, J. F. and Kroese, D. P. (2010): + * Kernel density estimation via diffusion. Ann. Stat. 38(5), 2916-2957. + * Fan, J. and Marron, J. S. (1994): Fast implementations of nonparametric + * curve estimators. J. Comput. Graph. Stat. 3(1), 35-56. + * KDEpy: https://github.com/tommyod/KDEpy (BSD 3-Clause licence) + */ +// --------------------------------------------------------------------------- +// FFT — Cooley-Tukey radix-2 DIT, in-place on Float64Arrays +// --------------------------------------------------------------------------- +function fftInPlace(re, im) { + const N = re.length; + // Bit-reversal permutation + let j = 0; + for (let i = 1; i < N; i++) { + let bit = N >> 1; + while (j & bit) { + j ^= bit; + bit >>= 1; + } + j ^= bit; + if (i < j) { + let t = re[i]; + re[i] = re[j]; + re[j] = t; + t = im[i]; + im[i] = im[j]; + im[j] = t; + } + } + // Butterfly passes + for (let len = 2; len <= N; len <<= 1) { + const half = len >> 1; + // W = exp(-2*pi*i/len) = exp(-pi*i/half) + const ang = -Math.PI / half; + const wRe = Math.cos(ang); + const wIm = Math.sin(ang); + for (let i = 0; i < N; i += len) { + let cRe = 1.0, + cIm = 0.0; + for (let k = 0; k < half; k++) { + const uRe = re[i + k]; + const uIm = im[i + k]; + const vRe = re[i + k + half] * cRe - im[i + k + half] * cIm; + const vIm = re[i + k + half] * cIm + im[i + k + half] * cRe; + re[i + k] = uRe + vRe; + im[i + k] = uIm + vIm; + re[i + k + half] = uRe - vRe; + im[i + k + half] = uIm - vIm; + const nextCRe = cRe * wRe - cIm * wIm; + cIm = cRe * wIm + cIm * wRe; + cRe = nextCRe; + } + } + } +} +// --------------------------------------------------------------------------- +// DCT-II — matches scipy.fftpack.dct(x, type=2, norm=None) +// Uses Lee's O(N log N) FFT-based reduction. N must be a power of 2. +// +// Algorithm: reorder as v[n]=x[2n], v[N-1-n]=x[2n+1], then +// y[k] = 2 * Re( FFT(v)[k] * exp(-i*pi*k/(2N)) ) +// --------------------------------------------------------------------------- +/** + * Type-II Discrete Cosine Transform. + * + * Used internally by improvedSheatherJones to transform the binned data into + * frequency space, where the ISJ fixed-point equation can be evaluated + * efficiently. Not needed directly in typical usage — call fftkde instead. + * + * @param x - real-valued array whose length must be a power of 2 + * @returns DCT-II coefficients, same length as x + */ +export function dct2(x) { + const N = x.length; + if (N < 2 || (N & (N - 1)) !== 0) + throw new Error(`dct2 requires power-of-2 length, got ${N}`); + const half = N >> 1; + const v = new Float64Array(N); + for (let n = 0; n < half; n++) { + v[n] = x[2 * n]; + v[N - 1 - n] = x[2 * n + 1]; + } + const re = new Float64Array(v); + const im = new Float64Array(N); + fftInPlace(re, im); + const y = new Array(N); + for (let k = 0; k < N; k++) { + const angle = (-Math.PI * k) / (2 * N); + y[k] = 2 * (re[k] * Math.cos(angle) - im[k] * Math.sin(angle)); + } + return y; +} +// --------------------------------------------------------------------------- +// Brent's root-finding — matches scipy.optimize.brentq +// --------------------------------------------------------------------------- +function brentq(f, a, b, xtol = 2e-12, rtol = 4.4e-16, maxIter = 100) { + let fa = f(a); + let fb = f(b); + if (fa === 0) return { x: a, converged: true }; + if (fb === 0) return { x: b, converged: true }; + if (fa * fb > 0) return { x: 0, converged: false }; + let c = b, + fc = fb; + let d = 0, + e = 0; + for (let iter = 0; iter < maxIter; iter++) { + if (fb * fc > 0) { + c = a; + fc = fa; + d = e = b - a; + } + if (Math.abs(fc) < Math.abs(fb)) { + a = b; + fa = fb; + b = c; + fb = fc; + c = a; + fc = fa; + } + const tol1 = 2 * rtol * Math.abs(b) + 0.5 * xtol; + const xm = 0.5 * (c - b); + if (Math.abs(xm) <= tol1 || fb === 0) return { x: b, converged: true }; + if (Math.abs(e) >= tol1 && Math.abs(fa) > Math.abs(fb)) { + let s = fb / fa; + let p, q; + if (a === c) { + p = 2 * xm * s; + q = 1 - s; + } else { + const r = fb / fc; + q = fa / fc; + p = s * (2 * xm * q * (q - r) - (b - a) * (r - 1)); + q = (q - 1) * (r - 1) * (s - 1); + } + if (p > 0) q = -q; + else p = -p; + if (2 * p < Math.min(3 * xm * q - Math.abs(tol1 * q), Math.abs(e * q))) { + e = d; + d = p / q; + } else { + d = xm; + e = xm; + } + } else { + d = xm; + e = xm; + } + a = b; + fa = fb; + b += Math.abs(d) > tol1 ? d : xm > 0 ? tol1 : -tol1; + fb = f(b); + } + return { x: b, converged: false }; +} +// --------------------------------------------------------------------------- +// 1D linear binning — port of KDEpy's linbin_cython / linbin_numpy +// +// Each data point distributes its weight linearly to the two nearest grid +// points (floor and ceil), proportional to the fractional distance. +// Returns a Float64Array of length gridPoints.length that sums to 1. +// --------------------------------------------------------------------------- +/** + * Bin scattered data onto a uniform grid using linear (tent) weighting. + * + * Rather than placing each data point in a single bucket (histogram-style), + * each point splits its weight between its two nearest grid neighbours + * proportionally to how close it is to each. This avoids the sharp edges + * of ordinary histograms and is required before FFT-based convolution. + * + * @param data - raw sample values + * @param gridPoints - uniformly-spaced grid positions (e.g. from autogrid1D) + * @param weights - per-sample weights; if omitted, all samples are equal + * @returns Float64Array of length gridPoints.length that sums to ≈1 + */ +export function linearBinning1D(data, gridPoints, weights) { + const G = gridPoints.length; + const minGrid = gridPoints[0]; + const maxGrid = gridPoints[G - 1]; + const dx = (maxGrid - minGrid) / (G - 1); + // Normalised weights + let w; + if (weights !== undefined) { + const wSum = weights.reduce((a, b) => a + b, 0); + w = weights.map((wi) => wi / wSum); + } else { + const uni = 1 / data.length; + w = new Array(data.length).fill(uni); + } + // Extra element absorbs any data point that lands exactly on the upper edge + const result = new Float64Array(G + 1); + for (let i = 0; i < data.length; i++) { + const t = (data[i] - minGrid) / dx; + const lo = Math.floor(t); + const frac = t - lo; + if (lo >= 0 && lo < G) result[lo] += w[i] * (1 - frac); + if (lo + 1 <= G) result[lo + 1] += w[i] * frac; + } + return result.slice(0, G); +} +// --------------------------------------------------------------------------- +// 1D autogrid — port of KDEpy's autogrid for 1D +// --------------------------------------------------------------------------- +/** + * Build a uniform evaluation grid that spans the data range with padding. + * + * The padding prevents the KDE from dropping to zero too abruptly at the + * edges of the observed data, which would distort bandwidth estimation. + * The grid size should be a power of 2 for efficient FFT convolution. + * + * @param data - raw sample values (used only to find min/max) + * @param boundaryAbs - minimum padding in data units on each side (default 3) + * @param numPoints - number of grid points; use a power of 2 (default 1024) + * @param boundaryRel - padding as a fraction of the data range (default 0.05) + * @returns Float64Array of numPoints evenly-spaced x values + */ +export function autogrid1D( + data, + boundaryAbs = 3, + numPoints = 1024, + boundaryRel = 0.05, +) { + let minData = data[0], + maxData = data[0]; + for (let i = 1; i < data.length; i++) { + if (data[i] < minData) minData = data[i]; + if (data[i] > maxData) maxData = data[i]; + } + const range = maxData - minData; + const outside = Math.max(boundaryRel * range, boundaryAbs); + const lo = minData - outside; + const hi = maxData + outside; + const grid = new Float64Array(numPoints); + for (let i = 0; i < numPoints; i++) { + grid[i] = lo + ((hi - lo) * i) / (numPoints - 1); + } + return grid; +} +// --------------------------------------------------------------------------- +// Gaussian kernel (1D) +// K(x, bw) = exp(-x^2 / (2*bw^2)) / (sqrt(2*pi) * bw) +// This is the standard Gaussian PDF with std = bw, matching KDEpy's +// Kernel(gaussian, var=1).evaluate(x, bw, norm=2) in 1D. +// --------------------------------------------------------------------------- +function gaussianKernel1D(x, bw) { + return Math.exp((-x * x) / (2 * bw * bw)) / (Math.sqrt(2 * Math.PI) * bw); +} +// Find x > 0 where Gaussian kernel drops to atol — matches +// KDEpy's Kernel.practical_support(bw, atol=10e-5) for Gaussian. +// 10e-5 in Python == 1e-4. +function gaussianPracticalSupport(bw, atol = 10e-5) { + const xtol = 1e-3; + const result = brentq((x) => gaussianKernel1D(x, bw) - atol, 0, 8 * bw, xtol); + if (!result.converged) { + throw new Error('Could not find practical support for Gaussian kernel.'); + } + return result.x + xtol; +} +// --------------------------------------------------------------------------- +// ISJ fixed-point function — port of KDEpy's _fixed_point +// +// Implements the fixed-point equation t = ξ γ^5(t) from Botev et al. (2010). +// I_sq = [1², 2², ..., (n-1)²] (length n-1) +// a2 = dct[1:]² (length n-1) +// --------------------------------------------------------------------------- +function fixedPoint(t, N, I_sq, a2) { + const ell = 7; // 5 derivative steps as recommended in the paper + const piSq = Math.PI * Math.PI; + // f = 0.5 * π^(2*ell) * Σ_i I_sq[i]^ell * a2[i] * exp(-I_sq[i] * π² * t) + let f = 0; + for (let i = 0; i < I_sq.length; i++) { + f += Math.pow(I_sq[i], ell) * a2[i] * Math.exp(-I_sq[i] * piSq * t); + } + f *= 0.5 * Math.pow(Math.PI, 2 * ell); + if (f <= 0) return -1; + // Loop s = ell-1 down to 2 (mirrors Python's reversed(range(2, ell))) + for (let s = ell - 1; s >= 2; s--) { + // odd_numbers_prod = 1 * 3 * 5 * ... * (2s-1) == (2s-1)!! + let oddProd = 1; + for (let k = 1; k <= 2 * s - 1; k += 2) oddProd *= k; + const K0 = oddProd / Math.sqrt(2 * Math.PI); + const constVal = (1 + Math.pow(0.5, s + 0.5)) / 3; + const time = Math.pow((2 * constVal * K0) / (N * f), 2 / (3 + 2 * s)); + f = 0; + for (let i = 0; i < I_sq.length; i++) { + f += Math.pow(I_sq[i], s) * a2[i] * Math.exp(-I_sq[i] * piSq * time); + } + f *= 0.5 * Math.pow(Math.PI, 2 * s); + } + const tOpt = Math.pow(2 * N * Math.sqrt(Math.PI) * f, -0.4); + return t - tOpt; +} +// --------------------------------------------------------------------------- +// ISJ root solver — port of KDEpy's _root +// --------------------------------------------------------------------------- +function isjRoot(N, I_sq, a2) { + const Nc = Math.max(Math.min(1050, N), 50); + let tol = 10e-12 + (0.01 * (Nc - 50)) / 1000; + for (;;) { + const res = brentq((t) => fixedPoint(t, N, I_sq, a2), 0, tol); + if (res.converged && res.x > 0) return res.x; + tol *= 2; + if (tol >= 1) + throw new Error('ISJ root finding did not converge. Need more data.'); + } +} +// --------------------------------------------------------------------------- +// ISJ bandwidth selection — port of KDEpy's improved_sheather_jones +// --------------------------------------------------------------------------- +/** + * Data-driven bandwidth selection using the Improved Sheather–Jones (ISJ) + * plug-in estimator. + * + * What is bandwidth? + * ------------------ + * KDE works by placing a small "bump" (kernel) at each data point and summing + * them. The bandwidth controls how wide each bump is — too narrow and the + * curve is spiky and noisy; too wide and it blurs out real features. Choosing + * the right bandwidth automatically is the central problem in KDE. + * + * Why ISJ? + * -------- + * Simpler rules like Silverman's rule assume the data looks roughly Gaussian. + * ISJ makes no such assumption: it finds the bandwidth that minimises the + * mean integrated squared error by solving a fixed-point equation derived + * from the data's own frequency content (via DCT). This makes it reliable + * for multimodal or skewed distributions, which are common in performance data. + * + * @param data - raw sample values (at least a few dozen points recommended) + * @param weights - optional per-sample weights; zero/negative weights are dropped + * @returns optimal bandwidth in the same units as the data + */ +export function improvedSheatherJones(data, weights) { + const n = 1024; // 2^10, matching KDEpy + let d = data; + let w = weights; + // Drop zero/negative weights (KDEpy does: data = data[weights > 0]) + if (w !== undefined) { + const pairs = d.map((v, i) => [v, w[i]]); + const pos = pairs.filter(([, wi]) => wi > 0); + d = pos.map(([v]) => v); + w = pos.map(([, wi]) => wi); + } + let minD = d[0], + maxD = d[0]; + for (let i = 1; i < d.length; i++) { + if (d[i] < minD) minD = d[i]; + if (d[i] > maxD) maxD = d[i]; + } + const R = maxD - minD; + const N = new Set(d).size; // number of unique values + // ISJ uses boundary_abs=6, boundary_rel=0.5 (wider grid for stable estimation) + const xmesh = autogrid1D(d, 6, n, 0.5); + const xmeshArr = Array.from(xmesh); + const initialData = linearBinning1D(d, xmeshArr, w); + // Type-2 DCT of the binned data + const a = dct2(Array.from(initialData)); + // I_sq = [1², 2², ..., (n-1)²] + const I_sq = new Float64Array(n - 1); + for (let i = 0; i < n - 1; i++) I_sq[i] = (i + 1) * (i + 1); + // a2 = a[1:]² (skip DC component) + const a2 = new Float64Array(n - 1); + for (let i = 0; i < n - 1; i++) a2[i] = a[i + 1] * a[i + 1]; + const tStar = isjRoot(N, I_sq, a2); + return Math.sqrt(tStar) * R; +} +// --------------------------------------------------------------------------- +// Silverman's rule — port of KDEpy's silvermans_rule +// --------------------------------------------------------------------------- +/** + * Simple rule-of-thumb bandwidth selection (Silverman 1986). + * + * Estimates bandwidth as a function of sample size and spread (std / IQR). + * Fast and robust, but assumes the data is roughly unimodal and bell-shaped. + * Prefer improvedSheatherJones for multimodal or heavy-tailed distributions. + * + * @param data - raw sample values + * @returns bandwidth estimate in the same units as the data + */ +export function silvermansRule(data) { + const n = data.length; + if (n <= 1) return 1; + const mean = data.reduce((a, b) => a + b, 0) / n; + const variance = data.reduce((s, x) => s + (x - mean) ** 2, 0) / (n - 1); + const std = Math.sqrt(variance); + const sorted = [...data].sort((a, b) => a - b); + // numpy percentile linear interpolation: index = q * (n-1), interpolate floor/ceil + function percentile(q) { + const idx = q * (n - 1); + const lo = Math.floor(idx), + hi = Math.ceil(idx); + return sorted[lo] + (idx - lo) * (sorted[hi] - sorted[lo]); + } + // scipy.stats.norm.ppf(.75) - scipy.stats.norm.ppf(.25) = 1.3489795003921634 + const iqr = (percentile(0.75) - percentile(0.25)) / 1.3489795003921634; + let sigma = Math.min(std, iqr > 0 ? iqr : std); + if (sigma <= 0) return 1; + return sigma * Math.pow((n * 3) / 4, -0.2); +} +// --------------------------------------------------------------------------- +// 1D convolution, mode='same' — matches scipy.signal.convolve(a, b, mode='same') +// +// Full convolution c[m] = Σ_k a[k] * b[m-k]. +// 'same' returns M elements starting at index (N-1)//2 of the full result, +// where M=len(a), N=len(b). +// --------------------------------------------------------------------------- +function convolve1DSame(a, b) { + const M = a.length; + const N = b.length; + const start = (N - 1) >> 1; + const result = new Float64Array(M); + for (let i = 0; i < M; i++) { + const cIdx = i + start; + const kMin = Math.max(0, cIdx - (N - 1)); + const kMax = Math.min(M - 1, cIdx); + let sum = 0; + for (let k = kMin; k <= kMax; k++) { + sum += a[k] * b[cIdx - k]; + } + result[i] = sum; + } + return result; +} +/** + * Kernel Density Estimate using FFT-based convolution (FFTKDE). + * + * What is KDE? + * ------------ + * A KDE turns a set of discrete samples into a smooth continuous curve that + * estimates the underlying probability density — think of it as a smooth + * histogram. Each sample contributes a small Gaussian "bump"; summing all + * bumps gives the density curve. + * + * Why FFT? + * -------- + * Naively evaluating the sum of N kernels at G grid points costs O(N·G). + * FFTKDE instead bins the data onto the grid (linearBinning1D) and convolves + * the binned data with the kernel using FFT, reducing cost to O(G log G) + * regardless of N. + * + * @param data - raw sample values (at least 2 required) + * @param bw - bandwidth: "ISJ" (default, data-driven), "silverman" + * (faster rule of thumb), or a positive number (fixed) + * @param weights - optional per-sample weights + * @param numGridPoints - number of x-axis evaluation points (default 1024) + * @param boundary - "none" (default) or "reflection" for data that cannot + * be negative (e.g. latency values): mirrors data at x=0 + * so the density doesn't leak below zero + * @returns { x, y, bandwidth } where x and y are the KDE curve coordinates + */ +export function fftkde( + data, + bw = 'ISJ', + weights, + numGridPoints = 1024, + boundary = 'none', +) { + if (data.length < 2) + throw new Error('fftkde requires at least 2 data points.'); + if (boundary === 'reflection') { + return fftkdeReflection(data, bw, weights, numGridPoints); + } + // 1. Bandwidth + let bandwidth; + if (bw === 'ISJ') { + bandwidth = improvedSheatherJones(data, weights); + } else if (bw === 'silverman') { + bandwidth = silvermansRule(data); + } else { + bandwidth = bw; + } + return fftkdeCore(data, bandwidth, weights, numGridPoints); +} +function fftkdeCore(data, bandwidth, weights, numGridPoints) { + // 2. Gaussian practical support — used as boundary_abs for the grid + const realBw = gaussianPracticalSupport(bandwidth); + // 3. Evaluation grid — boundary_abs = practical_support(bw), boundary_rel=0.05 + const gridArr = autogrid1D(data, realBw, numGridPoints, 0.05); + const grid = Array.from(gridArr); + // 4. Linear binning + const binnedData = linearBinning1D(data, grid, weights); + // 5. Grid spacing + const minGrid = grid[0]; + const maxGrid = grid[numGridPoints - 1]; + const dx = (maxGrid - minGrid) / (numGridPoints - 1); + // 6. L = number of grid steps for kernel half-width + const L = Math.min(Math.floor(realBw / dx), numGridPoints); + // 7. Evaluate kernel on [-L*dx, ..., 0, ..., L*dx] (2L+1 points) + const kernelSize = 2 * L + 1; + const kernelWeights = new Float64Array(kernelSize); + for (let k = 0; k < kernelSize; k++) { + kernelWeights[k] = gaussianKernel1D((k - L) * dx, bandwidth); + } + // 8. Convolve (mode='same') + const raw = convolve1DSame(binnedData, kernelWeights); + // 9. Clamp negatives (floating-point noise) + for (let i = 0; i < raw.length; i++) { + if (raw[i] < 0) raw[i] = 0; + } + return { x: grid, y: Array.from(raw), bandwidth }; +} +// Reflection method for non-negative data. +// Silverman, B. W. (1986). Density Estimation for Statistics and Data +// Analysis. Chapman and Hall, London. Pages 20–22. +// Mirror data at x=0, run KDE on augmented set, take x≥0 and double density. +function fftkdeReflection(data, bw, weights, numGridPoints) { + // Augment: original + reflection across 0 + const augmented = new Array(data.length * 2); + for (let i = 0; i < data.length; i++) { + augmented[i] = data[i]; + augmented[data.length + i] = -data[i]; + } + // Duplicate weights if provided + let augWeights; + if (weights) { + augWeights = new Array(weights.length * 2); + for (let i = 0; i < weights.length; i++) { + augWeights[i] = weights[i]; + augWeights[weights.length + i] = weights[i]; + } + } + // Bandwidth from original data (not augmented — augmented is symmetric and + // would give a wider bandwidth than appropriate for the one-sided distribution) + let bandwidth; + if (bw === 'ISJ') { + bandwidth = improvedSheatherJones(data, weights); + } else if (bw === 'silverman') { + bandwidth = silvermansRule(data); + } else { + bandwidth = bw; + } + // Run KDE on augmented data with double the grid points (we'll discard the left half) + const result = fftkdeCore( + augmented, + bandwidth, + augWeights, + numGridPoints * 2, + ); + // Keep only x ≥ 0, double density (the reflected half integrates to 0.5) + const x = []; + const y = []; + for (let i = 0; i < result.x.length; i++) { + if (result.x[i] >= 0) { + x.push(result.x[i]); + y.push(result.y[i] * 2); + } + } + return { x, y, bandwidth }; +} +// --------------------------------------------------------------------------- +// argrelmax — port of scipy.signal.argrelmax(y, order=order) +// +// Returns indices i where y[i] is strictly greater than all y[i±j] for +// j = 1..order. Boundary points (i < order or i >= N-order) are excluded. +// --------------------------------------------------------------------------- +/** + * Find indices of local maxima in an array. + * + * A point is a local maximum if it is strictly greater than all neighbours + * within `order` positions on each side. Larger `order` suppresses narrow + * noise spikes at the cost of merging closely-spaced genuine peaks. + * + * @param y - array of values (e.g. KDE density at each grid point) + * @param order - neighbourhood half-width to check (default 1) + * @returns array of indices where local maxima occur + */ +export function argrelmax(y, order = 1) { + const N = y.length; + const peaks = []; + for (let i = order; i < N - order; i++) { + let isMax = true; + for (let j = 1; j <= order; j++) { + if (y[i] <= y[i - j] || y[i] <= y[i + j]) { + isMax = false; + break; + } + } + if (isMax) peaks.push(i); + } + return peaks; +} +// --------------------------------------------------------------------------- +// fitKdeModes — port of perf_compare_stats.fit_kde_modes +// +// Runs FFTKDE with ISJ bandwidth, finds local maxima, applies valley-depth +// and data-fraction filters to produce a list of distinct modes. +// +// Parameters +// ---------- +// data : raw sample values +// valleyThreshold : mode boundary is valid only if valley < threshold * min(peak_heights) +// minPeakFraction : peak must be >= this fraction of the global max to count +// minDataFraction : a mode must contain >= this fraction of data to be kept +// --------------------------------------------------------------------------- +/** + * Detect distinct modes (peaks) in a sample distribution via KDE. + * + * Why does this matter for performance data? + * ------------------------------------------ + * Performance measurements are often multimodal: a benchmark may have a + * "fast path" (cache warm, branch predicted) and a "slow path" (cache miss, + * JIT deoptimisation). A single summary statistic like the mean or median + * conflates these paths and can hide regressions or improvements. Finding + * modes lets us report each code path separately. + * + * How it works + * ------------ + * 1. Fit a KDE to the data (trimmed to 1st–99th percentile for stability). + * 2. Find local maxima (peaks) in the density curve. + * 3. Valley-depth filter: two peaks are only counted as separate modes if + * the valley between them is deep enough (< valleyThreshold × shorter + * peak height). Shallow saddles are KDE smoothing artefacts. + * 4. Data-fraction filter: a mode must contain >= minDataFraction of the + * actual data points to be reported (avoids noise bumps at the tails). + * + * @param data - raw sample values (fewer than 4 → always 1 mode) + * @param valleyThreshold - how deep a valley must be to split two modes; + * 0 = never split, 1 = always split (default 0.5) + * @param minPeakFraction - minimum peak height as fraction of global max, + * filters tiny noise bumps (default 0.05) + * @param minDataFraction - minimum fraction of data a mode must contain + * to be kept (default 0.05) + * @returns { nModes, peakLocs, boundaries, x, y, bandwidth } + */ +export function fitKdeModes( + data, + valleyThreshold = 0.5, + minPeakFraction = 0.05, + minDataFraction = 0.05, +) { + const fallbackX = data.reduce((a, b) => a + b, 0) / data.length; + function fallback(x, y, bw) { + const medianVal = [...data].sort((a, b) => a - b)[ + Math.floor(data.length / 2) + ]; + return { + nModes: 1, + peakLocs: [medianVal], + boundaries: [], + x, + y, + bandwidth: bw, + }; + } + if (data.length < 4) { + return { + nModes: 1, + peakLocs: [fallbackX], + boundaries: [], + x: [], + y: [], + bandwidth: 0, + }; + } + // Trim 1st–99th percentile for fitting (matches Python) + const sorted = [...data].sort((a, b) => a - b); + const n = sorted.length; + function pct(q) { + const idx = q * (n - 1); + const lo = Math.floor(idx), + hi = Math.ceil(idx); + return sorted[lo] + (idx - lo) * (sorted[hi] - sorted[lo]); + } + const p1 = pct(0.01), + p99 = pct(0.99); + let dataFit = data.filter((v) => v >= p1 && v <= p99); + if (dataFit.length < 4) dataFit = data; + let kde; + try { + kde = fftkde(dataFit, 'ISJ'); + } catch { + return fallback([], [], 0); + } + const { x, y, bandwidth } = kde; + // Local maxima with order=3 (matches Python argrelmax order=3) + let peakIdxs = argrelmax(y, 3); + const yMax = Math.max(...y); + peakIdxs = peakIdxs.filter((i) => y[i] >= minPeakFraction * yMax); + if (peakIdxs.length === 0) { + const globalMaxIdx = y.indexOf(yMax); + return { + nModes: 1, + peakLocs: [x[globalMaxIdx]], + boundaries: [], + x, + y, + bandwidth, + }; + } + // Valley-depth filter — build `good` list of surviving peak indices + const good = [peakIdxs[0]]; + for (let k = 1; k < peakIdxs.length; k++) { + const nxt = peakIdxs[k]; + const prev = good[good.length - 1]; + let valleyMin = y[prev]; + for (let j = prev; j <= nxt; j++) if (y[j] < valleyMin) valleyMin = y[j]; + if (valleyMin < valleyThreshold * Math.min(y[prev], y[nxt])) { + good.push(nxt); + } else if (y[nxt] > y[good[good.length - 1]]) { + good[good.length - 1] = nxt; + } + } + // Compute boundaries (x-position of valley minimum between each adjacent pair) + function boundaries(peaks) { + const bs = []; + for (let i = 0; i < peaks.length - 1; i++) { + let minIdx = peaks[i]; + for (let j = peaks[i]; j <= peaks[i + 1]; j++) + if (y[j] < y[minIdx]) minIdx = j; + bs.push(x[minIdx]); + } + return bs; + } + // Data-fraction filter: drop modes with < minDataFraction of points + function assignModes(bounds) { + return data.map((v) => { + let m = 0; + while (m < bounds.length && v > bounds[m]) m++; + return m; + }); + } + const bs0 = boundaries(good); + const assignments0 = assignModes(bs0); + const keep = good + .map((_, i) => i) + .filter( + (i) => + assignments0.filter((a) => a === i).length / data.length >= + minDataFraction, + ); + if (keep.length < 2) { + const bestIdx = good.reduce((a, b) => (y[a] > y[b] ? a : b)); + return { + nModes: 1, + peakLocs: [x[bestIdx]], + boundaries: [], + x, + y, + bandwidth, + }; + } + const finalGood = keep.map((i) => good[i]); + const finalBounds = boundaries(finalGood); + return { + nModes: finalGood.length, + peakLocs: finalGood.map((i) => x[i]), + boundaries: finalBounds, + x, + y, + bandwidth, + }; +} From ce52f76fa8d2aa67185eeeca19db7f3646b98ff8 Mon Sep 17 00:00:00 2001 From: Olivier Mehani Date: Fri, 22 May 2026 13:00:00 +1000 Subject: [PATCH 07/11] Bug 2014041: add support for landoInstance QueryString parameter (#1038) This patch adds support for using different lando instances and is required for using the new lando instance instead of the old one from mach try perf. --- .../Search/fetchRevisionFromLando.test.tsx | 21 +++++++++++++++++++ .../CompareResults/landoToCommitLoader.ts | 17 ++++++++++----- src/logic/lando.ts | 17 ++++++++++++--- 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/__tests__/Search/fetchRevisionFromLando.test.tsx b/src/__tests__/Search/fetchRevisionFromLando.test.tsx index 5c55b137e..1a2b24d7d 100644 --- a/src/__tests__/Search/fetchRevisionFromLando.test.tsx +++ b/src/__tests__/Search/fetchRevisionFromLando.test.tsx @@ -53,6 +53,27 @@ describe('Lando to commit validating', () => { (console.error as jest.Mock).mockClear(); }); + it('should use the new Lando instance when landoInstance=lando-prod-2025', async () => { + jest.spyOn(console, 'error').mockImplementation(() => {}); + fetchMock.get('glob:https://lando.moz.tools/*', { status: 404 }); + await router.navigate( + '/compare-lando-results?baseLando=123&baseRepo=try&newLando=456&newRepo=try&framework=1&landoInstance=lando-prod-2025', + ); + render(); + expect(console.error).toHaveBeenCalledWith( + new Error('Error when requesting lando: (404) Not Found'), + ); + expect(fetchMock.callHistory.called('glob:https://lando.moz.tools/*')).toBe( + true, + ); + expect( + fetchMock.callHistory.called( + 'glob:https://api.lando.services.mozilla.com/*', + ), + ).toBe(false); + (console.error as jest.Mock).mockClear(); + }); + it('should reject', async () => { jest.spyOn(console, 'error').mockImplementation(() => {}); fetchMock.get( diff --git a/src/components/CompareResults/landoToCommitLoader.ts b/src/components/CompareResults/landoToCommitLoader.ts index 1cf91b07c..7f171613d 100644 --- a/src/components/CompareResults/landoToCommitLoader.ts +++ b/src/components/CompareResults/landoToCommitLoader.ts @@ -1,6 +1,6 @@ import { checkValues, getComparisonInformation } from './loader'; import { compareView } from '../../common/constants'; -import { fetchRevisionFromLandoId } from '../../logic/lando'; +import { fetchRevisionFromLandoId, LandoInstance } from '../../logic/lando'; import { Changeset, CombinedResultsItemType, @@ -24,6 +24,9 @@ export async function loader({ request }: { request: Request }) { 'newRepo', ) as Repository['name'][]; const frameworkFromUrl = url.searchParams.get('framework'); + const landoInstanceFromUrl = + (url.searchParams.get('landoInstance') as LandoInstance | null) ?? + undefined; if (!baseLandoIDFromUrl || !newLandoIDFromUrl) { throw new Error( 'Not all values were supplied please check you provided both baseLando and newLando', @@ -34,10 +37,14 @@ export async function loader({ request }: { request: Request }) { 'enable_silverman_kde', ); - const baseRevisionsFromLando = - await fetchRevisionFromLandoId(baseLandoIDFromUrl); - const newRevisionsFromLando = - await fetchRevisionFromLandoId(newLandoIDFromUrl); + const baseRevisionsFromLando = await fetchRevisionFromLandoId( + baseLandoIDFromUrl, + landoInstanceFromUrl, + ); + const newRevisionsFromLando = await fetchRevisionFromLandoId( + newLandoIDFromUrl, + landoInstanceFromUrl, + ); const testVersionFromUrl = url.searchParams.get( 'test_version', ) as TestVersion; diff --git a/src/logic/lando.ts b/src/logic/lando.ts index ce76dcc2e..8c50617bf 100644 --- a/src/logic/lando.ts +++ b/src/logic/lando.ts @@ -1,6 +1,13 @@ import { LandoToCommit } from '../types/state'; -const landoBaseUrl = 'https://api.lando.services.mozilla.com'; +const landoInstances = { + 'lando-dev': 'api.dev.lando.nonprod.cloudops.mozgcp.net', + 'lando-dev-2025': 'lando-dev.allizom.org', + 'lando-prod': 'api.lando.services.mozilla.com', + 'lando-prod-2025': 'lando.moz.tools', +}; + +export type LandoInstance = keyof typeof landoInstances; async function fetchFromLando(url: string) { const response = await fetch(url); @@ -12,8 +19,12 @@ async function fetchFromLando(url: string) { return response; } -export async function fetchRevisionFromLandoId(landoid: string) { - const url = `${landoBaseUrl}/landing_jobs/${landoid}`; +export async function fetchRevisionFromLandoId( + landoid: string, + instance: LandoInstance = 'lando-prod', +) { + const host = landoInstances[instance] ?? landoInstances['lando-prod']; + const url = `https://${host}/landing_jobs/${landoid}`; const response = await fetchFromLando(url); return response.json() as Promise; } From 67af18193e7c10c35daf188efcdd8e39e51e63ba Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Thu, 21 May 2026 16:08:46 +0000 Subject: [PATCH 08/11] use SJ bandwidth for top-level results, ISJ for subtests Top-level aggregated results typically have fewer, more clustered samples where ISJ over-tightens the bandwidth and produces spiky KDEs. Apply Silverman-Jones bandwidth (wider, always converges) when isSubtest=false, and keep ISJ for subtest results which have more data and benefit from the tighter fit. safeKde gains an optional numeric bw parameter that bypasses the ISJ string path entirely when a pre-computed bandwidth is supplied. --- .../CompareResults/CommonGraph.test.tsx | 16 +++--- src/components/CompareResults/CommonGraph.tsx | 54 ++++++++++++++++--- .../CompareResults/RevisionRowExpandable.tsx | 1 + 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/__tests__/CompareResults/CommonGraph.test.tsx b/src/__tests__/CompareResults/CommonGraph.test.tsx index 6eb37df38..c348ba6e0 100644 --- a/src/__tests__/CompareResults/CommonGraph.test.tsx +++ b/src/__tests__/CompareResults/CommonGraph.test.tsx @@ -82,7 +82,7 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render(); const option = getLatestEChartsOption(); const series = option.series as LineSeriesOption[]; @@ -119,7 +119,7 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render(); const option = getLatestEChartsOption(); const series = option.series as LineSeriesOption[]; @@ -144,7 +144,7 @@ describe('CommonGraph', () => { return { x: [10, 20, 30], y: [0.1, 0.2, 0.3], bandwidth: 1 }; }); - render(); + render(); const bandwidthArgs = (fftkde as jest.Mock).mock.calls.map( (call) => call[1] as string, @@ -167,7 +167,7 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render(); const option = getLatestEChartsOption(); const series = option.series as LineSeriesOption[]; @@ -197,7 +197,7 @@ describe('CommonGraph', () => { const initMock = echartsInit as jest.Mock; initMock.mockClear(); - render(); + render(); expect(initMock).not.toHaveBeenCalled(); @@ -212,7 +212,7 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render(); const formatter = getTooltipFormatter(getLatestEChartsOption()); @@ -247,7 +247,7 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render(); const option = getLatestEChartsOption(); const xAxis = option.xAxis as { @@ -278,7 +278,7 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render(); const formatter = getTooltipFormatter(getLatestEChartsOption()); diff --git a/src/components/CompareResults/CommonGraph.tsx b/src/components/CompareResults/CommonGraph.tsx index 661885ccb..0f87b36b5 100644 --- a/src/components/CompareResults/CommonGraph.tsx +++ b/src/components/CompareResults/CommonGraph.tsx @@ -38,13 +38,41 @@ function computeMax(a?: number, b?: number) { const CHART_HEIGHT = 300; const KDE_GRID_POINTS = 1024; +function quantileSorted(sorted: number[], q: number): number { + const pos = (sorted.length - 1) * q; + const base = Math.floor(pos); + const rest = pos - base; + if (sorted[base + 1] !== undefined) { + return sorted[base] + rest * (sorted[base + 1] - sorted[base]); + } + return sorted[base]; +} + +// Silverman-Jones bandwidth approximation — produces a wider (smoother) kernel +// than ISJ, which works better for the small sample counts typical of top-level +// aggregated results. +function approximateSJBandwidth(sorted: number[]): number { + const n = sorted.length; + if (n < 2) return sorted[0] * 0.0015; + const q25 = quantileSorted(sorted, 0.25); + const q75 = quantileSorted(sorted, 0.75); + const iqr = q75 - q25; + const mean = sorted.reduce((a, b) => a + b, 0) / n; + const std = Math.sqrt( + sorted.reduce((sum, x) => sum + Math.pow(x - mean, 2), 0) / n, + ); + const sigma = Math.min(std, iqr / 1.34); + return 0.9 * sigma * Math.pow(n, -1 / 5); +} + // ISJ bandwidth selection can fail to converge on tiny or degenerate samples // (few unique values, near-identical numbers). Fall back to Silverman's rule // in that case — coarser, but it never fails. -function safeKde(values: number[]) { +// When bw is provided it is passed straight through to fftkde. +function safeKde(values: number[], bw?: number) { if (values.length < 2) return null; try { - return fftkde(values, 'ISJ', undefined, KDE_GRID_POINTS); + return fftkde(values, bw ?? 'ISJ', undefined, KDE_GRID_POINTS); } catch { return fftkde(values, 'silverman', undefined, KDE_GRID_POINTS); } @@ -78,7 +106,7 @@ function resampleOnto( return out; } -function CommonGraph({ baseValues, newValues, unit }: CommonGraphProps) { +function CommonGraph({ baseValues, newValues, unit, isSubtest }: CommonGraphProps) { const chartContainerRef = useRef(null); const chartInstanceRef = useRef(null); @@ -90,9 +118,20 @@ function CommonGraph({ baseValues, newValues, unit }: CommonGraphProps) { const min = computeMin(statsForBase?.min, statsForNew?.min) * 0.95; const max = computeMax(statsForBase?.max, statsForNew?.max) * 1.05; - // ISJ auto-selects the bandwidth per dataset, so each KDE tunes itself. - const bKde = safeKde(baseValues); - const nKde = safeKde(newValues); + // Top-level results have fewer, more spread-out samples — use the SJ + // approximation for a wider (smoother) bandwidth. Subtest results have + // more data so ISJ can select a tighter, more accurate bandwidth. + let baseBw: number | undefined; + let newBw: number | undefined; + if (!isSubtest) { + const baseSorted = [...baseValues].sort((a, b) => a - b); + const newSorted = [...newValues].sort((a, b) => a - b); + baseBw = baseSorted.length >= 2 ? approximateSJBandwidth(baseSorted) : undefined; + newBw = newSorted.length >= 2 ? approximateSJBandwidth(newSorted) : undefined; + } + + const bKde = safeKde(baseValues, baseBw); + const nKde = safeKde(newValues, newBw); // Build a shared x-grid covering both KDEs' ranges. Resampling both // curves onto identical x positions is what lets the axis-trigger tooltip @@ -244,7 +283,7 @@ function CommonGraph({ baseValues, newValues, unit }: CommonGraphProps) { }, ], }; - }, [baseValues, newValues, unit]); + }, [baseValues, newValues, unit, isSubtest]); useEffect(() => { if (!chartContainerRef.current) { @@ -286,6 +325,7 @@ interface CommonGraphProps { baseValues: number[]; newValues: number[]; unit: string | null; + isSubtest: boolean; } export default CommonGraph; diff --git a/src/components/CompareResults/RevisionRowExpandable.tsx b/src/components/CompareResults/RevisionRowExpandable.tsx index 33b6d9ba6..f68ee67e4 100644 --- a/src/components/CompareResults/RevisionRowExpandable.tsx +++ b/src/components/CompareResults/RevisionRowExpandable.tsx @@ -70,6 +70,7 @@ function RevisionRowExpandable(props: RevisionRowExpandableProps) { baseValues={baseValues} newValues={newValues} unit={baseUnit || newUnit} + isSubtest={result.base_parent_signature !== null} /> )} {strategy.renderExpandedLeft(result)} From b19e116caddd5da562ebaf7ed3680551023e28d8 Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Thu, 21 May 2026 16:10:36 +0000 Subject: [PATCH 09/11] add scatter strip below KDE density chart Adds a second grid below the KDE plot showing individual run values as jittered triangles (Base row / New row). The two grids share a linked axisPointer and synchronized dataZoom so zooming or panning moves both views together. The tooltip branches on series type: scatter points show the raw run value, KDE lines continue to show density at the cursor x. --- .../CompareResults/CommonGraph.test.tsx | 7 +- .../CompareResults/ResultsView.test.tsx | 4 +- src/components/CompareResults/CommonGraph.tsx | 150 +++++++++++++----- 3 files changed, 118 insertions(+), 43 deletions(-) diff --git a/src/__tests__/CompareResults/CommonGraph.test.tsx b/src/__tests__/CompareResults/CommonGraph.test.tsx index c348ba6e0..5dffd161d 100644 --- a/src/__tests__/CompareResults/CommonGraph.test.tsx +++ b/src/__tests__/CompareResults/CommonGraph.test.tsx @@ -170,7 +170,8 @@ describe('CommonGraph', () => { render(); const option = getLatestEChartsOption(); - const series = option.series as LineSeriesOption[]; + const allSeries = option.series as LineSeriesOption[]; + const series = allSeries.filter((s) => s.type === 'line'); expect(series).toHaveLength(2); // Base side has a resampled density curve. expect(series[0].data as unknown[]).toHaveLength(1024); @@ -250,9 +251,9 @@ describe('CommonGraph', () => { render(); const option = getLatestEChartsOption(); - const xAxis = option.xAxis as { + const xAxis = (option.xAxis as Array<{ axisLabel: { formatter: (value: number) => string }; - }; + }>)[0]; const format = xAxis.axisLabel.formatter; // Whole numbers render without a trailing ".00". diff --git a/src/__tests__/CompareResults/ResultsView.test.tsx b/src/__tests__/CompareResults/ResultsView.test.tsx index f77041cf5..b44764071 100644 --- a/src/__tests__/CompareResults/ResultsView.test.tsx +++ b/src/__tests__/CompareResults/ResultsView.test.tsx @@ -219,7 +219,9 @@ describe('Results View', () => { // The expanded row renders the chart with the two KDE line series // (Base, New). Formatter behaviour is covered in CommonGraph.test.tsx. const option = getLatestEChartsOption(); - const series = option.series as LineSeriesOption[]; + const series = (option.series as LineSeriesOption[]).filter( + (s) => s.type === 'line', + ); expect(series).toHaveLength(2); expect(series).toMatchObject([ { diff --git a/src/components/CompareResults/CommonGraph.tsx b/src/components/CompareResults/CommonGraph.tsx index 0f87b36b5..0f3c60abd 100644 --- a/src/components/CompareResults/CommonGraph.tsx +++ b/src/components/CompareResults/CommonGraph.tsx @@ -37,6 +37,8 @@ function computeMax(a?: number, b?: number) { const CHART_HEIGHT = 300; const KDE_GRID_POINTS = 1024; +const KDE_GRID = { left: 70, right: 70, top: 28, height: 155 }; +const SCATTER_GRID = { left: 70, right: 70, top: 198, height: 55 }; function quantileSorted(sorted: number[], q: number): number { const pos = (sorted.length - 1) * q; @@ -161,48 +163,91 @@ function CommonGraph({ baseValues, newValues, unit, isSubtest }: CommonGraphProp const unitSuffix = unit ? ` (${unit})` : ''; + const totalCount = baseValues.length + newValues.length; + const symbolSize = totalCount < 20 ? 10 : 7; + + const JITTER = 0.6; + const baseScatterData: [number, number][] = baseValues.map((v) => [ + v, + (Math.random() - 0.5) * JITTER, + ]); + const newScatterData: [number, number][] = newValues.map((v) => [ + v, + 1 + (Math.random() - 0.5) * JITTER, + ]); + + const tickFormatter = (value: number) => { + const rounded = Math.round(value); + if (Math.abs(value - rounded) < 1e-9) return String(rounded); + return value.toFixed(2); + }; + return { animation: false, - grid: { left: 70, right: 70, top: 28, height: 200 }, - xAxis: { - type: 'value', - min, - max, - name: unit ?? '', - nameLocation: 'middle', - nameGap: 30, - nameTextStyle: { - fontSize: 13, - fontWeight: 'bold', - color: '#000', + grid: [KDE_GRID, SCATTER_GRID], + // axisPointer link keeps the vertical crosshair in sync across both grids. + axisPointer: { link: [{ xAxisIndex: 'all' }] }, + xAxis: [ + { + gridIndex: 0, + type: 'value', + min, + max, + // Tick labels show 2 dp for fractional values, drop ".00" for whole + // numbers. Floats near integers (e.g. 14 + 1e-15) collapse to "14". + axisLabel: { formatter: tickFormatter }, + splitLine: { show: true, lineStyle: { color: '#eee' } }, + axisLine: { show: true, lineStyle: { color: '#999' } }, + }, + { + gridIndex: 1, + type: 'value', + min, + max, + name: unit ?? '', + nameLocation: 'middle', + nameGap: 22, + nameTextStyle: { fontSize: 13, fontWeight: 'bold', color: '#000' }, + axisLabel: { show: false }, + splitLine: { show: false }, + axisLine: { show: true, lineStyle: { color: '#999' } }, + axisTick: { show: false }, + }, + ], + yAxis: [ + { + gridIndex: 0, + type: 'value', + min: 0, + splitLine: { show: true, lineStyle: { color: '#eee' } }, + axisLine: { show: true, lineStyle: { color: '#999' } }, + axisTick: { show: false }, + axisLabel: { show: true, color: '#000', fontSize: 12 }, }, - // Tick labels show 2 dp for fractional values, drop ".00" for whole - // numbers. Floats near integers (e.g. 14 + 1e-15) collapse to "14". - axisLabel: { - formatter: (value: number) => { - const rounded = Math.round(value); - if (Math.abs(value - rounded) < 1e-9) return String(rounded); - return value.toFixed(2); + { + gridIndex: 1, + type: 'value', + min: -0.5, + max: 1.5, + interval: 1, + axisTick: { show: false }, + axisLine: { show: true, lineStyle: { color: '#999' } }, + axisLabel: { + color: '#000', + fontSize: 12, + formatter: (v: number) => (v === 0 ? 'Base' : v === 1 ? 'New' : ''), }, + splitLine: { show: false }, }, - splitLine: { show: true, lineStyle: { color: '#eee' } }, - axisLine: { show: true, lineStyle: { color: '#999' } }, - }, - yAxis: { - type: 'value', - min: 0, - splitLine: { show: true, lineStyle: { color: '#eee' } }, - axisLine: { show: true, lineStyle: { color: '#999' } }, - axisTick: { show: false }, - axisLabel: { show: true, color: '#000', fontSize: 12 }, - }, + ], // Wheel to zoom on the x-axis; shift+drag pans. // filterMode: 'none' keeps every data point in place — the zoom only // changes the visible window, so KDE curves still extend to the edges. + // xAxisIndex: [0, 1] keeps both grids in sync. dataZoom: [ { type: 'inside', - xAxisIndex: 0, + xAxisIndex: [0, 1], filterMode: 'none', zoomOnMouseWheel: true, moveOnMouseMove: 'shift', @@ -210,7 +255,7 @@ function CommonGraph({ baseValues, newValues, unit, isSubtest }: CommonGraphProp }, { type: 'slider', - xAxisIndex: 0, + xAxisIndex: [0, 1], filterMode: 'none', height: 16, bottom: 4, @@ -220,25 +265,30 @@ function CommonGraph({ baseValues, newValues, unit, isSubtest }: CommonGraphProp ], tooltip: { trigger: 'axis', - // Vertical guide that snaps to data points, so the tooltip locks onto - // a single x position with both series' densities side by side. axisPointer: { type: 'line', snap: true, lineStyle: { color: '#999' } }, padding: 10, formatter: (params) => { const items = Array.isArray(params) ? params : [params]; if (items.length === 0) return ''; - // axisValue is the snapped x position shared by all series; fall back - // to the first item's x when it's absent (e.g. tooltip invoked - // outside the axis-trigger path). + // Scatter tooltip: show raw run values + if ((items[0] as { seriesType?: string }).seriesType === 'scatter') { + return items + .map((pts) => { + const marker = typeof pts.marker === 'string' ? pts.marker : ''; + const xVal = (pts.value as [number, number])[0]; + return `${marker}${pts.seriesName ?? ''}: ${xVal.toFixed(2)}${unitSuffix}`; + }) + .join('
'); + } + // KDE tooltip: show density at the cursor x const axisX = (items[0] as { axisValue?: number }).axisValue ?? (items[0].value as [number, number])[0]; const header = `Value: ${Number(axisX).toFixed(2)}${unitSuffix}`; const lines = items.map((pts) => { const marker = typeof pts.marker === 'string' ? pts.marker : ''; - const seriesName = pts.seriesName ?? ''; const y = (pts.value as [number, number])[1]; - return `${marker}${seriesName}: ${y.toFixed(4)}`; + return `${marker}${pts.seriesName ?? ''}: ${y.toFixed(4)}`; }); return [header, ...lines].join('
'); }, @@ -281,6 +331,28 @@ function CommonGraph({ baseValues, newValues, unit, isSubtest }: CommonGraphProp itemStyle: { color: Colors.ChartNew }, emphasis: { focus: 'none' }, }, + { + name: 'Base', + type: 'scatter', + xAxisIndex: 1, + yAxisIndex: 1, + data: baseScatterData, + symbol: 'triangle', + symbolSize, + itemStyle: { color: Colors.ChartBase, opacity: 0.6 }, + emphasis: { focus: 'none' }, + }, + { + name: 'New', + type: 'scatter', + xAxisIndex: 1, + yAxisIndex: 1, + data: newScatterData, + symbol: 'triangle', + symbolSize, + itemStyle: { color: Colors.ChartNew, opacity: 0.6 }, + emphasis: { focus: 'none' }, + }, ], }; }, [baseValues, newValues, unit, isSubtest]); From 610fc06c9677a5bf22de6d63fa88570f2762d9ff Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Fri, 22 May 2026 10:19:01 +0000 Subject: [PATCH 10/11] move x-axis unit label above scatter strip; fix prettier formatting The unit label was sitting on the scatter grid's bottom axis, overlapping the zoom slider. Move it to the KDE grid's bottom axis (above the scatter strip) and widen the gap between the two grids to give it room. Also absorbs prettier formatting fixes from npm run fix-all. --- .../CompareResults/CommonGraph.test.tsx | 80 ++++++++++++++++--- .../__snapshots__/ResultsView.test.tsx.snap | 4 +- src/components/CompareResults/CommonGraph.tsx | 25 +++--- 3 files changed, 87 insertions(+), 22 deletions(-) diff --git a/src/__tests__/CompareResults/CommonGraph.test.tsx b/src/__tests__/CompareResults/CommonGraph.test.tsx index 5dffd161d..9ff26d762 100644 --- a/src/__tests__/CompareResults/CommonGraph.test.tsx +++ b/src/__tests__/CompareResults/CommonGraph.test.tsx @@ -82,7 +82,14 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render( + , + ); const option = getLatestEChartsOption(); const series = option.series as LineSeriesOption[]; @@ -119,7 +126,14 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render( + , + ); const option = getLatestEChartsOption(); const series = option.series as LineSeriesOption[]; @@ -144,7 +158,14 @@ describe('CommonGraph', () => { return { x: [10, 20, 30], y: [0.1, 0.2, 0.3], bandwidth: 1 }; }); - render(); + render( + , + ); const bandwidthArgs = (fftkde as jest.Mock).mock.calls.map( (call) => call[1] as string, @@ -167,7 +188,14 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render( + , + ); const option = getLatestEChartsOption(); const allSeries = option.series as LineSeriesOption[]; @@ -198,7 +226,14 @@ describe('CommonGraph', () => { const initMock = echartsInit as jest.Mock; initMock.mockClear(); - render(); + render( + , + ); expect(initMock).not.toHaveBeenCalled(); @@ -213,7 +248,14 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render( + , + ); const formatter = getTooltipFormatter(getLatestEChartsOption()); @@ -248,12 +290,21 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render( + , + ); const option = getLatestEChartsOption(); - const xAxis = (option.xAxis as Array<{ - axisLabel: { formatter: (value: number) => string }; - }>)[0]; + const xAxis = ( + option.xAxis as Array<{ + axisLabel: { formatter: (value: number) => string }; + }> + )[0]; const format = xAxis.axisLabel.formatter; // Whole numbers render without a trailing ".00". @@ -279,7 +330,14 @@ describe('CommonGraph', () => { bandwidth: 1, })); - render(); + render( + , + ); const formatter = getTooltipFormatter(getLatestEChartsOption()); diff --git a/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap b/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap index 8f397bd6d..6740c38a7 100644 --- a/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap +++ b/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap @@ -34,7 +34,7 @@ exports[`Results View Should display Base, New and Common graphs with replicates class="MuiBox-root css-72fd9l" >
@@ -486,7 +486,7 @@ exports[`Results View Should display Base, New and Common graphs with tooltips 1 class="MuiBox-root css-72fd9l" >
diff --git a/src/components/CompareResults/CommonGraph.tsx b/src/components/CompareResults/CommonGraph.tsx index 0f3c60abd..8310e3e83 100644 --- a/src/components/CompareResults/CommonGraph.tsx +++ b/src/components/CompareResults/CommonGraph.tsx @@ -35,10 +35,10 @@ function computeMax(a?: number, b?: number) { return Math.max(a, b); } -const CHART_HEIGHT = 300; +const CHART_HEIGHT = 310; const KDE_GRID_POINTS = 1024; const KDE_GRID = { left: 70, right: 70, top: 28, height: 155 }; -const SCATTER_GRID = { left: 70, right: 70, top: 198, height: 55 }; +const SCATTER_GRID = { left: 70, right: 70, top: 220, height: 50 }; function quantileSorted(sorted: number[], q: number): number { const pos = (sorted.length - 1) * q; @@ -108,7 +108,12 @@ function resampleOnto( return out; } -function CommonGraph({ baseValues, newValues, unit, isSubtest }: CommonGraphProps) { +function CommonGraph({ + baseValues, + newValues, + unit, + isSubtest, +}: CommonGraphProps) { const chartContainerRef = useRef(null); const chartInstanceRef = useRef(null); @@ -128,8 +133,10 @@ function CommonGraph({ baseValues, newValues, unit, isSubtest }: CommonGraphProp if (!isSubtest) { const baseSorted = [...baseValues].sort((a, b) => a - b); const newSorted = [...newValues].sort((a, b) => a - b); - baseBw = baseSorted.length >= 2 ? approximateSJBandwidth(baseSorted) : undefined; - newBw = newSorted.length >= 2 ? approximateSJBandwidth(newSorted) : undefined; + baseBw = + baseSorted.length >= 2 ? approximateSJBandwidth(baseSorted) : undefined; + newBw = + newSorted.length >= 2 ? approximateSJBandwidth(newSorted) : undefined; } const bKde = safeKde(baseValues, baseBw); @@ -193,6 +200,10 @@ function CommonGraph({ baseValues, newValues, unit, isSubtest }: CommonGraphProp type: 'value', min, max, + name: unit ?? '', + nameLocation: 'middle', + nameGap: 30, + nameTextStyle: { fontSize: 13, fontWeight: 'bold', color: '#000' }, // Tick labels show 2 dp for fractional values, drop ".00" for whole // numbers. Floats near integers (e.g. 14 + 1e-15) collapse to "14". axisLabel: { formatter: tickFormatter }, @@ -204,10 +215,6 @@ function CommonGraph({ baseValues, newValues, unit, isSubtest }: CommonGraphProp type: 'value', min, max, - name: unit ?? '', - nameLocation: 'middle', - nameGap: 22, - nameTextStyle: { fontSize: 13, fontWeight: 'bold', color: '#000' }, axisLabel: { show: false }, splitLine: { show: false }, axisLine: { show: true, lineStyle: { color: '#999' } }, From 26606cb44fee2e0fff7b1a6fbcbba9cc0952bfc8 Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Fri, 22 May 2026 11:39:27 +0000 Subject: [PATCH 11/11] increase gap between unit label and scatter strip --- .../CompareResults/__snapshots__/ResultsView.test.tsx.snap | 4 ++-- src/components/CompareResults/CommonGraph.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap b/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap index 6740c38a7..823fa7f93 100644 --- a/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap +++ b/src/__tests__/CompareResults/__snapshots__/ResultsView.test.tsx.snap @@ -34,7 +34,7 @@ exports[`Results View Should display Base, New and Common graphs with replicates class="MuiBox-root css-72fd9l" >
@@ -486,7 +486,7 @@ exports[`Results View Should display Base, New and Common graphs with tooltips 1 class="MuiBox-root css-72fd9l" >
diff --git a/src/components/CompareResults/CommonGraph.tsx b/src/components/CompareResults/CommonGraph.tsx index 8310e3e83..bc21b5a63 100644 --- a/src/components/CompareResults/CommonGraph.tsx +++ b/src/components/CompareResults/CommonGraph.tsx @@ -35,10 +35,10 @@ function computeMax(a?: number, b?: number) { return Math.max(a, b); } -const CHART_HEIGHT = 310; +const CHART_HEIGHT = 325; const KDE_GRID_POINTS = 1024; const KDE_GRID = { left: 70, right: 70, top: 28, height: 155 }; -const SCATTER_GRID = { left: 70, right: 70, top: 220, height: 50 }; +const SCATTER_GRID = { left: 70, right: 70, top: 238, height: 50 }; function quantileSorted(sorted: number[], q: number): number { const pos = (sorted.length - 1) * q;