From 57630102e4bcc6b555dd546ba373a14a88256eed Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Mon, 28 Sep 2026 05:52:29 +0300 Subject: [PATCH] chore: add typed ESLint checks #18 Refs WealthLedger/main#18. Keep Biome; add no Prettier dependencies. --- AGENTS.md | 2 +- README.md | 28 +- biome.json | 1 + eslint.config.mjs | 131 + package-lock.json | 2444 +++++++++++++++++ package.json | 20 +- packages/ui/src/EntryCard/EntryCard.test.tsx | 16 + packages/ui/src/EntryCard/EntryCard.tsx | 2 +- scripts/test-eslint.mjs | 64 + src/app/routes/AppRoutes/AppRoutes.test.tsx | 32 +- .../RouteErrorBoundary.test.tsx | 6 +- .../ui/AppBreadcrumbs/AccountScopeSwitch.tsx | 2 +- src/entities/bank-card/api/mutations.ts | 1 + src/entities/bank-card/api/queries.ts | 2 + .../ui/CategoryManager/CategoryManager.tsx | 44 +- .../portfolio/api/resource-lifecycle.test.tsx | 1 + src/features/auth/index.ts | 3 +- src/features/auth/model/auth-context.ts | 23 + src/features/auth/model/auth.test.tsx | 3 +- src/features/auth/model/auth.tsx | 34 +- .../auth/ui/AuthForm/AuthForm.test.tsx | 4 +- src/features/auth/ui/LoginForm.tsx | 4 +- src/features/auth/ui/RegisterForm.tsx | 4 +- .../model/screen-sync.test.tsx | 55 +- .../tbank-connection/model/screen-sync.ts | 6 +- .../tbank-connection/model/sync.test.tsx | 6 +- .../TbankConnectionSection.test.tsx | 16 +- .../AccountEventsPage/AccountEventsPage.vm.ts | 9 +- .../AccountIncomePage.test.tsx | 16 +- .../AccountIncomePage/AccountIncomePage.vm.ts | 21 +- .../IncomePeriodForm.view.tsx | 25 +- .../AccountOperationsPage.vm.ts | 14 +- .../account/ui/AccountPage/AccountPage.vm.ts | 6 +- .../card/ui/OperationForm/OperationForm.vm.ts | 6 +- src/pages/settings/ui/SettingsPage.tsx | 2 +- src/shared/api/client.test.ts | 1 + src/shared/api/client.ts | 13 +- src/shared/ui/Button.stories.tsx | 2 +- 38 files changed, 2914 insertions(+), 155 deletions(-) create mode 100644 eslint.config.mjs create mode 100644 scripts/test-eslint.mjs create mode 100644 src/features/auth/model/auth-context.ts diff --git a/AGENTS.md b/AGENTS.md index 7936743..624f07e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,4 +34,4 @@ Testing и без production diff. Перед завершением повто | Money, account/position/income | Entity types, API snapshot, formatters и tests соответствующего slice | | Theme/tokens, UI-каталог | [UI](.ai/conventions/ui.md), [Storybook](README.md#ui-каталог-storybook) | | Поведение области | Source и tests её page/entity/feature; неясное ожидание уточни до изменения | -| Versions/scripts/check scope | Manifest/lockfile, Biome/TypeScript/dependency configs | +| Versions/scripts/check scope | Manifest/lockfile, ESLint/Biome/TypeScript/dependency configs | diff --git a/README.md b/README.md index ddaee74..7c50ea2 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,34 @@ npm run verify ``` Команда проверяет, что generated OpenAPI types соответствуют committed snapshot, -затем запускает strict TypeScript, Biome, один coverage-backed запуск Vitest, +затем запускает strict TypeScript, Biome, ESLint и проверку его конфигурации, +один coverage-backed запуск Vitest, Knip, dependency rules приложения и stories, сборки приложения и Storybook. + +`npm run lint` объединяет `lint:biome` и `lint:eslint`. Biome сохраняет +форматирование, сортировку импортов, magic numbers и accessibility-проверки. +[ESLint](eslint.config.mjs) добавляет typed linting, React Compiler/hooks, +Fast Refresh, TanStack Query, выбранные Unicorn-правила и проверки тестов/stories. +Prettier не добавлен. `npm run test:lint` проверяет правила на ошибочных примерах +и исключение generated/build artifacts. `lint:eslint` сначала собирает private +packages, чтобы анализ типов использовал их актуальные declarations. + +Пересекающиеся hook-диагностики принадлежат официальному `react-hooks`. +Testing Library проверяет async-корректность; DOM structure/geometry tests +сохраняют прямой доступ к узлам. Async doubles допускаются без искусственного +`await`; Vitest допускает второй аргумент сообщения и ветвления фиксированных +`it.each` сценариев. React event handlers допускают Promise, но их тела остаются +под `no-floating-promises`. Локальные исключения объясняют сохранение wire +ProblemDetails, generated query keys и раннее подключение rejection assertions +в race-тестах. Плагин Query не доказывает корректность всех generated/custom +обёрток: transport/cache сценарии по-прежнему проверяют MSW-тесты. + +Дополнительные `no-unnecessary-condition` и `no-deprecated` пока не включены: +первому нужна отдельная оценка индексного доступа и защитных runtime guards, +второму — отдельная миграция API зависимостей. Пакеты второй волны и +`jsx-a11y` без заявленной поддержки ESLint 10 отложены в +[workspace issue #18](https://git.personal.ksv741.ru/WealthLedger/main/issues/18). + Vitest включает JSDOM-тесты и проверку геометрии секций позиций в headless Chromium при ширинах 375 и 1280 px. Browser project не использует JSDOM setup и его заглушки layout API. Только браузерная проверка: diff --git a/biome.json b/biome.json index babf97e..2e7202f 100644 --- a/biome.json +++ b/biome.json @@ -36,6 +36,7 @@ "overrides": [ { "includes": [ + "eslint.config.mjs", "vite.config.ts", "vitest.config.ts", ".storybook/main.ts", diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..2b341d9 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,131 @@ +import js from "@eslint/js"; +import react from "@eslint-react/eslint-plugin"; +import query from "@tanstack/eslint-plugin-query"; +import vitest from "@vitest/eslint-plugin"; +import { defineConfig, globalIgnores } from "eslint/config"; +import { importX } from "eslint-plugin-import-x"; +import hooks from "eslint-plugin-react-hooks"; +import refresh from "eslint-plugin-react-refresh"; +import storybook from "eslint-plugin-storybook"; +import testingLibrary from "eslint-plugin-testing-library"; +import unicorn from "eslint-plugin-unicorn"; +import globals from "globals"; +import tseslint from "typescript-eslint"; + +export default defineConfig( + globalIgnores([ + "**/dist/**", + "dist-ssr/**", + "storybook-static/**", + "coverage/**", + ".vitest/**", + ".scannerwork/**", + "src/shared/api/generated/**", + "src/shared/api/spec/**", + ]), + { + files: ["**/*.{js,mjs,cjs,ts,tsx}"], + extends: [js.configs.recommended], + linterOptions: { reportUnusedDisableDirectives: "error" }, + }, + { + files: ["**/*.{js,mjs,cjs}", "**/*.config.ts", ".storybook/main.ts"], + languageOptions: { globals: globals.node }, + }, + { + files: ["**/*.{ts,tsx}"], + extends: [tseslint.configs.recommendedTypeChecked], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + "@typescript-eslint/switch-exhaustiveness-check": "error", + // React ignores handler return values; their bodies still check floating promises. + "@typescript-eslint/no-misused-promises": [ + "error", + { checksVoidReturn: { attributes: false } }, + ], + }, + }, + { + files: [ + "src/**/*.{ts,tsx}", + "packages/*/src/**/*.{ts,tsx}", + ".storybook/preview.tsx", + ], + extends: [ + react.configs["recommended-type-checked"], + react.configs["disable-naming-convention"], + hooks.configs.flat.recommended, + query.configs["flat/recommended"], + ], + languageOptions: { globals: globals.browser }, + plugins: { "import-x": importX, unicorn }, + rules: { + // The official hooks plugin owns Compiler and hook diagnostics. + "@eslint-react/error-boundaries": "off", + "@eslint-react/exhaustive-deps": "off", + "@eslint-react/purity": "off", + "@eslint-react/rules-of-hooks": "off", + "@eslint-react/set-state-in-effect": "off", + "@eslint-react/set-state-in-render": "off", + "@eslint-react/static-components": "off", + "@eslint-react/unsupported-syntax": "off", + "@eslint-react/use-memo": "off", + // useContext remains supported; a lint rollout does not require an API migration. + "@eslint-react/no-use-context": "off", + "import-x/no-default-export": "error", + "import-x/exports-last": "error", + // Biome owns formatting, import sorting and magic numbers during coexistence. + "unicorn/no-array-fill-with-reference-type": "error", + "unicorn/no-await-in-promise-methods": "error", + "unicorn/no-single-promise-in-promise-methods": "error", + "unicorn/prefer-array-some": "error", + "unicorn/prefer-includes": "error", + }, + }, + { + files: ["src/**/*.tsx", "packages/*/src/**/*.tsx"], + ignores: ["**/*.test.tsx", "**/*.stories.tsx", "src/test/**"], + extends: [refresh.configs.vite], + }, + { + files: ["**/*.test.{ts,tsx}"], + extends: [vitest.configs.recommended], + rules: { + "vitest/no-focused-tests": "error", + "vitest/valid-expect": ["error", { maxArgs: 2 }], + // it.each cases select different expected outcomes and recovery setup steps. + "vitest/no-conditional-expect": "off", + // Async test doubles preserve the public Promise contract without artificial awaits. + "@typescript-eslint/require-await": "off", + }, + }, + { + files: ["**/*.test.tsx"], + plugins: { "testing-library": testingLibrary }, + // Start with async correctness. DOM structure/geometry tests need direct node access. + rules: { + "testing-library/await-async-events": "error", + "testing-library/await-async-queries": "error", + "testing-library/await-async-utils": "error", + "testing-library/no-await-sync-events": "error", + "testing-library/no-await-sync-queries": "error", + "testing-library/no-promise-in-fire-event": "error", + "testing-library/no-wait-for-side-effects": "error", + "testing-library/no-wait-for-snapshot": "error", + "testing-library/prefer-find-by": "error", + }, + }, + storybook.configs["flat/recommended"], + { + files: ["**/*.stories.tsx", ".storybook/preview.tsx"], + rules: { + "import-x/no-default-export": "off", + "import-x/exports-last": "off", + }, + }, +); diff --git a/package-lock.json b/package-lock.json index 84a3bcd..1449d78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,10 +39,13 @@ "@biomejs/biome": "2.5.13", "@commitlint/cli": "21.2.2", "@commitlint/config-conventional": "21.2.2", + "@eslint-react/eslint-plugin": "5.21.0", + "@eslint/js": "10.0.1", "@release-it/conventional-changelog": "12.0.0", "@rolldown/plugin-babel": "0.2.4", "@storybook/addon-docs": "10.6.0", "@storybook/react-vite": "10.6.0", + "@tanstack/eslint-plugin-query": "5.104.0", "@testing-library/jest-dom": "7.0.1", "@testing-library/react": "16.3.3", "@types/node": "24.13.4", @@ -51,9 +54,18 @@ "@vitejs/plugin-react": "6.1.1", "@vitest/browser-playwright": "5.0.0", "@vitest/coverage-v8": "5.0.0", + "@vitest/eslint-plugin": "1.6.27", "babel-plugin-react-compiler": "1.0.0", "conventional-changelog-conventionalcommits": "10.4.0", "dependency-cruiser": "18.2.0", + "eslint": "10.11.0", + "eslint-plugin-import-x": "4.17.1", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-react-refresh": "0.5.7", + "eslint-plugin-storybook": "10.6.0", + "eslint-plugin-testing-library": "7.16.2", + "eslint-plugin-unicorn": "76.0.0", + "globals": "17.12.0", "husky": "9.1.7", "jsdom": "30.0.1", "knip": "6.35.1", @@ -64,6 +76,7 @@ "release-it": "21.0.2", "storybook": "10.6.0", "typescript": "5.9.3", + "typescript-eslint": "8.70.1", "vite": "8.3.0", "vite-plugin-pwa": "1.3.0", "vitest": "5.0.0" @@ -875,6 +888,30 @@ "specificity": "bin/cli.js" } }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, "node_modules/@commitlint/cli": { "version": "21.2.2", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.2.tgz", @@ -1805,6 +1842,327 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint-react/ast": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint-react/ast/-/ast-5.21.0.tgz", + "integrity": "sha512-9x+lQGJaVD7u6NIK6dx1/xZ0mJBcFlSVC9e9AVu4NOvZErFzVSK1tEP38iBFkP14bcqrLp7jbokze5FOC4gQGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.70.1", + "@typescript-eslint/typescript-estree": "^8.70.1", + "@typescript-eslint/utils": "^8.70.1", + "string-ts": "^2.3.1" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint-react/core": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint-react/core/-/core-5.21.0.tgz", + "integrity": "sha512-PAuhwpWxFyFa6YPX771ttuo0W4Ggw/BannJ+4XJmPr0MV1a9s6tBiddMH5qWeWiuuQTT/5fmKzRWGcIMyr6iPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.21.0", + "@eslint-react/eslint": "5.21.0", + "@eslint-react/shared": "5.21.0", + "@eslint-react/var": "5.21.0", + "@typescript-eslint/scope-manager": "^8.70.1", + "@typescript-eslint/types": "^8.70.1", + "@typescript-eslint/utils": "^8.70.1", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint-react/eslint": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint-react/eslint/-/eslint-5.21.0.tgz", + "integrity": "sha512-OdRp+P4s+d8CRd9rvv+5Ondp050imriYz4HH0qUM7REJpVl5NQewdwgYzQSlkJD6i+drmygt4gBh3hpwMPnBzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.70.1", + "@typescript-eslint/utils": "^8.70.1" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint-react/eslint-plugin": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint-react/eslint-plugin/-/eslint-plugin-5.21.0.tgz", + "integrity": "sha512-H4mlT5BiDu62Ya6QNMWxMka6Hz20SbWeeXhgxvW/sV2KInQv1UU5xvIMh3fWuBMKp8hWMheS5YGL/QmI3v7lGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/shared": "5.21.0", + "eslint-plugin-react-dom": "5.21.0", + "eslint-plugin-react-jsx": "5.21.0", + "eslint-plugin-react-naming-convention": "5.21.0", + "eslint-plugin-react-rsc": "5.21.0", + "eslint-plugin-react-web-api": "5.21.0", + "eslint-plugin-react-x": "5.21.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint-react/jsx": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint-react/jsx/-/jsx-5.21.0.tgz", + "integrity": "sha512-/dyHSVYm46aWt4ABZoZXuiXsU622UsONOHIHLodM8hZkgoL/IJC70LHn9ZYk8Dnj2bzWwAbktKrmNDoZDgkvbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.21.0", + "@eslint-react/eslint": "5.21.0", + "@eslint-react/shared": "5.21.0", + "@eslint-react/var": "5.21.0", + "@typescript-eslint/types": "^8.70.1", + "@typescript-eslint/utils": "^8.70.1", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint-react/shared": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint-react/shared/-/shared-5.21.0.tgz", + "integrity": "sha512-IIeMqRpbPMcUGhsMaXqlRfELfNLTAmywC671kag/soW580s+8LKU4DkTIULoorD+XxPenXlB1t0SE3fze23whw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/eslint": "5.21.0", + "@typescript-eslint/utils": "^8.70.1", + "ts-pattern": "^5.9.0", + "zod": "^3.25.0 || ^4.0.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint-react/var": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint-react/var/-/var-5.21.0.tgz", + "integrity": "sha512-segy1jD22R5mOP2GEmnwIDh0vdooJtTtsB+bWu3SC2LB6iLtASvDmIGUA7V0r6uz/YF8vyLf7iKTfv2liiMOBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.21.0", + "@eslint-react/eslint": "5.21.0", + "@typescript-eslint/scope-manager": "^8.70.1", + "@typescript-eslint/types": "^8.70.1", + "@typescript-eslint/utils": "^8.70.1", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/css-tree": { + "version": "4.1.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint/css-tree/-/css-tree-4.1.1.tgz", + "integrity": "sha512-A+s89eP+yDAPkKjnIOfjlkGifg5J7GPn01nWx14UjBVWktfkxOU4975F9ZZ13xNxkGiblNcBaAqgKhnQrW6SWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.35.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/css-tree/node_modules/mdn-data": { + "version": "2.35.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/mdn-data/-/mdn-data-2.35.0.tgz", + "integrity": "sha512-k+1+dEIm2z/BEfdvYzT/fIyAGeDBo/1AJNJI4ilFXJRYoxP/3Ds+GP8shAOIq8jYXY9N5nGB3Ag104NsagmLAA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.3", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@exodus/bytes": { "version": "1.15.1", "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", @@ -1986,6 +2344,72 @@ } } }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@inquirer/ansi": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.8.tgz", @@ -2664,6 +3088,30 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@mantine/charts": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@mantine/charts/-/charts-9.6.1.tgz", @@ -4959,6 +5407,29 @@ } } }, + "node_modules/@tanstack/eslint-plugin-query": { + "version": "5.104.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.104.0.tgz", + "integrity": "sha512-WHStlNTKraESaAjeuZ0zJr+t3ZigZgAJ/FQX1sdLGSF+U0vXlHgcAoPqm27VixMRyXaFHCx9xBJiUmPNcxMpjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.70.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": "^5.6.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@tanstack/query-core": { "version": "5.102.8", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.102.8.tgz", @@ -5260,6 +5731,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -5281,6 +5759,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mdx": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.14.tgz", @@ -5362,6 +5847,629 @@ "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.70.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.1.tgz", + "integrity": "sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/type-utils": "8.70.1", + "@typescript-eslint/utils": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.70.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.70.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@typescript-eslint/parser/-/parser-8.70.1.tgz", + "integrity": "sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.70.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@typescript-eslint/project-service/-/project-service-8.70.1.tgz", + "integrity": "sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.70.1", + "@typescript-eslint/types": "^8.70.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.70.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@typescript-eslint/scope-manager/-/scope-manager-8.70.1.tgz", + "integrity": "sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.70.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz", + "integrity": "sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.70.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@typescript-eslint/type-utils/-/type-utils-8.70.1.tgz", + "integrity": "sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1", + "@typescript-eslint/utils": "8.70.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.70.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@typescript-eslint/types/-/types-8.70.1.tgz", + "integrity": "sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.70.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz", + "integrity": "sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.70.1", + "@typescript-eslint/tsconfig-utils": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.70.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@typescript-eslint/utils/-/utils-8.70.1.tgz", + "integrity": "sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.70.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.1.tgz", + "integrity": "sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.70.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@vitejs/plugin-react": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", @@ -5469,6 +6577,37 @@ } } }, + "node_modules/@vitest/eslint-plugin": { + "version": "1.6.27", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@vitest/eslint-plugin/-/eslint-plugin-1.6.27.tgz", + "integrity": "sha512-X1RCAfwbatG4GFbJ/1PIHP9MSZMt0JJThqbYID+vS4L783k6QGlLPe421wQE8dHXuGCcenVLsydiM4qzsYWxhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "^8.58.0", + "@typescript-eslint/utils": "^8.58.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "*", + "eslint": ">=8.57.0", + "typescript": ">=5.0.0", + "vitest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, "node_modules/@vitest/istanbul-lib-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.1.tgz", @@ -5992,6 +7131,19 @@ "dev": true, "license": "MIT" }, + "node_modules/builtin-modules": { + "version": "5.4.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/builtin-modules/-/builtin-modules-5.4.0.tgz", + "integrity": "sha512-JCWSCdun+4Ovd9BhM/Vtlmwb7oD6Wl4YiPRXXwhAuwlPhW1un/MKgXn7GZoFm53ginnoNzus69V8JWx0K393jg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", @@ -6037,6 +7189,20 @@ } } }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -6348,6 +7514,16 @@ "node": ">=22.12.0" } }, + "node_modules/comment-parser": { + "version": "1.4.9", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/comment-parser/-/comment-parser-1.4.9.tgz", + "integrity": "sha512-+2AvZKjJaNq9GQljm3tr0utwrig5BL+jgJGvz5rDpGKNIp+ojcRXWUwBbh/sGIi1QCprR6PsKFakub+w1v+4hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/common-tags": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", @@ -6358,6 +7534,13 @@ "node": ">=4.0.0" } }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, "node_modules/concat-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", @@ -6957,6 +8140,13 @@ "node": ">=6" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -7152,6 +8342,19 @@ "dev": true, "license": "MIT" }, + "node_modules/detect-indent": { + "version": "7.0.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/detect-indent/-/detect-indent-7.0.2.tgz", + "integrity": "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -7531,6 +8734,19 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/escodegen": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", @@ -7553,6 +8769,677 @@ "source-map": "~0.6.1" } }, + "node_modules/eslint": { + "version": "10.11.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint/-/eslint-10.11.0.tgz", + "integrity": "sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "11.1.5 || >11.1.6 <12", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-import-x": { + "version": "4.17.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-import-x/-/eslint-plugin-import-x-4.17.1.tgz", + "integrity": "sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.56.0", + "comment-parser": "^1.4.1", + "debug": "^4.4.1", + "eslint-import-context": "^0.1.9", + "is-glob": "^4.0.3", + "minimatch": "^9.0.3 || ^10.1.2", + "semver": "^7.7.2", + "stable-hash-x": "^0.2.0", + "unrs-resolver": "^1.9.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-import-x" + }, + "peerDependencies": { + "@typescript-eslint/utils": "^8.56.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "eslint-import-resolver-node": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/utils": { + "optional": true + }, + "eslint-import-resolver-node": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-import-x/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-react-dom": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-react-dom/-/eslint-plugin-react-dom-5.21.0.tgz", + "integrity": "sha512-OtaQ73uqxnmjZ7muFthEbnVvJKrQ4MJneO21MFiZ5AR7XfVxew/cMOKJpr42Yvf5P8m7iB/QBRG8BHwXeqinxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.21.0", + "@eslint-react/core": "5.21.0", + "@eslint-react/eslint": "5.21.0", + "@eslint-react/jsx": "5.21.0", + "@eslint-react/shared": "5.21.0", + "@eslint-react/var": "5.21.0", + "@typescript-eslint/types": "^8.70.1", + "@typescript-eslint/utils": "^8.70.1", + "compare-versions": "^6.1.1" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-hooks/node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/eslint-plugin-react-hooks/node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/eslint-plugin-react-hooks/node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/eslint-plugin-react-hooks/node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/eslint-plugin-react-hooks/node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/eslint-plugin-react-hooks/node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/eslint-plugin-react-hooks/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/eslint-plugin-react-jsx": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-react-jsx/-/eslint-plugin-react-jsx-5.21.0.tgz", + "integrity": "sha512-ASHUFiPwlWR13LvZcXVXvieFqpb7EXv74T6y1IqrbAj17X36O+G90UXvkxcSF12lZkLHc1sImjsi0fUXPtFtTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.21.0", + "@eslint-react/core": "5.21.0", + "@eslint-react/eslint": "5.21.0", + "@eslint-react/jsx": "5.21.0", + "@eslint-react/shared": "5.21.0", + "@typescript-eslint/types": "^8.70.1", + "@typescript-eslint/utils": "^8.70.1" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-naming-convention": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-react-naming-convention/-/eslint-plugin-react-naming-convention-5.21.0.tgz", + "integrity": "sha512-aLE48o2hsDAkrGGBvsQNXAP+y5bnpvnqAipIE/EebiosOoY+3ty7j8Ep6f8q6+0bz6wVwZUmSEnI439p1DRA4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.21.0", + "@eslint-react/core": "5.21.0", + "@eslint-react/eslint": "5.21.0", + "@eslint-react/shared": "5.21.0", + "@eslint-react/var": "5.21.0", + "@typescript-eslint/types": "^8.70.1", + "@typescript-eslint/utils": "^8.70.1", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.7", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.7.tgz", + "integrity": "sha512-XhJSzLljuYD4UjNuFGJu2v7aD3sqNVK12w5/DCUfrNfHWegZo2+TsavNx8MuxMwEquuFAzNbNfXKU0WoZ+YUIg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-plugin-react-rsc": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-react-rsc/-/eslint-plugin-react-rsc-5.21.0.tgz", + "integrity": "sha512-v7Q6H0vHF/EPF2k3pnWcm9W38Nk/fjTRlZkrqjOU7EFNR2vvrzJ8196geyJwYQCG0duXyCQMOv8ZnDI7LYhkYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.21.0", + "@eslint-react/core": "5.21.0", + "@eslint-react/eslint": "5.21.0", + "@eslint-react/shared": "5.21.0", + "@eslint-react/var": "5.21.0", + "@typescript-eslint/types": "^8.70.1", + "@typescript-eslint/utils": "^8.70.1" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-web-api": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-react-web-api/-/eslint-plugin-react-web-api-5.21.0.tgz", + "integrity": "sha512-0V05F7DrUWNbeYTZQKF/cp//vFjQzexVByCWqlitkN3hm+d7NXqSLQ/dt2a0gvxSIWaz9gm3BdCmgNN0CEVCmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.21.0", + "@eslint-react/core": "5.21.0", + "@eslint-react/eslint": "5.21.0", + "@eslint-react/shared": "5.21.0", + "@eslint-react/var": "5.21.0", + "@typescript-eslint/types": "^8.70.1", + "@typescript-eslint/utils": "^8.70.1", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-x": { + "version": "5.21.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-react-x/-/eslint-plugin-react-x-5.21.0.tgz", + "integrity": "sha512-FEdx6h4Xf0aakOb4p6Pk1LpZCpxF0ArC0AMaLYQQEOOF5Z6OSRIZNWj6yKne64xQ6Ss6jQHicOheqbkg7Q0rPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-react/ast": "5.21.0", + "@eslint-react/core": "5.21.0", + "@eslint-react/eslint": "5.21.0", + "@eslint-react/jsx": "5.21.0", + "@eslint-react/shared": "5.21.0", + "@eslint-react/var": "5.21.0", + "@typescript-eslint/scope-manager": "^8.70.1", + "@typescript-eslint/types": "^8.70.1", + "@typescript-eslint/typescript-estree": "^8.70.1", + "@typescript-eslint/utils": "^8.70.1", + "compare-versions": "^6.1.1", + "string-ts": "^2.3.1", + "ts-api-utils": "^2.5.0", + "ts-pattern": "^5.9.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "eslint": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-storybook": { + "version": "10.6.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-storybook/-/eslint-plugin-storybook-10.6.0.tgz", + "integrity": "sha512-HK3yD5Nslv97buJ5JfT1xFkTOoyJgGIENmBoZZI3F8t2UpBzb/dHpKyCh5bQ8AvJWnhoM/WAtDNqyd9nq6xgcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "^8.60.0", + "@typescript-eslint/utils": "^8.60.0" + }, + "peerDependencies": { + "eslint": ">=8" + } + }, + "node_modules/eslint-plugin-testing-library": { + "version": "7.16.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-testing-library/-/eslint-plugin-testing-library-7.16.2.tgz", + "integrity": "sha512-8gleGnQXK2ZA3hHwjCwpYTZvM+9VsrJ+/9kDI8CjqAQGAdMQOdn/rJNu7ZySENuiWlGKQWyZJ4ZjEg2zamaRHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "^8.56.0", + "@typescript-eslint/utils": "^8.56.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-unicorn": { + "version": "76.0.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-plugin-unicorn/-/eslint-plugin-unicorn-76.0.0.tgz", + "integrity": "sha512-3ywJrCMHKYhcBP5yFzGaOOX3dY7+iR3hvpuoS/0EnhRFgVWLejly+t4fOdJvWQxJdHZl1C+QBW7hybXPDld4Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.10.1", + "@eslint/css-tree": "^4.0.5", + "browserslist": "^4.28.8", + "change-case": "^5.4.4", + "ci-info": "^4.4.0", + "core-js-compat": "^3.50.0", + "detect-indent": "^7.0.2", + "entities": "^8.0.0", + "find-up-simple": "^1.0.1", + "globals": "^17.11.0", + "identifier-regex": "^1.1.0", + "indent-string": "^5.0.0", + "is-builtin-module": "^5.0.0", + "pluralize": "^8.0.0", + "quote-js-string": "^0.1.0", + "regjsparser": "^0.13.2", + "reserved-identifiers": "^1.2.0", + "semver": "^7.8.5", + "strip-indent": "^4.1.1", + "yaml": "^2.9.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/eslint-plugin-unicorn?sponsor=1" + }, + "peerDependencies": { + "eslint": ">=10.4" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-unicorn/node_modules/strip-indent": { + "version": "4.1.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/strip-indent/-/strip-indent-4.1.1.tgz", + "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -7567,6 +9454,32 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -7647,6 +9560,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-string-truncated-width": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", @@ -7726,6 +9646,16 @@ "dev": true, "license": "MIT" }, + "node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.23" + } + }, "node_modules/filelist": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", @@ -7766,6 +9696,48 @@ "node": ">=10" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, "node_modules/flatted": { "version": "3.4.4", "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/flatted/-/flatted-3.4.4.tgz", @@ -8114,6 +10086,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/global-directory": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", @@ -8130,6 +10115,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals": { + "version": "17.12.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/globals/-/globals-17.12.0.tgz", + "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -8258,6 +10256,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -8289,6 +10300,30 @@ "dev": true, "license": "MIT" }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", @@ -8418,6 +10453,22 @@ "dev": true, "license": "ISC" }, + "node_modules/identifier-regex": { + "version": "1.1.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/identifier-regex/-/identifier-regex-1.1.0.tgz", + "integrity": "sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA==", + "dev": true, + "license": "MIT", + "dependencies": { + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ignore": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", @@ -8476,6 +10527,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", @@ -8638,6 +10699,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-builtin-module": { + "version": "5.0.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/is-builtin-module/-/is-builtin-module-5.0.0.tgz", + "integrity": "sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "builtin-modules": "^5.0.0" + }, + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -8734,6 +10811,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-finalizationregistry": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", @@ -8780,6 +10867,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-in-ssh": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", @@ -9358,6 +11458,13 @@ "devOptional": true, "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json-with-bigint": { "version": "3.5.12", "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.12.tgz", @@ -9408,6 +11515,16 @@ "node": ">=0.10.0" } }, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -9490,6 +11607,20 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lightningcss": { "version": "1.33.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", @@ -9805,6 +11936,22 @@ "node": ">=18" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash.capitalize": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", @@ -10283,6 +12430,29 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/netmask": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", @@ -10481,6 +12651,24 @@ "integrity": "sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw==", "license": "MIT" }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-9.4.1.tgz", @@ -10638,6 +12826,38 @@ "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pac-proxy-agent": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-9.1.0.tgz", @@ -10782,6 +13002,16 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -10987,6 +13217,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-bytes": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", @@ -11136,6 +13376,26 @@ "node": ">=6" } }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, "node_modules/quickjs-wasi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/quickjs-wasi/-/quickjs-wasi-2.2.0.tgz", @@ -11143,6 +13403,19 @@ "dev": true, "license": "MIT" }, + "node_modules/quote-js-string": { + "version": "0.1.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/quote-js-string/-/quote-js-string-0.1.0.tgz", + "integrity": "sha512-Y3NoRtprEEZQD8RfxMCfS0ZTqc4e+i18OrXEXAvpM6TfC/3y+0L5rNbZiSnbBBEkDfFzbpd8o+cE8q3/anjMGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/quote-js-string?sponsor=1" + } + }, "node_modules/rc9": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/rc9/-/rc9-3.1.0.tgz", @@ -11879,6 +14152,19 @@ "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", "license": "MIT" }, + "node_modules/reserved-identifiers": { + "version": "1.2.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", + "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/resolve": { "version": "1.22.12", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", @@ -12475,6 +14761,16 @@ "source-map": "^0.6.0" } }, + "node_modules/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -13423,6 +15719,13 @@ "node": ">=0.6.19" } }, + "node_modules/string-ts": { + "version": "2.3.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/string-ts/-/string-ts-2.3.1.tgz", + "integrity": "sha512-xSJq+BS52SaFFAVxuStmx6n5aYZU571uYUnUrPXkPFCfdHyZMMlbP2v2Wx5sNBnAVzq/2+0+mcBLBa3Xa5ubYw==", + "dev": true, + "license": "MIT" + }, "node_modules/string-width": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz", @@ -13870,6 +16173,19 @@ "node": ">=20" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-dedent": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", @@ -13880,6 +16196,13 @@ "node": ">=6.10" } }, + "node_modules/ts-pattern": { + "version": "5.9.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/ts-pattern/-/ts-pattern-5.9.0.tgz", + "integrity": "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==", + "dev": true, + "license": "MIT" + }, "node_modules/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", @@ -13917,6 +16240,19 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -14028,6 +16364,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.70.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/typescript-eslint/-/typescript-eslint-8.70.1.tgz", + "integrity": "sha512-AcWG7KDjZ2THNXsgwttMaGmzVi0VFRlFYfqFHYQRbDpF3owuYbuiL8c7UUrd2k8s3PoSfIQrWfrGXfcElrWLYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.70.1", + "@typescript-eslint/parser": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1", + "@typescript-eslint/utils": "8.70.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/unbash": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.11.tgz", @@ -14164,6 +16524,44 @@ "node": ">=18.12.0" } }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, "node_modules/until-async": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", @@ -14216,6 +16614,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/uri-js-replace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz", @@ -14768,6 +17176,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/workbox-background-sync": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.1.tgz", @@ -16597,6 +19015,19 @@ "node": "^20.19.0 || ^22.12.0 || >=23" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yoctocolors": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", @@ -16619,6 +19050,19 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://nexus.personal.ksv741.ru/repository/npm-all/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, "packages/icons": { "name": "@wealthledger/icons", "version": "0.3.0", diff --git a/package.json b/package.json index ad387de..7a2390f 100644 --- a/package.json +++ b/package.json @@ -13,15 +13,18 @@ "preview:catalog": "vite preview --outDir storybook-static", "typecheck": "npm run build:packages && tsc -b --noEmit", "build": "npm run build:packages && tsc -b && vite build", - "lint": "biome check .", + "lint": "npm run lint:biome && npm run lint:eslint", + "lint:biome": "biome check .", + "lint:eslint": "npm run build:packages && eslint . --max-warnings=0", "test": "npm run build:packages && vitest", "test:run": "npm run build:packages && vitest run", "test:coverage": "npm run build:packages && vitest run --coverage", + "test:lint": "node --test scripts/test-eslint.mjs", "openapi:generate": "openapi-typescript src/shared/api/spec/openapi.json -o src/shared/api/generated/schema.d.ts", "openapi:check": "node scripts/check-openapi.mjs", "knip": "npm run build:packages && knip", "deps": "npm run build:packages && depcruise src packages --config dependency-cruiser.cjs --output-type err-long", - "verify": "npm run build:packages && npm run openapi:check && tsc -b --noEmit && npm run lint && vitest run --coverage && knip && depcruise src packages --config dependency-cruiser.cjs --output-type err-long && depcruise src packages .storybook/preview.tsx --config .storybook/dependency-cruiser.cjs --output-type err-long && tsc -b && vite build && storybook build --output-dir storybook-static --disable-telemetry", + "verify": "npm run build:packages && npm run openapi:check && tsc -b --noEmit && npm run lint && npm run test:lint && vitest run --coverage && knip && depcruise src packages --config dependency-cruiser.cjs --output-type err-long && depcruise src packages .storybook/preview.tsx --config .storybook/dependency-cruiser.cjs --output-type err-long && tsc -b && vite build && storybook build --output-dir storybook-static --disable-telemetry", "prepare": "husky", "preview": "vite preview", "deps:storybook": "npm run build:packages && depcruise src packages .storybook/preview.tsx --config .storybook/dependency-cruiser.cjs --output-type err-long", @@ -62,10 +65,13 @@ "@biomejs/biome": "2.5.13", "@commitlint/cli": "21.2.2", "@commitlint/config-conventional": "21.2.2", + "@eslint-react/eslint-plugin": "5.21.0", + "@eslint/js": "10.0.1", "@release-it/conventional-changelog": "12.0.0", "@rolldown/plugin-babel": "0.2.4", "@storybook/addon-docs": "10.6.0", "@storybook/react-vite": "10.6.0", + "@tanstack/eslint-plugin-query": "5.104.0", "@testing-library/jest-dom": "7.0.1", "@testing-library/react": "16.3.3", "@types/node": "24.13.4", @@ -74,9 +80,18 @@ "@vitejs/plugin-react": "6.1.1", "@vitest/browser-playwright": "5.0.0", "@vitest/coverage-v8": "5.0.0", + "@vitest/eslint-plugin": "1.6.27", "babel-plugin-react-compiler": "1.0.0", "conventional-changelog-conventionalcommits": "10.4.0", "dependency-cruiser": "18.2.0", + "eslint": "10.11.0", + "eslint-plugin-import-x": "4.17.1", + "eslint-plugin-react-hooks": "7.1.1", + "eslint-plugin-react-refresh": "0.5.7", + "eslint-plugin-storybook": "10.6.0", + "eslint-plugin-testing-library": "7.16.2", + "eslint-plugin-unicorn": "76.0.0", + "globals": "17.12.0", "husky": "9.1.7", "jsdom": "30.0.1", "knip": "6.35.1", @@ -87,6 +102,7 @@ "release-it": "21.0.2", "storybook": "10.6.0", "typescript": "5.9.3", + "typescript-eslint": "8.70.1", "vite": "8.3.0", "vite-plugin-pwa": "1.3.0", "vitest": "5.0.0" diff --git a/packages/ui/src/EntryCard/EntryCard.test.tsx b/packages/ui/src/EntryCard/EntryCard.test.tsx index 2d0bbcf..8d355ae 100644 --- a/packages/ui/src/EntryCard/EntryCard.test.tsx +++ b/packages/ui/src/EntryCard/EntryCard.test.tsx @@ -4,6 +4,22 @@ import { assert, describe, expect, it } from "vitest"; import { EntryCard } from "./EntryCard"; describe("EntryCard", () => { + it("does not leak a numeric zero from an absent action", () => { + render( + + + , + ); + expect(screen.queryByText("0")).not.toBeInTheDocument(); + }); it("keeps a failed image fallback until a new source is supplied", () => { const props = { title: "Пример", diff --git a/packages/ui/src/EntryCard/EntryCard.tsx b/packages/ui/src/EntryCard/EntryCard.tsx index 535f0aa..2ca9127 100644 --- a/packages/ui/src/EntryCard/EntryCard.tsx +++ b/packages/ui/src/EntryCard/EntryCard.tsx @@ -58,7 +58,7 @@ const EntryCard: FC = (props) => { > {props.valueLabel} - {props.actions && ( + {Boolean(props.actions) && (
{props.actions}
)} diff --git a/scripts/test-eslint.mjs b/scripts/test-eslint.mjs new file mode 100644 index 0000000..e53490f --- /dev/null +++ b/scripts/test-eslint.mjs @@ -0,0 +1,64 @@ +import assert from "node:assert/strict"; +import { test } from "node:test"; +import { ESLint } from "eslint"; + +test("ESLint enforces the frontend profile and excludes generated artifacts", async () => { + const eslint = new ESLint(); + for (const [filePath, code, rule] of [ + [ + "src/shared/api/problem-details.ts", + "export function run() { Promise.resolve(); }", + "@typescript-eslint/no-floating-promises", + ], + [ + "packages/ui/src/EntryCard/EntryCard.tsx", + 'import { useState } from "react"; export function Example({ active }: { active: boolean }) { if (active) useState(0); return null; }', + "react-hooks/rules-of-hooks", + ], + [ + "src/shared/api/problem-details.ts", + "export const values = Array(3).fill({});", + "unicorn/no-array-fill-with-reference-type", + ], + [ + "src/shared/api/problem-details.ts", + "export default 1;", + "import-x/no-default-export", + ], + [ + "src/shared/api/client.test.ts", + 'import { it } from "vitest"; it.only("focused", () => {});', + "vitest/no-focused-tests", + ], + [ + "packages/ui/src/EntryCard/EntryCard.test.tsx", + 'import { screen } from "@testing-library/react"; screen.findByText("ready");', + "testing-library/await-async-queries", + ], + [ + "packages/ui/src/EntryCard/EntryCard.tsx", + 'import { QueryClient } from "@tanstack/react-query"; export function Example() { const client = new QueryClient(); return
{String(client)}
; }', + "@tanstack/query/stable-query-client", + ], + ]) { + const [result] = await eslint.lintText(code, { filePath }); + assert.equal(result.fatalErrorCount, 0, filePath); + assert.ok( + result.messages.some((message) => message.ruleId === rule), + rule, + ); + } + for (const file of [ + "src/shared/api/generated/schema.d.ts", + "packages/ui/dist/index.js", + "storybook-static/index.js", + "coverage/index.js", + ]) + assert.equal(await eslint.isPathIgnored(file), true, file); + const story = await eslint.calculateConfigForFile( + "src/shared/ui/Button.stories.tsx", + ); + assert.equal(story.rules["import-x/no-default-export"][0], 0); + assert.ok(story.rules["storybook/default-exports"][0] > 0); + assert.equal(story.rules["react-refresh/only-export-components"], undefined); +}); diff --git a/src/app/routes/AppRoutes/AppRoutes.test.tsx b/src/app/routes/AppRoutes/AppRoutes.test.tsx index 6900830..8ef2531 100644 --- a/src/app/routes/AppRoutes/AppRoutes.test.tsx +++ b/src/app/routes/AppRoutes/AppRoutes.test.tsx @@ -1,6 +1,6 @@ import { MantineProvider } from "@mantine/core"; import { QueryClientProvider } from "@tanstack/react-query"; -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen } from "@testing-library/react"; import { cssVariablesResolver, theme } from "@wealthledger/ui/theme"; import { HttpResponse, http } from "msw"; import { MemoryRouter } from "react-router"; @@ -41,22 +41,18 @@ describe("AppRoutes", () => { ])("renders %s with its page title", async (path, title) => { renderRoutes(path); - await waitFor(() => - expect( - screen.getByRole("heading", { level: 1, name: title }), - ).toBeInTheDocument(), - ); + expect( + await screen.findByRole("heading", { level: 1, name: title }), + ).toBeInTheDocument(); expect(document.title).toBe(`${title} — WealthLedger`); }); it("redirects anonymous users from accounts", async () => { renderRoutes("/accounts"); - await waitFor(() => - expect( - screen.getByRole("heading", { level: 1, name: "Вход" }), - ).toBeInTheDocument(), - ); + expect( + await screen.findByRole("heading", { level: 1, name: "Вход" }), + ).toBeInTheDocument(); }); it("logs in through the public form and reaches the protected landing", async () => { @@ -142,14 +138,12 @@ describe("AppRoutes", () => { ); renderRoutes("/accounts"); - await waitFor(() => - expect( - screen.getByRole("heading", { - level: 2, - name: "Нет инвестиционных счетов", - }), - ).toBeInTheDocument(), - ); + expect( + await screen.findByRole("heading", { + level: 2, + name: "Нет инвестиционных счетов", + }), + ).toBeInTheDocument(); expect( screen.getByRole("navigation", { name: "WealthLedger" }), ).toBeInTheDocument(); diff --git a/src/app/routes/RouteErrorBoundary/RouteErrorBoundary.test.tsx b/src/app/routes/RouteErrorBoundary/RouteErrorBoundary.test.tsx index 8f66930..a03237d 100644 --- a/src/app/routes/RouteErrorBoundary/RouteErrorBoundary.test.tsx +++ b/src/app/routes/RouteErrorBoundary/RouteErrorBoundary.test.tsx @@ -1,5 +1,5 @@ import { MantineProvider } from "@mantine/core"; -import { fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { fireEvent, render, screen } from "@testing-library/react"; import { type FC, lazy, Suspense } from "react"; import { MemoryRouter, useLocation, useNavigate } from "react-router"; import { describe, expect, it, vi } from "vitest"; @@ -135,8 +135,6 @@ describe("RouteErrorBoundary", () => { authenticated = true; renderBoundary("/accounts"); screen.getByRole("button", { name: "go healthy" }).click(); - await waitFor(() => - expect(screen.getByText("healthy route")).toBeInTheDocument(), - ); + expect(await screen.findByText("healthy route")).toBeInTheDocument(); }); }); diff --git a/src/app/ui/AppBreadcrumbs/AccountScopeSwitch.tsx b/src/app/ui/AppBreadcrumbs/AccountScopeSwitch.tsx index 69a7630..6d7abd4 100644 --- a/src/app/ui/AppBreadcrumbs/AccountScopeSwitch.tsx +++ b/src/app/ui/AppBreadcrumbs/AccountScopeSwitch.tsx @@ -33,7 +33,7 @@ const AccountScopeSwitch: FC = (props) => { if (!value || value === accountId) return; const filters = new URLSearchParams(search); filters.delete("cursor"); - navigate({ + void navigate({ pathname: `/accounts/${encodeURIComponent(value)}${section ? `/${section}` : ""}`, search: filters.toString(), }); diff --git a/src/entities/bank-card/api/mutations.ts b/src/entities/bank-card/api/mutations.ts index 910320d..76fff0e 100644 --- a/src/entities/bank-card/api/mutations.ts +++ b/src/entities/bank-card/api/mutations.ts @@ -13,6 +13,7 @@ async function mutationData( request: Promise<{ data?: T; error?: unknown }>, ) { const { data, error } = await request; + // eslint-disable-next-line @typescript-eslint/only-throw-error -- Preserve generated ProblemDetails for apiErrorCode and field-error consumers. if (error) throw error; return data; } diff --git a/src/entities/bank-card/api/queries.ts b/src/entities/bank-card/api/queries.ts index 3f1b5c5..ab841ec 100644 --- a/src/entities/bank-card/api/queries.ts +++ b/src/entities/bank-card/api/queries.ts @@ -81,6 +81,7 @@ function useBankOperations( "/api/v1/bank-cards/{id}/operations", { params }, ); + // eslint-disable-next-line @tanstack/query/exhaustive-deps -- Generated options.queryKey already contains params; retain the shared cache identity. return useAuthenticatedQuery({ queryKey: all ? ([...options.queryKey, "all"] as const) : options.queryKey, placeholderData: ( @@ -108,6 +109,7 @@ function useBankOperations( signal, }, ); + // eslint-disable-next-line @typescript-eslint/only-throw-error -- Preserve generated ProblemDetails for apiErrorCode and field-error consumers. if (error) throw error; if (!data) throw new Error("Missing bank operations response"); if (!all) return data; diff --git a/src/entities/bank-card/ui/CategoryManager/CategoryManager.tsx b/src/entities/bank-card/ui/CategoryManager/CategoryManager.tsx index 6c8dad9..10db669 100644 --- a/src/entities/bank-card/ui/CategoryManager/CategoryManager.tsx +++ b/src/entities/bank-card/ui/CategoryManager/CategoryManager.tsx @@ -101,28 +101,30 @@ const CategoryManager: FC = (props) => { >
{ - setError(undefined); - try { - if (editing) - await mutation.updateCategory.mutateAsync({ - id: editing.id, - body: { name: values.name, version: editing.version }, - }); - else { - const result = await mutation.createCategory.mutateAsync({ - name: values.name, - }); - if (result) props.onCreated?.(result.id); + onSubmit={(event) => { + void methods.handleSubmit(async (values) => { + setError(undefined); + try { + if (editing) + await mutation.updateCategory.mutateAsync({ + id: editing.id, + body: { name: values.name, version: editing.version }, + }); + else { + const result = await mutation.createCategory.mutateAsync({ + name: values.name, + }); + if (result) props.onCreated?.(result.id); + } + editSession.current++; + setEditing(undefined); + setLatest(undefined); + methods.reset({ name: "" }); + } catch (failure) { + setError(failure); } - editSession.current++; - setEditing(undefined); - setLatest(undefined); - methods.reset({ name: "" }); - } catch (failure) { - setError(failure); - } - })} + })(event); + }} >
diff --git a/src/entities/portfolio/api/resource-lifecycle.test.tsx b/src/entities/portfolio/api/resource-lifecycle.test.tsx index 16e8609..28b829e 100644 --- a/src/entities/portfolio/api/resource-lifecycle.test.tsx +++ b/src/entities/portfolio/api/resource-lifecycle.test.tsx @@ -141,6 +141,7 @@ describe("persistent resource query lifecycle", () => { "/api/v1/brokerage-accounts/{accountId}/portfolio", { params: { path: { accountId: "a" } } }, ); + // eslint-disable-next-line vitest/valid-expect -- Attach rejection handling before releasing the request; the assertion is awaited below. const rejected = expect(old).rejects.toMatchObject({ name: "AbortError" }); await apiClient.GET("/api/v1/brokerage-accounts/{accountId}/portfolio", { params: { path: { accountId: "a" }, query: { sync: "observe" } }, diff --git a/src/features/auth/index.ts b/src/features/auth/index.ts index 9322901..da4ea1a 100644 --- a/src/features/auth/index.ts +++ b/src/features/auth/index.ts @@ -1,4 +1,5 @@ -export { AuthProvider, useAuth } from "./model/auth"; +export { AuthProvider } from "./model/auth"; +export { useAuth } from "./model/auth-context"; export { AuthPageLayout } from "./ui/AuthPageLayout/AuthPageLayout"; export { LoginForm } from "./ui/LoginForm"; export { RegisterForm } from "./ui/RegisterForm"; diff --git a/src/features/auth/model/auth-context.ts b/src/features/auth/model/auth-context.ts new file mode 100644 index 0000000..66f4a5f --- /dev/null +++ b/src/features/auth/model/auth-context.ts @@ -0,0 +1,23 @@ +import { createContext, useContext } from "react"; +import type { paths } from "@/shared/api"; + +type User = + paths["/api/v1/auth/me"]["get"]["responses"][200]["content"]["application/json"]; +type AuthContextValue = { + user: User | null; + accessToken: string | null; + ready: boolean; + login: (email: string, password: string) => Promise; + register: (email: string, password: string) => Promise; + logout: () => Promise; + refresh: () => Promise; +}; +const AuthContext = createContext(null); + +function useAuth() { + const context = useContext(AuthContext); + if (!context) throw new Error("AuthProvider is required"); + return context; +} + +export { AuthContext, type User, useAuth }; diff --git a/src/features/auth/model/auth.test.tsx b/src/features/auth/model/auth.test.tsx index 972d790..a4e532a 100644 --- a/src/features/auth/model/auth.test.tsx +++ b/src/features/auth/model/auth.test.tsx @@ -7,7 +7,8 @@ import { apiClient, apiQueryClient, useAuthenticatedQuery } from "@/shared/api"; import { createTestQueryClient } from "@/test/query"; import { resource } from "@/test/resource"; import { server } from "@/test/server"; -import { AuthProvider, useAuth } from "./auth"; +import { AuthProvider } from "./auth"; +import { useAuth } from "./auth-context"; const bankCardsKey = apiQueryClient.queryOptions("get", "/api/v1/bank-cards", { params: { query: { includeArchived: "false" } }, diff --git a/src/features/auth/model/auth.tsx b/src/features/auth/model/auth.tsx index 3830b6d..4f7c160 100644 --- a/src/features/auth/model/auth.tsx +++ b/src/features/auth/model/auth.tsx @@ -1,32 +1,13 @@ import { useQueryClient } from "@tanstack/react-query"; import type { FC, PropsWithChildren } from "react"; -import { - createContext, - useCallback, - useContext, - useEffect, - useRef, - useState, -} from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { apiClient, configureAuthClient, type paths } from "@/shared/api"; +import { AuthContext, type User } from "./auth-context"; -type User = - paths["/api/v1/auth/me"]["get"]["responses"][200]["content"]["application/json"]; type Authentication = paths["/api/v1/auth/login"]["post"]["responses"][200]["content"]["application/json"]; type Credentials = paths["/api/v1/auth/login"]["post"]["requestBody"]["content"]["application/json"]; -type AuthContextValue = { - user: User | null; - accessToken: string | null; - ready: boolean; - login: (email: string, password: string) => Promise; - register: (email: string, password: string) => Promise; - logout: () => Promise; - refresh: () => Promise; -}; -const AuthContext = createContext(null); - async function request( path: "/api/v1/auth/login" | "/api/v1/auth/register", email: string, @@ -35,8 +16,9 @@ async function request( const { data, error } = await apiClient.POST(path, { body: { email, password }, } satisfies { body: Credentials }); + // eslint-disable-next-line @typescript-eslint/only-throw-error -- Preserve generated ProblemDetails for apiErrorCode and field-error consumers. if (error) throw error; - return data as Authentication; + return data; } const AuthProvider: FC = (props) => { @@ -165,10 +147,4 @@ const AuthProvider: FC = (props) => { ); }; -function useAuth() { - const context = useContext(AuthContext); - if (!context) throw new Error("AuthProvider is required"); - return context; -} - -export { AuthProvider, useAuth }; +export { AuthProvider }; diff --git a/src/features/auth/ui/AuthForm/AuthForm.test.tsx b/src/features/auth/ui/AuthForm/AuthForm.test.tsx index 76d8ff1..e9f22bb 100644 --- a/src/features/auth/ui/AuthForm/AuthForm.test.tsx +++ b/src/features/auth/ui/AuthForm/AuthForm.test.tsx @@ -27,8 +27,6 @@ describe("AuthForm", () => { fireEvent.click(screen.getByRole("button", { name: "Регистрация" })); await waitFor(() => expect(onSubmit).not.toHaveBeenCalled()); - await waitFor(() => - expect(screen.getByText("Пароли не совпадают")).toBeInTheDocument(), - ); + expect(await screen.findByText("Пароли не совпадают")).toBeInTheDocument(); }); }); diff --git a/src/features/auth/ui/LoginForm.tsx b/src/features/auth/ui/LoginForm.tsx index 84813d8..12c80d8 100644 --- a/src/features/auth/ui/LoginForm.tsx +++ b/src/features/auth/ui/LoginForm.tsx @@ -1,6 +1,6 @@ import type { FC } from "react"; import { useLocation, useNavigate } from "react-router"; -import { useAuth } from "../model/auth"; +import { useAuth } from "../model/auth-context"; import { AuthForm } from "./AuthForm/AuthForm"; const LoginForm: FC = () => { @@ -14,7 +14,7 @@ const LoginForm: FC = () => { { await login(email, password); - navigate(returnTo, { replace: true }); + await navigate(returnTo, { replace: true }); }} /> ); diff --git a/src/features/auth/ui/RegisterForm.tsx b/src/features/auth/ui/RegisterForm.tsx index f666da9..14d7149 100644 --- a/src/features/auth/ui/RegisterForm.tsx +++ b/src/features/auth/ui/RegisterForm.tsx @@ -1,6 +1,6 @@ import type { FC } from "react"; import { useLocation, useNavigate } from "react-router"; -import { useAuth } from "../model/auth"; +import { useAuth } from "../model/auth-context"; import { AuthForm } from "./AuthForm/AuthForm"; const RegisterForm: FC = () => { @@ -15,7 +15,7 @@ const RegisterForm: FC = () => { register onSubmit={async (email, password) => { await register(email, password); - navigate(returnTo, { replace: true }); + await navigate(returnTo, { replace: true }); }} /> ); diff --git a/src/features/tbank-connection/model/screen-sync.test.tsx b/src/features/tbank-connection/model/screen-sync.test.tsx index e73481f..ebf431b 100644 --- a/src/features/tbank-connection/model/screen-sync.test.tsx +++ b/src/features/tbank-connection/model/screen-sync.test.tsx @@ -18,6 +18,57 @@ function auth() { }); } describe("page-owned screen synchronization", () => { + it("ignores a late sync acceptance after the account scope changes", async () => { + auth(); + let release!: () => void; + const blocked = new Promise((resolve) => { + release = resolve; + }); + const posted: string[] = []; + const polled: string[] = []; + server.use( + http.post("*/api/v1/tbank-sync", async ({ request }) => { + const scope = (await request.json()) as { + screen: "account"; + accountId: string; + }; + posted.push(scope.accountId); + await blocked; + return HttpResponse.json( + { id: scope.accountId, scope, now: "2026-09-25T00:00:00Z" }, + { status: 202 }, + ); + }), + http.get("*/api/v1/tbank-sync/:id", ({ params }) => { + polled.push(String(params.id)); + return HttpResponse.json({ + id: params.id, + state: "succeeded", + resources: [], + }); + }), + ); + const { result, rerender } = renderHook( + ({ accountId }) => useScreenSync({ screen: "account", accountId }, []), + { + initialProps: { accountId: "old" }, + wrapper: createQueryWrapper().wrapper, + }, + ); + let pending!: Promise; + act(() => { + pending = result.current.refresh(); + }); + await waitFor(() => expect(posted).toEqual(["old"])); + rerender({ accountId: "new" }); + await act(async () => { + release(); + await pending; + }); + await act(() => result.current.refresh()); + await waitFor(() => expect(polled).toEqual(["new"])); + expect(posted).toEqual(["old", "new"]); + }); it.each([false, true])( "retries a failed GET without forcing the source (cached=%s)", async (cached) => { @@ -155,7 +206,7 @@ describe("page-owned screen synchronization", () => { ); expect(published).not.toContainEqual( expect.objectContaining({ - meta: expect.objectContaining({ stateVersion: "old" }), + meta: expect.objectContaining({ stateVersion: "old" }) as unknown, }), ); unsubscribe(); @@ -354,7 +405,7 @@ describe("page-owned screen synchronization", () => { }); expect(modes).toEqual([null, "observe"]); if (boundary === "leave") unmount(); - else await act(() => configureAuthClient(null)); + else act(() => configureAuthClient(null)); const stopped = polls; await act(async () => { await new Promise((resolve) => setTimeout(resolve, 1100)); diff --git a/src/features/tbank-connection/model/screen-sync.ts b/src/features/tbank-connection/model/screen-sync.ts index e77f469..5ae8c16 100644 --- a/src/features/tbank-connection/model/screen-sync.ts +++ b/src/features/tbank-connection/model/screen-sync.ts @@ -1,5 +1,5 @@ import { type QueryKey, useQueryClient } from "@tanstack/react-query"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { apiErrorCode, apiQueryClient, @@ -33,7 +33,9 @@ function useScreenSync(scope: InvestmentScreen, resources: ScreenResource[]) { } | null>(null); const requestInFlight = useRef(null); const currentScope = useRef(scopeKey); - currentScope.current = scopeKey; + useLayoutEffect(() => { + currentScope.current = scopeKey; + }, [scopeKey]); const currentMutation = JSON.stringify(mutation.variables?.body) === scopeKey; const observed = useRef(new Map()); const ids = [ diff --git a/src/features/tbank-connection/model/sync.test.tsx b/src/features/tbank-connection/model/sync.test.tsx index 425e36b..d6053f0 100644 --- a/src/features/tbank-connection/model/sync.test.tsx +++ b/src/features/tbank-connection/model/sync.test.tsx @@ -56,7 +56,10 @@ describe("sync connection and session boundaries", () => { const { wrapper, queryClient } = createQueryWrapper(); const published: string[] = []; const unsubscribe = queryClient.getQueryCache().subscribe((event) => { - if (event.query.queryKey[1] === "/api/v1/tbank-sync/{id}") { + if ( + (event.query.queryKey as readonly unknown[])[1] === + "/api/v1/tbank-sync/{id}" + ) { const data = event.query.state.data as { now?: string } | undefined; if (data?.now) published.push(data.now); } @@ -73,6 +76,7 @@ describe("sync connection and session boundaries", () => { act(() => { mutation = result.current.mutation.mutateAsync({ body: screen }); }); + // eslint-disable-next-line vitest/valid-expect -- Attach rejection handling before releasing the request; the assertion is awaited below. const discarded = expect(mutation).rejects.toMatchObject({ name: "AbortError", }); diff --git a/src/features/tbank-connection/ui/TbankConnectionSection/TbankConnectionSection.test.tsx b/src/features/tbank-connection/ui/TbankConnectionSection/TbankConnectionSection.test.tsx index 856d284..60e36f4 100644 --- a/src/features/tbank-connection/ui/TbankConnectionSection/TbankConnectionSection.test.tsx +++ b/src/features/tbank-connection/ui/TbankConnectionSection/TbankConnectionSection.test.tsx @@ -163,11 +163,9 @@ describe("TbankConnectionSection", () => { }); fireEvent.click(screen.getByRole("button", { name: "Подключить" })); - await waitFor(() => - expect( - screen.getByRole("button", { name: "Заменить token" }), - ).toBeInTheDocument(), - ); + expect( + await screen.findByRole("button", { name: "Заменить token" }), + ).toBeInTheDocument(); expect(client.getQueryData(brokerageAccountsQueryKey)).toBeUndefined(); expect(client.getQueryData(bankCardsQueryKey)).toEqual([ { id: "bank-card-1" }, @@ -224,11 +222,9 @@ describe("TbankConnectionSection", () => { ); fireEvent.click(await screen.findByRole("button", { name: /^Удалить$/ })); - await waitFor(() => - expect( - screen.getByRole("button", { name: "Подключить" }), - ).toBeInTheDocument(), - ); + expect( + await screen.findByRole("button", { name: "Подключить" }), + ).toBeInTheDocument(); expect(client.getQueryData(brokerageAccountsQueryKey)).toBeUndefined(); expect(client.getQueryData(bankCardsQueryKey)).toEqual([ { id: "bank-card-1" }, diff --git a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts index 8e93f07..c620b7e 100644 --- a/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts +++ b/src/pages/account-events/ui/AccountEventsPage/AccountEventsPage.vm.ts @@ -48,10 +48,15 @@ function useAccountEventsPageViewModel( normalized.delete("to"); setSearchParams(normalized, { replace: true }); }, [validUrlPeriod, searchParams, setSearchParams]); - useEffect(() => { + const [previousPeriod, setPreviousPeriod] = useState([ + appliedFrom, + appliedTo, + ]); + if (previousPeriod[0] !== appliedFrom || previousPeriod[1] !== appliedTo) { + setPreviousPeriod([appliedFrom, appliedTo]); setDraftFrom(appliedFrom); setDraftTo(appliedTo); - }, [appliedFrom, appliedTo]); + } const events = useBrokerageEvents(accountId, { ...(appliedFrom ? { from: appliedFrom } : {}), ...(appliedTo ? { to: appliedTo } : {}), diff --git a/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.test.tsx b/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.test.tsx index b2d6d4c..94faed9 100644 --- a/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.test.tsx +++ b/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.test.tsx @@ -462,11 +462,9 @@ describe("AccountIncomePage", () => { http.get("*/api/v1/brokerage-accounts/account-1/events", eventsRequest), ); renderPage(); - await waitFor(() => - expect( - screen.getByRole("heading", { name: "Пассивный доход" }), - ).toBeInTheDocument(), - ); + expect( + await screen.findByRole("heading", { name: "Пассивный доход" }), + ).toBeInTheDocument(); expect( screen.getByText("Налоговые операции не включены в этот результат."), ).toBeInTheDocument(); @@ -542,11 +540,9 @@ describe("AccountIncomePage", () => { ), ); renderPage(); - await waitFor(() => - expect( - screen.getByRole("button", { name: "Повторить" }), - ).toBeInTheDocument(), - ); + expect( + await screen.findByRole("button", { name: "Повторить" }), + ).toBeInTheDocument(); }); }); diff --git a/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts b/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts index e20b3fc..b079236 100644 --- a/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts +++ b/src/pages/account-income/ui/AccountIncomePage/AccountIncomePage.vm.ts @@ -43,18 +43,22 @@ type IncomeDisplayModel = { type AccountIncomePageViewModel = | { state: "loading" } - | { state: "error"; retry: () => void; period: IncomePeriod } + | { + state: "error"; + retry: () => undefined | Promise; + period: IncomePeriod; + } | { state: "loaded"; analytics: IncomeAnalytics; display: IncomeDisplayModel; - formRevision: number; + formRevision: string; sourceError?: boolean; lastSuccessfulAt?: string | null; partial?: boolean; refreshing: boolean; refreshError: boolean; - retry: () => void; + retry: () => undefined | Promise; form: ReturnType>; onReset: () => void; onSubmit: (values: IncomeForm) => void; @@ -102,7 +106,7 @@ const useAccountIncomePageViewModel = ( defaultValues: draftPeriod(period), resolver: zodResolver(incomeFormSchema), }); - const [formRevision, setFormRevision] = useState(0); + const [resetRevision, setResetRevision] = useState(0); const analytics = useIncomeAnalytics(accountId, period); const sync = useScreenSync( { screen: "income", accountId, from: period.from, to: period.to }, @@ -114,7 +118,6 @@ const useAccountIncomePageViewModel = ( }, [period, requested, setSearchParams]); useEffect(() => { form.reset(draftPeriod(period)); - setFormRevision((revision) => revision + 1); }, [form, period]); const onSubmit = (values: IncomeForm) => { @@ -126,7 +129,7 @@ const useAccountIncomePageViewModel = ( const next = defaultPeriod(); form.reset(draftPeriod(next)); // Reset the open calendar along with the form. - setFormRevision((revision) => revision + 1); + setResetRevision((revision) => revision + 1); setSearchParams(next, { replace: true }); }; if (analytics.isLoading || !analytics.data) { @@ -138,16 +141,14 @@ const useAccountIncomePageViewModel = ( if (!display) return { state: "error", - retry: () => { - void analytics.observe(); - }, + retry: () => analytics.observe(), period, }; return { state: "loaded", analytics: analytics.data, display, - formRevision, + formRevision: `${period.from}:${period.to}:${resetRevision}`, sourceError: sync.sourceError, lastSuccessfulAt: analytics.meta?.lastSuccessfulAt, partial: analytics.meta?.availability !== "complete", diff --git a/src/pages/account-income/ui/AccountIncomePage/components/IncomePeriodForm/IncomePeriodForm.view.tsx b/src/pages/account-income/ui/AccountIncomePage/components/IncomePeriodForm/IncomePeriodForm.view.tsx index c527fef..b9ab7aa 100644 --- a/src/pages/account-income/ui/AccountIncomePage/components/IncomePeriodForm/IncomePeriodForm.view.tsx +++ b/src/pages/account-income/ui/AccountIncomePage/components/IncomePeriodForm/IncomePeriodForm.view.tsx @@ -22,8 +22,14 @@ const IncomePeriodForm: FC = (props) => { control, formState: { isSubmitting }, } = useFormContext(); - const fromField = useController({ control, name: "from" }); - const toField = useController({ control, name: "to" }); + const { + field: { ref: fromRef, onChange: onFromChange, onBlur: onFromBlur }, + fieldState: { error: fromError }, + } = useController({ control, name: "from" }); + const { + field: { onChange: onToChange, onBlur: onToBlur }, + fieldState: { error: toError }, + } = useController({ control, name: "to" }); const [from, to] = useWatch({ control, name: ["from", "to"] }); const today = moscowDate(new Date()); const start = parseDraftDate(from); @@ -44,23 +50,20 @@ const IncomePeriodForm: FC = (props) => { allowSingleDateInRange clearable clearButtonProps={{ "aria-label": t("ledger.clearPeriod") }} - ref={fromField.field.ref} + ref={fromRef} label={t("income.period")} valueFormat="DD.MM.YYYY" value={[start, end]} onChange={([from, to]) => { - fromField.field.onChange(from ? formatDraftDate(from) : ""); - toField.field.onChange(to ? formatDraftDate(to) : ""); + onFromChange(from ? formatDraftDate(from) : ""); + onToChange(to ? formatDraftDate(to) : ""); }} onBlur={() => { - fromField.field.onBlur(); - toField.field.onBlur(); + onFromBlur(); + onToBlur(); }} maxDate={today} - error={ - fromField.fieldState.error?.message ?? - toField.fieldState.error?.message - } + error={fromError?.message ?? toError?.message} disabled={isSubmitting} popoverProps={{ returnFocus: true, diff --git a/src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.vm.ts b/src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.vm.ts index ef902bb..17549a5 100644 --- a/src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.vm.ts +++ b/src/pages/account-operations/ui/AccountOperationsPage/AccountOperationsPage.vm.ts @@ -72,11 +72,9 @@ const useAccountOperationsPageVm = ( }>({ scopeKey, cursors: [] }); const cursors = pagination.scopeKey === scopeKey ? pagination.cursors : []; const cursor = cursors.at(-1); - useEffect(() => { - setPagination((current) => - current.scopeKey === scopeKey ? current : { scopeKey, cursors: [] }, - ); - }, [scopeKey]); + if (pagination.scopeKey !== scopeKey) { + setPagination({ scopeKey, cursors: [] }); + } useEffect(() => { if (validUrlPeriod) return; const normalized = new URLSearchParams(searchParams); @@ -84,7 +82,9 @@ const useAccountOperationsPageVm = ( normalized.delete("to"); setSearchParams(normalized, { replace: true }); }, [validUrlPeriod, searchParams, setSearchParams]); - useEffect(() => { + const [previousSearch, setPreviousSearch] = useState(searchParams); + if (previousSearch !== searchParams) { + setPreviousSearch(searchParams); setDraftFrom(appliedFrom); setDraftTo(appliedTo); setDraftCategories( @@ -92,7 +92,7 @@ const useAccountOperationsPageVm = ( (searchParams.get("categories") ?? "").split(",").includes(category), ), ); - }, [appliedFrom, appliedTo, searchParams]); + } const firstPageQuery = { from: dateStart(appliedFrom), to: dateEnd(appliedTo), diff --git a/src/pages/account/ui/AccountPage/AccountPage.vm.ts b/src/pages/account/ui/AccountPage/AccountPage.vm.ts index 2e41552..a03d11a 100644 --- a/src/pages/account/ui/AccountPage/AccountPage.vm.ts +++ b/src/pages/account/ui/AccountPage/AccountPage.vm.ts @@ -58,7 +58,7 @@ type AccountPageRecentEvents = BrokerageEventsPreviewState; type AccountPageIncomeSummary = | { state: "loading" | "partial" | "empty"; to: string } - | { state: "error"; to: string; retry: () => void } + | { state: "error"; to: string; retry: () => undefined | Promise } | { state: "loaded"; to: string; @@ -75,7 +75,7 @@ type AccountPageContent = { accountId?: string; refreshing: boolean; refreshError: boolean; - retry: () => void; + retry: () => undefined | Promise; recentOperations: AccountPageRecentOperations; recentEvents: AccountPageRecentEvents; incomeSummary: AccountPageIncomeSummary; @@ -93,7 +93,7 @@ type AccountPageEmptyViewModel = AccountPageContent & { type AccountPageViewModel = | { state: "loading" } - | { state: "error"; code?: string; retry: () => void } + | { state: "error"; code?: string; retry: () => undefined | Promise } | AccountPageEmptyViewModel | AccountPageLoadedViewModel; diff --git a/src/pages/card/ui/OperationForm/OperationForm.vm.ts b/src/pages/card/ui/OperationForm/OperationForm.vm.ts index b9493ac..4d9693f 100644 --- a/src/pages/card/ui/OperationForm/OperationForm.vm.ts +++ b/src/pages/card/ui/OperationForm/OperationForm.vm.ts @@ -1,6 +1,6 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useState } from "react"; -import { useForm } from "react-hook-form"; +import { useForm, useWatch } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { z } from "zod"; import { @@ -157,12 +157,12 @@ const useOperationFormVm = (props: OperationFormProps) => { description: props.operation?.description ?? "", }, }); - const kind = methods.watch("kind"); + const kind = useWatch({ control: methods.control, name: "kind" }); const expenses = useBankExpenses( kind === "refund" ? props.card.id : undefined, { page: sourcePage }, ); - const source = methods.watch("cardId"); + const source = useWatch({ control: methods.control, name: "cardId" }); const pending = !opened || methods.formState.isSubmitting; const options = props.cards .filter((c) => !c.archivedAt) diff --git a/src/pages/settings/ui/SettingsPage.tsx b/src/pages/settings/ui/SettingsPage.tsx index b7c97f4..a5f672e 100644 --- a/src/pages/settings/ui/SettingsPage.tsx +++ b/src/pages/settings/ui/SettingsPage.tsx @@ -17,7 +17,7 @@ const SettingsPage: FC = () => {