diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..2863bd7 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,18 @@ +coverage: + status: + project: + default: + target: 40% + threshold: 5% + patch: + default: + target: 70% + +ignore: + - "**/*.test.ts" + - "**/*.test.tsx" + - "**/test-utils/**" + - "**/__tests__/**" + - "**/__mocks__/**" + - "vite.config.ts" + - "vitest.config.ts" \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index c579bc2..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended' - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true } - ] - } -} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7cd60d8 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,51 @@ +name: Tests + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Authenticate NPM registry + run: echo -e "//npm.pkg.github.com/:_authToken=${{ secrets.NPM_TOKEN }}\n@capawesome-team:registry=https://npm.pkg.github.com/" > ~/.npmrc + + - name: Add Firebase credentials + run: echo '${{ secrets.FIREBASE_CREDS }}' > ./src/firebase.json + + - name: Install dependencies + run: pnpm install + + - name: Type check + run: pnpm run typecheck + + - name: Lint + run: pnpm run lint + + - name: Run tests + run: pnpm run test + + - name: Generate coverage + run: pnpm run test:coverage + + - name: Upload coverage + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3ff81c2..044e4aa 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,11 @@ dist-ssr *.njsproj *.sln *.sw? + +.claude/ +CLAUDE.md + +coverage/ + +.tanstack/ +docs/plans/ diff --git a/bumpers.yml b/bumpers.yml new file mode 100644 index 0000000..525528d --- /dev/null +++ b/bumpers.yml @@ -0,0 +1,41 @@ +rules: + - match: ' /tmp' + send: Use a "tmp" directory in the project root instead. + - match: 'go test ' + send: | + Use "just test" instead for TDD integration: + - just test + - just test-unit + - just test-integration + - just test-e2e + These commands have *exactly* the same arguments as "go test" and + actually pass args straight through to the official commands. No + special syntax required. They still MUST include paths like ./... and ./internal/example/... + - match: ^(gci|go vet|goimports|gofumpt|go *fmt|golangci-lint) + send: Use "just lint fix" instead to resolve lint/formatting issues. + - match: git commit --no-verify|LEFTHOOK=0 + send: Pre-commit hooks must not be skipped. + - match: fieldalignment + send: Fix field alignment issues by adding names to struct fields and running "just lint fix". + - match: find\s+.*-exec\s+(rm(\s+-\w+)*|sed\s+-i|>\s*\S+)\s+ + send: Don't use dangerous exec in find commands. + - match: BUMPERS_SKIP=1 + send: Bumpers should not be skipped. + - match: ^bumpers\.yml^ + tool: Read|Edit|Grep + send: Bumpers configuration file should not be accessed. + - match: cat.*EOF.*>\s*[^\s] + send: Use Write tool instead of cat heredoc redirection to create files. +commands: + - name: test + send: Run "just test" to run all tests and fix ALL failing tests if any. + - name: lint + send: Run "just lint fix" and fix ALL linting issues if any. + - name: check + send: Run "just lint fix" and "just test" and fix ALL issues if any. + - name: commit + send: Commit ALL modified files. + - name: logs + send: Read log file in ~/.local/share/bumpers/bumpers.log +session: + - add: 'Today''s date is: {{.Today}}' diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..3d36751 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,113 @@ +import js from '@eslint/js' +import typescript from '@typescript-eslint/eslint-plugin' +import typescriptParser from '@typescript-eslint/parser' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import react from 'eslint-plugin-react' +import jsxA11y from 'eslint-plugin-jsx-a11y' +import importX from 'eslint-plugin-import-x' +import globals from 'globals' + +export default [ + { + ignores: [ + 'dist/**', + 'node_modules/**', + 'coverage/**', + 'android/**', + 'ios/**', + '*.config.{js,ts}', + 'vite.config.ts', + 'vitest.config.ts', + 'tailwind.config.js', + 'postcss.config.js', + 'src/__tests__/**', + 'src/__mocks__/**', + 'src/test-setup.ts', + 'src/test-utils/**' + ] + }, + + // Base configs + js.configs.recommended, + { + files: ['src/**/*.{ts,tsx}'], + languageOptions: { + parser: typescriptParser, + ecmaVersion: 2022, + sourceType: 'module', + globals: { + ...globals.browser, + ...globals.es2022 + }, + parserOptions: { + ecmaFeatures: { + jsx: true + } + } + }, + + plugins: { + '@typescript-eslint': typescript, + 'react': react, + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + 'jsx-a11y': jsxA11y, + 'import-x': importX + }, + + rules: { + // TypeScript rules + ...typescript.configs.recommended.rules, + '@typescript-eslint/no-unused-vars': 'error', + '@typescript-eslint/no-explicit-any': 'warn', + 'prefer-const': 'error', + + // React rules + ...react.configs.recommended.rules, + 'react/react-in-jsx-scope': 'off', // Not needed with React 17+ JSX transform + 'react/jsx-uses-react': 'off', // Not needed with React 17+ JSX transform + + // React Hooks + ...reactHooks.configs.recommended.rules, + + // React Refresh + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true } + ], + + // Accessibility rules + ...jsxA11y.configs.recommended.rules, + + // Import rules + 'import-x/no-unresolved': 'error', + 'import-x/order': [ + 'warn', + { + groups: [ + 'builtin', + 'external', + 'internal', + 'parent', + 'sibling', + 'index' + ], + 'newlines-between': 'never' + } + ] + }, + + settings: { + react: { + version: 'detect' + }, + 'import-x/resolver': { + typescript: { + alwaysTryTypes: true, + project: './tsconfig.json' + } + } + } + } +] \ No newline at end of file diff --git a/package.json b/package.json index e53a665..0ab5f96 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,13 @@ "scripts": { "dev": "vite", "build": "tsc && vite build && pnpm exec cap sync", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 3", "eslist": "pnpm run lint", "preview": "vite preview", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "test": "vitest", + "test:coverage": "vitest --coverage", + "test:ui": "vitest --ui" }, "dependencies": { "@capacitor-community/keep-awake": "^7.1.0", @@ -60,8 +63,13 @@ "devDependencies": { "@capacitor/assets": "^3.0.5", "@capacitor/cli": "^7.4.1", + "@eslint/js": "^9.34.0", + "@faker-js/faker": "^10.0.0", "@tanstack/eslint-plugin-query": "^5.81.2", "@tanstack/router-vite-plugin": "^1.125.6", + "@testing-library/jest-dom": "^6.8.0", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.6.1", "@types/lodash": "^4.17.20", "@types/node": "^24.0.11", "@types/react": "^19.1.8", @@ -70,25 +78,35 @@ "@typescript-eslint/eslint-plugin": "^8.36.0", "@typescript-eslint/parser": "^8.36.0", "@vitejs/plugin-react": "^4.6.0", + "@vitest/coverage-v8": "^3.2.4", + "@vitest/ui": "^3.2.4", "autoprefixer": "^10.4.21", - "eslint": "^9.30.1", + "eslint": "^9.34.0", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import-x": "^4.16.1", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "happy-dom": "^18.0.1", + "msw": "^2.10.5", "postcss": "^8.5.6", "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.6.13", "tailwindcss": "^4.1.11", + "tdd-guard-vitest": "^0.1.3", "tw-animate-css": "^1.3.5", "typescript": "^5.8.3", - "vite": "7.0.3" + "vite": "7.0.3", + "vitest": "^3.2.4" }, "pnpm": { "onlyBuiltDependencies": [ "@firebase/util", "@tailwindcss/oxide", "esbuild", - "protobufjs", - "sharp" + "protobufjs" ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 494562b..56326a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -147,12 +147,27 @@ importers: '@capacitor/cli': specifier: ^7.4.1 version: 7.4.1 + '@eslint/js': + specifier: ^9.34.0 + version: 9.34.0 + '@faker-js/faker': + specifier: ^10.0.0 + version: 10.0.0 '@tanstack/eslint-plugin-query': specifier: ^5.81.2 - version: 5.81.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + version: 5.81.2(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3) '@tanstack/router-vite-plugin': specifier: ^1.125.6 version: 1.125.6(@tanstack/react-router@1.125.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) + '@testing-library/jest-dom': + specifier: ^6.8.0 + version: 6.8.0 + '@testing-library/react': + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) '@types/lodash': specifier: ^4.17.20 version: 4.17.20 @@ -170,25 +185,52 @@ importers: version: 10.0.0 '@typescript-eslint/eslint-plugin': specifier: ^8.36.0 - version: 8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + version: 8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/parser': specifier: ^8.36.0 - version: 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + version: 8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3) '@vitejs/plugin-react': specifier: ^4.6.0 version: 4.6.0(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/coverage-v8': + specifier: ^3.2.4 + version: 3.2.4(vitest@3.2.4) + '@vitest/ui': + specifier: ^3.2.4 + version: 3.2.4(vitest@3.2.4) autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) eslint: - specifier: ^9.30.1 - version: 9.30.1(jiti@2.4.2) + specifier: ^9.34.0 + version: 9.34.0(jiti@2.4.2) + eslint-import-resolver-typescript: + specifier: ^4.4.4 + version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.34.0(jiti@2.4.2)))(eslint@9.34.0(jiti@2.4.2)) + eslint-plugin-import-x: + specifier: ^4.16.1 + version: 4.16.1(@typescript-eslint/utils@8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.34.0(jiti@2.4.2)) + eslint-plugin-jsx-a11y: + specifier: ^6.10.2 + version: 6.10.2(eslint@9.34.0(jiti@2.4.2)) + eslint-plugin-react: + specifier: ^7.37.5 + version: 7.37.5(eslint@9.34.0(jiti@2.4.2)) eslint-plugin-react-hooks: specifier: ^5.2.0 - version: 5.2.0(eslint@9.30.1(jiti@2.4.2)) + version: 5.2.0(eslint@9.34.0(jiti@2.4.2)) eslint-plugin-react-refresh: specifier: ^0.4.20 - version: 0.4.20(eslint@9.30.1(jiti@2.4.2)) + version: 0.4.20(eslint@9.34.0(jiti@2.4.2)) + globals: + specifier: ^16.3.0 + version: 16.3.0 + happy-dom: + specifier: ^18.0.1 + version: 18.0.1 + msw: + specifier: ^2.10.5 + version: 2.11.0(@types/node@24.0.11)(typescript@5.8.3) postcss: specifier: ^8.5.6 version: 8.5.6 @@ -201,6 +243,9 @@ importers: tailwindcss: specifier: ^4.1.11 version: 4.1.11 + tdd-guard-vitest: + specifier: ^0.1.3 + version: 0.1.3(vitest@3.2.4) tw-animate-css: specifier: ^1.3.5 version: 1.3.5 @@ -210,9 +255,15 @@ importers: vite: specifier: 7.0.3 version: 7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@24.0.11)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.4.2)(lightningcss@1.30.1)(msw@2.11.0(@types/node@24.0.11)(typescript@5.8.3))(tsx@4.20.3)(yaml@2.8.0) packages: + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -221,6 +272,10 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} + '@anthropic-ai/sdk@0.57.0': + resolution: {integrity: sha512-z5LMy0MWu0+w2hflUgj4RlJr1R+0BxKXL7ldXTO8FasU8fu599STghO+QKwId2dAD0d464aHtU+ChWuRHw4FNw==} + hasBin: true + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -366,6 +421,19 @@ packages: resolution: {integrity: sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@bundled-es-modules/cookie@2.0.1': + resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + '@capacitor-community/keep-awake@7.1.0': resolution: {integrity: sha512-4Hj6OKnBd/DAOeiMnqBy74tg1rwC+qLYWQbWJmysMZm2e2nptz43hxaKu22Xlyyw5O71CwwskLCM0skDFMPpQQ==} peerDependencies: @@ -447,6 +515,15 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@emnapi/core@1.5.0': + resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} + + '@emnapi/runtime@1.5.0': + resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@esbuild/aix-ppc64@0.25.6': resolution: {integrity: sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==} engines: {node: '>=18'} @@ -617,34 +694,34 @@ packages: resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-helpers@0.3.0': - resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/core@0.14.0': - resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} + '@eslint/config-helpers@0.3.1': + resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.15.1': - resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.30.1': - resolution: {integrity: sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==} + '@eslint/js@9.34.0': + resolution: {integrity: sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.3': - resolution: {integrity: sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==} + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@faker-js/faker@10.0.0': + resolution: {integrity: sha512-UollFEUkVXutsaP+Vndjxar40Gs5JL2HeLcl8xO1QAjJgOdhc3OmBFWyEylS+RddWaaBiAzH+5/17PLQJwDiLw==} + engines: {node: ^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0, npm: '>=10'} + '@firebase/ai@1.4.1': resolution: {integrity: sha512-bcusQfA/tHjUjBTnMx6jdoPMpDl3r8K15Z+snHz9wq0Foox0F/V+kNLXucEOHoTL2hTc9l+onZCyBJs2QoIC3g==} engines: {node: '>=18.0.0'} @@ -888,6 +965,37 @@ packages: resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} engines: {node: '>=6.9.0'} + '@inquirer/confirm@5.1.16': + resolution: {integrity: sha512-j1a5VstaK5KQy8Mu8cHmuQvN1Zc62TbLhjJxwHvKPPKEoowSF6h/0UdOpA9DNdWZ+9Inq73+puRq1df6OJ8Sag==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.2.0': + resolution: {integrity: sha512-NyDSjPqhSvpZEMZrLCYUquWNl+XC/moEcVFqS55IEYIYsY0a1cUCevSqk7ctOlnm/RaSBU5psFryNlxcmGrjaA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.13': + resolution: {integrity: sha512-lGPVU3yO9ZNqA7vTYz26jny41lE7yoQansmqdMLBEfqaGsmdg7V3W9mK9Pvb5IL4EVZ9GnSDGMO/cJXud5dMaw==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.8': + resolution: {integrity: sha512-lg9Whz8onIHRthWaN1Q9EGLa/0LFJjyM8mEUbL1eTi6yMGvBf8gvyDLtxSXztQsxMvhxxNpJYrwa1YHdq+w4Jw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@ionic/cli-framework-output@2.2.8': resolution: {integrity: sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==} engines: {node: '>=16.0.0'} @@ -952,6 +1060,10 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.3.12': resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} @@ -965,9 +1077,19 @@ packages: '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@mswjs/interceptors@0.39.6': + resolution: {integrity: sha512-bndDP83naYYkfayr/qhBHMhk0YGwS1iv6vaEGcr0SQbO0IZtbOPqjKjds/WcG+bJA+1T5vCx6kprKOzn5Bg+Vw==} + engines: {node: '>=18'} + + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} @@ -984,9 +1106,25 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@paralleldrive/cuid2@2.2.2': resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@prettier/plugin-xml@2.2.0': resolution: {integrity: sha512-UWRmygBsyj4bVXvDiqSccwT1kmsorcwQwaIy30yVh8T+Gspx4OlC0shX1y+ZuwXZvgnafmpRYKks0bAu9urJew==} @@ -1482,6 +1620,35 @@ packages: resolution: {integrity: sha512-3nuYsTyaq6ZN7jRZ9z6Gj3GXZqBOqOT0yzd/WZ33ZFfv4yVNIvsa5Lw+M1j3sgyEAxKMqGu/FaNi7FCjr3yOdw==} engines: {node: '>=12'} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.8.0': + resolution: {integrity: sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@trapezedev/gradle-parse@7.1.3': resolution: {integrity: sha512-WQVF5pEJ5o/mUyvfGTG9nBKx9Te/ilKM3r2IT69GlbaooItT5ao7RyF1MUTBNjHLPk/xpGUY3c6PyVnjDlz0Vw==} @@ -1500,6 +1667,12 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@tybys/wasm-util@0.10.0': + resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1512,6 +1685,15 @@ packages: '@types/babel__traverse@7.20.7': resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -1527,6 +1709,9 @@ packages: '@types/minimist@1.2.5': resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + '@types/node@20.19.11': + resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==} + '@types/node@24.0.11': resolution: {integrity: sha512-CJV8eqrYnwQJGMrvcRhQmZfpyniDavB+7nAZYJc6w99hFYJyFN3INV1/2W3QfQrqM36WTLrijJ1fxxvGBmCSxA==} @@ -1544,9 +1729,18 @@ packages: '@types/slice-ansi@4.0.0': resolution: {integrity: sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==} + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/whatwg-mimetype@3.0.2': + resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} + '@typescript-eslint/eslint-plugin@8.36.0': resolution: {integrity: sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1613,12 +1807,150 @@ packages: react: '>=18.0.0' react-dom: '>=18.0.0' + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.11.1': + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.11.1': + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + cpu: [x64] + os: [win32] + '@vitejs/plugin-react@4.6.0': resolution: {integrity: sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + '@vitest/coverage-v8@3.2.4': + resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} + peerDependencies: + '@vitest/browser': 3.2.4 + vitest: 3.2.4 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/ui@3.2.4': + resolution: {integrity: sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==} + peerDependencies: + vitest: 3.2.4 + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@xml-tools/parser@1.0.11': resolution: {integrity: sha512-aKqQ077XnR+oQtHJlrAflaZaL7qZsulWc/i/ZEooar5JiWj1eLt0+Wg28cpa+XLney107wXqneC+oG1IZvxkTA==} @@ -1659,6 +1991,10 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1675,6 +2011,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -1697,13 +2037,48 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + array-ify@1.0.0: resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + arrify@1.0.1: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} @@ -1711,14 +2086,28 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + ast-types@0.16.1: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} + ast-v8-to-istanbul@0.3.5: + resolution: {integrity: sha512-9SdXjNheSiE8bALAQCQQuT6fgQaoxJh7IRYrRGZ8/9nv8WhJeC1aXAwN8TbaOssGOukUvyvnkgD9+Yuykvl1aA==} + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1733,9 +2122,21 @@ packages: peerDependencies: postcss: ^8.1.0 + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axe-core@4.10.3: + resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} + engines: {node: '>=4'} + axios@1.10.0: resolution: {integrity: sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==} + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + b4a@1.6.7: resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==} @@ -1824,10 +2225,22 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -1848,6 +2261,10 @@ packages: peerDependencies: '@capacitor/core': '>=7.0.0' + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -1856,6 +2273,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chevrotain@7.1.1: resolution: {integrity: sha512-wy3mC1x4ye+O+QkEinVJkPf5u2vsrDIYW9G7ZuwFl6v/Yu0LwUuT2POsb+NUWApebyxfkQq6+yDfRExbnI5rcw==} @@ -1884,6 +2305,10 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} @@ -1934,6 +2359,10 @@ packages: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} + comment-parser@1.4.1: + resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} + engines: {node: '>= 12.0.0'} + compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} @@ -2008,6 +2437,10 @@ packages: cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -2029,13 +2462,31 @@ packages: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + dargs@7.0.0: resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} engines: {node: '>=8'} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + dateformat@3.0.3: resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} @@ -2069,6 +2520,10 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -2076,10 +2531,18 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + define-lazy-prop@2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + del@6.1.1: resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} engines: {node: '>=10'} @@ -2088,6 +2551,10 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} @@ -2114,6 +2581,16 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-serializer@1.4.1: resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} @@ -2135,6 +2612,10 @@ packages: resolution: {integrity: sha512-tG9VUTJTuju6GcXgbdsOuRhupE8cb4mRgY5JLRCh4MtGoVo3/gfGUtOMwmProM6d0ba2mCFvv+WrpYJV6qgJXQ==} engines: {node: '>=12'} + dotenv@17.2.1: + resolution: {integrity: sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2176,6 +2657,10 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -2184,6 +2669,13 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -2192,6 +2684,14 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + esbuild@0.25.6: resolution: {integrity: sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==} engines: {node: '>=18'} @@ -2209,6 +2709,47 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-import-context@0.1.9: + resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + peerDependencies: + unrs-resolver: ^1.0.0 + peerDependenciesMeta: + unrs-resolver: + optional: true + + eslint-import-resolver-typescript@4.4.4: + resolution: {integrity: sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==} + engines: {node: ^16.17.0 || >=18.6.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-plugin-import-x@4.16.1: + resolution: {integrity: sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/utils': ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + eslint-import-resolver-node: '*' + peerDependenciesMeta: + '@typescript-eslint/utils': + optional: true + eslint-import-resolver-node: + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + eslint-plugin-react-hooks@5.2.0: resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} engines: {node: '>=10'} @@ -2220,6 +2761,12 @@ packages: peerDependencies: eslint: '>=8.40' + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2232,8 +2779,8 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.30.1: - resolution: {integrity: sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==} + eslint@9.34.0: + resolution: {integrity: sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -2263,6 +2810,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -2271,6 +2821,10 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2305,6 +2859,9 @@ packages: picomatch: optional: true + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -2344,6 +2901,10 @@ packages: debug: optional: true + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -2389,6 +2950,13 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -2414,7 +2982,11 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-tsconfig@4.10.1: + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} git-raw-commits@2.0.11: @@ -2445,6 +3017,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + glob@11.0.3: resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} engines: {node: 20 || >=22} @@ -2462,6 +3038,14 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} + globals@16.3.0: + resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + globby@11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -2485,15 +3069,27 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql@16.11.0: + resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} hasBin: true + happy-dom@18.0.1: + resolution: {integrity: sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==} + engines: {node: '>=20.0.0'} + hard-rejection@2.1.0: resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} engines: {node: '>=6'} + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -2502,6 +3098,13 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -2518,6 +3121,9 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -2525,6 +3131,9 @@ packages: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-parse-stringify@3.0.1: resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} @@ -2586,20 +3195,55 @@ packages: resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -2609,14 +3253,37 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -2637,14 +3304,50 @@ packages: resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} engines: {node: '>=0.10.0'} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + is-text-path@1.0.1: resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} engines: {node: '>=0.10.0'} + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -2652,6 +3355,9 @@ packages: isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isbot@5.1.28: resolution: {integrity: sha512-qrOp4g3xj8YNse4biorv6O5ZShwsJM0trsoda4y7j/Su7ZtTTfVXFzbKkpgcSoDrHS8FcTuUwcU04YimZlZOxw==} engines: {node: '>=18'} @@ -2659,6 +3365,29 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@4.1.1: resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} engines: {node: 20 || >=22} @@ -2670,6 +3399,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -2709,6 +3441,10 @@ packages: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2724,6 +3460,13 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -2826,6 +3569,13 @@ packages: long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2845,9 +3595,20 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -2960,12 +3721,30 @@ packages: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.11.0: + resolution: {integrity: sha512-jEqa5J5B1OMD1jHu0gasCb5YriIDiWGdoS22Ie8CNNrl1iGRzCVfok3zuerdfb7GNFeIbYePXDA5c2bwRKMpBA==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2974,6 +3753,11 @@ packages: napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + napi-postinstall@0.3.3: + resolution: {integrity: sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + native-run@2.0.1: resolution: {integrity: sha512-XfG1FBZLM50J10xH9361whJRC9SHZ0Bub4iNRhhI61C8Jv0e1ud19muex6sNKB51ibQNUJNuYn25MuYET/rE6w==} engines: {node: '>=16.0.0'} @@ -3025,6 +3809,34 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -3036,6 +3848,13 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + p-limit@1.3.0: resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} engines: {node: '>=4'} @@ -3114,6 +3933,9 @@ packages: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} @@ -3122,6 +3944,13 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -3148,6 +3977,10 @@ packages: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -3229,6 +4062,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -3236,6 +4073,9 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + protobufjs@7.5.3: resolution: {integrity: sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==} engines: {node: '>=12.0.0'} @@ -3243,6 +4083,9 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + pump@3.0.3: resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} @@ -3258,6 +4101,9 @@ packages: (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -3297,6 +4143,12 @@ packages: typescript: optional: true + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -3375,9 +4227,17 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + regexp-to-ast@0.5.0: resolution: {integrity: sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==} + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + replace@1.2.2: resolution: {integrity: sha512-C4EDifm22XZM2b2JOYe6Mhn+lBsLBAvLbK8drfUQLTfD1KYl/n3VaW/CDju0Ny4w3xTtegBpg8YNSpFJPUDSjA==} engines: {node: '>= 6'} @@ -3390,6 +4250,9 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3402,6 +4265,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -3429,12 +4296,24 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + sax@1.1.4: resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==} @@ -3460,6 +4339,18 @@ packages: set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + sharp@0.32.6: resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} engines: {node: '>=14.15.0'} @@ -3472,6 +4363,25 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -3491,6 +4401,10 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + engines: {node: '>=18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -3536,6 +4450,24 @@ packages: split@1.0.1: resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + stable-hash-x@0.2.0: + resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} + engines: {node: '>=12.0.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + stream-buffers@2.2.0: resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} engines: {node: '>= 0.10.0'} @@ -3543,6 +4475,9 @@ packages: streamx@2.22.1: resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -3551,6 +4486,29 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -3581,6 +4539,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + supports-color@5.5.0: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} @@ -3629,6 +4590,15 @@ packages: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} + tdd-guard-vitest@0.1.3: + resolution: {integrity: sha512-n3FwqVs5zSlMi5tfA9cUl6NMV7woICouSl/eYCGvRJDp/YdI4RN+vHkLeQHXM/UUr9GUKjYwcsDLnsLOCkSbIw==} + peerDependencies: + vitest: '>=3.2.4' + + tdd-guard@0.8.1: + resolution: {integrity: sha512-5s1axCnB9UNmJ6bfAh/mm6UivLSefOy3qsPUZ9qtGAkZZs66gDlfq94x0OdTPaJ8ZaYPsvUaNs0k2iSUYnQL9g==} + hasBin: true + temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} @@ -3637,6 +4607,10 @@ packages: resolution: {integrity: sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==} engines: {node: '>=10'} + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-decoder@1.2.3: resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} @@ -3659,10 +4633,28 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + tmp@0.2.3: resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} engines: {node: '>=14.14'} @@ -3671,6 +4663,14 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -3731,6 +4731,10 @@ packages: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} @@ -3739,6 +4743,26 @@ packages: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} @@ -3749,6 +4773,13 @@ packages: engines: {node: '>=0.8.0'} hasBin: true + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.8.0: resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} @@ -3756,6 +4787,10 @@ packages: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -3764,6 +4799,9 @@ packages: resolution: {integrity: sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==} engines: {node: '>=18.12.0'} + unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -3777,6 +4815,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -3831,6 +4872,11 @@ packages: react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + vite@7.0.3: resolution: {integrity: sha512-y2L5oJZF7bj4c0jgGYgBNSdIu+5HF+m68rn2cQXFbGoShdhV1phX9rbnxy9YXj82aS8MMsCLAAFkRxZeWdldrQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3871,6 +4917,34 @@ packages: yaml: optional: true + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + void-elements@3.1.0: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} @@ -3895,17 +4969,42 @@ packages: websocket-heartbeat-js@1.1.3: resolution: {integrity: sha512-wiCMVsWK7KflVCC5f+rSTyzILDZX88A7BJfdJXt5THNDut7uUNP83r5AvdZ1El/8yOwGujC7K3iuxj96CT4gbQ==} + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -4021,9 +5120,16 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.1.5: + resolution: {integrity: sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==} + zustand@5.0.6: resolution: {integrity: sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==} engines: {node: '>=12.20.0'} @@ -4044,6 +5150,8 @@ packages: snapshots: + '@adobe/css-tools@4.4.4': {} + '@alloc/quick-lru@5.2.0': {} '@ampproject/remapping@2.3.0': @@ -4051,6 +5159,8 @@ snapshots: '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 + '@anthropic-ai/sdk@0.57.0': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -4249,14 +5359,29 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 - '@capacitor-community/keep-awake@7.1.0(@capacitor/core@7.4.1)': + '@bcoe/v8-coverage@1.0.2': {} + + '@bundled-es-modules/cookie@2.0.1': dependencies: - '@capacitor/core': 7.4.1 + cookie: 0.7.2 - '@capacitor-firebase/authentication@7.2.0(@capacitor/core@7.4.1)(firebase@11.10.0)': + '@bundled-es-modules/statuses@1.0.1': dependencies: - '@capacitor/core': 7.4.1 - optionalDependencies: + statuses: 2.0.2 + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + + '@capacitor-community/keep-awake@7.1.0(@capacitor/core@7.4.1)': + dependencies: + '@capacitor/core': 7.4.1 + + '@capacitor-firebase/authentication@7.2.0(@capacitor/core@7.4.1)(firebase@11.10.0)': + dependencies: + '@capacitor/core': 7.4.1 + optionalDependencies: firebase: 11.10.0 '@capacitor-mlkit/barcode-scanning@7.2.1(@capacitor/core@7.4.1)': @@ -4370,6 +5495,22 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@emnapi/core@1.5.0': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.5.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + '@esbuild/aix-ppc64@0.25.6': optional: true @@ -4448,9 +5589,9 @@ snapshots: '@esbuild/win32-x64@0.25.6': optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@9.30.1(jiti@2.4.2))': + '@eslint-community/eslint-utils@4.7.0(eslint@9.34.0(jiti@2.4.2))': dependencies: - eslint: 9.30.1(jiti@2.4.2) + eslint: 9.34.0(jiti@2.4.2) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -4463,13 +5604,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.3.0': {} - - '@eslint/core@0.14.0': - dependencies: - '@types/json-schema': 7.0.15 + '@eslint/config-helpers@0.3.1': {} - '@eslint/core@0.15.1': + '@eslint/core@0.15.2': dependencies: '@types/json-schema': 7.0.15 @@ -4487,15 +5624,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.30.1': {} + '@eslint/js@9.34.0': {} '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.3.3': + '@eslint/plugin-kit@0.3.5': dependencies: - '@eslint/core': 0.15.1 + '@eslint/core': 0.15.2 levn: 0.4.1 + '@faker-js/faker@10.0.0': {} + '@firebase/ai@1.4.1(@firebase/app-types@0.9.3)(@firebase/app@0.13.2)': dependencies: '@firebase/app': 0.13.2 @@ -4841,6 +5980,32 @@ snapshots: '@hutson/parse-repository-url@3.0.2': {} + '@inquirer/confirm@5.1.16(@types/node@24.0.11)': + dependencies: + '@inquirer/core': 10.2.0(@types/node@24.0.11) + '@inquirer/type': 3.0.8(@types/node@24.0.11) + optionalDependencies: + '@types/node': 24.0.11 + + '@inquirer/core@10.2.0(@types/node@24.0.11)': + dependencies: + '@inquirer/figures': 1.0.13 + '@inquirer/type': 3.0.8(@types/node@24.0.11) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 24.0.11 + + '@inquirer/figures@1.0.13': {} + + '@inquirer/type@3.0.8(@types/node@24.0.11)': + optionalDependencies: + '@types/node': 24.0.11 + '@ionic/cli-framework-output@2.2.8': dependencies: '@ionic/utils-terminal': 2.3.5 @@ -4981,6 +6146,8 @@ snapshots: dependencies: minipass: 7.1.2 + '@istanbuljs/schema@0.1.3': {} + '@jridgewell/gen-mapping@0.3.12': dependencies: '@jridgewell/sourcemap-codec': 1.5.4 @@ -4995,11 +6162,32 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 + '@mswjs/interceptors@0.39.6': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + + '@napi-rs/wasm-runtime@0.2.12': + dependencies: + '@emnapi/core': 1.5.0 + '@emnapi/runtime': 1.5.0 + '@tybys/wasm-util': 0.10.0 + optional: true + '@noble/hashes@1.8.0': {} '@nodelib/fs.scandir@2.1.5': @@ -5014,10 +6202,24 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + '@paralleldrive/cuid2@2.2.2': dependencies: '@noble/hashes': 1.8.0 + '@pkgjs/parseargs@0.11.0': + optional: true + + '@polka/url@1.0.0-next.29': {} + '@prettier/plugin-xml@2.2.0': dependencies: '@xml-tools/parser': 1.0.11 @@ -5346,10 +6548,10 @@ snapshots: tailwindcss: 4.1.11 vite: 7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) - '@tanstack/eslint-plugin-query@5.81.2(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@tanstack/eslint-plugin-query@5.81.2(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.30.1(jiti@2.4.2) + '@typescript-eslint/utils': 8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.34.0(jiti@2.4.2) transitivePeerDependencies: - supports-color - typescript @@ -5452,6 +6654,40 @@ snapshots: '@tanstack/virtual-file-routes@1.121.21': {} + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.27.6 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.8.0': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.27.6 + '@testing-library/dom': 10.4.1 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + '@trapezedev/gradle-parse@7.1.3': {} '@trapezedev/project@7.1.3(@types/node@24.0.11)(typescript@5.8.3)': @@ -5496,6 +6732,13 @@ snapshots: '@tsconfig/node16@1.0.4': {} + '@tybys/wasm-util@0.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.0 @@ -5517,6 +6760,14 @@ snapshots: dependencies: '@babel/types': 7.28.0 + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/cookie@0.6.0': {} + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} '@types/fs-extra@8.1.5': @@ -5529,6 +6780,10 @@ snapshots: '@types/minimist@1.2.5': {} + '@types/node@20.19.11': + dependencies: + undici-types: 6.21.0 + '@types/node@24.0.11': dependencies: undici-types: 7.8.0 @@ -5545,17 +6800,23 @@ snapshots: '@types/slice-ansi@4.0.0': {} + '@types/statuses@2.0.6': {} + + '@types/tough-cookie@4.0.5': {} + '@types/uuid@10.0.0': {} - '@typescript-eslint/eslint-plugin@8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@types/whatwg-mimetype@3.0.2': {} + + '@typescript-eslint/eslint-plugin@8.36.0(@typescript-eslint/parser@8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/scope-manager': 8.36.0 - '@typescript-eslint/type-utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/type-utils': 8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.36.0 - eslint: 9.30.1(jiti@2.4.2) + eslint: 9.34.0(jiti@2.4.2) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -5564,14 +6825,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/parser@8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.36.0 '@typescript-eslint/types': 8.36.0 '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) '@typescript-eslint/visitor-keys': 8.36.0 debug: 4.4.1 - eslint: 9.30.1(jiti@2.4.2) + eslint: 9.34.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -5594,12 +6855,12 @@ snapshots: dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3) debug: 4.4.1 - eslint: 9.30.1(jiti@2.4.2) + eslint: 9.34.0(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.8.3) typescript: 5.8.3 transitivePeerDependencies: @@ -5623,13 +6884,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/utils@8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.4.2)) '@typescript-eslint/scope-manager': 8.36.0 '@typescript-eslint/types': 8.36.0 '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) - eslint: 9.30.1(jiti@2.4.2) + eslint: 9.34.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color @@ -5644,6 +6905,65 @@ snapshots: react: 19.1.0 react-dom: 19.1.0(react@19.1.0) + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + optional: true + + '@unrs/resolver-binding-android-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + optional: true + '@vitejs/plugin-react@4.6.0(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@babel/core': 7.28.0 @@ -5656,6 +6976,79 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/coverage-v8@3.2.4(vitest@3.2.4)': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + ast-v8-to-istanbul: 0.3.5 + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.17 + magicast: 0.3.5 + std-env: 3.9.0 + test-exclude: 7.0.1 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/node@24.0.11)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.4.2)(lightningcss@1.30.1)(msw@2.11.0(@types/node@24.0.11)(typescript@5.8.3))(tsx@4.20.3)(yaml@2.8.0) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(msw@2.11.0(@types/node@24.0.11)(typescript@5.8.3))(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + msw: 2.11.0(@types/node@24.0.11)(typescript@5.8.3) + vite: 7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.3 + + '@vitest/ui@3.2.4(vitest@3.2.4)': + dependencies: + '@vitest/utils': 3.2.4 + fflate: 0.8.2 + flatted: 3.3.3 + pathe: 2.0.3 + sirv: 3.0.1 + tinyglobby: 0.2.14 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/node@24.0.11)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.4.2)(lightningcss@1.30.1)(msw@2.11.0(@types/node@24.0.11)(typescript@5.8.3))(tsx@4.20.3)(yaml@2.8.0) + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@xml-tools/parser@1.0.11': dependencies: chevrotain: 7.1.1 @@ -5693,6 +7086,10 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -5705,6 +7102,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansi-styles@6.2.1: {} ansis@4.1.0: {} @@ -5722,20 +7121,95 @@ snapshots: dependencies: tslib: 2.8.1 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + array-ify@1.0.0: {} + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + array-union@2.1.0: {} + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + arrify@1.0.1: {} asap@2.0.6: {} + assertion-error@2.0.1: {} + + ast-types-flow@0.0.8: {} + ast-types@0.16.1: dependencies: tslib: 2.8.1 + ast-v8-to-istanbul@0.3.5: + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + estree-walker: 3.0.3 + js-tokens: 9.0.1 + astral-regex@2.0.0: {} + async-function@1.0.0: {} + asynckit@0.4.0: {} at-least-node@1.0.0: {} @@ -5750,6 +7224,12 @@ snapshots: postcss: 8.5.6 postcss-value-parser: 4.2.0 + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axe-core@4.10.3: {} + axios@1.10.0: dependencies: follow-redirects: 1.15.9 @@ -5758,6 +7238,8 @@ snapshots: transitivePeerDependencies: - debug + axobject-query@4.1.0: {} + b4a@1.6.7: {} babel-dead-code-elimination@1.0.10: @@ -5849,11 +7331,25 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + callsites@3.1.0: {} camelcase-keys@6.2.2: @@ -5870,6 +7366,14 @@ snapshots: dependencies: '@capacitor/core': 7.4.1 + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -5881,6 +7385,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + check-error@2.1.1: {} + chevrotain@7.1.1: dependencies: regexp-to-ast: 0.5.0 @@ -5911,6 +7417,8 @@ snapshots: clean-stack@2.2.0: {} + cli-width@4.1.0: {} + cliui@6.0.0: dependencies: string-width: 4.2.3 @@ -5963,6 +7471,8 @@ snapshots: commander@9.5.0: {} + comment-parser@1.4.1: {} + compare-func@2.0.0: dependencies: array-ify: 1.0.0 @@ -6073,6 +7583,8 @@ snapshots: cookie-es@1.2.2: {} + cookie@0.7.2: {} + core-util-is@1.0.3: {} create-require@1.1.1: {} @@ -6095,10 +7607,32 @@ snapshots: css-what@6.2.2: {} + css.escape@1.5.1: {} + csstype@3.1.3: {} + damerau-levenshtein@1.0.8: {} + dargs@7.0.0: {} + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + dateformat@3.0.3: {} debug@4.3.4: @@ -6120,12 +7654,26 @@ snapshots: dependencies: mimic-response: 3.1.0 + deep-eql@5.0.2: {} + deep-extend@0.6.0: {} deep-is@0.1.4: {} + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + define-lazy-prop@2.0.0: {} + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + del@6.1.1: dependencies: globby: 11.1.0 @@ -6139,6 +7687,8 @@ snapshots: delayed-stream@1.0.0: {} + dequal@2.0.3: {} + detect-libc@2.0.4: {} detect-node-es@1.1.0: {} @@ -6158,6 +7708,14 @@ snapshots: dependencies: path-type: 4.0.0 + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + dom-serializer@1.4.1: dependencies: domelementtype: 2.3.0 @@ -6182,6 +7740,8 @@ snapshots: dotenv@17.1.0: {} + dotenv@17.2.1: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6219,11 +7779,89 @@ snapshots: dependencies: is-arrayish: 0.2.1 + es-abstract@1.24.0: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-object-atoms@1.1.1: + es-iterator-helpers@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -6234,6 +7872,16 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + esbuild@0.25.6: optionalDependencies: '@esbuild/aix-ppc64': 0.25.6 @@ -6269,13 +7917,93 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-plugin-react-hooks@5.2.0(eslint@9.30.1(jiti@2.4.2)): + eslint-import-context@0.1.9(unrs-resolver@1.11.1): dependencies: - eslint: 9.30.1(jiti@2.4.2) + get-tsconfig: 4.10.1 + stable-hash-x: 0.2.0 + optionalDependencies: + unrs-resolver: 1.11.1 - eslint-plugin-react-refresh@0.4.20(eslint@9.30.1(jiti@2.4.2)): + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.34.0(jiti@2.4.2)))(eslint@9.34.0(jiti@2.4.2)): dependencies: - eslint: 9.30.1(jiti@2.4.2) + debug: 4.4.1 + eslint: 9.34.0(jiti@2.4.2) + eslint-import-context: 0.1.9(unrs-resolver@1.11.1) + get-tsconfig: 4.10.1 + is-bun-module: 2.0.0 + stable-hash-x: 0.2.0 + tinyglobby: 0.2.14 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.34.0(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.34.0(jiti@2.4.2)): + dependencies: + '@typescript-eslint/types': 8.36.0 + comment-parser: 1.4.1 + debug: 4.4.1 + eslint: 9.34.0(jiti@2.4.2) + eslint-import-context: 0.1.9(unrs-resolver@1.11.1) + is-glob: 4.0.3 + minimatch: 10.0.3 + semver: 7.7.2 + stable-hash-x: 0.2.0 + unrs-resolver: 1.11.1 + optionalDependencies: + '@typescript-eslint/utils': 8.36.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@9.34.0(jiti@2.4.2)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.10.3 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.34.0(jiti@2.4.2) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@5.2.0(eslint@9.34.0(jiti@2.4.2)): + dependencies: + eslint: 9.34.0(jiti@2.4.2) + + eslint-plugin-react-refresh@0.4.20(eslint@9.34.0(jiti@2.4.2)): + dependencies: + eslint: 9.34.0(jiti@2.4.2) + + eslint-plugin-react@7.37.5(eslint@9.34.0(jiti@2.4.2)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.34.0(jiti@2.4.2) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 eslint-scope@8.4.0: dependencies: @@ -6286,16 +8014,16 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.30.1(jiti@2.4.2): + eslint@9.34.0(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) + '@eslint-community/eslint-utils': 4.7.0(eslint@9.34.0(jiti@2.4.2)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 - '@eslint/config-helpers': 0.3.0 - '@eslint/core': 0.14.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.30.1 - '@eslint/plugin-kit': 0.3.3 + '@eslint/js': 9.34.0 + '@eslint/plugin-kit': 0.3.5 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 @@ -6346,10 +8074,16 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} expand-template@2.0.3: {} + expect-type@1.2.2: {} + fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} @@ -6382,6 +8116,8 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fflate@0.8.2: {} + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -6446,6 +8182,10 @@ snapshots: follow-redirects@1.15.9: {} + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -6499,6 +8239,17 @@ snapshots: function-bind@1.1.2: {} + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -6530,6 +8281,12 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + get-tsconfig@4.10.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -6566,6 +8323,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@11.0.3: dependencies: foreground-child: 3.3.1 @@ -6593,6 +8359,13 @@ snapshots: globals@14.0.0: {} + globals@16.3.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + globby@11.1.0: dependencies: array-union: 2.1.0 @@ -6616,6 +8389,8 @@ snapshots: graphemer@1.4.0: {} + graphql@16.11.0: {} + handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -6625,12 +8400,28 @@ snapshots: optionalDependencies: uglify-js: 3.19.3 + happy-dom@18.0.1: + dependencies: + '@types/node': 20.19.11 + '@types/whatwg-mimetype': 3.0.2 + whatwg-mimetype: 3.0.0 + hard-rejection@2.1.0: {} + has-bigints@1.1.0: {} + has-flag@3.0.0: {} has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -6643,12 +8434,16 @@ snapshots: he@1.2.0: {} + headers-polyfill@4.0.3: {} + hosted-git-info@2.8.9: {} hosted-git-info@4.1.0: dependencies: lru-cache: 6.0.0 + html-escaper@2.0.2: {} + html-parse-stringify@3.0.1: dependencies: void-elements: 3.1.0 @@ -6695,28 +8490,96 @@ snapshots: ini@4.1.3: {} + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-arrayish@0.2.1: {} is-arrayish@0.3.2: {} + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.7.2 + + is-callable@1.2.7: {} + is-core-module@2.16.1: dependencies: hasown: 2.0.2 + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-docker@2.2.1: {} is-extglob@2.1.1: {} + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-node-process@1.2.0: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-number@7.0.0: {} is-obj@2.0.0: {} @@ -6727,22 +8590,99 @@ snapshots: is-plain-obj@1.1.0: {} + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + is-stream@2.0.1: {} + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + is-text-path@1.0.1: dependencies: text-extensions: 1.9.0 + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 isarray@1.0.0: {} + isarray@2.0.5: {} + isbot@5.1.28: {} isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.29 + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jackspeak@4.1.1: dependencies: '@isaacs/cliui': 8.0.2 @@ -6751,6 +8691,8 @@ snapshots: js-tokens@4.0.0: {} + js-tokens@9.0.1: {} + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -6779,6 +8721,13 @@ snapshots: jsonparse@1.3.1: {} + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -6789,6 +8738,12 @@ snapshots: kleur@4.1.5: {} + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -6871,6 +8826,12 @@ snapshots: long@5.3.2: {} + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + loupe@3.2.1: {} + lru-cache@10.4.3: {} lru-cache@11.1.0: {} @@ -6887,10 +8848,22 @@ snapshots: dependencies: react: 19.1.0 + lz-string@1.5.0: {} + magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.4 + magicast@0.3.5: + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + make-error@1.3.6: {} map-obj@1.0.1: {} @@ -6991,14 +8964,45 @@ snapshots: modify-values@1.0.1: {} + mrmime@2.0.1: {} + ms@2.1.2: {} ms@2.1.3: {} + msw@2.11.0(@types/node@24.0.11)(typescript@5.8.3): + dependencies: + '@bundled-es-modules/cookie': 2.0.1 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.1.16(@types/node@24.0.11) + '@mswjs/interceptors': 0.39.6 + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.6 + graphql: 16.11.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + strict-event-emitter: 0.5.1 + type-fest: 4.41.0 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - '@types/node' + + mute-stream@2.0.0: {} + nanoid@3.3.11: {} napi-build-utils@2.0.0: {} + napi-postinstall@0.3.3: {} + native-run@2.0.1: dependencies: '@ionic/utils-fs': 3.1.7 @@ -7058,6 +9062,42 @@ snapshots: dependencies: boolbase: 1.0.0 + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -7077,6 +9117,14 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + outvariant@1.4.3: {} + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + p-limit@1.3.0: dependencies: p-try: 1.0.0 @@ -7147,12 +9195,18 @@ snapshots: lru-cache: 11.1.0 minipass: 7.1.2 + path-to-regexp@6.3.0: {} + path-type@3.0.0: dependencies: pify: 3.0.0 path-type@4.0.0: {} + pathe@2.0.3: {} + + pathval@2.0.1: {} + pend@1.2.0: {} picocolors@1.1.1: {} @@ -7171,6 +9225,8 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + possible-typed-array-names@1.1.0: {} + postcss-value-parser@4.2.0: {} postcss@8.5.6: @@ -7204,6 +9260,12 @@ snapshots: prettier@3.6.2: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + process-nextick-args@2.0.1: {} prompts@2.4.2: @@ -7211,6 +9273,12 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + protobufjs@7.5.3: dependencies: '@protobufjs/aspromise': 1.1.2 @@ -7228,6 +9296,10 @@ snapshots: proxy-from-env@1.1.0: {} + psl@1.15.0: + dependencies: + punycode: 2.3.1 + pump@3.0.3: dependencies: end-of-stream: 1.4.5 @@ -7237,6 +9309,8 @@ snapshots: q@1.5.1: {} + querystringify@2.2.0: {} + queue-microtask@1.2.3: {} quick-lru@4.0.1: {} @@ -7270,6 +9344,10 @@ snapshots: react-dom: 19.1.0(react@19.1.0) typescript: 5.8.3 + react-is@16.13.1: {} + + react-is@17.0.2: {} + react-refresh@0.17.0: {} react-remove-scroll-bar@2.3.8(@types/react@19.1.8)(react@19.1.0): @@ -7362,8 +9440,28 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + regexp-to-ast@0.5.0: {} + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + replace@1.2.2: dependencies: chalk: 2.4.2 @@ -7374,6 +9472,8 @@ snapshots: require-main-filename@2.0.0: {} + requires-port@1.0.0: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -7384,6 +9484,12 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + reusify@1.1.0: {} rimraf@3.0.2: @@ -7429,10 +9535,29 @@ snapshots: dependencies: queue-microtask: 1.2.3 + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + sax@1.1.4: {} sax@1.4.1: {} @@ -7447,6 +9572,28 @@ snapshots: set-blocking@2.0.0: {} + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + sharp@0.32.6: dependencies: color: 4.2.3 @@ -7466,6 +9613,36 @@ snapshots: shebang-regex@3.0.0: {} + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -7488,6 +9665,12 @@ snapshots: dependencies: is-arrayish: 0.3.2 + sirv@3.0.1: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -7528,6 +9711,19 @@ snapshots: dependencies: through: 2.3.8 + stable-hash-x@0.2.0: {} + + stackback@0.0.2: {} + + statuses@2.0.2: {} + + std-env@3.9.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + stream-buffers@2.2.0: {} streamx@2.22.1: @@ -7537,6 +9733,8 @@ snapshots: optionalDependencies: bare-events: 2.5.4 + strict-event-emitter@0.5.1: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -7549,6 +9747,56 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.0 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -7575,6 +9823,10 @@ snapshots: strip-json-comments@3.1.1: {} + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + supports-color@5.5.0: dependencies: has-flag: 3.0.0 @@ -7644,6 +9896,19 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 + tdd-guard-vitest@0.1.3(vitest@3.2.4): + dependencies: + tdd-guard: 0.8.1 + vitest: 3.2.4(@types/node@24.0.11)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.4.2)(lightningcss@1.30.1)(msw@2.11.0(@types/node@24.0.11)(typescript@5.8.3))(tsx@4.20.3)(yaml@2.8.0) + + tdd-guard@0.8.1: + dependencies: + '@anthropic-ai/sdk': 0.57.0 + '@types/uuid': 10.0.0 + dotenv: 17.2.1 + uuid: 11.1.0 + zod: 4.1.5 + temp-dir@2.0.0: {} tempy@1.0.1: @@ -7654,6 +9919,12 @@ snapshots: type-fest: 0.16.0 unique-string: 2.0.0 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-decoder@1.2.3: dependencies: b4a: 1.6.7 @@ -7675,17 +9946,36 @@ snapshots: tiny-warning@1.0.3: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + tinyglobby@0.2.14: dependencies: fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + tmp@0.2.3: {} to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + totalist@3.0.1: {} + + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + tr46@0.0.3: {} tree-kill@1.2.2: {} @@ -7739,21 +10029,69 @@ snapshots: type-fest@0.18.1: {} + type-fest@0.21.3: {} + type-fest@0.6.0: {} type-fest@0.8.1: {} + type-fest@4.41.0: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + typescript@5.8.3: {} uglify-js@3.19.3: optional: true + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + undici-types@7.8.0: {} unique-string@2.0.0: dependencies: crypto-random-string: 2.0.0 + universalify@0.2.0: {} + universalify@2.0.1: {} unplugin@2.3.5: @@ -7762,6 +10100,30 @@ snapshots: picomatch: 4.0.2 webpack-virtual-modules: 0.6.2 + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.3 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + untildify@4.0.0: {} update-browserslist-db@1.1.3(browserslist@4.25.1): @@ -7774,6 +10136,11 @@ snapshots: dependencies: punycode: 2.3.1 + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + use-callback-ref@1.3.3(@types/react@19.1.8)(react@19.1.0): dependencies: react: 19.1.0 @@ -7819,6 +10186,27 @@ snapshots: - '@types/react' - '@types/react-dom' + vite-node@3.2.4(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0): dependencies: esbuild: 0.25.6 @@ -7835,6 +10223,49 @@ snapshots: tsx: 4.20.3 yaml: 2.8.0 + vitest@3.2.4(@types/node@24.0.11)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.4.2)(lightningcss@1.30.1)(msw@2.11.0(@types/node@24.0.11)(typescript@5.8.3))(tsx@4.20.3)(yaml@2.8.0): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(msw@2.11.0(@types/node@24.0.11)(typescript@5.8.3))(vite@7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.1 + expect-type: 1.2.2 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.0.3(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@24.0.11)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.0.11 + '@vitest/ui': 3.2.4(vitest@3.2.4) + happy-dom: 18.0.1 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + void-elements@3.1.0: {} web-vitals@4.2.4: {} @@ -7853,17 +10284,65 @@ snapshots: websocket-heartbeat-js@1.1.3: {} + whatwg-mimetype@3.0.0: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + which-module@2.0.1: {} + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wordwrap@1.0.0: {} @@ -7982,8 +10461,12 @@ snapshots: yocto-queue@0.1.0: {} + yoctocolors-cjs@2.1.3: {} + zod@3.25.76: {} + zod@4.1.5: {} + zustand@5.0.6(@types/react@19.1.8)(react@19.1.0)(use-sync-external-store@1.5.0(react@19.1.0)): optionalDependencies: '@types/react': 19.1.8 diff --git a/src/App.tsx b/src/App.tsx index 50a71d0..6447750 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,21 +1,21 @@ import { useEffect, useState } from "react"; - import { createRouter, RouterProvider } from "@tanstack/react-router"; -import { routeTree } from "./routeTree.gen"; import toast, { Toaster } from "react-hot-toast"; import { Capacitor } from "@capacitor/core"; -import { useStatusStore } from "./lib/store"; -import { DatabaseIcon, PlayIcon } from "./lib/images"; import { usePrevious } from "@uidotdev/usehooks"; import { StatusBar, Style } from "@capacitor/status-bar"; import { useTranslation } from "react-i18next"; +import { FirebaseAuthentication } from "@capacitor-firebase/authentication"; +import { ErrorComponent } from "@/components/ErrorComponent.tsx"; +import { routeTree } from "./routeTree.gen"; +import { useStatusStore } from "./lib/store"; +import { DatabaseIcon, PlayIcon } from "./lib/images"; import { CoreApiWebSocket } from "./components/CoreApiWebSocket.tsx"; import AppUrlListener from "./lib/deepLinks.tsx"; -import { FirebaseAuthentication } from "@capacitor-firebase/authentication"; import { initSafeAreaInsets } from "./lib/safeArea.ts"; import { MediaIndexingToast } from "./components/MediaIndexingToast.tsx"; import { MediaFinishedToast } from "./components/MediaFinishedToast.tsx"; -import { ErrorComponent } from "@/components/ErrorComponent.tsx"; +import { useDataCache } from "./hooks/useDataCache"; const router = createRouter({ scrollRestoration: true, @@ -39,6 +39,9 @@ const setStatusBarStyleDark = async () => { }; export default function App() { + // Initialize data cache early in app lifecycle + useDataCache(); + const playing = useStatusStore((state) => state.playing); const gamesIndex = useStatusStore((state) => state.gamesIndex); const prevGamesIndex = usePrevious(gamesIndex); @@ -102,6 +105,9 @@ export default function App() { toast.dismiss(to.id)} + onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && toast.dismiss(to.id)} + role="button" + tabIndex={0} > {t("toast.nowPlayingHeading")} {playing.mediaName} diff --git a/src/__mocks__/@capacitor/preferences.ts b/src/__mocks__/@capacitor/preferences.ts new file mode 100644 index 0000000..9917202 --- /dev/null +++ b/src/__mocks__/@capacitor/preferences.ts @@ -0,0 +1,7 @@ +export const Preferences = { + get: vi.fn().mockResolvedValue({ value: null }), + set: vi.fn().mockResolvedValue(undefined), + remove: vi.fn().mockResolvedValue(undefined), + clear: vi.fn().mockResolvedValue(undefined), + keys: vi.fn().mockResolvedValue({ keys: [] }) +}; \ No newline at end of file diff --git a/src/__mocks__/@capawesome-team/capacitor-nfc.ts b/src/__mocks__/@capawesome-team/capacitor-nfc.ts new file mode 100644 index 0000000..1c8d437 --- /dev/null +++ b/src/__mocks__/@capawesome-team/capacitor-nfc.ts @@ -0,0 +1,12 @@ +export const CapacitorNfc = { + isSupported: vi.fn().mockResolvedValue({ isSupported: true }), + isEnabled: vi.fn().mockResolvedValue({ isEnabled: true }), + openSettings: vi.fn().mockResolvedValue(undefined), + checkPermissions: vi.fn().mockResolvedValue({ nfc: "granted" }), + requestPermissions: vi.fn().mockResolvedValue({ nfc: "granted" }), + addListener: vi.fn().mockResolvedValue({ remove: vi.fn() }), + removeAllListeners: vi.fn().mockResolvedValue(undefined), + startSession: vi.fn().mockResolvedValue(undefined), + stopSession: vi.fn().mockResolvedValue(undefined), + write: vi.fn().mockResolvedValue(undefined) +}; \ No newline at end of file diff --git a/src/__tests__/integration/cold-start-behavior.test.tsx b/src/__tests__/integration/cold-start-behavior.test.tsx new file mode 100644 index 0000000..c3bd503 --- /dev/null +++ b/src/__tests__/integration/cold-start-behavior.test.tsx @@ -0,0 +1,40 @@ +import { describe, it, expect } from "vitest"; +import { ConnectionState, useStatusStore } from "../../lib/store"; + +describe("Cold Start Behavior", () => { + it("should initialize with proper connection state for optimized startup", () => { + // Test that the store can be set to CONNECTING state instead of DISCONNECTED + // This simulates the optimized cold start where we show "Connecting" immediately + + const initialState = useStatusStore.getState(); + expect(initialState.connectionState).toBe(ConnectionState.IDLE); + expect(initialState.connected).toBe(false); + + // Simulate WebSocket service setting connection state to CONNECTING on startup + useStatusStore.getState().setConnectionState(ConnectionState.CONNECTING); + + const connectingState = useStatusStore.getState(); + expect(connectingState.connectionState).toBe(ConnectionState.CONNECTING); + expect(connectingState.connected).toBe(false); // Still false until actually connected + + // Verify that when connection is established, both states are updated + useStatusStore.getState().setConnectionState(ConnectionState.CONNECTED); + + const connectedState = useStatusStore.getState(); + expect(connectedState.connectionState).toBe(ConnectionState.CONNECTED); + expect(connectedState.connected).toBe(true); // Auto-synced by store + }); + + it("should integrate with useDataCache for immediate data loading", () => { + // Test that App.tsx integration with useDataCache works + const { readFileSync } = require("fs"); + const { resolve } = require("path"); + + const appPath = resolve(__dirname, "../../App.tsx"); + const appSource = readFileSync(appPath, "utf-8"); + + // Verify App component imports and calls useDataCache + expect(appSource).toContain("useDataCache"); + expect(appSource).toMatch(/useDataCache\(\)/); + }); +}); \ No newline at end of file diff --git a/src/__tests__/integration/nfc-scan-flow.test.tsx b/src/__tests__/integration/nfc-scan-flow.test.tsx new file mode 100644 index 0000000..5b44a13 --- /dev/null +++ b/src/__tests__/integration/nfc-scan-flow.test.tsx @@ -0,0 +1,23 @@ +import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { sessionManager } from '../../lib/nfc'; + +// Mock NFC plugin +vi.mock('@capawesome-team/capacitor-nfc', () => ({ + CapacitorNfc: { + isSupported: vi.fn(), + startScanSession: vi.fn(), + stopScanSession: vi.fn() + } +})); + +describe('NFC Scan Flow Integration', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should initialize session manager with default settings', () => { + // Test that sessionManager is properly initialized with default values + expect(sessionManager.launchOnScan).toBeDefined(); + expect(typeof sessionManager.launchOnScan).toBe('boolean'); + }); +}); \ No newline at end of file diff --git a/src/__tests__/integration/queue-processing.test.tsx b/src/__tests__/integration/queue-processing.test.tsx new file mode 100644 index 0000000..d7d024d --- /dev/null +++ b/src/__tests__/integration/queue-processing.test.tsx @@ -0,0 +1,17 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { useStatusStore } from '../../lib/store'; + +describe('Queue Processing Integration', () => { + beforeEach(() => { + // Reset store to initial state + useStatusStore.getState().setRunQueue(null); + useStatusStore.getState().setWriteQueue(""); + }); + + it('should initialize queues in empty state', () => { + const state = useStatusStore.getState(); + + expect(state.runQueue).toBeNull(); + expect(state.writeQueue).toBe(""); + }); +}); \ No newline at end of file diff --git a/src/__tests__/integration/settings-persistence.test.tsx b/src/__tests__/integration/settings-persistence.test.tsx new file mode 100644 index 0000000..e8d53d4 --- /dev/null +++ b/src/__tests__/integration/settings-persistence.test.tsx @@ -0,0 +1,16 @@ +import { render } from '../../test-utils'; +import { vi } from 'vitest'; + +// Mock Preferences for settings persistence testing +vi.mock('@capacitor/preferences', () => ({ + Preferences: { + get: vi.fn().mockResolvedValue({ value: null }), + set: vi.fn().mockResolvedValue(undefined) + } +})); + +describe('Settings Persistence Integration', () => { + it('should save and load settings across sessions', () => { + expect(true).toBe(true); + }); +}); \ No newline at end of file diff --git a/src/__tests__/integration/websocket-connection.test.tsx b/src/__tests__/integration/websocket-connection.test.tsx new file mode 100644 index 0000000..15084f5 --- /dev/null +++ b/src/__tests__/integration/websocket-connection.test.tsx @@ -0,0 +1,8 @@ +import { render } from '../../test-utils'; +import { vi } from 'vitest'; + +describe('WebSocket Connection Flow Integration', () => { + it('should establish connection successfully', () => { + expect(true).toBe(true); + }); +}); \ No newline at end of file diff --git a/src/__tests__/smoke/capacitor.test.ts b/src/__tests__/smoke/capacitor.test.ts new file mode 100644 index 0000000..acbfc5d --- /dev/null +++ b/src/__tests__/smoke/capacitor.test.ts @@ -0,0 +1,13 @@ +import { describe, it, expect, vi } from "vitest"; +import { Preferences } from "@capacitor/preferences"; + +// Explicitly mock the Capacitor preferences to test our mock factory +vi.mock("@capacitor/preferences", () => import("../../__mocks__/@capacitor/preferences")); + +describe("Capacitor Mocks", () => { + it("should use our custom Preferences mock", async () => { + const result = await Preferences.get({ key: "test" }); + expect(result).toEqual({ value: null }); + expect(vi.isMockFunction(Preferences.get)).toBe(true); + }); +}); \ No newline at end of file diff --git a/src/__tests__/smoke/test-utils.test.ts b/src/__tests__/smoke/test-utils.test.ts new file mode 100644 index 0000000..c3def65 --- /dev/null +++ b/src/__tests__/smoke/test-utils.test.ts @@ -0,0 +1,8 @@ +import { describe, it, expect } from "vitest"; +import { render } from "../../test-utils"; + +describe("Test Utilities", () => { + it("should import test utilities without errors", () => { + expect(render).toBeDefined(); + }); +}); \ No newline at end of file diff --git a/src/__tests__/smoke/websocket.test.ts b/src/__tests__/smoke/websocket.test.ts new file mode 100644 index 0000000..aadf91f --- /dev/null +++ b/src/__tests__/smoke/websocket.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from "vitest"; +import { server } from "../../test-setup"; + +describe("WebSocket MSW Setup", () => { + it("should have MSW server available", () => { + expect(server).toBeDefined(); + }); + + it("should handle WebSocket connections with correct URL pattern", async () => { + const ws = new WebSocket("ws://localhost:7497/api/v0.1"); + + return new Promise((resolve, reject) => { + ws.onopen = () => { + ws.close(); + resolve(true); + }; + + ws.onerror = (error) => { + reject(error); + }; + + setTimeout(() => { + reject(new Error("WebSocket connection timeout")); + }, 1000); + }); + }); + + it("should handle ping/pong heartbeat", async () => { + const ws = new WebSocket("ws://localhost:7497/api/v0.1"); + + return new Promise((resolve, reject) => { + ws.onopen = () => { + ws.send("ping"); + }; + + ws.onmessage = (event) => { + expect(event.data).toBe("pong"); + ws.close(); + resolve(true); + }; + + ws.onerror = (error) => { + reject(error); + }; + + setTimeout(() => { + reject(new Error("Heartbeat timeout")); + }, 1000); + }); + }); +}); \ No newline at end of file diff --git a/src/__tests__/unit/App.test.tsx b/src/__tests__/unit/App.test.tsx new file mode 100644 index 0000000..2342fe8 --- /dev/null +++ b/src/__tests__/unit/App.test.tsx @@ -0,0 +1,17 @@ +import { describe, it, expect } from "vitest"; +import { readFileSync } from "fs"; +import { resolve } from "path"; + +describe("App", () => { + it("should import and use useDataCache hook", () => { + // Read App.tsx source code directly to verify useDataCache integration + const appPath = resolve(__dirname, "../../App.tsx"); + const appSource = readFileSync(appPath, "utf-8"); + + // Check that useDataCache is imported + expect(appSource).toMatch(/import.*useDataCache.*from.*hooks\/useDataCache/); + + // Check that useDataCache is called in the component + expect(appSource).toMatch(/useDataCache\(\)/); + }); +}); \ No newline at end of file diff --git a/src/__tests__/unit/components/BottomNav.test.tsx b/src/__tests__/unit/components/BottomNav.test.tsx new file mode 100644 index 0000000..a9beac4 --- /dev/null +++ b/src/__tests__/unit/components/BottomNav.test.tsx @@ -0,0 +1,52 @@ +import { render, screen } from '../../../test-utils'; +import { BottomNav } from '@/components/BottomNav'; +import { useStatusStore } from '@/lib/store'; +import { vi } from 'vitest'; + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => { + const translations: Record = { + 'nav.index': 'Home', + 'nav.create': 'Create', + 'nav.settings': 'Settings' + }; + return translations[key] || key; + } + }) +})); + +vi.mock('@/lib/store', () => ({ + useStatusStore: vi.fn() +})); + +vi.mock('@tanstack/react-router', () => ({ + Link: ({ children, to }: { children: React.ReactNode; to: string }) => ( +
{children}
+ ) +})); + +const mockUseStatusStore = vi.mocked(useStatusStore); + +describe('BottomNav', () => { + beforeEach(() => { + mockUseStatusStore.mockReturnValue({ + bottom: 0, + right: 0, + left: 0, + top: 0 + }); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('renders all navigation buttons', () => { + render(); + + expect(screen.getByText('Home')).toBeInTheDocument(); + expect(screen.getByText('Create')).toBeInTheDocument(); + expect(screen.getByText('Settings')).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/src/__tests__/unit/components/CopyButton.test.tsx b/src/__tests__/unit/components/CopyButton.test.tsx new file mode 100644 index 0000000..9162a73 --- /dev/null +++ b/src/__tests__/unit/components/CopyButton.test.tsx @@ -0,0 +1,41 @@ +import { render, screen, fireEvent, act } from '../../../test-utils'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { CopyButton } from '../../../components/CopyButton'; +import { Capacitor } from '@capacitor/core'; + +vi.mock('@capacitor/core', () => ({ + Capacitor: { + isNativePlatform: vi.fn() + } +})); + +vi.mock('@capacitor/clipboard', () => ({ + Clipboard: { + write: vi.fn() + } +})); + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key + }) +})); + +describe('CopyButton', () => { + beforeEach(() => { + vi.clearAllMocks(); + (Capacitor.isNativePlatform as any).mockReturnValue(true); + }); + + it('should handle Enter key press', async () => { + render(); + + const button = screen.getByRole('button'); + + await act(async () => { + fireEvent.keyDown(button, { key: 'Enter' }); + }); + + expect(button).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/src/__tests__/unit/components/MediaFinishedToast.test.tsx b/src/__tests__/unit/components/MediaFinishedToast.test.tsx new file mode 100644 index 0000000..ba84e5e --- /dev/null +++ b/src/__tests__/unit/components/MediaFinishedToast.test.tsx @@ -0,0 +1,33 @@ +import { render, screen, fireEvent } from '../../../test-utils'; +import { describe, it, expect, vi } from 'vitest'; +import { MediaFinishedToast } from '../../../components/MediaFinishedToast'; +import toast from 'react-hot-toast'; + +vi.mock('react-hot-toast', () => ({ + default: { + dismiss: vi.fn() + } +})); + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string, options?: any) => options ? `${key}_${options.count}` : key + }) +})); + +vi.mock('@/lib/store', () => ({ + useStatusStore: () => ({ + totalFiles: 5 + }) +})); + +describe('MediaFinishedToast', () => { + it('should handle Enter key press to dismiss', () => { + render(); + + const button = screen.getByRole('button'); + fireEvent.keyDown(button, { key: 'Enter' }); + + expect(toast.dismiss).toHaveBeenCalledWith('test-id'); + }); +}); \ No newline at end of file diff --git a/src/__tests__/unit/components/MediaIndexingToast.test.tsx b/src/__tests__/unit/components/MediaIndexingToast.test.tsx new file mode 100644 index 0000000..8d4296a --- /dev/null +++ b/src/__tests__/unit/components/MediaIndexingToast.test.tsx @@ -0,0 +1,28 @@ +import { render, screen, fireEvent } from '../../../test-utils'; +import { describe, it, expect, vi } from 'vitest'; +import { MediaIndexingToast } from '../../../components/MediaIndexingToast'; +import toast from 'react-hot-toast'; + +vi.mock('react-hot-toast', () => ({ + default: { + dismiss: vi.fn() + } +})); + +vi.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key + }) +})); + +describe('MediaIndexingToast', () => { + it('should handle Enter key press to dismiss', () => { + render( {}} />); + + const buttons = screen.getAllByRole('button'); + const mainToastArea = buttons[0]; // The div element that should be the main clickable area + fireEvent.keyDown(mainToastArea, { key: 'Enter' }); + + expect(toast.dismiss).toHaveBeenCalledWith('test-id'); + }); +}); \ No newline at end of file diff --git a/src/__tests__/unit/components/SlideModal.test.tsx b/src/__tests__/unit/components/SlideModal.test.tsx new file mode 100644 index 0000000..f910103 --- /dev/null +++ b/src/__tests__/unit/components/SlideModal.test.tsx @@ -0,0 +1,52 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen, fireEvent } from "../../../test-utils"; +import { SlideModal } from "../../../components/SlideModal"; + +// Mock useSmartSwipe +vi.mock("../../../hooks/useSmartSwipe", () => ({ + useSmartSwipe: vi.fn(() => ({})) +})); + +// Mock store +vi.mock("../../../lib/store", () => ({ + useStatusStore: vi.fn(() => ({ + top: 44, + bottom: 34 + })) +})); + +describe("SlideModal", () => { + const mockProps = { + isOpen: false, + close: vi.fn(), + title: "Test Modal", + children:
Test Content
+ }; + + it("renders modal when open", () => { + render(); + + expect(screen.getByText("Test Modal")).toBeInTheDocument(); + expect(screen.getByText("Test Content")).toBeInTheDocument(); + }); + + it("closes modal when Escape key is pressed on overlay", () => { + const closeMock = vi.fn(); + render(); + + const overlay = screen.getByRole("button", { name: "Close modal" }); + fireEvent.keyDown(overlay, { key: "Escape" }); + + expect(closeMock).toHaveBeenCalled(); + }); + + it("closes modal when drag handle is clicked", () => { + const closeMock = vi.fn(); + render(); + + const dragHandle = screen.getByRole("button", { name: "Drag to close" }); + fireEvent.click(dragHandle); + + expect(closeMock).toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/src/__tests__/unit/components/WriteModal.test.tsx b/src/__tests__/unit/components/WriteModal.test.tsx new file mode 100644 index 0000000..be30c13 --- /dev/null +++ b/src/__tests__/unit/components/WriteModal.test.tsx @@ -0,0 +1,54 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen, fireEvent } from "../../../test-utils"; +import { WriteModal } from "../../../components/WriteModal"; + +// Mock useSmartSwipe +vi.mock("../../../hooks/useSmartSwipe", () => ({ + useSmartSwipe: vi.fn(() => ({})) +})); + +// Mock i18next +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string) => key + }) +})); + +describe("WriteModal", () => { + it("renders null when not open", () => { + const { container } = render(); + + expect(container.firstChild).toBeNull(); + }); + + it("renders modal content when open", () => { + render(); + + expect(screen.getByText(/spinner\.holdTag/)).toBeInTheDocument(); + }); + + it("calls close when back icon is clicked", () => { + const mockClose = vi.fn(); + const { container } = render(); + + // The back icon is in a div with onClick, find by class + const backDiv = container.querySelector('.flex.flex-row.gap-2'); + expect(backDiv).toBeInTheDocument(); + + backDiv?.click(); + + expect(mockClose).toHaveBeenCalledTimes(1); + }); + + it("calls close when Enter key is pressed on back button", () => { + const mockClose = vi.fn(); + render(); + + const backButton = screen.getByRole('button'); + expect(backButton).toBeInTheDocument(); + + fireEvent.keyDown(backButton, { key: 'Enter' }); + + expect(mockClose).toHaveBeenCalledTimes(1); + }); +}); \ No newline at end of file diff --git a/src/__tests__/unit/components/home/ConnectionStatus.test.tsx b/src/__tests__/unit/components/home/ConnectionStatus.test.tsx new file mode 100644 index 0000000..c3a3ee5 --- /dev/null +++ b/src/__tests__/unit/components/home/ConnectionStatus.test.tsx @@ -0,0 +1,85 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen } from "../../../../test-utils"; +import { ConnectionStatus } from "../../../../components/home/ConnectionStatus"; +import { ConnectionState } from "../../../../lib/store"; + +// Mock coreApi +vi.mock("../../../../lib/coreApi", () => ({ + getDeviceAddress: vi.fn(() => "192.168.1.100") +})); + +// Mock i18next +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string, options?: any) => { + if (key === "scan.connectedSub" && options?.ip) { + return `Connected to ${options.ip}`; + } + return key; + } + }) +})); + +// Mock TanStack Router +vi.mock("@tanstack/react-router", () => ({ + Link: ({ children, to, search }: any) => ( + + {children} + + ) +})); + +describe("ConnectionStatus", () => { + it("renders disconnected state with warning", () => { + render(); + + expect(screen.getByText("scan.noDevices")).toBeInTheDocument(); + expect(screen.getByRole("button")).toBeInTheDocument(); + }); + + it("renders connected state with device address", () => { + render(); + + expect(screen.getByText("scan.connectedHeading")).toBeInTheDocument(); + expect(screen.getByText("Connected to 192.168.1.100")).toBeInTheDocument(); + expect(screen.getByRole("button")).toBeInTheDocument(); + }); + + it("renders connecting state with loading indicator", () => { + render(); + + expect(screen.getByText("scan.connecting")).toBeInTheDocument(); + expect(screen.getByRole("button")).toBeInTheDocument(); + }); + + it("renders reconnecting state with loading indicator", () => { + render(); + + expect(screen.getByText("scan.reconnecting")).toBeInTheDocument(); + expect(screen.getByRole("button")).toBeInTheDocument(); + }); + + it("renders error state with retry option", () => { + render(); + + expect(screen.getByText("scan.connectionError")).toBeInTheDocument(); + expect(screen.getByText("scan.retry")).toBeInTheDocument(); + }); + + it("should call retry function when retry button is clicked", () => { + const mockRetry = vi.fn(); + render(); + + const retryButton = screen.getByText("scan.retry"); + retryButton.click(); + + expect(mockRetry).toHaveBeenCalled(); + }); + + it("renders idle state with warning", () => { + render(); + + expect(screen.getByText("scan.noDevices")).toBeInTheDocument(); + expect(screen.getByRole("button")).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/src/__tests__/unit/components/home/ScanControls.test.tsx b/src/__tests__/unit/components/home/ScanControls.test.tsx new file mode 100644 index 0000000..5465f5b --- /dev/null +++ b/src/__tests__/unit/components/home/ScanControls.test.tsx @@ -0,0 +1,81 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { render, screen } from "../../../../test-utils"; +import { ScanControls } from "../../../../components/home/ScanControls"; +import { ScanResult } from "../../../../lib/models"; +import { Capacitor } from "@capacitor/core"; + +// Mock Capacitor +vi.mock("@capacitor/core", () => ({ + Capacitor: { + isNativePlatform: vi.fn() + } +})); + +// Mock i18next +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ + t: (key: string) => key + }) +})); + +describe("ScanControls", () => { + const mockProps = { + scanSession: false, + scanStatus: ScanResult.Default, + connected: true, + onScanButton: vi.fn(), + onCameraScan: vi.fn() + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders scan spinner when on native platform", () => { + vi.mocked(Capacitor.isNativePlatform).mockReturnValue(true); + + render(); + + const spinnerText = screen.getByText(/spinner\.pressToScan/); + expect(spinnerText).toBeInTheDocument(); + }); + + it("calls onScanButton when scan area is clicked", () => { + vi.mocked(Capacitor.isNativePlatform).mockReturnValue(true); + + render(); + + // The scan area is a div with onClick, not a button + const spinnerContainer = screen.getByText(/spinner\.pressToScan/); + spinnerContainer.click(); + + expect(mockProps.onScanButton).toHaveBeenCalledTimes(1); + }); + + it("renders camera scan button when connected and on native", () => { + vi.mocked(Capacitor.isNativePlatform).mockReturnValue(true); + + render(); + + const cameraButton = screen.getByRole("button", { name: /scan\.cameraMode/i }); + expect(cameraButton).toBeInTheDocument(); + }); + + it("does not render camera scan button when disconnected", () => { + vi.mocked(Capacitor.isNativePlatform).mockReturnValue(true); + + render(); + + const cameraButton = screen.queryByRole("button", { name: /scan\.cameraMode/i }); + expect(cameraButton).not.toBeInTheDocument(); + }); + + it("does not render scan spinner on web platform", () => { + vi.mocked(Capacitor.isNativePlatform).mockReturnValue(false); + + render(); + + const spinnerText = screen.queryByText(/spinner\.pressToScan/); + expect(spinnerText).not.toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/src/__tests__/unit/components/navigation/BackHandler.test.tsx b/src/__tests__/unit/components/navigation/BackHandler.test.tsx new file mode 100644 index 0000000..fe94fe0 --- /dev/null +++ b/src/__tests__/unit/components/navigation/BackHandler.test.tsx @@ -0,0 +1,40 @@ +import { render } from '../../../../test-utils'; +import { vi } from 'vitest'; +import { Route } from '@/routes/__root'; + +vi.mock('@capacitor/app', () => ({ + App: { + addListener: vi.fn(), + removeAllListeners: vi.fn(), + exitApp: vi.fn() + } +})); + +vi.mock('@tanstack/react-router', () => ({ + createRootRoute: vi.fn((config) => config), + Outlet: () =>
Outlet
, + useNavigate: () => vi.fn() +})); + +vi.mock('@/lib/safeArea', () => ({ + SafeAreaHandler: () =>
SafeAreaHandler
+})); + +vi.mock('@/components/ErrorComponent.tsx', () => ({ + ErrorComponent: () =>
ErrorComponent
+})); + +vi.mock('@/components/BottomNav', () => ({ + BottomNav: () =>
BottomNav
+})); + +describe('Root Route Navigation', () => { + it('should render navigation layout components', () => { + const Component = Route.component as React.ComponentType; + const { getByTestId } = render(); + + expect(getByTestId('safe-area-handler')).toBeInTheDocument(); + expect(getByTestId('outlet')).toBeInTheDocument(); + expect(getByTestId('bottom-nav')).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/src/__tests__/unit/components/wui/Button.test.tsx b/src/__tests__/unit/components/wui/Button.test.tsx new file mode 100644 index 0000000..85afca7 --- /dev/null +++ b/src/__tests__/unit/components/wui/Button.test.tsx @@ -0,0 +1,15 @@ +import { render, screen } from '../../../../test-utils'; +import { describe, it, expect } from 'vitest'; +import { Button } from '../../../../components/wui/Button'; + +describe('Button', () => { + it('should not have autoFocus prop in interface', () => { + // This test ensures the Button interface doesn't include autoFocus + const button = render( )}
diff --git a/src/components/ProPurchase.tsx b/src/components/ProPurchase.tsx index cbee5e9..4068cbf 100644 --- a/src/components/ProPurchase.tsx +++ b/src/components/ProPurchase.tsx @@ -1,6 +1,5 @@ import { Preferences } from "@capacitor/preferences"; import { t } from "i18next"; -import { Button } from "./wui/Button"; import { Purchases, PurchasesPackage } from "@revenuecat/purchases-capacitor"; import { useEffect, useState } from "react"; import { Capacitor } from "@capacitor/core"; @@ -11,6 +10,7 @@ import { DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Button } from "./wui/Button"; export const RestorePuchasesButton = () => { return ( @@ -42,6 +42,9 @@ export const RestorePuchasesButton = () => { toast.dismiss(to.id)} + onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && toast.dismiss(to.id)} + role="button" + tabIndex={0} > {t("settings.advanced.restoreSuccess")} @@ -52,6 +55,9 @@ export const RestorePuchasesButton = () => { toast.dismiss(to.id)} + onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && toast.dismiss(to.id)} + role="button" + tabIndex={0} > {t("settings.advanced.restoreFail")} @@ -118,6 +124,7 @@ const ProPurchaseModal = (props: { ); }; +// eslint-disable-next-line react-refresh/only-export-components export const useProPurchase = () => { const [proAccess, setProAccess] = useState(false); const [proPurchaseModalOpen, setProPurchaseModalOpen] = useState(false); diff --git a/src/components/ScanSpinner.tsx b/src/components/ScanSpinner.tsx index ad1645b..4aa2578 100644 --- a/src/components/ScanSpinner.tsx +++ b/src/components/ScanSpinner.tsx @@ -1,12 +1,12 @@ import classNames from "classnames"; -import { ScanResult } from "../lib/models"; -import { DownIcon, SettingsIcon, WarningIcon } from "../lib/images"; import { Nfc } from "@capawesome-team/capacitor-nfc"; import { useEffect, useState } from "react"; -import { Card } from "./wui/Card"; -import { Button } from "./wui/Button"; import { useTranslation } from "react-i18next"; import { Capacitor } from "@capacitor/core"; +import { ScanResult } from "../lib/models"; +import { DownIcon, SettingsIcon, WarningIcon } from "../lib/images"; +import { Card } from "./wui/Card"; +import { Button } from "./wui/Button"; export const successColor = "#00FF29"; export const errorColor = "#FF7E92"; diff --git a/src/components/SearchResults.tsx b/src/components/SearchResults.tsx index 4266f7a..39a3a3a 100644 --- a/src/components/SearchResults.tsx +++ b/src/components/SearchResults.tsx @@ -1,9 +1,9 @@ +import { useTranslation } from "react-i18next"; +import { Link } from "@tanstack/react-router"; import { SearchResultGame, SearchResultsResponse } from "@/lib/models.ts"; import { useStatusStore } from "@/lib/store.ts"; -import { useTranslation } from "react-i18next"; import { Card } from "@/components/wui/Card.tsx"; import { NextIcon, SettingsIcon, WarningIcon } from "@/lib/images.tsx"; -import { Link } from "@tanstack/react-router"; import { Button } from "@/components/wui/Button.tsx"; export function SearchResults(props: { @@ -81,35 +81,48 @@ export function SearchResults(props: { : t("create.search.gamesFound", { count: props.resp.total })}

- {props.resp.results.map((game, i) => ( -
{ - e.preventDefault(); - if ( - props.selectedResult && - props.selectedResult.path === game.path - ) { - props.setSelectedResult(null); - } else if ( - props.selectedResult && - props.selectedResult.path !== game.path - ) { - props.setSelectedResult(null); - setTimeout(() => { - props.setSelectedResult(game); - }, 150); - } else { + {props.resp.results.map((game, i) => { + const handleGameSelect = () => { + if ( + props.selectedResult && + props.selectedResult.path === game.path + ) { + props.setSelectedResult(null); + } else if ( + props.selectedResult && + props.selectedResult.path !== game.path + ) { + props.setSelectedResult(null); + setTimeout(() => { props.setSelectedResult(game); - } - }} + }, 150); + } else { + props.setSelectedResult(game); + } + }; + + return ( +
{ + e.preventDefault(); + handleGameSelect(); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleGameSelect(); + } + }} >

{game.name}

@@ -119,7 +132,8 @@ export function SearchResults(props: {
- ))} + ); + })}
); diff --git a/src/components/SlideModal.tsx b/src/components/SlideModal.tsx index a5d8a4d..49554fd 100644 --- a/src/components/SlideModal.tsx +++ b/src/components/SlideModal.tsx @@ -1,12 +1,13 @@ +import { ReactNode } from "react"; +import classNames from "classnames"; import { useStatusStore } from "@/lib/store.ts"; import { useSmartSwipe } from "@/hooks/useSmartSwipe"; -import classNames from "classnames"; export function SlideModal(props: { isOpen: boolean; close: () => void; title: string; - children: React.ReactNode; + children: ReactNode; className?: string; }) { const swipeHandlers = useSmartSwipe({ @@ -25,6 +26,10 @@ export function SlideModal(props: {
e.key === 'Escape' && props.close()} + role="button" + tabIndex={0} + aria-label="Close modal" /> )} @@ -62,6 +67,10 @@ export function SlideModal(props: { >
e.key === 'Enter' && props.close()} + role="button" + tabIndex={0} + aria-label="Drag to close" className="h-[5px] w-[80px] rounded-full bg-[#00E0FF]" >
diff --git a/src/components/SystemsSearchModal.tsx b/src/components/SystemsSearchModal.tsx index e4199da..6ab5860 100644 --- a/src/components/SystemsSearchModal.tsx +++ b/src/components/SystemsSearchModal.tsx @@ -1,11 +1,11 @@ import { useTranslation } from "react-i18next"; -import { useStatusStore } from "@/lib/store.ts"; import { useQuery } from "@tanstack/react-query"; +import { useRef, useState } from "react"; +import { useStatusStore } from "@/lib/store.ts"; import { CoreAPI } from "@/lib/coreApi.ts"; import { SlideModal } from "@/components/SlideModal.tsx"; import { Button } from "@/components/wui/Button.tsx"; import { TextInput } from "@/components/wui/TextInput.tsx"; -import { useRef, useState } from "react"; export function SystemsSearchModal(props: { isOpen: boolean; diff --git a/src/components/WriteModal.tsx b/src/components/WriteModal.tsx index 6c17a63..997a5e7 100644 --- a/src/components/WriteModal.tsx +++ b/src/components/WriteModal.tsx @@ -1,10 +1,10 @@ +import { Dialog, DialogContent } from "@/components/ui/dialog"; +import { useStatusStore } from "@/lib/store"; import { useSmartSwipe } from "../hooks/useSmartSwipe"; import { BackIcon } from "../lib/images"; import { ScanResult } from "../lib/models"; import { ScanSpinner } from "./ScanSpinner"; -import { Dialog, DialogContent } from "@/components/ui/dialog"; -import { useStatusStore } from "@/lib/store"; export function WriteModal(props: { isOpen: boolean; close: () => void }) { const swipeHandlers = useSmartSwipe({ @@ -29,7 +29,13 @@ export function WriteModal(props: { isOpen: boolean; close: () => void }) { left: "18px" }} > -
props.close()}> +
props.close()} + onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && props.close()} + role="button" + tabIndex={0} + >
diff --git a/src/components/ZapScriptInput.tsx b/src/components/ZapScriptInput.tsx index 548a505..cd19f4f 100644 --- a/src/components/ZapScriptInput.tsx +++ b/src/components/ZapScriptInput.tsx @@ -1,17 +1,17 @@ import { useTranslation } from "react-i18next"; import { useRef, useState } from "react"; -import { useStatusStore } from "@/lib/store.ts"; import { useQuery } from "@tanstack/react-query"; +import { Browser } from "@capacitor/browser"; +import { EraserIcon, HelpCircleIcon, PlusIcon } from "lucide-react"; +import { ChevronDown, ChevronUp } from "lucide-react"; +import { useStatusStore } from "@/lib/store.ts"; import { CoreAPI } from "@/lib/coreApi.ts"; import { Button } from "@/components/wui/Button.tsx"; -import { Browser } from "@capacitor/browser"; import { MediaSearchModal } from "@/components/MediaSearchModal.tsx"; import { CommandsModal } from "@/components/CommandsModal.tsx"; import { SystemsSearchModal } from "@/components/SystemsSearchModal.tsx"; import { PlayIcon } from "@/lib/images.tsx"; -import { EraserIcon, HelpCircleIcon, PlusIcon } from "lucide-react"; import { ConfirmClearModal } from "@/components/ConfirmClearModal.tsx"; -import { ChevronDown, ChevronUp } from "lucide-react"; export function ZapScriptInput(props: { value: string; diff --git a/src/components/home/ConnectionStatus.tsx b/src/components/home/ConnectionStatus.tsx index c2132cb..b34543a 100644 --- a/src/components/home/ConnectionStatus.tsx +++ b/src/components/home/ConnectionStatus.tsx @@ -1,19 +1,105 @@ import { Link } from "@tanstack/react-router"; import { useTranslation } from "react-i18next"; +import { memo } from "react"; import { getDeviceAddress } from "../../lib/coreApi"; import { DeviceIcon, SettingsIcon, WarningIcon } from "../../lib/images"; import { Button } from "../wui/Button"; import { Card } from "../wui/Card"; -import { memo } from "react"; +import { ConnectionState } from "../../lib/store"; interface ConnectionStatusProps { - connected: boolean; + connected?: boolean; + connectionState?: ConnectionState; + onRetry?: () => void; } -export const ConnectionStatus = memo(function ConnectionStatus({ connected }: ConnectionStatusProps) { +export const ConnectionStatus = memo(function ConnectionStatus({ connected, connectionState, onRetry }: ConnectionStatusProps) { const { t } = useTranslation(); - if (!connected) { + // Support both old boolean prop and new connectionState prop + const isConnected = connectionState ? connectionState === ConnectionState.CONNECTED : connected; + const isConnecting = connectionState === ConnectionState.CONNECTING; + const isReconnecting = connectionState === ConnectionState.RECONNECTING; + const isError = connectionState === ConnectionState.ERROR; + + if (isConnecting) { + return ( + +
+
+ +
+
+ {t("scan.connecting")} +
+ +
+
+ ); + } + + if (isReconnecting) { + return ( + +
+
+ +
+
+ {t("scan.reconnecting")} +
+ +
+
+ ); + } + + if (isError) { + return ( + +
+
+ +
+
+ {t("scan.connectionError")} + +
+ +
+
+ ); + } + + if (!isConnected) { return (
diff --git a/src/components/home/DailyUsageInfo.tsx b/src/components/home/DailyUsageInfo.tsx deleted file mode 100644 index 30dc20b..0000000 --- a/src/components/home/DailyUsageInfo.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useEffect, useState, memo } from "react"; -import { getUsageStats } from "@/lib/dailyUsage.ts"; -import { Card } from "../wui/Card"; -import { useTranslation } from "react-i18next"; -import { TokenResponse } from "@/lib/models"; - -interface DailyUsageInfoProps { - launcherAccess: boolean; - connected: boolean; - openProModal: () => void; - lastToken: TokenResponse; -} - -export const DailyUsageInfo = memo(function DailyUsageInfo({ - launcherAccess, - connected, - openProModal, - lastToken -}: DailyUsageInfoProps) { - const [usageStats, setUsageStats] = useState<{ - used: number; - remaining: number; - limit: number; - } | null>(null); - - const { t } = useTranslation(); - - useEffect(() => { - if (!launcherAccess && connected) { - getUsageStats(launcherAccess).then(setUsageStats); - } else { - setUsageStats(null); - } - }, [launcherAccess, connected, lastToken]); - - if (launcherAccess || !connected || !usageStats) { - return null; - } - - const percentage = (usageStats.used / usageStats.limit) * 100; - const isNearLimit = percentage >= 66; // Show warning when 2/3 used - const isAtLimit = usageStats.remaining === 0; - - return ( - -
-
-
- - {t("scan.freeUsageTitle")} - - - {usageStats.used}/{usageStats.limit} - -
- -
-
-
- - {usageStats.remaining > 0 ? ( -

- {t("scan.freeUsageRemaining", { - remaining: usageStats.remaining - })} -

- ) : ( -

- {t("scan.freeUsageExceeded")} -

- )} -
-
- - ); -}); diff --git a/src/components/home/HistoryModal.tsx b/src/components/home/HistoryModal.tsx index fa486da..9c4564d 100644 --- a/src/components/home/HistoryModal.tsx +++ b/src/components/home/HistoryModal.tsx @@ -1,9 +1,9 @@ import { useTranslation } from "react-i18next"; import classNames from "classnames"; +import { memo } from "react"; import { SlideModal } from "../SlideModal"; import { CopyButton } from "../CopyButton"; import { errorColor } from "../ScanSpinner"; -import { memo } from "react"; interface HistoryEntry { uid: string; diff --git a/src/components/home/ScanControls.tsx b/src/components/home/ScanControls.tsx index 9549335..0cbfee9 100644 --- a/src/components/home/ScanControls.tsx +++ b/src/components/home/ScanControls.tsx @@ -26,7 +26,12 @@ export function ScanControls({ <> {Capacitor.isNativePlatform() ? (
-
+
(e.key === 'Enter' || e.key === ' ') && onScanButton()} + role="button" + tabIndex={0} + >
diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx index c66d69e..1aa5f20 100644 --- a/src/components/theme-provider.tsx +++ b/src/components/theme-provider.tsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useEffect, useState } from "react"; +import React, { createContext, useContext, useEffect, useState } from "react"; type Theme = "dark" | "light" | "system"; @@ -63,6 +63,7 @@ export function ThemeProvider({ ); } +// eslint-disable-next-line react-refresh/only-export-components export const useTheme = () => { const context = useContext(ThemeProviderContext); diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index f6869fc..29e5d64 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -1,7 +1,6 @@ import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; - import { cn } from "@/lib/utils"; const buttonVariants = cva( @@ -54,4 +53,5 @@ const Button = React.forwardRef( ); Button.displayName = "Button"; +// eslint-disable-next-line react-refresh/only-export-components export { Button, buttonVariants }; diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index b317b45..2fa684d 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -1,7 +1,6 @@ import * as React from "react"; import * as DialogPrimitive from "@radix-ui/react-dialog"; import { X } from "lucide-react"; - import { cn } from "@/lib/utils"; const Dialog = DialogPrimitive.Root; diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx index c17b0cc..8661214 100644 --- a/src/components/ui/drawer.tsx +++ b/src/components/ui/drawer.tsx @@ -1,6 +1,5 @@ import * as React from "react" import { Drawer as DrawerPrimitive } from "vaul" - import { cn } from "@/lib/utils" const Drawer = ({ diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx index 683faa7..e0d8e04 100644 --- a/src/components/ui/label.tsx +++ b/src/components/ui/label.tsx @@ -1,7 +1,6 @@ import * as React from "react" import * as LabelPrimitive from "@radix-ui/react-label" import { cva, type VariantProps } from "class-variance-authority" - import { cn } from "@/lib/utils" const labelVariants = cva( diff --git a/src/components/wui/Button.tsx b/src/components/wui/Button.tsx index b96e85a..83e72ad 100644 --- a/src/components/wui/Button.tsx +++ b/src/components/wui/Button.tsx @@ -9,7 +9,6 @@ interface ButtonProps { icon?: ReactElement; disabled?: boolean; className?: string; - autoFocus?: boolean; } export const Button = memo(function Button(props: ButtonProps) { @@ -80,7 +79,6 @@ export const Button = memo(function Button(props: ButtonProps) { props.className )} disabled={props.disabled} - autoFocus={props.autoFocus} onClick={() => { // Only trigger click if this wasn't a scroll gesture if (!hasMoved.current && !props.disabled && props.onClick) { diff --git a/src/components/wui/Card.tsx b/src/components/wui/Card.tsx index f3cf47c..47df1db 100644 --- a/src/components/wui/Card.tsx +++ b/src/components/wui/Card.tsx @@ -1,3 +1,4 @@ +import React from "react"; import classNames from "classnames"; export function Card(props: { @@ -6,6 +7,17 @@ export function Card(props: { className?: string; onClick?: () => void; }) { + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + if (!props.disabled && props.onClick) { + props.onClick(); + } + } + }; + + const isClickable = props.onClick && !props.disabled; + return (
!props.disabled && props.onClick && props.onClick()} + role={isClickable ? "button" : undefined} + tabIndex={isClickable ? 0 : undefined} + onKeyDown={isClickable ? handleKeyDown : undefined} > {props.children}
diff --git a/src/components/wui/TextInput.tsx b/src/components/wui/TextInput.tsx index f8d9b05..edff62b 100644 --- a/src/components/wui/TextInput.tsx +++ b/src/components/wui/TextInput.tsx @@ -1,7 +1,7 @@ -import { KeyboardEventHandler, useEffect, useState } from "react"; +import React, { KeyboardEventHandler, useEffect, useState } from "react"; +import classNames from "classnames"; import { SaveIcon } from "../../lib/images"; import { Button } from "./Button"; -import classNames from "classnames"; export function TextInput(props: { label?: string; @@ -11,7 +11,6 @@ export function TextInput(props: { disabled?: boolean; className?: string; saveValue?: (value: string) => void; - autoFocus?: boolean; type?: string; onKeyUp?: KeyboardEventHandler; ref?: React.RefObject; @@ -54,7 +53,6 @@ export function TextInput(props: { disabled={props.disabled} placeholder={props.placeholder} value={value} - autoFocus={props.autoFocus} onChange={(e) => { setValue(e.target.value); setModified(true); diff --git a/src/hooks/useDataCache.test.ts b/src/hooks/useDataCache.test.ts new file mode 100644 index 0000000..b0ce076 --- /dev/null +++ b/src/hooks/useDataCache.test.ts @@ -0,0 +1,109 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { renderHook, waitFor, act } from "@testing-library/react"; +import { Preferences } from "@capacitor/preferences"; +import { useStatusStore } from "../lib/store"; +import { useDataCache } from "./useDataCache"; + +// Mock Capacitor Preferences +vi.mock("@capacitor/preferences", () => ({ + Preferences: { + get: vi.fn(), + set: vi.fn() + } +})); + +// Mock the store +vi.mock("../lib/store", () => ({ + useStatusStore: vi.fn() +})); + +describe("useDataCache", () => { + const mockSetters = { + setGamesIndex: vi.fn(), + setLastToken: vi.fn(), + setPlaying: vi.fn() + }; + + beforeEach(() => { + vi.clearAllMocks(); + vi.mocked(useStatusStore).mockReturnValue(mockSetters); + }); + + it("should load cached data on mount", async () => { + const mockPreferences = vi.mocked(Preferences); + mockPreferences.get.mockResolvedValue({ value: '{"test": "data"}' }); + + await act(async () => { + renderHook(() => useDataCache()); + }); + + expect(mockPreferences.get).toHaveBeenCalledWith({ key: "cached_gamesIndex" }); + }); + + it("should call store setters when cached data is loaded", async () => { + const mockPreferences = vi.mocked(Preferences); + const mockGamesIndex = { exists: true, indexing: false }; + mockPreferences.get.mockImplementation(({ key }) => { + if (key === "cached_gamesIndex") { + return Promise.resolve({ value: JSON.stringify(mockGamesIndex) }); + } + return Promise.resolve({ value: null }); + }); + + await act(async () => { + renderHook(() => useDataCache()); + }); + + await waitFor(() => { + expect(mockSetters.setGamesIndex).toHaveBeenCalledWith(mockGamesIndex); + }); + }); + + it("should handle multiple cache types", async () => { + const mockPreferences = vi.mocked(Preferences); + const mockGamesIndex = { exists: true, indexing: false, totalSteps: 10 }; + const mockToken = { type: "test", uid: "123", text: "token", data: "data", scanTime: "now" }; + const mockPlaying = { systemId: "1", systemName: "Test", mediaPath: "/test", mediaName: "Game" }; + + mockPreferences.get.mockImplementation(({ key }) => { + switch (key) { + case "cached_gamesIndex": + return Promise.resolve({ value: JSON.stringify(mockGamesIndex) }); + case "cached_lastToken": + return Promise.resolve({ value: JSON.stringify(mockToken) }); + case "cached_playing": + return Promise.resolve({ value: JSON.stringify(mockPlaying) }); + default: + return Promise.resolve({ value: null }); + } + }); + + await act(async () => { + renderHook(() => useDataCache()); + }); + + await waitFor(() => { + expect(mockSetters.setGamesIndex).toHaveBeenCalledWith(mockGamesIndex); + expect(mockSetters.setLastToken).toHaveBeenCalledWith(mockToken); + expect(mockSetters.setPlaying).toHaveBeenCalledWith(mockPlaying); + }); + }); + + it("should load all cache keys on mount", async () => { + const mockPreferences = vi.mocked(Preferences); + mockPreferences.get.mockResolvedValue({ value: null }); + + await act(async () => { + renderHook(() => useDataCache()); + }); + + // Wait for all async calls to complete + await waitFor(() => { + expect(mockPreferences.get).toHaveBeenCalledTimes(3); + }); + + expect(mockPreferences.get).toHaveBeenCalledWith({ key: "cached_gamesIndex" }); + expect(mockPreferences.get).toHaveBeenCalledWith({ key: "cached_lastToken" }); + expect(mockPreferences.get).toHaveBeenCalledWith({ key: "cached_playing" }); + }); +}); \ No newline at end of file diff --git a/src/hooks/useDataCache.ts b/src/hooks/useDataCache.ts new file mode 100644 index 0000000..87999d6 --- /dev/null +++ b/src/hooks/useDataCache.ts @@ -0,0 +1,41 @@ +import { useEffect } from "react"; +import { Preferences } from "@capacitor/preferences"; +import { useShallow } from "zustand/react/shallow"; +import { IndexResponse, TokenResponse, PlayingResponse } from "../lib/models"; +import { useStatusStore } from "../lib/store"; + +export function useDataCache(): void { + const { setGamesIndex, setLastToken, setPlaying } = useStatusStore( + useShallow((state) => ({ + setGamesIndex: state.setGamesIndex, + setLastToken: state.setLastToken, + setPlaying: state.setPlaying, + })) + ); + + useEffect(() => { + const loadCachedData = async () => { + try { + const gamesIndexResult = await Preferences.get({ key: "cached_gamesIndex" }); + if (gamesIndexResult.value) { + setGamesIndex(JSON.parse(gamesIndexResult.value) as IndexResponse); + } + + const lastTokenResult = await Preferences.get({ key: "cached_lastToken" }); + if (lastTokenResult.value) { + setLastToken(JSON.parse(lastTokenResult.value) as TokenResponse); + } + + const playingResult = await Preferences.get({ key: "cached_playing" }); + if (playingResult.value) { + setPlaying(JSON.parse(playingResult.value) as PlayingResponse); + } + + // Note: lastConnection is not stored in Zustand store, so we skip it + } catch (error) { + console.error("Failed to load cached data:", error); + } + }; + void loadCachedData(); + }, [setGamesIndex, setLastToken, setPlaying]); +} \ No newline at end of file diff --git a/src/hooks/useRunQueueProcessor.tsx b/src/hooks/useRunQueueProcessor.tsx index 854af76..6845b20 100644 --- a/src/hooks/useRunQueueProcessor.tsx +++ b/src/hooks/useRunQueueProcessor.tsx @@ -1,9 +1,9 @@ import { useCallback, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import toast from "react-hot-toast"; -import { useStatusStore } from "../lib/store"; -import { TokenResponse } from "@/lib/models.ts"; import { App } from "@capacitor/app"; +import { TokenResponse } from "@/lib/models.ts"; +import { useStatusStore } from "../lib/store"; interface UseRunQueueProcessorProps { launcherAccess: boolean; diff --git a/src/hooks/useScanOperations.tsx b/src/hooks/useScanOperations.tsx index 60feabd..408abaa 100644 --- a/src/hooks/useScanOperations.tsx +++ b/src/hooks/useScanOperations.tsx @@ -7,7 +7,6 @@ import { cancelSession, readTag, sessionManager, Status } from "../lib/nfc"; import { CoreAPI } from "../lib/coreApi"; import { ScanResult, TokenResponse } from "../lib/models"; import { useNfcWriter, WriteAction } from "../lib/writeNfcHook"; -import { canUseRunToken, incrementRunTokenUsage } from "../lib/dailyUsage"; const zapUrls = [ "https://zpr.au", @@ -48,19 +47,8 @@ const runToken = async ( } const run = async () => { + // Only allow launch for Pro users, Zap URLs, or override if (launcherAccess || isZapUrl(text) || override) { - if (!launcherAccess && !isZapUrl(text) && !override) { - const usageCheck = await canUseRunToken(launcherAccess); - if (!usageCheck.canUse) { - setProPurchaseModalOpen(true); - return resolve(false); - } - } - - if (!isZapUrl(text) && !override) { - await incrementRunTokenUsage(launcherAccess); - } - CoreAPI.run({ uid: uid, text: text, @@ -74,6 +62,9 @@ const runToken = async ( toast.dismiss(to.id)} + onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && toast.dismiss(to.id)} + role="button" + tabIndex={0} > {e.message} @@ -83,35 +74,9 @@ const runToken = async ( }); return; } else { - const usageCheck = await canUseRunToken(launcherAccess); - if (usageCheck.canUse) { - await incrementRunTokenUsage(launcherAccess); - - CoreAPI.run({ - uid: uid, - text: text, - unsafe: unsafe - }) - .then(() => { - resolve(true); - }) - .catch((e) => { - toast.error((to) => ( - toast.dismiss(to.id)} - > - {e.message} - - )); - console.error("launch error", e); - resolve(false); - }); - return; - } else { - setProPurchaseModalOpen(true); - return resolve(false); - } + // Non-Pro users without Zap URL should see Pro purchase modal + setProPurchaseModalOpen(true); + return resolve(false); } }; @@ -196,6 +161,7 @@ export function useScanOperations({ setScanStatus(ScanResult.Error); setScanSession(false); toast.error((to) => ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions toast.dismiss(to.id)} diff --git a/src/hooks/useWriteQueueProcessor.tsx b/src/hooks/useWriteQueueProcessor.tsx index fa355b2..c2718d8 100644 --- a/src/hooks/useWriteQueueProcessor.tsx +++ b/src/hooks/useWriteQueueProcessor.tsx @@ -2,9 +2,9 @@ import { useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import toast from "react-hot-toast"; import { Nfc } from "@capawesome-team/capacitor-nfc"; -import { useStatusStore } from "../lib/store"; import { WriteAction } from "@/lib/writeNfcHook"; import { Status } from "@/lib/nfc.ts"; +import { useStatusStore } from "../lib/store"; interface UseWriteQueueProcessorProps { nfcWriter: { @@ -38,7 +38,7 @@ export function useWriteQueueProcessor({ const maxRetries = 10; const retryInterval = 500; let retryCount = 0; - let timeoutId: NodeJS.Timeout; + let timeoutId: ReturnType; const checkNfcAndWrite = () => { Promise.all([Nfc.isAvailable()]) @@ -46,8 +46,16 @@ export function useWriteQueueProcessor({ if (!availableResult.nfc) { toast.error((to) => ( toast.dismiss(to.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toast.dismiss(to.id); + } + }} + role="button" + tabIndex={0} > {t("write.nfcNotSupported")} @@ -71,8 +79,16 @@ export function useWriteQueueProcessor({ } else { toast.error((to) => ( toast.dismiss(to.id)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + toast.dismiss(to.id); + } + }} + role="button" + tabIndex={0} > {e.message} diff --git a/src/i18n.ts b/src/i18n.ts index 01860e3..1a7f804 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -1,7 +1,6 @@ import i18n from "i18next"; import { initReactI18next } from "react-i18next"; import LanguageDetector from "i18next-browser-languagedetector"; - import en_US from "./translations/en-US.json"; import zh_CN from "./translations/zh-CN.json"; import ko_KR from "./translations/ko-KR.json"; diff --git a/src/lib/coreApi.ts b/src/lib/coreApi.ts index 8acb763..b3c8f15 100644 --- a/src/lib/coreApi.ts +++ b/src/lib/coreApi.ts @@ -1,3 +1,6 @@ +import { Preferences } from "@capacitor/preferences"; +import { v4 as uuidv4 } from "uuid"; +import { Capacitor } from "@capacitor/core"; import { AddMappingRequest, AllMappingsResponse, @@ -16,9 +19,6 @@ import { VersionResponse, WriteRequest } from "./models"; -import { Preferences } from "@capacitor/preferences"; -import { v4 as uuidv4 } from "uuid"; -import { Capacitor } from "@capacitor/core"; const RequestTimeout = 30 * 1000; diff --git a/src/lib/dailyUsage.ts b/src/lib/dailyUsage.ts deleted file mode 100644 index 73be166..0000000 --- a/src/lib/dailyUsage.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { Preferences } from "@capacitor/preferences"; - -const DAILY_LIMIT = 3; -const USAGE_KEY = "dailyRunTokenUsage"; - -interface DailyUsage { - date: string; - count: number; -} - -function getCurrentDate(): string { - return new Date().toISOString().split("T")[0]; -} - -async function getTodaysUsage(): Promise { - const today = getCurrentDate(); - - try { - const result = await Preferences.get({ key: USAGE_KEY }); - if (result.value) { - const usage: DailyUsage = JSON.parse(result.value); - - if (usage.date === today) { - return usage; - } - } - } catch (error) { - console.error("Error reading daily usage:", error); - } - - return { date: today, count: 0 }; -} - -async function saveTodaysUsage(usage: DailyUsage): Promise { - try { - await Preferences.set({ - key: USAGE_KEY, - value: JSON.stringify(usage) - }); - } catch (error) { - console.error("Error saving daily usage:", error); - } -} - -export async function canUseRunToken(launcherAccess: boolean): Promise<{ - canUse: boolean; - remaining: number; - used: number; - limit: number; -}> { - // Pro users can always use it - if (launcherAccess) { - return { - canUse: true, - remaining: Infinity, - used: 0, - limit: Infinity - }; - } - - const usage = await getTodaysUsage(); - const remaining = Math.max(0, DAILY_LIMIT - usage.count); - - return { - canUse: usage.count < DAILY_LIMIT, - remaining, - used: usage.count, - limit: DAILY_LIMIT - }; -} - -export async function incrementRunTokenUsage( - launcherAccess: boolean -): Promise { - if (launcherAccess) { - return; - } - - const usage = await getTodaysUsage(); - usage.count += 1; - - await saveTodaysUsage(usage); -} - -export async function getUsageStats(launcherAccess: boolean): Promise<{ - used: number; - remaining: number; - limit: number; -}> { - if (launcherAccess) { - return { - used: 0, - remaining: Infinity, - limit: Infinity - }; - } - - const usage = await getTodaysUsage(); - const remaining = Math.max(0, DAILY_LIMIT - usage.count); - - return { - used: usage.count, - remaining, - limit: DAILY_LIMIT - }; -} - -export async function resetDailyUsage(): Promise { - try { - await Preferences.remove({ key: USAGE_KEY }); - } catch (error) { - console.error("Error resetting daily usage:", error); - } -} diff --git a/src/lib/safeArea.ts b/src/lib/safeArea.ts index c6e395d..712de60 100644 --- a/src/lib/safeArea.ts +++ b/src/lib/safeArea.ts @@ -1,7 +1,7 @@ import { Capacitor } from "@capacitor/core"; import { SafeArea } from "capacitor-plugin-safe-area"; -import { useStatusStore } from "./store"; import { useEffect } from "react"; +import { useStatusStore } from "./store"; export interface SafeAreaInsets { top: string; diff --git a/src/lib/store.connectionState.test.ts b/src/lib/store.connectionState.test.ts new file mode 100644 index 0000000..c947b70 --- /dev/null +++ b/src/lib/store.connectionState.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from "vitest"; +import { useStatusStore, ConnectionState } from "./store"; + +describe("Store ConnectionState Integration", () => { + it("should update connected boolean when connectionState is set to CONNECTED", () => { + const { setConnectionState } = useStatusStore.getState(); + + setConnectionState(ConnectionState.CONNECTED); + + const newState = useStatusStore.getState(); + expect(newState.connectionState).toBe(ConnectionState.CONNECTED); + expect(newState.connected).toBe(true); + }); + + it("should increment retryCount when retryConnection is called", () => { + const initialState = useStatusStore.getState(); + const initialRetryCount = initialState.retryCount; + + const { retryConnection } = useStatusStore.getState(); + retryConnection(); + + const newState = useStatusStore.getState(); + expect(newState.retryCount).toBe(initialRetryCount + 1); + }); +}); \ No newline at end of file diff --git a/src/lib/store.ts b/src/lib/store.ts index f219f75..c315f97 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -1,8 +1,24 @@ import { create } from "zustand"; -import { IndexResponse, PlayingResponse, TokenResponse } from "./models"; import { User } from "@capacitor-firebase/authentication"; -import { defaultSafeAreaInsets, SafeAreaInsets } from "./safeArea"; import { Preferences } from "@capacitor/preferences"; +import { IndexResponse, PlayingResponse, TokenResponse } from "./models"; +import { SafeAreaInsets } from "./safeArea"; + +const defaultSafeAreaInsets: SafeAreaInsets = { + top: "0px", + bottom: "0px", + left: "0px", + right: "0px" +}; + +export enum ConnectionState { + IDLE = "IDLE", + CONNECTING = "CONNECTING", + CONNECTED = "CONNECTED", + RECONNECTING = "RECONNECTING", + ERROR = "ERROR", + DISCONNECTED = "DISCONNECTED" +} export interface DeviceHistoryEntry { address: string; @@ -12,9 +28,18 @@ interface StatusState { connected: boolean; setConnected: (status: boolean) => void; + connectionState: ConnectionState; + setConnectionState: (state: ConnectionState) => void; + + lastConnectionTime: number | null; + setLastConnectionTime: (time: number | null) => void; + connectionError: string; setConnectionError: (error: string) => void; + retryCount: number; + retryConnection: () => void; + lastToken: TokenResponse; setLastToken: (token: TokenResponse) => void; @@ -53,9 +78,21 @@ export const useStatusStore = create()((set) => ({ connected: false, setConnected: (status) => set({ connected: status }), + connectionState: ConnectionState.IDLE, + setConnectionState: (state) => set({ + connectionState: state, + connected: state === ConnectionState.CONNECTED + }), + + lastConnectionTime: null, + setLastConnectionTime: (time) => set({ lastConnectionTime: time }), + connectionError: "", setConnectionError: (error) => set({ connectionError: error }), + retryCount: 0, + retryConnection: () => set((state) => ({ retryCount: state.retryCount + 1 })), + lastToken: { type: "", uid: "", text: "", data: "", scanTime: "" }, setLastToken: (token) => set({ lastToken: token }), diff --git a/src/lib/writeNfcHook.tsx b/src/lib/writeNfcHook.tsx index 669fad8..b1a7760 100644 --- a/src/lib/writeNfcHook.tsx +++ b/src/lib/writeNfcHook.tsx @@ -1,4 +1,8 @@ import { useEffect, useState } from "react"; +import toast from "react-hot-toast"; +import { useTranslation } from "react-i18next"; +import { Capacitor } from "@capacitor/core"; +import { CheckIcon, WarningIcon } from "./images"; import { cancelSession, eraseTag, @@ -9,10 +13,6 @@ import { Result, Status } from "./nfc"; -import toast from "react-hot-toast"; -import { CheckIcon, WarningIcon } from "./images"; -import { useTranslation } from "react-i18next"; -import { Capacitor } from "@capacitor/core"; import { CoreAPI } from "./coreApi.ts"; interface WriteNfcHook { @@ -126,6 +126,7 @@ export function useNfcWriter(): WriteNfcHook { } toast.success( (to) => ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions toast.dismiss(to.id)} @@ -154,6 +155,7 @@ export function useNfcWriter(): WriteNfcHook { } toast.error( (to) => ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions toast.dismiss(to.id)} diff --git a/src/main.tsx b/src/main.tsx index a47dd06..65aecfc 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,18 +1,14 @@ import { StrictMode } from "react"; import ReactDOM from "react-dom/client"; - import "./i18n"; import "./index.css"; - -import App from "./App"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { LOG_LEVEL, Purchases } from "@revenuecat/purchases-capacitor"; import { Capacitor } from "@capacitor/core"; import { Preferences } from "@capacitor/preferences"; - import { initializeApp } from "firebase/app"; +import App from "./App"; import firebaseConfig from "./firebase.json"; - import { ThemeProvider } from "./components/theme-provider"; initializeApp(firebaseConfig); diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 24227a9..4d7e2ff 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -1,9 +1,9 @@ import { createRootRoute, Outlet, useNavigate } from "@tanstack/react-router"; -import { BottomNav } from "../components/BottomNav"; import { App } from "@capacitor/app"; import { useEffect } from "react"; import { SafeAreaHandler } from "@/lib/safeArea"; import { ErrorComponent } from "@/components/ErrorComponent.tsx"; +import { BottomNav } from "../components/BottomNav"; function BackHandler() { const navigate = useNavigate(); diff --git a/src/routes/create.index.tsx b/src/routes/create.index.tsx index a612ce3..ef1fa32 100644 --- a/src/routes/create.index.tsx +++ b/src/routes/create.index.tsx @@ -1,14 +1,14 @@ import { createFileRoute, Link } from "@tanstack/react-router"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { ListPlusIcon, NfcIcon } from "lucide-react"; import { NextIcon, PlayIcon, SearchIcon, TextIcon } from "../lib/images"; import { useStatusStore } from "../lib/store"; import { useNfcWriter, WriteAction } from "../lib/writeNfcHook"; import { Card } from "../components/wui/Card"; import { Button } from "../components/wui/Button"; import { WriteModal } from "../components/WriteModal"; -import { useEffect, useState } from "react"; import { PageFrame } from "../components/PageFrame"; -import { useTranslation } from "react-i18next"; -import { ListPlusIcon, NfcIcon } from "lucide-react"; export const Route = createFileRoute("/create/")({ component: Create diff --git a/src/routes/create.mappings.tsx b/src/routes/create.mappings.tsx index 7a5bebb..93b5bdb 100644 --- a/src/routes/create.mappings.tsx +++ b/src/routes/create.mappings.tsx @@ -1,21 +1,21 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { useSmartSwipe } from "../hooks/useSmartSwipe"; -import { WriteModal } from "../components/WriteModal"; import { useEffect, useState } from "react"; -import { useNfcWriter, WriteAction } from "../lib/writeNfcHook"; -import { PageFrame } from "../components/PageFrame"; import { useTranslation } from "react-i18next"; -import { Button } from "../components/wui/Button"; -import { TextInput } from "@/components/wui/TextInput.tsx"; import { ArrowDownIcon, CameraIcon, NfcIcon, SaveIcon } from "lucide-react"; import { BarcodeScanner } from "@capacitor-mlkit/barcode-scanning"; +import { useQuery } from "@tanstack/react-query"; +import toast from "react-hot-toast"; +import { TextInput } from "@/components/wui/TextInput.tsx"; import { ZapScriptInput } from "@/components/ZapScriptInput.tsx"; import { ClearIcon } from "@/lib/images.tsx"; -import { useQuery } from "@tanstack/react-query"; import { CoreAPI } from "@/lib/coreApi.ts"; -import toast from "react-hot-toast"; import { useStatusStore } from "@/lib/store.ts"; import { MappingResponse } from "@/lib/models.ts"; +import { Button } from "../components/wui/Button"; +import { PageFrame } from "../components/PageFrame"; +import { useNfcWriter, WriteAction } from "../lib/writeNfcHook"; +import { WriteModal } from "../components/WriteModal"; +import { useSmartSwipe } from "../hooks/useSmartSwipe"; export const Route = createFileRoute("/create/mappings")({ component: Mappings diff --git a/src/routes/create.nfc.tsx b/src/routes/create.nfc.tsx index f0cd451..4c2af5a 100644 --- a/src/routes/create.nfc.tsx +++ b/src/routes/create.nfc.tsx @@ -1,10 +1,5 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { Button } from "../components/wui/Button"; -import { useSmartSwipe } from "../hooks/useSmartSwipe"; -import { WriteModal } from "../components/WriteModal"; import { useEffect, useState } from "react"; -import { useNfcWriter, WriteAction } from "../lib/writeNfcHook"; -import { PageFrame } from "../components/PageFrame"; import { useTranslation } from "react-i18next"; import { Capacitor } from "@capacitor/core"; import { @@ -14,6 +9,11 @@ import { SquareAsteriskIcon } from "lucide-react"; import { Label } from "@/components/ui/label"; +import { Button } from "../components/wui/Button"; +import { useSmartSwipe } from "../hooks/useSmartSwipe"; +import { WriteModal } from "../components/WriteModal"; +import { useNfcWriter, WriteAction } from "../lib/writeNfcHook"; +import { PageFrame } from "../components/PageFrame"; export const Route = createFileRoute("/create/nfc")({ component: NfcUtils diff --git a/src/routes/create.search.tsx b/src/routes/create.search.tsx index 0c52819..1734275 100644 --- a/src/routes/create.search.tsx +++ b/src/routes/create.search.tsx @@ -1,6 +1,11 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; import { useMutation, useQuery } from "@tanstack/react-query"; import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Preferences } from "@capacitor/preferences"; +import { SearchResults } from "@/components/SearchResults.tsx"; +import { CopyButton } from "@/components/CopyButton.tsx"; +import { BackToTop } from "@/components/BackToTop.tsx"; import { CoreAPI } from "../lib/coreApi.ts"; import { CreateIcon, PlayIcon, SearchIcon } from "../lib/images"; import { useNfcWriter, WriteAction } from "../lib/writeNfcHook"; @@ -12,11 +17,6 @@ import { useStatusStore } from "../lib/store"; import { TextInput } from "../components/wui/TextInput"; import { WriteModal } from "../components/WriteModal"; import { PageFrame } from "../components/PageFrame"; -import { useTranslation } from "react-i18next"; -import { Preferences } from "@capacitor/preferences"; -import { SearchResults } from "@/components/SearchResults.tsx"; -import { CopyButton } from "@/components/CopyButton.tsx"; -import { BackToTop } from "@/components/BackToTop.tsx"; const initData = { systemQuery: "all" diff --git a/src/routes/create.text.tsx b/src/routes/create.text.tsx index 99f8377..b129e60 100644 --- a/src/routes/create.text.tsx +++ b/src/routes/create.text.tsx @@ -1,14 +1,14 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Preferences } from "@capacitor/preferences"; +import { ZapScriptInput } from "@/components/ZapScriptInput.tsx"; import { CreateIcon } from "../lib/images"; import { Button } from "../components/wui/Button"; import { useSmartSwipe } from "../hooks/useSmartSwipe"; import { WriteModal } from "../components/WriteModal"; -import { useEffect, useState } from "react"; import { useNfcWriter, WriteAction } from "../lib/writeNfcHook"; import { PageFrame } from "../components/PageFrame"; -import { useTranslation } from "react-i18next"; -import { Preferences } from "@capacitor/preferences"; -import { ZapScriptInput } from "@/components/ZapScriptInput.tsx"; const initData = { customText: "" diff --git a/src/routes/index.tsx b/src/routes/index.tsx index f4ac725..a44e2d7 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,29 +1,27 @@ import { createFileRoute } from "@tanstack/react-router"; import { useEffect, useState } from "react"; -import { cancelSession } from "../lib/nfc"; -import { CoreAPI } from "../lib/coreApi.ts"; -import { HistoryIcon } from "../lib/images"; -import { useStatusStore } from "../lib/store"; import { useQuery } from "@tanstack/react-query"; import { KeepAwake } from "@capacitor-community/keep-awake"; -import { ToggleChip } from "../components/wui/ToggleChip"; import { Preferences } from "@capacitor/preferences"; -import { PageFrame } from "../components/PageFrame"; import { useNfcWriter } from "@/lib/writeNfcHook.tsx"; import { useProPurchase } from "@/components/ProPurchase.tsx"; import { WriteModal } from "@/components/WriteModal.tsx"; +import { useWriteQueueProcessor } from "@/hooks/useWriteQueueProcessor.tsx"; +import { useRunQueueProcessor } from "@/hooks/useRunQueueProcessor.tsx"; +import { cancelSession } from "../lib/nfc"; +import { CoreAPI } from "../lib/coreApi.ts"; +import { HistoryIcon } from "../lib/images"; +import { useStatusStore } from "../lib/store"; +import { ToggleChip } from "../components/wui/ToggleChip"; +import { PageFrame } from "../components/PageFrame"; import { ConnectionStatus } from "../components/home/ConnectionStatus"; import { ScanControls } from "../components/home/ScanControls"; import { LastScannedInfo } from "../components/home/LastScannedInfo"; import { NowPlayingInfo } from "../components/home/NowPlayingInfo"; import { HistoryModal } from "../components/home/HistoryModal"; import { StopConfirmModal } from "../components/home/StopConfirmModal"; -import { DailyUsageInfo } from "../components/home/DailyUsageInfo"; import { useScanOperations } from "../hooks/useScanOperations"; import { useAppSettings } from "../hooks/useAppSettings"; -import { useWriteQueueProcessor } from "@/hooks/useWriteQueueProcessor.tsx"; -import { useRunQueueProcessor } from "@/hooks/useRunQueueProcessor.tsx"; - import logoImage from "../../public/lockup.png"; interface LoaderData { @@ -163,12 +161,6 @@ function Index() {
- setProPurchaseModalOpen(true)} - lastToken={lastToken} - /> diff --git a/src/routes/settings.about.tsx b/src/routes/settings.about.tsx index 4643fb7..20dec65 100644 --- a/src/routes/settings.about.tsx +++ b/src/routes/settings.about.tsx @@ -1,7 +1,7 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { PageFrame } from "../components/PageFrame"; import { useTranslation } from "react-i18next"; import { Browser } from "@capacitor/browser"; +import { PageFrame } from "../components/PageFrame"; import { Button } from "../components/wui/Button.tsx"; import { useSmartSwipe } from "../hooks/useSmartSwipe"; @@ -88,7 +88,7 @@ function About() { Phil Felice,{" "} Glenn,{" "} Alexander Facchini,{" "} - Lu's Retro Source,{" "} + Lu's Retro Source,{" "} Alexis Conrad,{" "} Tony Shadwick,{" "} Clinton Cronin,{" "} @@ -97,7 +97,7 @@ function About() { Retrosoft Studios, Casey McGinty, Biddle,{" "} Chris Platts,{" "} - Gentlemen's Pixel Club,{" "} + Gentlemen's Pixel Club,{" "} VolJoe,{" "} Shijuro,{" "} Tim Sullivan,{" "} diff --git a/src/routes/settings.advanced.tsx b/src/routes/settings.advanced.tsx index 317f2ba..80093bd 100644 --- a/src/routes/settings.advanced.tsx +++ b/src/routes/settings.advanced.tsx @@ -1,11 +1,11 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { useTranslation } from "react-i18next"; import { CoreAPI } from "../lib/coreApi.ts"; import { ToggleSwitch } from "../components/wui/ToggleSwitch"; import { useSmartSwipe } from "../hooks/useSmartSwipe"; -import { useMutation, useQuery } from "@tanstack/react-query"; import { useStatusStore } from "../lib/store"; import { PageFrame } from "../components/PageFrame"; -import { useTranslation } from "react-i18next"; import { UpdateSettingsRequest } from "../lib/models.ts"; export const Route = createFileRoute("/settings/advanced")({ diff --git a/src/routes/settings.help.tsx b/src/routes/settings.help.tsx index 94f17c0..8b7cc54 100644 --- a/src/routes/settings.help.tsx +++ b/src/routes/settings.help.tsx @@ -1,9 +1,9 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; +import { Browser } from "@capacitor/browser"; +import { useTranslation } from "react-i18next"; import { Button } from "../components/wui/Button"; import { useSmartSwipe } from "../hooks/useSmartSwipe"; -import { Browser } from "@capacitor/browser"; import { PageFrame } from "../components/PageFrame"; -import { useTranslation } from "react-i18next"; export const Route = createFileRoute("/settings/help")({ component: Help diff --git a/src/routes/settings.index.tsx b/src/routes/settings.index.tsx index d294b60..c5f15a6 100644 --- a/src/routes/settings.index.tsx +++ b/src/routes/settings.index.tsx @@ -1,24 +1,16 @@ import { createFileRoute, Link } from "@tanstack/react-router"; -import { getDeviceAddress, setDeviceAddress, CoreAPI } from "../lib/coreApi.ts"; -import { CheckIcon, DatabaseIcon, ExternalIcon, NextIcon } from "../lib/images"; -import { Button } from "../components/wui/Button"; -import { Button as SCNButton } from "@/components/ui/button"; import classNames from "classnames"; import { useEffect, useState } from "react"; import { useMutation, useQuery } from "@tanstack/react-query"; -import { TextInput } from "../components/wui/TextInput"; -import { useStatusStore } from "../lib/store"; import { Browser } from "@capacitor/browser"; -import { PageFrame } from "../components/PageFrame"; import { useTranslation } from "react-i18next"; -import i18n from "../i18n"; import { Capacitor } from "@capacitor/core"; -import { UpdateSettingsRequest } from "../lib/models.ts"; +import { Preferences } from "@capacitor/preferences"; +import { ArrowLeftRightIcon, TrashIcon } from "lucide-react"; import { RestorePuchasesButton, useProPurchase } from "@/components/ProPurchase.tsx"; -import { Preferences } from "@capacitor/preferences"; import { Dialog, DialogContent, @@ -26,9 +18,17 @@ import { DialogTitle, DialogTrigger } from "@/components/ui/dialog.tsx"; -import { ArrowLeftRightIcon, TrashIcon } from "lucide-react"; +import { Button as SCNButton } from "@/components/ui/button"; import { ScanSettings } from "@/components/home/ScanSettings.tsx"; import { useAppSettings } from "@/hooks/useAppSettings.ts"; +import { UpdateSettingsRequest } from "../lib/models.ts"; +import i18n from "../i18n"; +import { PageFrame } from "../components/PageFrame"; +import { useStatusStore } from "../lib/store"; +import { TextInput } from "../components/wui/TextInput"; +import { Button } from "../components/wui/Button"; +import { CheckIcon, DatabaseIcon, ExternalIcon, NextIcon } from "../lib/images"; +import { getDeviceAddress, setDeviceAddress, CoreAPI } from "../lib/coreApi.ts"; interface LoaderData { restartScan: boolean; @@ -138,7 +138,7 @@ function Settings() { {deviceHistory .sort((a, b) => (a.address > b.address ? 1 : -1)) .map((entry) => ( -
+
server.listen()); + +// Reset handlers after each test +afterEach(() => { + server.resetHandlers(); + cleanup(); +}); + +// Close server after all tests +afterAll(() => server.close()); + +// Mock window.matchMedia +Object.defineProperty(window, "matchMedia", { + writable: true, + value: (query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: () => {}, + removeListener: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => {} + }) +}); + +// Mock IntersectionObserver +global.IntersectionObserver = class IntersectionObserver { + constructor() {} + observe() {} + unobserve() {} + disconnect() {} +} as any; + +// Platform detection mock +Object.defineProperty(navigator, "platform", { + writable: true, + value: "MacIntel" +}); + +// WebSocket mock for happy-dom environment +global.WebSocket = class MockWebSocket { + constructor(url: string) {} + close() {} +} as any; \ No newline at end of file diff --git a/src/test-utils/factories.ts b/src/test-utils/factories.ts new file mode 100644 index 0000000..9fb1d4c --- /dev/null +++ b/src/test-utils/factories.ts @@ -0,0 +1,94 @@ +import { faker } from "@faker-js/faker"; +import { + VersionResponse, + LaunchRequest, + WriteRequest, + SearchParams, + SearchResultGame, + System, + SystemsResponse, + MappingResponse, + AddMappingRequest, + UpdateMappingRequest, + HistoryResponseEntry, + HistoryResponse, + SettingsResponse, + UpdateSettingsRequest, + TokenResponse, + IndexResponse, + PlayingResponse, + MediaResponse, + TokensResponse, + MappingType +} from "../lib/models"; + +export const mockSystem = (overrides?: Partial): System => ({ + id: faker.string.uuid(), + name: faker.word.words(2), + category: faker.helpers.arrayElement(["console", "computer", "handheld"]), + ...overrides +}); + +export const mockVersionResponse = (overrides?: Partial): VersionResponse => ({ + version: faker.system.semver(), + platform: faker.helpers.arrayElement(["linux", "windows", "macos"]), + ...overrides +}); + +export const mockLaunchRequest = (overrides?: Partial): LaunchRequest => ({ + uid: faker.string.uuid(), + text: faker.lorem.words(3), + data: faker.string.alphanumeric(8), + unsafe: faker.datatype.boolean(), + ...overrides +}); + +export const mockWriteRequest = (overrides?: Partial): WriteRequest => ({ + text: faker.lorem.words(5), + ...overrides +}); + +export const mockTokenResponse = (overrides?: Partial): TokenResponse => ({ + type: faker.helpers.arrayElement(["ntag213", "ntag215", "ntag216"]), + uid: faker.string.hexadecimal({ length: 14 }).slice(2), + text: faker.lorem.words(3), + data: faker.string.alphanumeric(8), + scanTime: faker.date.recent().toISOString(), + ...overrides +}); + +export const mockPlayingResponse = (overrides?: Partial): PlayingResponse => ({ + systemId: faker.string.uuid(), + systemName: faker.word.words(2), + mediaName: faker.word.words(3), + mediaPath: faker.system.filePath(), + ...overrides +}); + +export const mockIndexResponse = (overrides?: Partial): IndexResponse => ({ + exists: faker.datatype.boolean(), + indexing: faker.datatype.boolean(), + totalSteps: faker.number.int({ min: 1, max: 100 }), + currentStep: faker.number.int({ min: 1, max: 50 }), + currentStepDisplay: faker.lorem.words(2), + totalFiles: faker.number.int({ min: 10, max: 1000 }), + ...overrides +}); + +export const mockMediaResponse = (overrides?: Partial): MediaResponse => ({ + database: mockIndexResponse(), + active: faker.helpers.multiple(() => mockPlayingResponse(), { count: { min: 0, max: 3 } }), + ...overrides +}); + +export const mockMappingResponse = (overrides?: Partial): MappingResponse => ({ + id: faker.string.uuid(), + added: faker.date.recent().toISOString(), + label: faker.word.words(2), + enabled: faker.datatype.boolean(), + type: faker.helpers.arrayElement(["uid", "text", "data"]), + match: faker.lorem.words(2), + pattern: faker.lorem.words(2), + override: faker.lorem.words(2), + ...overrides +}); \ No newline at end of file diff --git a/src/test-utils/helpers.ts b/src/test-utils/helpers.ts new file mode 100644 index 0000000..770f3c4 --- /dev/null +++ b/src/test-utils/helpers.ts @@ -0,0 +1,66 @@ +import { vi } from "vitest"; + +// Helper to wait for async operations +export const waitFor = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +// Helper to create a mock WebSocket that can be controlled in tests +export const createMockWebSocket = () => { + const mockWs = { + send: vi.fn(), + close: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + readyState: 1, // OPEN + url: "ws://test:7497/api/v0.1", + onopen: null as ((event: Event) => void) | null, + onclose: null as ((event: CloseEvent) => void) | null, + onmessage: null as ((event: MessageEvent) => void) | null, + onerror: null as ((event: Event) => void) | null, + }; + + // Helper methods to simulate events + const simulateOpen = () => { + if (mockWs.onopen) mockWs.onopen(new Event("open")); + }; + + const simulateMessage = (data: string) => { + if (mockWs.onmessage) mockWs.onmessage(new MessageEvent("message", { data })); + }; + + const simulateClose = () => { + if (mockWs.onclose) mockWs.onclose(new CloseEvent("close")); + }; + + const simulateError = () => { + if (mockWs.onerror) mockWs.onerror(new Event("error")); + }; + + return { + mockWs, + simulateOpen, + simulateMessage, + simulateClose, + simulateError + }; +}; + +// Helper to mock console methods +export const mockConsole = () => { + const originalConsole = { ...console }; + + const restore = () => { + Object.assign(console, originalConsole); + }; + + const mockMethods = { + log: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + info: vi.fn() + }; + + Object.assign(console, mockMethods); + + return { ...mockMethods, restore }; +}; \ No newline at end of file diff --git a/src/test-utils/index.tsx b/src/test-utils/index.tsx new file mode 100644 index 0000000..86ce9af --- /dev/null +++ b/src/test-utils/index.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { render, RenderOptions } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { createMemoryRouter, RouterProvider } from "@tanstack/react-router"; + +// Create a test query client +const createTestQueryClient = () => + new QueryClient({ + defaultOptions: { + queries: { + retry: false, + staleTime: Infinity + }, + mutations: { + retry: false + } + } + }); + +// Custom render function that wraps components with necessary providers +function customRender( + ui: React.ReactElement, + options?: Omit +) { + const queryClient = createTestQueryClient(); + + function Wrapper({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); + } + + return render(ui, { wrapper: Wrapper, ...options }); +} + +// Re-export everything from testing library +export * from "@testing-library/react"; +export { customRender as render }; \ No newline at end of file diff --git a/src/test-utils/msw-handlers.ts b/src/test-utils/msw-handlers.ts new file mode 100644 index 0000000..c9085e0 --- /dev/null +++ b/src/test-utils/msw-handlers.ts @@ -0,0 +1,68 @@ +import { ws } from "msw"; + +// WebSocket handler that matches the actual app URL pattern +export const websocketHandler = ws.link("ws://*/api/v0.1"); + +export const handlers = [ + websocketHandler.addEventListener("connection", ({ client }) => { + console.log("MSW: WebSocket connection established"); + + // Handle ping messages for heartbeat + client.addEventListener("message", (event) => { + if (event.data === "ping") { + client.send("pong"); + return; + } + + try { + const message = JSON.parse(event.data.toString()); + + // Mock responses for common API calls + if (message.method === "version") { + client.send(JSON.stringify({ + jsonrpc: "2.0", + id: message.id, + timestamp: Date.now(), + result: { + version: "1.0.0-test", + commit: "abc123" + } + })); + } else if (message.method === "media") { + client.send(JSON.stringify({ + jsonrpc: "2.0", + id: message.id, + timestamp: Date.now(), + result: { + database: { + total_files: 100, + indexed_files: 95, + processing: false + }, + active: [] + } + })); + } else if (message.method === "tokens") { + client.send(JSON.stringify({ + jsonrpc: "2.0", + id: message.id, + timestamp: Date.now(), + result: { + last: null + } + })); + } else { + // Generic success response for other methods + client.send(JSON.stringify({ + jsonrpc: "2.0", + id: message.id, + timestamp: Date.now(), + result: {} + })); + } + } catch (error) { + console.error("MSW: Error processing WebSocket message:", error); + } + }); + }) +]; \ No newline at end of file diff --git a/src/translations/en-US.json b/src/translations/en-US.json index 60196a8..37bf5f2 100644 --- a/src/translations/en-US.json +++ b/src/translations/en-US.json @@ -66,10 +66,7 @@ "purchaseProP1": "Zaparoo Pro can be unlocked with a one-time purchase of {{price}}, which will help support future development of Zaparoo.", "purchaseProP2": "Pro includes the Launch on scan feature, which turns your phone into a wireless Zaparoo reader! All future Pro features will be included at no extra cost.", "purchaseProAction": "Unlock Zaparoo Pro", - "completeIntro": "Let's Zaparoo", - "freeUsageTitle": "Daily Free Zaps", - "freeUsageExceeded": "None left today. Unlock Pro for unlimited.", - "freeUsageRemaining": "{{remaining}} left today." + "completeIntro": "Let's Zaparoo" }, "create": { "title": "Create a New Tag", diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index 11f02fe..97ce452 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -1 +1,2 @@ /// +/// diff --git a/tsconfig.json b/tsconfig.json index 4a83aa7..4a23179 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,5 +26,6 @@ } }, "include": ["src"], + "exclude": ["src/__tests__", "src/__mocks__", "src/test-utils", "src/test-setup.ts"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..52952c7 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react"; +import { TanStackRouterVite } from "@tanstack/router-vite-plugin"; +import { VitestReporter } from "tdd-guard-vitest"; +import path from "path"; + +export default defineConfig({ + plugins: [react(), TanStackRouterVite()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src") + } + }, + test: { + globals: true, + environment: "happy-dom", + setupFiles: ["./src/test-setup.ts"], + reporters: ["default", new VitestReporter(__dirname)], + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], + exclude: [ + "node_modules/", + "src/test-setup.ts", + "**/*.d.ts", + "**/*.config.ts", + "src/__mocks__/**", + "src/__tests__/**" + ] + } + } +});