diff --git a/.gitignore b/.gitignore index 96c4569..b4d09c0 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ build/Release node_modules # Generated plugin build output -chunks/*.js index.js reporters/*.js sentry.js +storybook-static diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 0000000..73d6f4b --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,11 @@ +import type { StorybookConfig } from '@storybook/react-vite' + +const config: StorybookConfig = { + stories: ['../src/**/*.stories.@(ts|tsx)'], + framework: { + name: '@storybook/react-vite', + options: {}, + }, +} + +export default config diff --git a/.storybook/preview.ts b/.storybook/preview.ts new file mode 100644 index 0000000..0795624 --- /dev/null +++ b/.storybook/preview.ts @@ -0,0 +1,14 @@ +import type { Preview } from '@storybook/react-vite' + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, +} + +export default preview diff --git a/README.md b/README.md index d738d29..30bfd90 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,13 @@ Report ship creating info and drop info, and so on. - `pnpm run lint` checks JavaScript and TypeScript sources. - `pnpm run typecheck` runs TypeScript in strict mode. +- `pnpm run storybook` previews React plugin UI surfaces. - `pnpm test` builds the plugin and runs the Vitest suite. + +## Remodel debug recorder + +For local remodel recipe validation, the plugin includes an opt-in in-memory recorder. It is off by default and only records the three Akashi remodel APIs. + +Enable it from the plugin settings panel. The settings panel also shows the in-memory record count and provides `Export` and `Clear` actions. + +Captures stay in memory and are not sent anywhere; allowlisted, sanitized data is written only when `Export` is clicked. Disable the setting after collecting the local validation data. diff --git a/eslint.config.mjs b/eslint.config.mjs index 97c40c5..14ff4e1 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,19 +9,19 @@ import tseslint from 'typescript-eslint' export default tseslint.config( { ignores: [ - 'chunks/**', 'coverage/**', 'index.js', 'node_modules/**', 'reporters/**', 'sentry.js', + 'storybook-static/**', ], }, js.configs.recommended, ...tseslint.configs.recommended, importX.flatConfigs.recommended, { - files: ['**/*.{js,cjs,mjs,ts}'], + files: ['**/*.{js,cjs,mjs,ts,tsx}'], languageOptions: { ecmaVersion: 2022, sourceType: 'module', @@ -60,6 +60,7 @@ export default tseslint.config( js: 'never', mjs: 'never', ts: 'never', + tsx: 'never', }, ], 'import-x/no-unresolved': [ @@ -77,7 +78,7 @@ export default tseslint.config( }, }, { - files: ['src/**/*.ts'], + files: ['src/**/*.{ts,tsx}'], extends: [tseslint.configs.recommendedTypeChecked], languageOptions: { parserOptions: { diff --git a/i18n/en-US.json b/i18n/en-US.json index 2c63c08..18aae46 100644 --- a/i18n/en-US.json +++ b/i18n/en-US.json @@ -1,2 +1,8 @@ { + "Captured records": "Captured records: {{count}}", + "Clear": "Clear", + "Enable remodel debug recorder": "Enable remodel debug recorder", + "Export": "Export", + "Remodel recipe debug recorder": "Remodel recipe debug recorder", + "Remodel recipe debug recorder description": "Opt-in local recorder for validating Akashi remodel API sequences. It captures only allowlisted remodel fields, keeps records in memory, and writes a file only when Export is clicked." } diff --git a/i18n/ja-JP.json b/i18n/ja-JP.json index b9e25a2..dc92e0d 100644 --- a/i18n/ja-JP.json +++ b/i18n/ja-JP.json @@ -1,4 +1,10 @@ { "Data Report": "統計データベースツール", - "Report data to database(http://db.kcwiki.moe)": "統計データベース(http://db.kcwiki.moe)登録用ツール" + "Report data to database(http://db.kcwiki.moe)": "統計データベース(http://db.kcwiki.moe)登録用ツール", + "Captured records": "記録数: {{count}}", + "Clear": "クリア", + "Enable remodel debug recorder": "改修デバッグレコーダーを有効化", + "Export": "エクスポート", + "Remodel recipe debug recorder": "改修レシピデバッグレコーダー", + "Remodel recipe debug recorder description": "明石の改修APIシーケンス検証用のローカルレコーダーです。許可された改修フィールドだけをメモリ上に記録し、エクスポートをクリックしたときだけファイルへ書き出します。" } diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json index abe8a64..bd02bff 100644 --- a/i18n/zh-CN.json +++ b/i18n/zh-CN.json @@ -1,4 +1,10 @@ { "Data Report": "数据汇报", - "Report data to database(http://db.kcwiki.moe)": "汇报建造数据、海域掉落数据、开发数据" + "Report data to database(http://db.kcwiki.moe)": "汇报建造数据、海域掉落数据、开发数据", + "Captured records": "已捕获记录:{{count}}", + "Clear": "清空", + "Enable remodel debug recorder": "启用改修调试记录器", + "Export": "导出", + "Remodel recipe debug recorder": "改修配方调试记录器", + "Remodel recipe debug recorder description": "用于验证明石改修 API 顺序的本地可选记录器。只捕获允许的改修字段,记录仅保存在内存中,并且只会在点击导出时写入文件。" } diff --git a/i18n/zh-TW.json b/i18n/zh-TW.json index 0174d33..93628b6 100644 --- a/i18n/zh-TW.json +++ b/i18n/zh-TW.json @@ -1,4 +1,10 @@ { "Data Report": "數據匯報", - "Report data to database(http://db.kcwiki.moe)": "匯報建造數據、海域掉落數據、開發數據" + "Report data to database(http://db.kcwiki.moe)": "匯報建造數據、海域掉落數據、開發數據", + "Captured records": "已擷取記錄:{{count}}", + "Clear": "清除", + "Enable remodel debug recorder": "啟用改修除錯記錄器", + "Export": "匯出", + "Remodel recipe debug recorder": "改修配方除錯記錄器", + "Remodel recipe debug recorder description": "用於驗證明石改修 API 順序的本機選用記錄器。只擷取允許的改修欄位,記錄只保存在記憶體中,且只會在點擊匯出時寫入檔案。" } diff --git a/package.json b/package.json index d4f60a4..50200ff 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,8 @@ "main": "index.js", "packageManager": "pnpm@10.28.0", "files": [ - "chunks", "i18n", "index.js", - "reporters", - "sentry.js", "package.json", "README.md", "LICENSE" @@ -18,8 +15,10 @@ "build": "tsdown", "lint": "eslint .", "prepack": "tsdown", - "postpublish": "git clean -fd -- index.js sentry.js reporters chunks && git checkout .", + "postpublish": "git clean -fd -- index.js && git checkout .", "smoke": "node scripts/smoke-load.cjs", + "storybook": "storybook dev -p 6006", + "storybook:build": "storybook build", "test": "tsdown && vitest run", "test:coverage": "tsdown && vitest run --coverage", "typecheck": "tsc --noEmit", @@ -51,9 +50,12 @@ "@commitlint/cli": "^8.2.0", "@commitlint/config-conventional": "^8.2.0", "@eslint/js": "^10.0.1", + "@storybook/react-vite": "^10.4.6", "@types/lodash": "^4.17.21", "@types/node": "^24.0.0", "@types/node-fetch": "^2.6.13", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", "@types/semver": "^7.7.1", "@vitest/coverage-v8": "^4.0.0", "eslint": "^10.6.0", @@ -65,7 +67,12 @@ "husky": "^3.0.8", "kcsapi": "^1.260604.0", "lint-staged": "^9.4.2", + "lodash": "^4.18.1", "prettier": "^3.9.4", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-i18next": "^17.0.8", + "storybook": "^10.4.6", "tsdown": "^0.22.0", "typescript": "^5.9.0", "typescript-eslint": "^8.62.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6063906..a1f1200 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@eslint/js': specifier: ^10.0.1 version: 10.0.1(eslint@10.6.0) + '@storybook/react-vite': + specifier: ^10.4.6 + version: 10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7))(typescript@5.9.3)(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1)) '@types/lodash': specifier: ^4.17.21 version: 4.17.24 @@ -26,6 +29,12 @@ importers: '@types/node-fetch': specifier: ^2.6.13 version: 2.6.13 + '@types/react': + specifier: ^19.2.17 + version: 19.2.17 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.17) '@types/semver': specifier: ^7.7.1 version: 7.7.1 @@ -59,12 +68,27 @@ importers: lint-staged: specifier: ^9.4.2 version: 9.5.0 + lodash: + specifier: ^4.18.1 + version: 4.18.1 prettier: specifier: ^3.9.4 version: 3.9.4 + react: + specifier: ^19.2.7 + version: 19.2.7 + react-dom: + specifier: ^19.2.7 + version: 19.2.7(react@19.2.7) + react-i18next: + specifier: ^17.0.8 + version: 17.0.8(i18next@26.3.4(typescript@5.9.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@5.9.3) + storybook: + specifier: ^10.4.6 + version: 10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7) tsdown: specifier: ^0.22.0 - version: 0.22.3(typescript@5.9.3)(unrun@0.2.39(synckit@0.11.13)) + version: 0.22.3(oxc-resolver@11.23.0)(typescript@5.9.3)(unrun@0.2.39(synckit@0.11.13)) typescript: specifier: ^5.9.0 version: 5.9.3 @@ -73,18 +97,51 @@ importers: version: 8.62.1(eslint@10.6.0)(typescript@5.9.3) vitest: specifier: ^4.0.0 - version: 4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(vite@8.1.1(@types/node@24.13.2)) + version: 4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1)) packages: + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + '@babel/generator@8.0.0': resolution: {integrity: sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} @@ -101,6 +158,14 @@ packages: resolution: {integrity: sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==} engines: {node: ^22.18.0 || >=24.11.0} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.29.7': resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} @@ -111,6 +176,18 @@ packages: engines: {node: ^22.18.0 || >=24.11.0} hasBin: true + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + '@babel/types@7.29.7': resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} @@ -189,18 +266,180 @@ packages: '@emnapi/core@1.11.1': resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -260,9 +499,21 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0': + resolution: {integrity: sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ==} + peerDependencies: + typescript: '>= 4.3.x' + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -294,12 +545,226 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oxc-parser/binding-android-arm-eabi@0.127.0': + resolution: {integrity: sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.127.0': + resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.127.0': + resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.127.0': + resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.127.0': + resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} '@oxc-project/types@0.137.0': resolution: {integrity: sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==} + '@oxc-resolver/binding-android-arm-eabi@11.23.0': + resolution: {integrity: sha512-8IJyWRLVAyhTfe9/TIEbQqSQnl5rUqYJrUOS6Dkr+Mq9FGHMxDGeiEmwkBqCvDP5KckpPh/GYSgbag66O6JsCw==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.23.0': + resolution: {integrity: sha512-pprVojnNhHxupwTT2gdeUlkxll6XEvWWBk3oVicOSNVWQC99OBnDhMQDoirqnzrE1bScQSMS2JgPpqdlrhz/Fg==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.23.0': + resolution: {integrity: sha512-mbIrWIMAJeytyee36OyUP5XH92TP7FaKaQ2m5AjokKy7STgjrhRt7SMXqpqLjhGm6Xn721Xmsg6H3Rtd9YQETw==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.23.0': + resolution: {integrity: sha512-UnIphmZ1LazUCr9DXWaKYWtKDefPMbgLsywaoYxRqVCNHhq4MM6d2q1Nz1i9Vzxt5i+cE2nRUYpAUHr/lijNYA==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.23.0': + resolution: {integrity: sha512-aaZ/cSEYFkSxgS2hOrobT6RQcsWNviOX8dW6CEkVx2/UYkAf9MeHbjl3W0usWV53rVV//ndBdn2nb1y7jsu4lw==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.23.0': + resolution: {integrity: sha512-IoJLvO5SjLSVMaq83BNTrPCb1FppvoJc1IhZ5CoUVl3PykUBku7D+LK1j0GSurhJcIc6zfjghsvaZNpq5ev6Mg==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.23.0': + resolution: {integrity: sha512-vskFpwg44T/LFsfjSCnVZ5ygcuqzPC1yUzVEiKa8BgHAQz0+QLQQW3EGWLPVi8EXFghzjR4EtgPBtOhCjU4jdw==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.23.0': + resolution: {integrity: sha512-//TcHVhrChyw5RYtgts6WO7KcWq9387c1Z5Zvhqpk/ktAbyaRYgBZrpSY1GDCFq50ASt6B6jhh+JxB1rB45IAg==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-musl@11.23.0': + resolution: {integrity: sha512-ZFqlwiTf7CXLLSGyAR9tYiO33LiaeIEXW+xm42d8mnUGpDgPltyrCGYtQezyMMEXvjhOgCz1X+i7sbDTJEx+bg==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.23.0': + resolution: {integrity: sha512-oZ5LeN5+H1R19dRjTAxKrxQguH+AsemHcnthEfFxf4OjmBSty2doHLeSmMunKy3zpTHJQ3lh3Af+dNS+W6dYeA==} + cpu: [ppc64] + os: [linux] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.23.0': + resolution: {integrity: sha512-O4ciFDyX5ebQd0qkb1bjAIg8IEfiLT03GbSeylwlwlUMK9KwBWaALwrxSbc0Msaz4U6iPj+T9eRXpD5mxBfmvA==} + cpu: [riscv64] + os: [linux] + + '@oxc-resolver/binding-linux-riscv64-musl@11.23.0': + resolution: {integrity: sha512-P3o8Y9kISYjcxadmbO+94ThRwLhwGuDAbA7dcdd4+YLpfeF+mmobz8fXf4NmSdfSqjyRSkceJDBRZha9NVYkiQ==} + cpu: [riscv64] + os: [linux] + + '@oxc-resolver/binding-linux-s390x-gnu@11.23.0': + resolution: {integrity: sha512-oj03m1E3RmTFczKhcKJDzHaEDKJnPIsDcQFVxBJsSdXGSuIPdt5TvcM332FfMQgzI6yDJqyl4InrnFfXrmUTKQ==} + cpu: [s390x] + os: [linux] + + '@oxc-resolver/binding-linux-x64-gnu@11.23.0': + resolution: {integrity: sha512-BqJxbSC8FdP7mSuSpRePTGHm0hXWV+dfz//f7SjsteZncLaBgWTBmi/OZNv7sX6CyG/Pt/eJkPorP+DkMOhMwQ==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-linux-x64-musl@11.23.0': + resolution: {integrity: sha512-utmw+VmUrW4K8LI5/6jhg4aGYKJHOIjQ9syYOOA6pF3w7haKu4r4enTe2U0C04/HbUvkq/Zif43xFsKW1Pnq9w==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-openharmony-arm64@11.23.0': + resolution: {integrity: sha512-V6lbRrthHa4TbvsLjPtg+EkXT1tRY+s4I8rYLXUfiHlZzGx3sLv1EH9CEOOevjvUYHLsbe/gqCIc73XnQfPb9A==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.23.0': + resolution: {integrity: sha512-gRoOxQPdnAmIAjxcuQNBxfihvx+wjTaQM/9/eP12xwnGNawOG/+Zz9RHN4WNSxT45b5CrscK4NB8aPh+oZQXAQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.23.0': + resolution: {integrity: sha512-CgTGMYsJVe1eUiCdJTpGw21svXw79ITsemN1h0hcNkiswasDbN5MoibSLY+gRMWP5syfEz5iffrjZnwEP8xeUA==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.23.0': + resolution: {integrity: sha512-gUGJpr+Rn6zMxm5juApV0K3U845i8t47o8k+rbO0BHbi4PoJIfSPeQmrE2dgohQm2g5k6iviNFyXCGqvmaYUpw==} + cpu: [x64] + os: [win32] + '@pkgr/core@0.3.6': resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} engines: {node: ^14.18.0 || >=16.0.0} @@ -491,6 +956,15 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@samverschueren/stream-to-observable@0.3.1': resolution: {integrity: sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==} engines: {node: '>=6'} @@ -506,15 +980,118 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@storybook/builder-vite@10.4.6': + resolution: {integrity: sha512-BHBtD81HiXUiDQz/CaFynLtWmm7AFUQn8VnXuHipZ8KlnUANopa4yqdVuy/Gwz8ub254uFI5NMZsW/KlgWNgNg==} + peerDependencies: + storybook: ^10.4.6 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@storybook/csf-plugin@10.4.6': + resolution: {integrity: sha512-NILLxDqpA/JR/AazGWpsz+4fadJwRU4uhHephGtYpVOWnQA/DkJfKT6zpcJVq8+QA8A2zKMLX3GVKsXIrxjuDA==} + peerDependencies: + esbuild: '*' + rollup: '*' + storybook: ^10.4.6 + vite: '*' + webpack: '*' + peerDependenciesMeta: + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + + '@storybook/global@5.0.0': + resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} + + '@storybook/icons@2.1.0': + resolution: {integrity: sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@storybook/react-dom-shim@10.4.6': + resolution: {integrity: sha512-iGNmKzrq9vgl2PDrYAnZKI+yvac3Ym+lJXXuQaqlFRS23zA5MNm4EBX+rAG7WulqchoK6NaZ0KQOs2mAgEpTMg==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.4.6 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@storybook/react-vite@10.4.6': + resolution: {integrity: sha512-0arEQtybqGYXHbXpTot+Wv9YtG+V5Vp43QayXavPKQ20M8mpEzhyCPKd0EhqMGSC1Z1UEt0hm365WUBhI9LfKA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.4.6 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@storybook/react@10.4.6': + resolution: {integrity: sha512-9Y7YecrVFe1/01KYjfOLxVqTg2Aq+IO6TEv6sC2U0PfD0AWCSCmQ91QqgBpN/XW4aFFWoiZNinyXMUlU8zxy2w==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.4.6 + typescript: '>= 4.9.x' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + typescript: + optional: true + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@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' + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} @@ -549,6 +1126,17 @@ packages: '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + + '@types/resolve@1.20.6': + resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} + '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} @@ -730,6 +1318,9 @@ packages: '@vitest/browser': optional: true + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.1.9': resolution: {integrity: sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==} @@ -744,6 +1335,9 @@ packages: vite: optional: true + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.1.9': resolution: {integrity: sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==} @@ -753,12 +1347,21 @@ packages: '@vitest/snapshot@4.1.9': resolution: {integrity: sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.1.9': resolution: {integrity: sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.1.9': resolution: {integrity: sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==} + '@webcontainer/env@1.1.1': + resolution: {integrity: sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==} + JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -792,6 +1395,10 @@ packages: resolution: {integrity: sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==} engines: {node: '>=4'} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-styles@2.2.1: resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} engines: {node: '>=0.10.0'} @@ -800,6 +1407,10 @@ packages: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansis@4.3.1: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} @@ -819,6 +1430,13 @@ packages: argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + 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'} @@ -854,6 +1472,10 @@ packages: resolution: {integrity: sha512-8OG92q3R35qjC/4i6BLBMg8IB+fClWu/1PEwg2Z9Rn+BuNaiEgJzpzn+pxWOdHJWDCAwu2JP0wCDTozAM4QirQ==} engines: {node: ^22.18.0 || >=24.11.0} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + ast-v8-to-istanbul@1.0.4: resolution: {integrity: sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==} @@ -881,6 +1503,11 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + baseline-browser-mapping@2.10.41: + resolution: {integrity: sha512-WwS7MHhqGHHlaVsqRZnhvCEMS0owDX+SxRlve7JkuH7My1Ara3ZriTmCQupPfYjxMZ8I/tgxtJYr2t7taHaH4A==} + engines: {node: '>=6.0.0'} + hasBin: true + birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} @@ -895,6 +1522,15 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.28.4: + resolution: {integrity: sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + cac@7.0.0: resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} engines: {node: '>=20.19.0'} @@ -943,6 +1579,13 @@ packages: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} + caniuse-lite@1.0.30001800: + resolution: {integrity: sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -955,6 +1598,10 @@ packages: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} @@ -1028,6 +1675,12 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + currently-unhandled@0.4.1: resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} engines: {node: '>=0.10.0'} @@ -1079,13 +1732,29 @@ packages: dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -1101,6 +1770,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.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1109,6 +1782,16 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dot-prop@3.0.0: resolution: {integrity: sha512-k4ELWeEU3uCcwub7+dWydqQBRjAjkV9L33HjVRG5Xo2QybI6ja/v+4W73SRi8ubCqJz0l9XsTP1NbewfyqaSlw==} engines: {node: '>=0.10.0'} @@ -1126,6 +1809,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + electron-to-chromium@1.5.387: + resolution: {integrity: sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ==} + elegant-spinner@1.0.1: resolution: {integrity: sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==} engines: {node: '>=0.10.0'} @@ -1175,6 +1861,15 @@ packages: resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} engines: {node: '>= 0.4'} + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -1284,6 +1979,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -1396,6 +2094,10 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1444,6 +2146,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -1523,11 +2229,22 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + husky@3.1.0: resolution: {integrity: sha512-FJkPoHHB+6s4a+jwPqBudBDvYZsoQW5/HBuMSehC8qDiCe50kpcxeqFoDSlow+9I6wg47YxBoT3WxaURlrDIIQ==} engines: {node: '>=8.6.0'} hasBin: true + i18next@26.3.4: + resolution: {integrity: sha512-pa7m0d7pBDqGHZxljT+WPFeyFgQ7P7SciPPo1tTqYuO0z4sqADYhwnBESmmGp/wEof1inwdls/k8ZgTg8rxFHA==} + peerDependencies: + typescript: ^5 || ^6 + peerDependenciesMeta: + typescript: + optional: true + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1616,6 +2333,11 @@ packages: resolution: {integrity: sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==} engines: {node: '>=0.10.0'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-document.all@1.0.0: resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} engines: {node: '>= 0.4'} @@ -1644,6 +2366,11 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -1735,6 +2462,10 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -1783,6 +2514,11 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsonparse@1.3.1: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} @@ -1934,10 +2670,24 @@ packages: resolution: {integrity: sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==} engines: {node: '>=0.10.0'} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -2021,6 +2771,10 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -2048,6 +2802,10 @@ packages: resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==} engines: {node: '>= 0.4'} + node-releases@2.0.50: + resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} + engines: {node: '>=18'} + normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -2106,6 +2864,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + opencollective-postinstall@2.0.3: resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==} hasBin: true @@ -2118,6 +2880,13 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + oxc-parser@0.127.0: + resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.23.0: + resolution: {integrity: sha512-f0+l598CJMOLnYPXsXxttJALH0ljtivdRMKtvHhxRuWa5FYmw5+qODARl8oYjMC/brpzKcrpdORsOBrTqhBZ9A==} + p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} @@ -2201,6 +2970,10 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} @@ -2212,6 +2985,10 @@ packages: 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'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2255,6 +3032,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} + pump@3.0.4: resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} @@ -2284,6 +3065,43 @@ packages: resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} engines: {node: '>=8'} + react-docgen-typescript@2.4.0: + resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} + peerDependencies: + typescript: '>= 4.3.x' + + react-docgen@8.0.3: + resolution: {integrity: sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==} + engines: {node: ^20.9.0 || >=22} + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react-i18next@17.0.8: + resolution: {integrity: sha512-0ooKbGLU8JXhe1zwpQUWIeXSgLPOfwJmgheWRIUpcoA0CpyabpGhayjdG+/eA5esC1AQ8h2jWpXjJfzQzeDOCw==} + peerDependencies: + i18next: '>= 26.2.0' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + typescript: ^5 || ^6 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + typescript: + optional: true + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + read-pkg-up@3.0.0: resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} engines: {node: '>=4'} @@ -2304,6 +3122,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + recast@0.23.12: + resolution: {integrity: sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA==} + engines: {node: '>= 4'} + redent@2.0.0: resolution: {integrity: sha512-XNwrTx77JQCEMXTeb8movBKuK75MgH0RZkujNuDKCezemx/voapl9i2gCSi8WWm8+ox5ycJi1gxF22fR7c0Ciw==} engines: {node: '>=4'} @@ -2402,6 +3224,10 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-node@1.0.0: resolution: {integrity: sha512-kc120TBlQ3mih1LSzdAJXo4xn/GWS2ec0l3S+syHDXP9uRr0JAT8Qd3mdMuyjqCzeZktgP3try92cEgf9Nks8A==} engines: {node: '>=4'} @@ -2429,6 +3255,9 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} @@ -2511,6 +3340,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -2543,6 +3376,21 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + storybook@10.4.6: + resolution: {integrity: sha512-6wkA6LxfDSSilloITsrFOJfsnw0mDUP2h8Ls+lRt8oRsudtz2RWFhLv+Toiwg6NW7hUpdTDc2hzR7DztJid6+A==} + hasBin: true + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + prettier: ^2 || ^3 + vite-plus: ^0.1.15 + peerDependenciesMeta: + '@types/react': + optional: true + prettier: + optional: true + vite-plus: + optional: true + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -2602,6 +3450,10 @@ packages: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} engines: {node: '>=0.8.0'} @@ -2636,6 +3488,9 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -2647,10 +3502,18 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2673,6 +3536,14 @@ packages: peerDependencies: typescript: '>=4.8.4' + ts-dedent@2.3.0: + resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} + engines: {node: '>=6.10'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + tsdown@0.22.3: resolution: {integrity: sha512-louqbfA8Qf//B9jTTL0FPtXTNpjCWv1VPkbcmQMph2pTpzs+LnB1tbe4tDDRVpo2BjF5SgUXaTZe45SxB8pWHg==} engines: {node: ^22.18.0 || >=24.11.0} @@ -2767,6 +3638,10 @@ packages: undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + unrs-resolver@1.12.2: resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} @@ -2780,9 +3655,20 @@ packages: synckit: optional: true + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -2873,6 +3759,13 @@ packages: jsdom: optional: true + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -2914,6 +3807,25 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -2930,12 +3842,44 @@ packages: snapshots: + '@adobe/css-tools@4.5.0': {} + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/generator@8.0.0': dependencies: '@babel/parser': 8.0.0 @@ -2945,6 +3889,32 @@ snapshots: '@types/jsesc': 2.5.1 jsesc: 3.1.0 + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/helper-string-parser@7.29.7': {} '@babel/helper-string-parser@8.0.0': {} @@ -2953,6 +3923,13 @@ snapshots: '@babel/helper-validator-identifier@8.0.2': {} + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + '@babel/parser@7.29.7': dependencies: '@babel/types': 7.29.7 @@ -2961,6 +3938,26 @@ snapshots: dependencies: '@babel/types': 8.0.0 + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.29.7': dependencies: '@babel/helper-string-parser': 7.29.7 @@ -3029,7 +4026,7 @@ snapshots: dependencies: conventional-changelog-angular: 1.6.6 conventional-commits-parser: 3.2.4 - lodash: 4.17.21 + lodash: 4.18.1 '@commitlint/read@8.3.6': dependencies: @@ -3070,6 +4067,12 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 @@ -3080,6 +4083,11 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 @@ -3090,6 +4098,84 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0)': dependencies: eslint: 10.6.0 @@ -3134,62 +4220,206 @@ snapshots: '@humanfs/types': 0.15.0 '@humanwhocodes/retry': 0.4.3 - '@humanfs/types@0.15.0': {} + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@5.9.3)(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1))': + dependencies: + glob: 13.0.6 + react-docgen-typescript: 2.4.0(typescript@5.9.3) + vite: 8.1.1(@types/node@24.13.2)(esbuild@0.28.1) + optionalDependencies: + typescript: 5.9.3 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@marionebl/sander@0.6.1': + dependencies: + graceful-fs: 4.2.11 + mkdirp: 0.5.6 + rimraf: 2.7.1 + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': + dependencies: + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@oxc-parser/binding-android-arm-eabi@0.127.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.127.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + optional: true + + '@oxc-project/types@0.127.0': {} + + '@oxc-project/types@0.137.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.23.0': + optional: true + + '@oxc-resolver/binding-android-arm64@11.23.0': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.23.0': + optional: true - '@humanwhocodes/module-importer@1.0.1': {} + '@oxc-resolver/binding-darwin-x64@11.23.0': + optional: true - '@humanwhocodes/retry@0.4.3': {} + '@oxc-resolver/binding-freebsd-x64@11.23.0': + optional: true - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + '@oxc-resolver/binding-linux-arm-gnueabihf@11.23.0': + optional: true - '@jridgewell/resolve-uri@3.1.2': {} + '@oxc-resolver/binding-linux-arm-musleabihf@11.23.0': + optional: true - '@jridgewell/sourcemap-codec@1.5.5': {} + '@oxc-resolver/binding-linux-arm64-gnu@11.23.0': + optional: true - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@oxc-resolver/binding-linux-arm64-musl@11.23.0': + optional: true - '@marionebl/sander@0.6.1': - dependencies: - graceful-fs: 4.2.11 - mkdirp: 0.5.6 - rimraf: 2.7.1 + '@oxc-resolver/binding-linux-ppc64-gnu@11.23.0': + optional: true - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.3 + '@oxc-resolver/binding-linux-riscv64-gnu@11.23.0': optional: true - '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': - dependencies: - '@emnapi/core': 1.11.1 - '@emnapi/runtime': 1.11.1 - '@tybys/wasm-util': 0.10.3 + '@oxc-resolver/binding-linux-riscv64-musl@11.23.0': optional: true - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + '@oxc-resolver/binding-linux-s390x-gnu@11.23.0': + optional: true - '@nodelib/fs.stat@2.0.5': {} + '@oxc-resolver/binding-linux-x64-gnu@11.23.0': + optional: true - '@nodelib/fs.walk@1.2.8': + '@oxc-resolver/binding-linux-x64-musl@11.23.0': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.23.0': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.23.0': dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true - '@oxc-project/types@0.127.0': + '@oxc-resolver/binding-win32-arm64-msvc@11.23.0': optional: true - '@oxc-project/types@0.137.0': {} + '@oxc-resolver/binding-win32-x64-msvc@11.23.0': + optional: true '@pkgr/core@0.3.6': {} @@ -3300,6 +4530,12 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} + '@rollup/pluginutils@5.4.0': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.4 + '@samverschueren/stream-to-observable@0.3.1(rxjs@6.6.7)': dependencies: any-observable: 0.3.0(rxjs@6.6.7) @@ -3310,11 +4546,132 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@storybook/builder-vite@10.4.6(esbuild@0.28.1)(storybook@10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7))(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1))': + dependencies: + '@storybook/csf-plugin': 10.4.6(esbuild@0.28.1)(storybook@10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7))(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1)) + storybook: 10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7) + ts-dedent: 2.3.0 + vite: 8.1.1(@types/node@24.13.2)(esbuild@0.28.1) + transitivePeerDependencies: + - esbuild + - rollup + - webpack + + '@storybook/csf-plugin@10.4.6(esbuild@0.28.1)(storybook@10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7))(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1))': + dependencies: + storybook: 10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7) + unplugin: 2.3.11 + optionalDependencies: + esbuild: 0.28.1 + vite: 8.1.1(@types/node@24.13.2)(esbuild@0.28.1) + + '@storybook/global@5.0.0': {} + + '@storybook/icons@2.1.0(react@19.2.7)': + dependencies: + react: 19.2.7 + + '@storybook/react-dom-shim@10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7))': + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + storybook: 10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@storybook/react-vite@10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7))(typescript@5.9.3)(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1))': + dependencies: + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@5.9.3)(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1)) + '@rollup/pluginutils': 5.4.0 + '@storybook/builder-vite': 10.4.6(esbuild@0.28.1)(storybook@10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7))(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1)) + '@storybook/react': 10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7))(typescript@5.9.3) + empathic: 2.0.1 + magic-string: 0.30.21 + react: 19.2.7 + react-docgen: 8.0.3 + react-dom: 19.2.7(react@19.2.7) + resolve: 1.22.12 + storybook: 10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7) + tsconfig-paths: 4.2.0 + vite: 8.1.1(@types/node@24.13.2)(esbuild@0.28.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - esbuild + - rollup + - supports-color + - typescript + - webpack + + '@storybook/react@10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7))(typescript@5.9.3)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/react-dom-shim': 10.4.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7)) + react: 19.2.7 + react-docgen: 8.0.3 + react-docgen-typescript: 2.4.0(typescript@5.9.3) + react-dom: 19.2.7(react@19.2.7) + storybook: 10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@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.9.1': + dependencies: + '@adobe/css-tools': 4.5.0 + 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/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 optional: true + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.7 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -3322,6 +4679,8 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/doctrine@0.0.9': {} + '@types/esrecurse@4.3.1': {} '@types/estree@1.0.9': {} @@ -3354,6 +4713,16 @@ snapshots: '@types/normalize-package-data@2.4.4': {} + '@types/react-dom@19.2.3(@types/react@19.2.17)': + dependencies: + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@types/resolve@1.20.6': {} + '@types/semver@7.7.1': {} '@typescript-eslint/eslint-plugin@8.62.1(@typescript-eslint/parser@8.62.1(eslint@10.6.0)(typescript@5.9.3))(eslint@10.6.0)(typescript@5.9.3)': @@ -3529,7 +4898,15 @@ snapshots: obug: 2.1.3 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(vite@8.1.1(@types/node@24.13.2)) + vitest: 4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1)) + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 '@vitest/expect@4.1.9': dependencies: @@ -3540,13 +4917,17 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.9(vite@8.1.1(@types/node@24.13.2))': + '@vitest/mocker@4.1.9(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1))': dependencies: '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.1.1(@types/node@24.13.2) + vite: 8.1.1(@types/node@24.13.2)(esbuild@0.28.1) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 '@vitest/pretty-format@4.1.9': dependencies: @@ -3564,14 +4945,26 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + '@vitest/spy@4.1.9': {} + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@vitest/utils@4.1.9': dependencies: '@vitest/pretty-format': 4.1.9 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@webcontainer/env@1.1.1': {} + JSONStream@1.3.5: dependencies: jsonparse: 1.3.1 @@ -3601,12 +4994,16 @@ snapshots: ansi-regex@3.0.1: {} + ansi-regex@5.0.1: {} + ansi-styles@2.2.1: {} ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 + ansi-styles@5.2.0: {} + ansis@4.3.1: {} any-observable@0.3.0(rxjs@6.6.7): @@ -3617,6 +5014,12 @@ snapshots: dependencies: sprintf-js: 1.0.3 + 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 @@ -3658,6 +5061,10 @@ snapshots: estree-walker: 3.0.3 pathe: 2.0.3 + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + ast-v8-to-istanbul@1.0.4: dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -3689,6 +5096,8 @@ snapshots: balanced-match@4.0.4: {} + baseline-browser-mapping@2.10.41: {} + birpc@4.0.0: {} brace-expansion@1.1.15: @@ -3704,6 +5113,18 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.28.4: + dependencies: + baseline-browser-mapping: 2.10.41 + caniuse-lite: 1.0.30001800 + electron-to-chromium: 1.5.387 + node-releases: 2.0.50 + update-browserslist-db: 1.2.3(browserslist@4.28.4) + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + cac@7.0.0: {} call-bind-apply-helpers@1.0.2: @@ -3753,6 +5174,16 @@ snapshots: camelcase@5.3.1: {} + caniuse-lite@1.0.30001800: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chai@6.2.2: {} chalk@1.1.3: @@ -3769,6 +5200,8 @@ snapshots: escape-string-regexp: 1.0.5 supports-color: 5.5.0 + check-error@2.1.3: {} + ci-info@2.0.0: {} clean-stack@2.2.0: {} @@ -3820,7 +5253,7 @@ snapshots: dependencies: JSONStream: 1.3.5 is-text-path: 1.0.1 - lodash: 4.17.21 + lodash: 4.18.1 meow: 8.1.2 split2: 3.2.2 through2: 4.0.2 @@ -3850,6 +5283,10 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css.escape@1.5.1: {} + + csstype@3.2.3: {} + currently-unhandled@0.4.1: dependencies: array-find-index: 1.0.2 @@ -3897,8 +5334,17 @@ snapshots: dedent@0.7.0: {} + deep-eql@5.0.2: {} + deep-is@0.1.4: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -3906,6 +5352,8 @@ snapshots: gopd: 1.2.0 optional: true + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -3928,17 +5376,29 @@ snapshots: delayed-stream@1.0.0: {} + dequal@2.0.3: {} + detect-libc@2.1.2: {} dir-glob@3.0.1: dependencies: path-type: 4.0.0 + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + dot-prop@3.0.0: dependencies: is-obj: 1.0.1 - dts-resolver@3.0.0: {} + dts-resolver@3.0.0(oxc-resolver@11.23.0): + optionalDependencies: + oxc-resolver: 11.23.0 dunder-proto@1.0.1: dependencies: @@ -3946,6 +5406,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + electron-to-chromium@1.5.387: {} + elegant-spinner@1.0.1: {} empathic@2.0.1: {} @@ -4056,6 +5518,37 @@ snapshots: is-symbol: 1.1.1 optional: true + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + escalade@3.2.0: {} + escape-string-regexp@1.0.5: {} escape-string-regexp@4.0.0: {} @@ -4186,6 +5679,8 @@ snapshots: estraverse@5.3.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -4317,6 +5812,8 @@ snapshots: generator-function@2.0.1: optional: true + gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4365,7 +5862,7 @@ snapshots: git-raw-commits@2.0.11: dependencies: dargs: 7.0.0 - lodash: 4.17.21 + lodash: 4.18.1 meow: 8.1.2 split2: 3.2.2 through2: 4.0.2 @@ -4378,6 +5875,12 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -4457,6 +5960,10 @@ snapshots: html-escaper@2.0.2: {} + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + husky@3.1.0: dependencies: chalk: 2.4.2 @@ -4471,6 +5978,10 @@ snapshots: run-node: 1.0.0 slash: 3.0.0 + i18next@26.3.4(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4564,6 +6075,8 @@ snapshots: is-directory@0.3.1: {} + is-docker@3.0.0: {} + is-document.all@1.0.0: dependencies: call-bound: 1.0.4 @@ -4595,6 +6108,10 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-map@2.0.3: optional: true @@ -4681,6 +6198,10 @@ snapshots: get-intrinsic: 1.3.0 optional: true + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isarray@2.0.5: optional: true @@ -4720,6 +6241,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json5@2.2.3: {} + jsonparse@1.3.1: {} kcsapi@1.260604.0: {} @@ -4886,10 +6409,20 @@ snapshots: currently-unhandled: 0.4.1 signal-exit: 3.0.7 + loupe@3.2.1: {} + + lru-cache@11.5.1: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lru-cache@6.0.0: dependencies: yallist: 4.0.0 + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -4980,6 +6513,8 @@ snapshots: minimist@1.2.8: {} + minipass@7.1.3: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -5002,6 +6537,8 @@ snapshots: semver: 6.3.1 optional: true + node-releases@2.0.50: {} + normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 @@ -5068,6 +6605,13 @@ snapshots: dependencies: mimic-fn: 2.1.0 + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + opencollective-postinstall@2.0.3: {} optionator@0.9.4: @@ -5086,6 +6630,53 @@ snapshots: safe-push-apply: 1.0.0 optional: true + oxc-parser@0.127.0: + dependencies: + '@oxc-project/types': 0.127.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.127.0 + '@oxc-parser/binding-android-arm64': 0.127.0 + '@oxc-parser/binding-darwin-arm64': 0.127.0 + '@oxc-parser/binding-darwin-x64': 0.127.0 + '@oxc-parser/binding-freebsd-x64': 0.127.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.127.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.127.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.127.0 + '@oxc-parser/binding-linux-arm64-musl': 0.127.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.127.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-musl': 0.127.0 + '@oxc-parser/binding-openharmony-arm64': 0.127.0 + '@oxc-parser/binding-wasm32-wasi': 0.127.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.127.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 + '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + + oxc-resolver@11.23.0: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.23.0 + '@oxc-resolver/binding-android-arm64': 11.23.0 + '@oxc-resolver/binding-darwin-arm64': 11.23.0 + '@oxc-resolver/binding-darwin-x64': 11.23.0 + '@oxc-resolver/binding-freebsd-x64': 11.23.0 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.23.0 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.23.0 + '@oxc-resolver/binding-linux-arm64-gnu': 11.23.0 + '@oxc-resolver/binding-linux-arm64-musl': 11.23.0 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.23.0 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.23.0 + '@oxc-resolver/binding-linux-riscv64-musl': 11.23.0 + '@oxc-resolver/binding-linux-s390x-gnu': 11.23.0 + '@oxc-resolver/binding-linux-x64-gnu': 11.23.0 + '@oxc-resolver/binding-linux-x64-musl': 11.23.0 + '@oxc-resolver/binding-openharmony-arm64': 11.23.0 + '@oxc-resolver/binding-wasm32-wasi': 11.23.0 + '@oxc-resolver/binding-win32-arm64-msvc': 11.23.0 + '@oxc-resolver/binding-win32-x64-msvc': 11.23.0 + p-finally@1.0.0: {} p-finally@2.0.1: {} @@ -5152,6 +6743,11 @@ snapshots: path-parse@1.0.7: {} + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.1 + minipass: 7.1.3 + path-type@3.0.0: dependencies: pify: 3.0.0 @@ -5160,6 +6756,8 @@ snapshots: pathe@2.0.3: {} + pathval@2.0.1: {} + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -5193,6 +6791,12 @@ snapshots: prettier@3.9.4: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + pump@3.0.4: dependencies: end-of-stream: 1.4.5 @@ -5210,6 +6814,45 @@ snapshots: quick-lru@4.0.1: {} + react-docgen-typescript@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + react-docgen@8.0.3: + dependencies: + '@babel/core': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + '@types/doctrine': 0.0.9 + '@types/resolve': 1.20.6 + doctrine: 3.0.0 + resolve: 1.22.12 + strip-indent: 4.1.1 + transitivePeerDependencies: + - supports-color + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react-i18next@17.0.8(i18next@26.3.4(typescript@5.9.3))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@5.9.3): + dependencies: + '@babel/runtime': 7.29.7 + html-parse-stringify: 3.0.1 + i18next: 26.3.4(typescript@5.9.3) + react: 19.2.7 + use-sync-external-store: 1.6.0(react@19.2.7) + optionalDependencies: + react-dom: 19.2.7(react@19.2.7) + typescript: 5.9.3 + + react-is@17.0.2: {} + + react@19.2.7: {} + read-pkg-up@3.0.0: dependencies: find-up: 2.1.0 @@ -5240,6 +6883,14 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + recast@0.23.12: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + redent@2.0.0: dependencies: indent-string: 3.2.0 @@ -5320,14 +6971,14 @@ snapshots: dependencies: glob: 7.2.3 - rolldown-plugin-dts@0.26.0(rolldown@1.1.3)(typescript@5.9.3): + rolldown-plugin-dts@0.26.0(oxc-resolver@11.23.0)(rolldown@1.1.3)(typescript@5.9.3): dependencies: '@babel/generator': 8.0.0 '@babel/helper-validator-identifier': 8.0.2 '@babel/parser': 8.0.0 ast-kit: 3.0.0 birpc: 4.0.0 - dts-resolver: 3.0.0 + dts-resolver: 3.0.0(oxc-resolver@11.23.0) get-tsconfig: 5.0.0-beta.5 obug: 2.1.3 rolldown: 1.1.3 @@ -5379,6 +7030,8 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.1.3 '@rolldown/binding-win32-x64-msvc': 1.1.3 + run-applescript@7.1.0: {} + run-node@1.0.0: {} run-parallel@1.2.0: @@ -5413,14 +7066,15 @@ snapshots: is-regex: 1.2.1 optional: true + scheduler@0.27.0: {} + semver-compare@1.0.0: {} semver@5.7.2: {} semver@6.3.0: {} - semver@6.3.1: - optional: true + semver@6.3.1: {} semver@7.8.5: {} @@ -5503,6 +7157,8 @@ snapshots: source-map-js@1.2.1: {} + source-map@0.6.1: {} + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -5535,6 +7191,32 @@ snapshots: internal-slot: 1.1.0 optional: true + storybook@10.4.6(@testing-library/dom@10.4.1)(@types/react@19.2.17)(prettier@3.9.4)(react@19.2.7): + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.1.0(react@19.2.7) + '@testing-library/jest-dom': 6.9.1 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) + '@vitest/expect': 3.2.4 + '@vitest/spy': 3.2.4 + '@webcontainer/env': 1.1.1 + esbuild: 0.28.1 + open: 10.2.0 + oxc-parser: 0.127.0 + oxc-resolver: 11.23.0 + recast: 0.23.12 + semver: 7.8.5 + use-sync-external-store: 1.6.0(react@19.2.7) + ws: 8.21.0 + optionalDependencies: + '@types/react': 19.2.17 + prettier: 3.9.4 + transitivePeerDependencies: + - '@testing-library/dom' + - bufferutil + - react + - utf-8-validate + string-argv@0.3.2: {} string-width@1.0.2: @@ -5605,6 +7287,8 @@ snapshots: dependencies: min-indent: 1.0.1 + strip-indent@4.1.1: {} + supports-color@2.0.0: {} supports-color@5.5.0: @@ -5631,6 +7315,8 @@ snapshots: through@2.3.8: {} + tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} tinyexec@1.2.4: {} @@ -5640,8 +7326,12 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyrainbow@2.0.0: {} + tinyrainbow@3.1.0: {} + tinyspy@4.0.4: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -5656,7 +7346,15 @@ snapshots: dependencies: typescript: 5.9.3 - tsdown@0.22.3(typescript@5.9.3)(unrun@0.2.39(synckit@0.11.13)): + ts-dedent@2.3.0: {} + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tsdown@0.22.3(oxc-resolver@11.23.0)(typescript@5.9.3)(unrun@0.2.39(synckit@0.11.13)): dependencies: ansis: 4.3.1 cac: 7.0.0 @@ -5667,7 +7365,7 @@ snapshots: obug: 2.1.3 picomatch: 4.0.4 rolldown: 1.1.3 - rolldown-plugin-dts: 0.26.0(rolldown@1.1.3)(typescript@5.9.3) + rolldown-plugin-dts: 0.26.0(oxc-resolver@11.23.0)(rolldown@1.1.3)(typescript@5.9.3) semver: 7.8.5 tinyexec: 1.2.4 tinyglobby: 0.2.17 @@ -5684,8 +7382,7 @@ snapshots: tslib@1.14.1: {} - tslib@2.8.1: - optional: true + tslib@2.8.1: {} type-check@0.4.0: dependencies: @@ -5762,6 +7459,13 @@ snapshots: undici-types@7.18.2: {} + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.17.0 + picomatch: 4.0.4 + webpack-virtual-modules: 0.6.2 + unrs-resolver@1.12.2: dependencies: napi-postinstall: 0.3.4 @@ -5796,10 +7500,20 @@ snapshots: synckit: 0.11.13 optional: true + update-browserslist-db@1.2.3(browserslist@4.28.4): + dependencies: + browserslist: 4.28.4 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 + use-sync-external-store@1.6.0(react@19.2.7): + dependencies: + react: 19.2.7 + util-deprecate@1.0.2: {} validate-npm-package-license@3.0.4: @@ -5807,7 +7521,7 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite@8.1.1(@types/node@24.13.2): + vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -5816,12 +7530,13 @@ snapshots: tinyglobby: 0.2.17 optionalDependencies: '@types/node': 24.13.2 + esbuild: 0.28.1 fsevents: 2.3.3 - vitest@4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(vite@8.1.1(@types/node@24.13.2)): + vitest@4.1.9(@types/node@24.13.2)(@vitest/coverage-v8@4.1.9)(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1)): dependencies: '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(vite@8.1.1(@types/node@24.13.2)) + '@vitest/mocker': 4.1.9(vite@8.1.1(@types/node@24.13.2)(esbuild@0.28.1)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 @@ -5838,7 +7553,7 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: 8.1.1(@types/node@24.13.2) + vite: 8.1.1(@types/node@24.13.2)(esbuild@0.28.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.13.2 @@ -5846,6 +7561,10 @@ snapshots: transitivePeerDependencies: - msw + void-elements@3.1.0: {} + + webpack-virtual-modules@0.6.2: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -5913,6 +7632,14 @@ snapshots: wrappy@1.0.2: {} + ws@8.21.0: {} + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + + yallist@3.1.1: {} + yallist@4.0.0: {} yargs-parser@10.1.0: diff --git a/scripts/smoke-load.cjs b/scripts/smoke-load.cjs index a1fb38b..554c1c8 100644 --- a/scripts/smoke-load.cjs +++ b/scripts/smoke-load.cjs @@ -5,16 +5,13 @@ const path = require('path') const projectRoot = path.resolve(__dirname, '..') const entryPath = path.join(projectRoot, 'index.js') -const baseReporterPath = path.join(projectRoot, 'reporters', 'base.js') if (!fs.existsSync(entryPath)) { console.log('[smoke-load] index.js not found; skipping until built output exists.') process.exit(0) } -const packageMeta = require(path.join(projectRoot, 'package.json')) const listeners = new Map() -const fetchCalls = [] const startupFetchCalls = [] global.window = { @@ -75,6 +72,47 @@ const sentryStub = { }, } +class ReactComponent { + constructor(props) { + this.props = props + this.state = {} + } + + setState(state) { + this.state = { + ...this.state, + ...state, + } + } +} + +const reactStub = { + Component: ReactComponent, + createElement(type, props, ...children) { + return { + type, + props, + children, + } + }, +} + +const reactJsxRuntimeStub = { + Fragment: Symbol.for('react.fragment'), + jsx(type, props) { + return { + type, + props, + } + }, + jsxs(type, props) { + return { + type, + props, + } + }, +} + const lodashImplementations = { compact(values) { return values.filter(Boolean) @@ -162,8 +200,9 @@ const lodashStub = new Proxy(lodashImplementations, { }) const fetchStub = async (requestUrl, options = {}) => { - const calls = requestUrl.endsWith('/api/report/v3/known_quests') ? startupFetchCalls : fetchCalls - calls.push({ requestUrl, options }) + if (requestUrl.endsWith('/api/report/v3/known_quests')) { + startupFetchCalls.push({ requestUrl, options }) + } return { ok: true, status: 200, @@ -203,6 +242,18 @@ Module._load = function smokeLoad(request, parent, isMain) { return sentryStub case 'node-fetch': return fetchStub + case 'react': + return reactStub + case 'react/jsx-runtime': + case 'react/jsx-dev-runtime': + return reactJsxRuntimeStub + case 'react-i18next': + return { + useTranslation: () => ({ + t: (key, options) => + options && typeof options.count !== 'undefined' ? `${key}: ${options.count}` : key, + }), + } case 'lodash': return lodashStub case 'moment-timezone': @@ -222,13 +273,12 @@ Module._load = function smokeLoad(request, parent, isMain) { } } -const loadExport = (mod) => mod && (mod.default || mod) - async function main() { try { const plugin = require(entryPath) assert.strictEqual(plugin.show, false) + assert.strictEqual(typeof plugin.settingsClass, 'function') assert.strictEqual(typeof plugin.pluginDidLoad, 'function') assert.strictEqual(typeof plugin.pluginWillUnload, 'function') @@ -245,23 +295,7 @@ async function main() { 'https://example.invalid/api/report/v3/known_quests', ) - const BaseReporter = loadExport(require(baseReporterPath)) - const reporter = new BaseReporter() - assert.strictEqual(reporter.SERVER_HOSTNAME, 'example.invalid') - assert.strictEqual(reporter.USERAGENT, `Reporter/${packageMeta.version} poi/10.7.0`) - - const json = await reporter.getJson('/api/smoke') - assert.deepStrictEqual(json, { ok: true }) - assert.strictEqual(fetchCalls[0].requestUrl, 'https://example.invalid/api/smoke') - assert.strictEqual(fetchCalls[0].options.headers['User-Agent'], reporter.USERAGENT) - assert.strictEqual(fetchCalls[0].options.headers['X-Reporter'], reporter.USERAGENT) - - await reporter.report('/api/report/smoke', { ok: true }) - assert.strictEqual(fetchCalls[1].requestUrl, 'https://example.invalid/api/report/smoke') - assert.strictEqual(fetchCalls[1].options.method, 'POST') - assert.deepStrictEqual(JSON.parse(fetchCalls[1].options.body), { data: { ok: true } }) - - console.log('[smoke-load] built plugin loaded and reporter base exercised.') + console.log('[smoke-load] built plugin loaded.') } finally { Module._load = originalLoad } diff --git a/src/index.ts b/src/index.ts index ebd9223..b5eda1b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,8 @@ import semver from 'semver' import { init } from './sentry' import type { GameResponseEvent } from './types/game-api' import type { Reporter } from './types/reporter' +import { recordRemodelDebugEvent } from './remodel-debug-recorder' +import RemodelDebugSettings from './remodel-debug-settings' import * as remote from '@electron/remote' @@ -44,6 +46,7 @@ const handleResponse = (e: GameResponseEvent) => { return } const { method, path, body, postBody, time = Date.now() } = e.detail + recordRemodelDebugEvent({ method, path, body, postBody, time }) for (const reporter of reporters) { try { reporter.handle(method, path, body, postBody, time) @@ -58,6 +61,7 @@ const handleResponse = (e: GameResponseEvent) => { } export const show = false +export const settingsClass = RemodelDebugSettings export const pluginDidLoad = (_e: unknown) => { reporters = [ new QuestReporter(), diff --git a/src/remodel-debug-recorder.ts b/src/remodel-debug-recorder.ts new file mode 100644 index 0000000..9f5ee23 --- /dev/null +++ b/src/remodel-debug-recorder.ts @@ -0,0 +1,333 @@ +import type { GameResponseEventDetail } from './types/game-api' + +const ENABLED_KEY = 'poi-plugin-report:remodel-debug-recorder' +const MAX_RECORDS = 200 + +const REMODEL_PATHS = new Set([ + '/kcsapi/api_req_kousyou/remodel_slotlist', + '/kcsapi/api_req_kousyou/remodel_slotlist_detail', + '/kcsapi/api_req_kousyou/remodel_slot', +]) + +interface SanitizedFleetContext { + firstFleet?: { + flagship?: { + api_ship_id?: number + } + secondShip?: { + api_ship_id?: number + } + } + selectedSlotItem?: { + api_slotitem_id?: number + api_level?: number + } +} + +export interface RemodelDebugRecord { + time: number + method: string + path: string + postBody: unknown + body: unknown + context: SanitizedFleetContext +} + +const records: RemodelDebugRecord[] = [] +const listeners = new Set<() => void>() +let enabledCache: boolean | undefined + +const notifyListeners = (): void => { + for (const listener of listeners) { + try { + listener() + } catch (err) { + console.error(err) + } + } +} + +const cloneJson = (value: unknown): unknown => { + if (value == null) { + return value + } + return JSON.parse(JSON.stringify(value)) as unknown +} + +export const isRemodelDebugRecorderEnabled = (): boolean => { + if (enabledCache != null) { + return enabledCache + } + + try { + enabledCache = window.localStorage?.getItem(ENABLED_KEY) === '1' + } catch { + enabledCache = false + } + + return enabledCache +} + +export const setRemodelDebugRecorderEnabled = (enabled: boolean): void => { + enabledCache = enabled + try { + if (enabled) { + window.localStorage?.setItem(ENABLED_KEY, '1') + } else { + window.localStorage?.removeItem(ENABLED_KEY) + } + } catch (err) { + console.error(err) + } + notifyListeners() +} + +export const subscribeRemodelDebugRecorder = (listener: () => void): (() => void) => { + listeners.add(listener) + return () => { + listeners.delete(listener) + } +} + +const getPostBodySlotId = (postBody: unknown): string | number | undefined => { + if (postBody == null || typeof postBody !== 'object') { + return undefined + } + const slotId = (postBody as Record).api_slot_id + return typeof slotId === 'string' || typeof slotId === 'number' ? slotId : undefined +} + +const pickNumber = (record: Record, key: string): number | undefined => { + const value = record[key] + return typeof value === 'number' ? value : undefined +} + +const pickStringOrNumber = ( + record: Record, + key: string, +): string | number | undefined => { + const value = record[key] + return typeof value === 'string' || typeof value === 'number' ? value : undefined +} + +const pickBooleanOrNumber = ( + record: Record, + key: string, +): boolean | number | undefined => { + const value = record[key] + return typeof value === 'boolean' || typeof value === 'number' ? value : undefined +} + +const sanitizePostBody = (path: string, postBody: unknown): unknown => { + if (postBody == null || typeof postBody !== 'object') { + return {} + } + const record = postBody as Record + + switch (path) { + case '/kcsapi/api_req_kousyou/remodel_slotlist_detail': + return { + api_id: pickStringOrNumber(record, 'api_id'), + } + case '/kcsapi/api_req_kousyou/remodel_slot': + return { + api_id: pickStringOrNumber(record, 'api_id'), + api_certain_flag: pickStringOrNumber(record, 'api_certain_flag'), + } + default: + return {} + } +} + +const sanitizeListBody = (body: unknown): unknown => { + if (!Array.isArray(body)) { + return [] + } + + return body.map((entry: unknown) => { + const record = + entry != null && typeof entry === 'object' ? (entry as Record) : {} + return { + api_id: pickNumber(record, 'api_id'), + api_slot_id: pickNumber(record, 'api_slot_id'), + api_req_fuel: pickNumber(record, 'api_req_fuel'), + api_req_bull: pickNumber(record, 'api_req_bull'), + api_req_steel: pickNumber(record, 'api_req_steel'), + api_req_bauxite: pickNumber(record, 'api_req_bauxite'), + } + }) +} + +const sanitizeDetailBody = (body: unknown): unknown => { + const record = body != null && typeof body === 'object' ? (body as Record) : {} + + return { + api_req_buildkit: pickNumber(record, 'api_req_buildkit'), + api_req_remodelkit: pickNumber(record, 'api_req_remodelkit'), + api_certain_buildkit: pickNumber(record, 'api_certain_buildkit'), + api_certain_remodelkit: pickNumber(record, 'api_certain_remodelkit'), + api_req_slot_id: pickNumber(record, 'api_req_slot_id'), + api_req_slot_num: pickNumber(record, 'api_req_slot_num'), + api_req_slot_id2: pickNumber(record, 'api_req_slot_id2'), + api_req_slot_num2: pickNumber(record, 'api_req_slot_num2'), + api_req_useitem_id: pickNumber(record, 'api_req_useitem_id'), + api_req_useitem_num: pickNumber(record, 'api_req_useitem_num'), + api_req_useitem_id2: pickNumber(record, 'api_req_useitem_id2'), + api_req_useitem_num2: pickNumber(record, 'api_req_useitem_num2'), + api_change_flag: pickNumber(record, 'api_change_flag'), + } +} + +const sanitizeSlotBody = (body: unknown): unknown => { + const record = body != null && typeof body === 'object' ? (body as Record) : {} + const afterSlot = + record.api_after_slot != null && typeof record.api_after_slot === 'object' + ? (record.api_after_slot as Record) + : {} + + return { + api_remodel_flag: pickBooleanOrNumber(record, 'api_remodel_flag'), + api_remodel_id: Array.isArray(record.api_remodel_id) + ? record.api_remodel_id.filter((value) => typeof value === 'number') + : undefined, + api_after_slot: { + api_slotitem_id: pickNumber(afterSlot, 'api_slotitem_id'), + api_level: pickNumber(afterSlot, 'api_level'), + api_alv: pickNumber(afterSlot, 'api_alv'), + }, + api_voice_ship_id: pickNumber(record, 'api_voice_ship_id'), + } +} + +const sanitizeBody = (path: string, body: unknown): unknown => { + switch (path) { + case '/kcsapi/api_req_kousyou/remodel_slotlist': + return sanitizeListBody(body) + case '/kcsapi/api_req_kousyou/remodel_slotlist_detail': + return sanitizeDetailBody(body) + case '/kcsapi/api_req_kousyou/remodel_slot': + return sanitizeSlotBody(body) + /* c8 ignore next 2 -- createRemodelDebugRecord filters paths before sanitizing. */ + default: + return {} + } +} + +const createSanitizedContext = (slotId: string | number | undefined): SanitizedFleetContext => { + const deckShipIds = window._decks?.[0]?.api_ship?.slice(0, 2) || [] + const flagship = deckShipIds[0] == null ? undefined : window._ships?.[deckShipIds[0]] + const secondShip = deckShipIds[1] == null ? undefined : window._ships?.[deckShipIds[1]] + if (slotId != null) { + const slotitem = window._slotitems?.[slotId] + if (slotitem) { + return { + firstFleet: { + flagship: flagship ? { api_ship_id: flagship.api_ship_id } : undefined, + secondShip: secondShip ? { api_ship_id: secondShip.api_ship_id } : undefined, + }, + selectedSlotItem: { + api_slotitem_id: slotitem.api_slotitem_id, + api_level: slotitem.api_level, + }, + } + } + } + + return { + firstFleet: { + flagship: flagship ? { api_ship_id: flagship.api_ship_id } : undefined, + secondShip: secondShip ? { api_ship_id: secondShip.api_ship_id } : undefined, + }, + } +} + +const stripUndefined = (value: unknown): unknown => { + if (Array.isArray(value)) { + return value.map(stripUndefined) + } + if (value != null && typeof value === 'object') { + return Object.fromEntries( + Object.entries(value) + .filter(([, entry]) => entry !== undefined) + .map(([key, entry]) => [key, stripUndefined(entry)]), + ) + } + return value +} + +const createSanitizedRecordBody = (path: string, body: unknown): unknown => + stripUndefined(sanitizeBody(path, body)) + +const createSanitizedRecordPostBody = (path: string, postBody: unknown): unknown => + stripUndefined(sanitizePostBody(path, postBody)) + +const createSanitizedRecordContext = (slotId: string | number | undefined): SanitizedFleetContext => + stripUndefined(createSanitizedContext(slotId)) as SanitizedFleetContext + +export const createRemodelDebugRecord = ( + event: GameResponseEventDetail, +): RemodelDebugRecord | undefined => { + if (!REMODEL_PATHS.has(event.path)) { + return undefined + } + + return { + time: event.time, + method: event.method, + path: event.path, + postBody: cloneJson(createSanitizedRecordPostBody(event.path, event.postBody)), + body: cloneJson(createSanitizedRecordBody(event.path, event.body)), + context: createSanitizedRecordContext(getPostBodySlotId(event.postBody)), + } +} + +export const recordRemodelDebugEvent = (event: GameResponseEventDetail): void => { + if (!REMODEL_PATHS.has(event.path)) { + return + } + if (!isRemodelDebugRecorderEnabled()) { + return + } + + try { + const record = createRemodelDebugRecord(event) + /* c8 ignore next 3 -- createRemodelDebugRecord can only return undefined for paths filtered above. */ + if (!record) { + return + } + + records.push(record) + if (records.length > MAX_RECORDS) { + records.splice(0, records.length - MAX_RECORDS) + } + notifyListeners() + } catch (err) { + console.error(err) + } +} + +export const clearRemodelDebugRecords = (): void => { + records.length = 0 + notifyListeners() +} + +const cloneRecord = (record: RemodelDebugRecord): RemodelDebugRecord => + cloneJson(record) as RemodelDebugRecord + +export const getRemodelDebugRecords = (): readonly RemodelDebugRecord[] => records.map(cloneRecord) + +export const getRemodelDebugRecordCount = (): number => records.length + +export const exportRemodelDebugRecords = (): void => { + const blob = new Blob([JSON.stringify({ records: getRemodelDebugRecords() }, null, 2)], { + type: 'application/json', + }) + const href = URL.createObjectURL(blob) + const anchor = document.createElement('a') + anchor.href = href + anchor.download = `plugin-report-remodel-debug-${new Date() + .toISOString() + .replace(/[:.]/g, '-')}.json` + anchor.click() + setTimeout(() => URL.revokeObjectURL(href), 1000) +} diff --git a/src/remodel-debug-settings.stories.tsx b/src/remodel-debug-settings.stories.tsx new file mode 100644 index 0000000..52a1b15 --- /dev/null +++ b/src/remodel-debug-settings.stories.tsx @@ -0,0 +1,88 @@ +import type { ReactElement } from 'react' + +import RemodelDebugSettings from './remodel-debug-settings' +import { + clearRemodelDebugRecords, + recordRemodelDebugEvent, + setRemodelDebugRecorderEnabled, +} from './remodel-debug-recorder' + +type StoryRender = () => ReactElement + +const resetRecorder = (enabled: boolean): void => { + setRemodelDebugRecorderEnabled(enabled) + clearRemodelDebugRecords() +} + +const setupPoiGlobals = (): void => { + window._decks = [{ api_ship: [1, 2] }] + window._ships = { + 1: { api_ship_id: 187 }, + 2: { api_ship_id: 141 }, + } + window._slotitems = { + 501: { + api_level: 6, + api_slotitem_id: 39, + }, + } +} + +const withRecorderState = + (setup: () => void) => + (Story: StoryRender): ReactElement => { + setup() + return + } + +const meta = { + title: 'Plugin/RemodelDebugSettings', + component: RemodelDebugSettings, + decorators: [ + (Story: StoryRender) => ( +
+ +
+ ), + ], +} + +export default meta + +interface Story { + decorators: Array<(Story: StoryRender) => ReactElement> +} + +export const Empty: Story = { + decorators: [withRecorderState(() => resetRecorder(false))], +} + +export const EnabledWithCapture: Story = { + decorators: [ + withRecorderState(() => { + setupPoiGlobals() + resetRecorder(true) + recordRemodelDebugEvent({ + time: Date.UTC(2026, 6, 3, 15), + method: 'POST', + path: '/kcsapi/api_req_kousyou/remodel_slotlist_detail', + postBody: { + api_id: '33', + api_slot_id: '501', + api_token: 'redacted by sanitizer', + }, + body: { + api_req_buildkit: 3, + api_req_remodelkit: 4, + api_certain_buildkit: 5, + api_certain_remodelkit: 6, + }, + }) + }), + ], +} diff --git a/src/remodel-debug-settings.tsx b/src/remodel-debug-settings.tsx new file mode 100644 index 0000000..3edc266 --- /dev/null +++ b/src/remodel-debug-settings.tsx @@ -0,0 +1,69 @@ +import { type CSSProperties, type ReactElement, useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + clearRemodelDebugRecords, + getRemodelDebugRecordCount, + exportRemodelDebugRecords, + isRemodelDebugRecorderEnabled, + setRemodelDebugRecorderEnabled, + subscribeRemodelDebugRecorder, +} from './remodel-debug-recorder' + +const rootStyle: CSSProperties = { + display: 'grid', + gap: 8, + lineHeight: 1.5, + maxWidth: 640, +} + +const buttonRowStyle: CSSProperties = { + display: 'flex', + gap: 8, +} + +const getRecorderState = () => ({ + enabled: isRemodelDebugRecorderEnabled(), + count: getRemodelDebugRecordCount(), +}) + +export default function RemodelDebugSettings(): ReactElement { + const { t } = useTranslation('poi-plugin-report') + const [recorderState, setRecorderState] = useState(getRecorderState) + + useEffect(() => { + const unsubscribe = subscribeRemodelDebugRecorder(() => setRecorderState(getRecorderState())) + return unsubscribe + }, []) + + const toggleRecorder = () => { + setRemodelDebugRecorderEnabled(!isRemodelDebugRecorderEnabled()) + } + + const clearRecords = () => { + clearRemodelDebugRecords() + } + + return ( +
+

{t('Remodel recipe debug recorder')}

+

{t('Remodel recipe debug recorder description')}

+ +

{t('Captured records', { count: recorderState.count })}

+
+ + +
+
+ ) +} diff --git a/src/reporters/base.ts b/src/reporters/base.ts index 1f4eb0f..fd3ec85 100644 --- a/src/reporters/base.ts +++ b/src/reporters/base.ts @@ -2,7 +2,6 @@ import url from 'url' import * as Sentry from '@sentry/electron' import fetch, { type RequestInit } from 'node-fetch' import https from 'https' -import packageMeta from '../../package.json' import type { ReportPayload } from '../types/reporter' // Because let's encrypt has switched to a new root cert which is not supported in older version of Electron, @@ -11,15 +10,26 @@ const insecureAgent = new https.Agent({ rejectUnauthorized: false, }) -const { SERVER_HOSTNAME, POI_VERSION } = window +const getReporterVersion = (): string => { + const globalReporterVersion = (globalThis as { __REPORTER_VERSION__?: string }) + .__REPORTER_VERSION__ + return typeof __REPORTER_VERSION__ === 'string' + ? __REPORTER_VERSION__ + : (globalReporterVersion ?? '0.0.0-dev') +} export default class BaseReporter { SERVER_HOSTNAME: string USERAGENT: string + POI_VERSION: string + REPORTER_VERSION: string constructor() { + const { SERVER_HOSTNAME, POI_VERSION } = globalThis.window this.SERVER_HOSTNAME = SERVER_HOSTNAME - this.USERAGENT = `Reporter/${packageMeta.version} poi/${POI_VERSION}` + this.POI_VERSION = POI_VERSION + this.REPORTER_VERSION = getReporterVersion() + this.USERAGENT = `Reporter/${this.REPORTER_VERSION} poi/${this.POI_VERSION}` } getJson = async (path: string): Promise> => { @@ -42,8 +52,8 @@ export default class BaseReporter { path, }) Sentry.setContext('versions', { - reporter: packageMeta.version, - poi: POI_VERSION, + reporter: this.REPORTER_VERSION, + poi: this.POI_VERSION, }) Sentry.captureException(err) }) @@ -79,8 +89,8 @@ export default class BaseReporter { path, }) Sentry.setContext('versions', { - reporter: packageMeta.version, - poi: POI_VERSION, + reporter: this.REPORTER_VERSION, + poi: this.POI_VERSION, }) Sentry.setContext('data', info) Sentry.captureException(err) diff --git a/src/reporters/remodel-recipe.ts b/src/reporters/remodel-recipe.ts index d52bb2c..32a923d 100644 --- a/src/reporters/remodel-recipe.ts +++ b/src/reporters/remodel-recipe.ts @@ -1,4 +1,3 @@ -import moment from 'moment-timezone' import _ from 'lodash' import BaseReporter from './base' import type { APIReqKousyouRemodelSlotRequest } from 'kcsapi/api_req_kousyou/remodel_slot/request' @@ -12,10 +11,11 @@ import type { GameApiPostBody, GameApiResponseBody, } from '../types/game-api' +import { parseInt10 } from '../types/window-state' type RemodelRecipeListItem = Pick< APIReqKousyouRemodelSlotlistResponse, - 'api_id' | 'api_req_bauxite' | 'api_req_bull' | 'api_req_fuel' | 'api_req_steel' + 'api_id' | 'api_req_bauxite' | 'api_req_bull' | 'api_req_fuel' | 'api_req_steel' | 'api_slot_id' > & Partial> @@ -33,7 +33,15 @@ type RemodelRecipeDetailBody = Partial< | 'api_req_slot_id' | 'api_req_slot_num' > -> +> & { + api_change_flag?: number + api_req_slot_id2?: number + api_req_slot_num2?: number + api_req_useitem_id?: number + api_req_useitem_num?: number + api_req_useitem_id2?: number + api_req_useitem_num2?: number +} type RemodelRecipeSlotPostBody = { api_id: string | number @@ -44,11 +52,78 @@ type RemodelRecipeSlotBody = Pick< 'api_remodel_id' | 'api_voice_ship_id' > & { api_after_slot?: { + api_slotitem_id?: number api_level?: number } api_remodel_flag: boolean | number } +interface RequiredItem { + id: number + count: number +} + +interface RemodelRecipeFleetContext { + observedSecondShipId: number + observedFlagshipId: number +} + +interface ItemImprovementAvailabilityPayload { + schemaVersion: 1 + source: 'list' + clientObservedAt: number + recipeId: number + itemId: number + day: number + observedSecondShipId: number + observedFlagshipId: number + detailObserved: false +} + +interface ItemImprovementCostPayload { + schemaVersion: 1 + source: 'detail' + clientObservedAt: number + recipeId: number + itemId: number + itemLevel: number + stage: number + day: number + observedSecondShipId: number + observedFlagshipId: number + fuel: number + ammo: number + steel: number + bauxite: number + buildkit: number + remodelkit: number + certainBuildkit: number + certainRemodelkit: number + reqSlotItems: RequiredItem[] + reqUseItems: RequiredItem[] + changeFlag: number + detailObserved: true +} + +interface ItemImprovementUpdatePayload { + schemaVersion: 1 + source: 'execution' + clientObservedAt: number + recipeId: number + itemId: number + itemLevel: number + day: number + observedSecondShipId: number + observedFlagshipId: number + upgradeObserved: true + upgradeToItemId: number + upgradeToItemLevel: number +} + +type CurrentDetail = ItemImprovementCostPayload & { + slotId: string | number +} + interface RemodelRecipeReportPayload { recipeId: number itemId: number @@ -70,10 +145,77 @@ interface RemodelRecipeReportPayload { key: string } +const ITEM_IMPROVEMENT_RECIPE_REPORT_PATH = '/api/report/v3/item_improvement_recipe' + +const hasOwn = (record: object, key: string): boolean => + Object.prototype.hasOwnProperty.call(record, key) + +const getJstDay = (time: number): number => { + const date = new Date(time) + const utcDay = date.getUTCDay() + const utcHour = date.getUTCHours() + return utcHour >= 15 ? (utcDay + 1) % 7 : utcDay +} + +const normalizeRequiredPairs = ( + ...pairs: Array<{ + id: number | undefined + count: number | undefined + present: boolean + }> +): RequiredItem[] => { + const counts = new Map() + + for (const { id, count, present } of pairs) { + if (!present || (id === 0 && count === 0)) { + continue + } + if ( + !Number.isInteger(id) || + !Number.isInteger(count) || + id == null || + count == null || + id <= 0 || + count <= 0 + ) { + throw new Error(`Invalid required item pair: ${String(id)}/${String(count)}`) + } + counts.set(id, (counts.get(id) || 0) + count) + } + + return [...counts.entries()].map(([id, count]) => ({ id, count })).sort((a, b) => a.id - b.id) +} + +const toCostPayload = (detail: CurrentDetail): ItemImprovementCostPayload => ({ + schemaVersion: detail.schemaVersion, + source: detail.source, + clientObservedAt: detail.clientObservedAt, + recipeId: detail.recipeId, + itemId: detail.itemId, + itemLevel: detail.itemLevel, + stage: detail.stage, + day: detail.day, + observedSecondShipId: detail.observedSecondShipId, + observedFlagshipId: detail.observedFlagshipId, + fuel: detail.fuel, + ammo: detail.ammo, + steel: detail.steel, + bauxite: detail.bauxite, + buildkit: detail.buildkit, + remodelkit: detail.remodelkit, + certainBuildkit: detail.certainBuildkit, + certainRemodelkit: detail.certainRemodelkit, + reqSlotItems: detail.reqSlotItems, + reqUseItems: detail.reqUseItems, + changeFlag: detail.changeFlag, + detailObserved: detail.detailObserved, +}) + // Collecting remodel recipes export default class RemodelRecipeReporter extends BaseReporter { id: number itemId: number + itemLevel: number recipeId: number recipes: Record day: number @@ -88,6 +230,7 @@ export default class RemodelRecipeReporter extends BaseReporter { remodelkit: number certainBuildkit: number certainRemodelkit: number + currentDetail: CurrentDetail | undefined // a recipe = // id -> /kcsapi/api_req_kousyou/remodel_slotlist_detail postBody.api_id, @@ -113,6 +256,7 @@ export default class RemodelRecipeReporter extends BaseReporter { super() this.id = -1 this.itemId = -1 + this.itemLevel = -1 this.recipeId = -1 this.recipes = {} this.day = -1 @@ -127,8 +271,13 @@ export default class RemodelRecipeReporter extends BaseReporter { this.remodelkit = 0 this.certainBuildkit = 0 this.certainRemodelkit = 0 + this.currentDetail = undefined } - getStage(level: number) { + getStage(level: number, changeFlag = 0) { + if (changeFlag) { + // api_change_flag marks an update/conversion detail, which belongs to the +10 stage. + return 2 + } switch (true) { case level >= 0 && level < 6: return 0 @@ -140,50 +289,239 @@ export default class RemodelRecipeReporter extends BaseReporter { return -1 } } + + getFleetContext(): RemodelRecipeFleetContext | undefined { + const deck = window._decks?.[0] + const flagshipRosterId = deck?.api_ship?.[0] + if (flagshipRosterId == null || Number(flagshipRosterId) <= 0) { + return undefined + } + + const flagship = window._ships[flagshipRosterId] + const observedFlagshipId = flagship?.api_ship_id + if (!observedFlagshipId) { + return undefined + } + + const secondRosterId = deck?.api_ship?.[1] + if (secondRosterId == null || Number(secondRosterId) <= 0) { + return { + observedSecondShipId: 0, + observedFlagshipId, + } + } + + const secondShip = window._ships[secondRosterId] + const observedSecondShipId = secondShip?.api_ship_id + if (!observedSecondShipId) { + return undefined + } + + return { + observedSecondShipId, + observedFlagshipId, + } + } + + normalizeReqSlotItems(response: RemodelRecipeDetailBody): RequiredItem[] { + return normalizeRequiredPairs( + { + id: response.api_req_slot_id, + count: response.api_req_slot_num, + present: hasOwn(response, 'api_req_slot_id') || hasOwn(response, 'api_req_slot_num'), + }, + { + id: response.api_req_slot_id2, + count: response.api_req_slot_num2, + present: hasOwn(response, 'api_req_slot_id2') || hasOwn(response, 'api_req_slot_num2'), + }, + ) + } + + normalizeReqUseItems(response: RemodelRecipeDetailBody): RequiredItem[] { + return normalizeRequiredPairs( + { + id: response.api_req_useitem_id, + count: response.api_req_useitem_num, + present: hasOwn(response, 'api_req_useitem_id') || hasOwn(response, 'api_req_useitem_num'), + }, + { + id: response.api_req_useitem_id2, + count: response.api_req_useitem_num2, + present: + hasOwn(response, 'api_req_useitem_id2') || hasOwn(response, 'api_req_useitem_num2'), + }, + ) + } + + hasExactDetailCosts(response: RemodelRecipeDetailBody): boolean { + return ( + hasOwn(response, 'api_req_buildkit') && + hasOwn(response, 'api_req_remodelkit') && + hasOwn(response, 'api_certain_buildkit') && + hasOwn(response, 'api_certain_remodelkit') + ) + } + + resetExecutionState() { + this.currentDetail = undefined + this.fuel = undefined + } + handle( method: GameApiMethod, path: GameApiPath, body: GameApiResponseBody, postBody: GameApiPostBody, + time = Date.now(), ) { switch (path) { case '/kcsapi/api_req_kousyou/remodel_slotlist': { - this.recipes = _.keyBy(body as RemodelRecipeListItem[], 'api_id') + const response = body as RemodelRecipeListItem[] + const day = getJstDay(time) + const context = this.getFleetContext() + this.recipes = _.keyBy(response, 'api_id') + + if (!context) { + console.error('Invalid remodel recipe fleet context') + return + } + + const records = response + .map((recipe): ItemImprovementAvailabilityPayload | undefined => { + const recipeId = recipe.api_id + const itemId = recipe.api_slot_id + if (!Number.isInteger(recipeId) || !Number.isInteger(itemId)) { + return undefined + } + return { + schemaVersion: 1, + source: 'list', + clientObservedAt: time, + recipeId, + itemId, + day, + observedSecondShipId: context.observedSecondShipId, + observedFlagshipId: context.observedFlagshipId, + detailObserved: false, + } + }) + .filter((record): record is ItemImprovementAvailabilityPayload => record != null) + + if (records.length) { + void this.report(ITEM_IMPROVEMENT_RECIPE_REPORT_PATH, { records }) + } } break case '/kcsapi/api_req_kousyou/remodel_slotlist_detail': { const response = body as RemodelRecipeDetailBody const request = postBody as RemodelRecipeDetailPostBody + this.resetExecutionState() + if (Object.keys(this.recipes).length === 0) { return } - const utc = moment.utc() - const hour = utc.hour() - const day = utc.day() - // remodel list refreshes at 00:00 UTC+9 - this.day = hour >= 15 ? (day + 1) % 7 : day - this.recipeId = parseInt(request.api_id) + this.day = getJstDay(time) + this.recipeId = parseInt10(request.api_id) + if (!Number.isInteger(this.recipeId)) { + console.error(`Invalid remodel recipe id: ${String(request.api_id)}`) + return + } const itemSlotId = request.api_slot_id - this.itemId = (window._slotitems[itemSlotId] || {}).api_slotitem_id || -1 - const itemLevel = (window._slotitems[itemSlotId] || {}).api_level || -1 - this.stage = this.getStage(itemLevel) + const slotItem = window._slotitems[itemSlotId] + this.itemId = slotItem?.api_slotitem_id ?? -1 + this.itemLevel = slotItem?.api_level ?? -1 + const changeFlag = response.api_change_flag ?? 0 + this.stage = this.getStage(this.itemLevel, changeFlag) + if (this.itemId <= 0 || this.stage < 0) { + console.error('Invalid remodel recipe slot item data') + return + } + const recipe: Partial = this.recipes[this.recipeId] || {} + const fuel = recipe.api_req_fuel + const ammo = recipe.api_req_bull + const steel = recipe.api_req_steel + const bauxite = recipe.api_req_bauxite + const buildkit = response.api_req_buildkit + const remodelkit = response.api_req_remodelkit + const certainBuildkit = response.api_certain_buildkit + const certainRemodelkit = response.api_certain_remodelkit + if ( + fuel == null || + ammo == null || + steel == null || + bauxite == null || + buildkit == null || + remodelkit == null || + certainBuildkit == null || + certainRemodelkit == null || + !this.hasExactDetailCosts(response) + ) { + console.error('Invalid remodel recipe detail data') + return + } - this.fuel = recipe.api_req_fuel || 0 - this.ammo = recipe.api_req_bull || 0 - this.steel = recipe.api_req_steel || 0 - this.bauxite = recipe.api_req_bauxite || 0 + this.fuel = fuel + this.ammo = ammo + this.steel = steel + this.bauxite = bauxite this.reqItemId = response.api_req_slot_id || -1 this.reqItemCount = response.api_req_slot_num || 0 - this.buildkit = response.api_req_buildkit || 0 - this.remodelkit = response.api_req_remodelkit || 0 - this.certainBuildkit = response.api_certain_buildkit || 0 - this.certainRemodelkit = response.api_certain_remodelkit || 0 + this.buildkit = buildkit + this.remodelkit = remodelkit + this.certainBuildkit = certainBuildkit + this.certainRemodelkit = certainRemodelkit + + const context = this.getFleetContext() + if (!context) { + console.error('Invalid remodel recipe fleet context') + return + } + + let reqSlotItems: RequiredItem[] + let reqUseItems: RequiredItem[] + try { + reqSlotItems = this.normalizeReqSlotItems(response) + reqUseItems = this.normalizeReqUseItems(response) + } catch (err) { + console.error(err instanceof Error ? err.message : err) + return + } + + const detail: CurrentDetail = { + schemaVersion: 1, + source: 'detail', + clientObservedAt: time, + recipeId: this.recipeId, + itemId: this.itemId, + itemLevel: this.itemLevel, + stage: this.stage, + day: this.day, + observedSecondShipId: context.observedSecondShipId, + observedFlagshipId: context.observedFlagshipId, + fuel: this.fuel, + ammo: this.ammo, + steel: this.steel, + bauxite: this.bauxite, + buildkit: this.buildkit, + remodelkit: this.remodelkit, + certainBuildkit: this.certainBuildkit, + certainRemodelkit: this.certainRemodelkit, + reqSlotItems, + reqUseItems, + changeFlag, + detailObserved: true, + slotId: itemSlotId, + } + this.currentDetail = detail + + void this.report(ITEM_IMPROVEMENT_RECIPE_REPORT_PATH, toCostPayload(detail)) } break case '/kcsapi/api_req_kousyou/remodel_slot': @@ -193,13 +531,29 @@ export default class RemodelRecipeReporter extends BaseReporter { if (typeof this.fuel === 'undefined') { return } + const currentDetail = this.currentDetail if (this.itemId != response.api_remodel_id[0]) { - console.error(`Inconsistent remodel item data: ${this.itemId}, ${request.api_slot_id}`) + console.error( + `Inconsistent remodel item data: expected item ${this.itemId}, got ${response.api_remodel_id[0]}`, + ) + this.resetExecutionState() return } if (this.recipeId != request.api_id) { console.error(`Inconsistent remodel item data: ${this.recipeId}, ${request.api_id}`) + this.resetExecutionState() + return + } + if ( + currentDetail && + request.api_slot_id != null && + currentDetail.slotId != request.api_slot_id + ) { + console.error( + `Inconsistent remodel item data: ${currentDetail.slotId}, ${request.api_slot_id}`, + ) + this.resetExecutionState() return } @@ -208,12 +562,19 @@ export default class RemodelRecipeReporter extends BaseReporter { // stage == -1 because /port will not update slotitems with api_level, they are // updated only when restarting game if (!response.api_remodel_flag || this.stage == -1) { + this.resetExecutionState() return } - const upgradeToItemId = - response.api_remodel_id[1] != this.itemId ? response.api_remodel_id[1] : -1 const afterSlot = response.api_after_slot || {} + const afterSlotItemId = afterSlot.api_slotitem_id + const remodelTargetItemId = response.api_remodel_id[1] + const upgradeToItemId = + afterSlotItemId != null && afterSlotItemId !== this.itemId + ? afterSlotItemId + : remodelTargetItemId != null && remodelTargetItemId !== this.itemId + ? remodelTargetItemId + : -1 const upgradeToItemLevel = upgradeToItemId >= 0 ? (afterSlot.api_level ?? -1) : -1 const secretary = response.api_voice_ship_id || -1 @@ -239,6 +600,25 @@ export default class RemodelRecipeReporter extends BaseReporter { } void this.report('/api/report/v2/remodel_recipe', info) + + if (currentDetail && upgradeToItemId >= 0 && upgradeToItemLevel >= 0) { + const update: ItemImprovementUpdatePayload = { + schemaVersion: 1, + source: 'execution', + clientObservedAt: time, + recipeId: currentDetail.recipeId, + itemId: currentDetail.itemId, + itemLevel: currentDetail.itemLevel, + day: getJstDay(time), + observedSecondShipId: currentDetail.observedSecondShipId, + observedFlagshipId: currentDetail.observedFlagshipId, + upgradeObserved: true, + upgradeToItemId, + upgradeToItemLevel, + } + void this.report(ITEM_IMPROVEMENT_RECIPE_REPORT_PATH, update) + } + this.resetExecutionState() } break } diff --git a/src/types/vendor.d.ts b/src/types/vendor.d.ts index 1aa5c38..c0a86ec 100644 --- a/src/types/vendor.d.ts +++ b/src/types/vendor.d.ts @@ -20,6 +20,8 @@ declare module 'electron' { export = electron } +declare const __REPORTER_VERSION__: string | undefined + declare module 'moment-timezone' { interface Moment { day(): number diff --git a/tests/helpers/reporter-test-harness.ts b/tests/helpers/reporter-test-harness.ts index 5c22c9f..45f2c12 100644 --- a/tests/helpers/reporter-test-harness.ts +++ b/tests/helpers/reporter-test-harness.ts @@ -1,49 +1,167 @@ import { createHash } from 'node:crypto' -import Module, { createRequire } from 'node:module' +import Module from 'node:module' import { vi } from 'vitest' +import packageMeta from '../../package.json' import type { WindowSlotItem } from '../../src/types/window-state' +import SourceAACIReporter from '../../src/reporters/aaci' +import SourceBaseReporter from '../../src/reporters/base' +import SourceCreateItemReporter from '../../src/reporters/create-item' +import SourceCreateShipReporter from '../../src/reporters/create-ship' +import SourceDropShipReporter from '../../src/reporters/drop-ship' +import SourceNightBattleCIReporter from '../../src/reporters/night-battle-ci' +import SourceNightContactReportor from '../../src/reporters/night-contact' +import SourceQuestReporter from '../../src/reporters/quest' +import SourceRemodelItemReporter from '../../src/reporters/remodel-item' +import SourceRemodelRecipeReporter from '../../src/reporters/remodel-recipe' +import SourceShipStatReporter from '../../src/reporters/ship-stat' + +const state = vi.hoisted(() => { + const ship = (overrides: Record = {}): any => ({ + api_ship_id: 100, + api_lv: 50, + api_cond: 49, + api_slot: [], + api_onslot: [], + ...overrides, + }) + + const selectorState = { + store: {} as any, + ships: new Map(), + equips: new Map(), + } + + const aaciState: { getShipAACIs: any } = { + getShipAACIs: vi.fn(() => [] as number[]), + } + + const momentState = { + hour: 16, + day: 2, + } + + const fetchState = { + calls: [] as any[][], + implementation: async (..._args: any[]): Promise => ({ + ok: true, + status: 200, + statusText: 'OK', + json: async () => ({}), + text: async () => '', + }), + } + + const sentryState = { + captured: [] as unknown[], + contexts: [] as Array<{ name: string; context: unknown }>, + tags: [] as unknown[], + } + + const createWindow = () => + ({ + POI_VERSION: '10.7.0', + SERVER_HOSTNAME: 'example.invalid', + _decks: [{ api_ship: [1, 2] }], + _ships: { + 1: ship({ api_ship_id: 101, api_lv: 80, api_cond: 53 }), + 2: ship({ api_ship_id: 102, api_lv: 70, api_cond: 49 }), + }, + $ships: { + 101: { api_id: 101, api_yomi: 'alpha' }, + 102: { api_id: 102, api_yomi: 'bravo' }, + }, + _slotitems: {}, + _teitokuId: 12345, + _teitokuLv: 120, + _nickName: 'Admiral', + _nickNameId: 99, + getStore: () => selectorState.store, + }) as unknown as Window & typeof globalThis + + globalThis.window = createWindow() + ;(globalThis as any).__reporterTestHarnessState = { + aaciState, + selectorState, + } + + return { + aaciState, + createWindow, + fetchState, + momentState, + selectorState, + sentryState, + } +}) + +export const { aaciState, fetchState, momentState, selectorState, sentryState } = state + +;(globalThis as unknown as { __REPORTER_VERSION__: string }).__REPORTER_VERSION__ = + packageMeta.version declare global { var __reporterTestHarnessPatched: boolean | undefined } -const require = createRequire(__filename) type ModuleWithLoad = typeof Module & { _load(request: string, parent: NodeJS.Module | null | undefined, isMain: boolean): unknown } + const moduleWithLoad = Module as ModuleWithLoad const originalLoad = moduleWithLoad._load -export const selectorState = { - store: {} as any, - ships: new Map(), - equips: new Map(), -} +vi.mock('@sentry/electron', () => ({ + captureException(error: unknown) { + sentryState.captured.push(error) + }, + setContext(name: string, context: unknown) { + sentryState.contexts.push({ name, context }) + }, + withScope(callback: (scope: { setTags(tags: unknown): void }) => void) { + callback({ + setTags(tags: unknown) { + sentryState.tags.push(tags) + }, + }) + }, +})) -export const aaciState: { getShipAACIs: any } = { - getShipAACIs: vi.fn(() => [] as number[]), -} +vi.mock('node-fetch', () => ({ + default: async (...args: any[]) => { + fetchState.calls.push(args) + return fetchState.implementation(...args) + }, +})) + +vi.mock('moment-timezone', () => ({ + default: { + utc: () => ({ + hour: () => momentState.hour, + day: () => momentState.day, + }), + }, +})) -export const momentState = { - hour: 16, - day: 2, -} +vi.mock('views/utils/selectors', () => ({ + shipDataSelectorFactory: (shipId: number) => () => selectorState.ships.get(shipId), + shipEquipDataSelectorFactory: (shipId: number) => () => selectorState.equips.get(shipId), +})) -export const fetchState = { - calls: [] as any[][], - implementation: async (..._args: any[]): Promise => ({ - ok: true, - status: 200, - statusText: 'OK', - json: async () => ({}), - text: async () => '', - }), -} +vi.mock('views/utils/aaci', () => aaciState) -export const sentryState = { - captured: [] as unknown[], - contexts: [] as Array<{ name: string; context: unknown }>, - tags: [] as unknown[], +if (!globalThis.__reporterTestHarnessPatched) { + moduleWithLoad._load = function loadReporterTestStub( + request: string, + parent: NodeJS.Module | null | undefined, + isMain: boolean, + ) { + if (request === 'views/utils/aaci') { + return aaciState + } + + return originalLoad.call(this, request, parent, isMain) + } + globalThis.__reporterTestHarnessPatched = true } export const ship = (overrides: Record = {}): any => ({ @@ -68,96 +186,20 @@ export const equip = ({ api_houm: houm, }) -const createWindow = () => - ({ - POI_VERSION: '10.7.0', - SERVER_HOSTNAME: 'example.invalid', - _decks: [{ api_ship: [1, 2] }], - _ships: { - 1: ship({ api_ship_id: 101, api_lv: 80, api_cond: 53 }), - 2: ship({ api_ship_id: 102, api_lv: 70, api_cond: 49 }), - }, - $ships: { - 101: { api_id: 101, api_yomi: 'alpha' }, - 102: { api_id: 102, api_yomi: 'bravo' }, - }, - _slotitems: {}, - _teitokuId: 12345, - _teitokuLv: 120, - _nickName: 'Admiral', - _nickNameId: 99, - getStore: () => selectorState.store, - }) as unknown as Window & typeof globalThis - -globalThis.window = createWindow() - -const sentryStub = { - captureException(error: unknown) { - sentryState.captured.push(error) - }, - setContext(name: string, context: unknown) { - sentryState.contexts.push({ name, context }) - }, - withScope(callback: (scope: { setTags(tags: unknown): void }) => void) { - callback({ - setTags(tags: unknown) { - sentryState.tags.push(tags) - }, - }) - }, -} - -if (!globalThis.__reporterTestHarnessPatched) { - moduleWithLoad._load = function loadReporterTestStub( - request: string, - parent: NodeJS.Module | null | undefined, - isMain: boolean, - ) { - switch (request) { - case '@sentry/electron': - return sentryStub - case 'node-fetch': - return async (...args: any[]) => { - fetchState.calls.push(args) - return fetchState.implementation(...args) - } - case 'moment-timezone': - return { - utc: () => ({ - hour: () => momentState.hour, - day: () => momentState.day, - }), - } - case 'views/utils/selectors': - return { - shipDataSelectorFactory: (shipId: number) => () => selectorState.ships.get(shipId), - shipEquipDataSelectorFactory: (shipId: number) => () => selectorState.equips.get(shipId), - } - case 'views/utils/aaci': - return aaciState - default: - return originalLoad.call(this, request, parent, isMain) - } - } - globalThis.__reporterTestHarnessPatched = true -} - -const loadDefault = (mod: any) => mod.default || mod - -export const AACIReporter = loadDefault(require('../../reporters/aaci.js')) -export const BaseReporter = loadDefault(require('../../reporters/base.js')) -export const CreateItemReporter = loadDefault(require('../../reporters/create-item.js')) -export const CreateShipReporter = loadDefault(require('../../reporters/create-ship.js')) -export const DropShipReporter = loadDefault(require('../../reporters/drop-ship.js')) -export const NightBattleCIReporter = loadDefault(require('../../reporters/night-battle-ci.js')) -export const NightContactReportor = loadDefault(require('../../reporters/night-contact.js')) -export const QuestReporter = loadDefault(require('../../reporters/quest.js')) -export const RemodelItemReporter = loadDefault(require('../../reporters/remodel-item.js')) -export const RemodelRecipeReporter = loadDefault(require('../../reporters/remodel-recipe.js')) -export const ShipStatReporter = loadDefault(require('../../reporters/ship-stat.js')) +export const AACIReporter: any = SourceAACIReporter +export const BaseReporter: any = SourceBaseReporter +export const CreateItemReporter: any = SourceCreateItemReporter +export const CreateShipReporter: any = SourceCreateShipReporter +export const DropShipReporter: any = SourceDropShipReporter +export const NightBattleCIReporter: any = SourceNightBattleCIReporter +export const NightContactReportor: any = SourceNightContactReportor +export const QuestReporter: any = SourceQuestReporter +export const RemodelItemReporter: any = SourceRemodelItemReporter +export const RemodelRecipeReporter: any = SourceRemodelRecipeReporter +export const ShipStatReporter: any = SourceShipStatReporter export const resetReporterTestState = () => { - globalThis.window = createWindow() + globalThis.window = state.createWindow() selectorState.store = { sortie: { sortieMapId: 1 }, battle: { _status: { result: { deckHp: [] } } }, diff --git a/tests/remodel-debug-recorder.test.ts b/tests/remodel-debug-recorder.test.ts new file mode 100644 index 0000000..0111ee9 --- /dev/null +++ b/tests/remodel-debug-recorder.test.ts @@ -0,0 +1,422 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { + clearRemodelDebugRecords, + createRemodelDebugRecord, + getRemodelDebugRecordCount, + getRemodelDebugRecords, + recordRemodelDebugEvent, + setRemodelDebugRecorderEnabled, + subscribeRemodelDebugRecorder, + exportRemodelDebugRecords, +} from '../src/remodel-debug-recorder' +import type { GameResponseEventDetail } from '../src/types/game-api' + +const storage = new Map() + +const createWindow = () => + ({ + localStorage: { + getItem(key: string) { + return storage.get(key) ?? null + }, + setItem(key: string, value: string) { + storage.set(key, value) + }, + removeItem(key: string) { + storage.delete(key) + }, + }, + _decks: [{ api_ship: [1, 2, 3] }], + _ships: { + 1: { api_ship_id: 101, api_lv: 80 }, + 2: { api_ship_id: 102, api_lv: 70 }, + 3: { api_ship_id: 103, api_lv: 60 }, + }, + _slotitems: { + 501: { api_slotitem_id: 700, api_level: 6, api_locked: 1 }, + }, + }) as unknown as Window & typeof globalThis + +const remodelDetailEvent: GameResponseEventDetail = { + time: 1710000000000, + method: 'POST', + path: '/kcsapi/api_req_kousyou/remodel_slotlist_detail', + postBody: { + api_id: '33', + api_slot_id: '501', + api_token: 'secret-token', + }, + body: { + api_req_buildkit: 3, + api_token: 'secret-token', + }, +} + +beforeEach(() => { + storage.clear() + globalThis.window = createWindow() + setRemodelDebugRecorderEnabled(false) + clearRemodelDebugRecords() +}) + +describe('remodel debug recorder', () => { + it('does not record unless explicitly enabled', () => { + recordRemodelDebugEvent(remodelDetailEvent) + + expect(getRemodelDebugRecords()).toHaveLength(0) + }) + + it('ignores unrelated APIs before checking localStorage', () => { + const getItem = vi.fn(() => '1') + globalThis.window = { + ...createWindow(), + localStorage: { + getItem, + setItem: vi.fn(), + removeItem: vi.fn(), + }, + } as unknown as Window & typeof globalThis + + recordRemodelDebugEvent({ + ...remodelDetailEvent, + path: '/kcsapi/api_get_member/ship2', + }) + + expect(getItem).not.toHaveBeenCalled() + expect(getRemodelDebugRecords()).toHaveLength(0) + }) + + it('does not create records for unrelated APIs', () => { + expect( + createRemodelDebugRecord({ + ...remodelDetailEvent, + path: '/kcsapi/api_get_member/ship2', + }), + ).toBeUndefined() + }) + + it('records only remodel APIs when enabled', () => { + setRemodelDebugRecorderEnabled(true) + + recordRemodelDebugEvent({ + ...remodelDetailEvent, + path: '/kcsapi/api_get_member/ship2', + }) + recordRemodelDebugEvent(remodelDetailEvent) + + expect(getRemodelDebugRecords()).toHaveLength(1) + }) + + it('caches enabled state after the first storage read', () => { + const getItem = vi.fn(() => '1') + globalThis.window = { + ...createWindow(), + localStorage: { + getItem, + setItem: vi.fn(), + removeItem: vi.fn(), + }, + } as unknown as Window & typeof globalThis + setRemodelDebugRecorderEnabled(true) + + recordRemodelDebugEvent(remodelDetailEvent) + recordRemodelDebugEvent(remodelDetailEvent) + + expect(getItem).not.toHaveBeenCalled() + expect(getRemodelDebugRecords()).toHaveLength(2) + }) + + it('sanitizes fleet and slot context for captured remodel records', () => { + const record = createRemodelDebugRecord(remodelDetailEvent) + + expect(record).toEqual({ + time: 1710000000000, + method: 'POST', + path: '/kcsapi/api_req_kousyou/remodel_slotlist_detail', + postBody: { + api_id: '33', + }, + body: { + api_req_buildkit: 3, + }, + context: { + firstFleet: { + flagship: { + api_ship_id: 101, + }, + secondShip: { + api_ship_id: 102, + }, + }, + selectedSlotItem: { + api_slotitem_id: 700, + api_level: 6, + }, + }, + }) + }) + + it('keeps boolean remodel flags in slot execution captures', () => { + const record = createRemodelDebugRecord({ + time: 1710000000001, + method: 'POST', + path: '/kcsapi/api_req_kousyou/remodel_slot', + postBody: { + api_id: '33', + api_slot_id: '501', + api_token: 'secret-token', + }, + body: { + api_remodel_flag: true, + api_remodel_id: [700, 701], + api_after_slot: { + api_slotitem_id: 701, + api_level: 0, + }, + }, + }) + + expect(record).toMatchObject({ + postBody: { + api_id: '33', + }, + body: { + api_remodel_flag: true, + }, + }) + }) + + it('sanitizes malformed list and slot response shapes', () => { + expect( + createRemodelDebugRecord({ + time: 1, + method: 'GET', + path: '/kcsapi/api_req_kousyou/remodel_slotlist', + postBody: null as unknown as GameResponseEventDetail['postBody'], + body: [ + { + api_id: 33, + api_slot_id: 700, + api_req_fuel: 10, + api_req_bull: 20, + api_req_steel: 30, + api_req_bauxite: 40, + api_token: 'secret-token', + }, + null, + ], + }), + ).toMatchObject({ + body: [ + { + api_id: 33, + api_slot_id: 700, + api_req_fuel: 10, + api_req_bull: 20, + api_req_steel: 30, + api_req_bauxite: 40, + }, + {}, + ], + postBody: {}, + }) + expect( + createRemodelDebugRecord({ + time: 2, + method: 'POST', + path: '/kcsapi/api_req_kousyou/remodel_slot', + postBody: { + api_id: '33', + }, + body: { + api_after_slot: null, + api_remodel_flag: 1, + api_remodel_id: ['bad', 701], + }, + }), + ).toMatchObject({ + body: { + api_after_slot: {}, + api_remodel_flag: 1, + api_remodel_id: [701], + }, + }) + }) + + it('handles missing deck ship arrays while recording', () => { + window._decks = [{} as (typeof window._decks)[number]] + + expect(() => recordRemodelDebugEvent(remodelDetailEvent)).not.toThrow() + }) + + it('handles missing decks while creating sanitized records', () => { + window._decks = undefined as unknown as typeof window._decks + + expect(createRemodelDebugRecord(remodelDetailEvent)).toMatchObject({ + context: { + firstFleet: {}, + selectedSlotItem: { + api_slotitem_id: 700, + api_level: 6, + }, + }, + }) + }) + + it('keeps recording when slot item context is missing', () => { + window._slotitems = undefined as unknown as typeof window._slotitems + setRemodelDebugRecorderEnabled(true) + + recordRemodelDebugEvent(remodelDetailEvent) + + expect(getRemodelDebugRecords()).toHaveLength(1) + expect(getRemodelDebugRecords()[0]?.context).toEqual({ + firstFleet: { + flagship: { api_ship_id: 101 }, + secondShip: { api_ship_id: 102 }, + }, + }) + }) + + it('keeps recording when ship context is missing', () => { + window._ships = undefined as unknown as typeof window._ships + setRemodelDebugRecorderEnabled(true) + + recordRemodelDebugEvent(remodelDetailEvent) + + expect(getRemodelDebugRecords()).toHaveLength(1) + expect(getRemodelDebugRecords()[0]?.context).toEqual({ + firstFleet: {}, + selectedSlotItem: { + api_level: 6, + api_slotitem_id: 700, + }, + }) + }) + + it('caps in-memory captures to the latest 200 records', () => { + setRemodelDebugRecorderEnabled(true) + + for (let index = 0; index < 205; index += 1) { + recordRemodelDebugEvent({ + ...remodelDetailEvent, + time: index, + }) + } + + expect(getRemodelDebugRecords()).toHaveLength(200) + expect(getRemodelDebugRecords()[0]?.time).toBe(5) + }) + + it('returns a copy of captured records', () => { + setRemodelDebugRecorderEnabled(true) + recordRemodelDebugEvent(remodelDetailEvent) + + const snapshot = getRemodelDebugRecords() as unknown as Array<{ + context?: { + selectedSlotItem?: { + api_level?: number + } + } + }> + snapshot[0]!.context!.selectedSlotItem!.api_level = 99 + snapshot.length = 0 + + expect(getRemodelDebugRecords()).toHaveLength(1) + expect(getRemodelDebugRecordCount()).toBe(1) + expect(getRemodelDebugRecords()[0]?.context.selectedSlotItem?.api_level).toBe(6) + }) + + it('notifies subscribers when setting or records change', () => { + const listener = vi.fn() + const unsubscribe = subscribeRemodelDebugRecorder(listener) + + setRemodelDebugRecorderEnabled(true) + recordRemodelDebugEvent(remodelDetailEvent) + clearRemodelDebugRecords() + + expect(listener).toHaveBeenCalledTimes(3) + unsubscribe() + setRemodelDebugRecorderEnabled(false) + expect(listener).toHaveBeenCalledTimes(3) + }) + + it('isolates listener failures during notifications', () => { + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + const healthyListener = vi.fn() + const unsubscribeBad = subscribeRemodelDebugRecorder(() => { + throw new Error('listener failed') + }) + const unsubscribeHealthy = subscribeRemodelDebugRecorder(healthyListener) + + expect(() => setRemodelDebugRecorderEnabled(true)).not.toThrow() + + expect(consoleError).toHaveBeenCalledWith(expect.any(Error)) + expect(healthyListener).toHaveBeenCalled() + unsubscribeBad() + unsubscribeHealthy() + consoleError.mockRestore() + }) + + it('does not throw when localStorage writes fail', () => { + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + globalThis.window = { + ...createWindow(), + localStorage: { + getItem: () => null, + setItem: () => { + throw new Error('blocked') + }, + removeItem: () => { + throw new Error('blocked') + }, + }, + } as unknown as Window & typeof globalThis + + expect(() => setRemodelDebugRecorderEnabled(true)).not.toThrow() + expect(() => setRemodelDebugRecorderEnabled(false)).not.toThrow() + expect(consoleError).toHaveBeenCalledTimes(2) + consoleError.mockRestore() + }) + + it('exports captures as a delayed-revoked JSON blob', async () => { + vi.useFakeTimers() + setRemodelDebugRecorderEnabled(true) + recordRemodelDebugEvent(remodelDetailEvent) + const click = vi.fn() + const anchor = { + click, + download: '', + href: '', + } + const createElement = vi.fn(() => anchor as unknown as HTMLElement) + const originalDocument = globalThis.document + globalThis.document = { + createElement, + } as unknown as Document + let exportedBlob: Blob | undefined + const createObjectUrl = vi.spyOn(URL, 'createObjectURL').mockImplementation((blob) => { + exportedBlob = blob as Blob + return 'blob:debug' + }) + const revokeObjectUrl = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => {}) + + exportRemodelDebugRecords() + + expect(createElement).toHaveBeenCalledWith('a') + expect(createObjectUrl).toHaveBeenCalledWith(expect.any(Blob)) + await expect(exportedBlob?.text()).resolves.toContain('"records"') + expect(anchor.href).toBe('blob:debug') + expect(anchor.download).toMatch(/^plugin-report-remodel-debug-/) + expect(click).toHaveBeenCalled() + expect(revokeObjectUrl).not.toHaveBeenCalled() + vi.advanceTimersByTime(1000) + expect(revokeObjectUrl).toHaveBeenCalledWith('blob:debug') + + globalThis.document = originalDocument + createObjectUrl.mockRestore() + revokeObjectUrl.mockRestore() + vi.useRealTimers() + }) +}) diff --git a/tests/reporter-utils.test.ts b/tests/reporter-utils.test.ts index 7a55690..23a7e7b 100644 --- a/tests/reporter-utils.test.ts +++ b/tests/reporter-utils.test.ts @@ -1,20 +1,26 @@ -import { createRequire } from 'node:module' -import { describe, expect, it } from 'vitest' +import { beforeAll, describe, expect, it } from 'vitest' +import type * as ReporterUtils from '../src/reporters/utils' -globalThis.window = { - _teitokuId: 1, - _ships: {}, - $ships: {}, -} as unknown as Window & typeof globalThis +let getFirstPlaneCounts: typeof ReporterUtils.getFirstPlaneCounts +let getHpStyle: typeof ReporterUtils.getHpStyle +let getNightBattleCVCIType: typeof ReporterUtils.getNightBattleCVCIType +let getNightBattleDDCIType: typeof ReporterUtils.getNightBattleDDCIType +let getNightBattleSSCIType: typeof ReporterUtils.getNightBattleSSCIType -const require = createRequire(__filename) -const { - getFirstPlaneCounts, - getHpStyle, - getNightBattleCVCIType, - getNightBattleDDCIType, - getNightBattleSSCIType, -} = require('../reporters/utils.js') +beforeAll(async () => { + globalThis.window = { + _teitokuId: 1, + _ships: {}, + $ships: {}, + } as unknown as Window & typeof globalThis + ;({ + getFirstPlaneCounts, + getHpStyle, + getNightBattleCVCIType, + getNightBattleDDCIType, + getNightBattleSSCIType, + } = await import('../src/reporters/utils')) +}) const equip = ({ id = 0, type2 = 0, type3 = 0, houm = 0 } = {}) => ({ api_slotitem_id: id, diff --git a/tests/reporters/remodel-recipe.test.ts b/tests/reporters/remodel-recipe.test.ts index 3079b04..b0dcd97 100644 --- a/tests/reporters/remodel-recipe.test.ts +++ b/tests/reporters/remodel-recipe.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { attachReportSpy, @@ -6,38 +6,134 @@ import { resetReporterTestState, } from '../helpers/reporter-test-harness' -beforeEach(resetReporterTestState) +const fixedTestTime = Date.UTC(2026, 6, 3, 15) + +beforeEach(() => { + resetReporterTestState() + vi.useFakeTimers() + vi.setSystemTime(fixedTestTime) +}) + +afterEach(() => { + vi.useRealTimers() +}) describe('RemodelRecipeReporter', () => { - it('reports successful remodel recipes with cached recipe cost and day data', () => { - window._slotitems[501] = { api_slotitem_id: 700, api_level: 6 } + const detailBody = { + api_req_slot_id: 90, + api_req_slot_num: 2, + api_req_buildkit: 3, + api_req_remodelkit: 4, + api_certain_buildkit: 5, + api_certain_remodelkit: 6, + } + + const listBody = [ + { + api_id: 33, + api_slot_id: 700, + api_req_fuel: 10, + api_req_bull: 20, + api_req_steel: 30, + api_req_bauxite: 40, + }, + ] + const detailTime = fixedTestTime + const executionTime = Date.UTC(2026, 6, 4, 15) + + it('reports v3 availability and detail facts without execution', () => { + window._slotitems[501] = { api_slotitem_id: 700, api_level: 0 } + window._decks[0] = { api_ship: [1, -1] } const reporter = new RemodelRecipeReporter() const report = attachReportSpy(reporter) - reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', [ - { - api_id: 33, - api_req_fuel: 10, - api_req_bull: 20, - api_req_steel: 30, - api_req_bauxite: 40, - }, - ]) + reporter.handle( + 'GET', + '/kcsapi/api_req_kousyou/remodel_slotlist', + listBody, + {}, + Date.UTC(2026, 6, 3, 14), + ) reporter.handle( 'POST', '/kcsapi/api_req_kousyou/remodel_slotlist_detail', { - api_req_slot_id: 90, - api_req_slot_num: 2, - api_req_buildkit: 3, - api_req_remodelkit: 4, - api_certain_buildkit: 5, - api_certain_remodelkit: 6, + ...detailBody, + api_req_slot_id2: 90, + api_req_slot_num2: 1, + api_req_useitem_id: 65, + api_req_useitem_num: 1, + api_req_useitem_id2: 66, + api_req_useitem_num2: 2, + api_change_flag: 0, }, { api_id: '33', api_slot_id: 501, }, + Date.UTC(2026, 6, 3, 15), + ) + + expect(report).toHaveBeenNthCalledWith(1, '/api/report/v3/item_improvement_recipe', { + records: [ + { + schemaVersion: 1, + source: 'list', + clientObservedAt: Date.UTC(2026, 6, 3, 14), + recipeId: 33, + itemId: 700, + day: 5, + observedSecondShipId: 0, + observedFlagshipId: 101, + detailObserved: false, + }, + ], + }) + expect(report).toHaveBeenNthCalledWith(2, '/api/report/v3/item_improvement_recipe', { + schemaVersion: 1, + source: 'detail', + clientObservedAt: Date.UTC(2026, 6, 3, 15), + recipeId: 33, + itemId: 700, + itemLevel: 0, + stage: 0, + day: 6, + observedSecondShipId: 0, + observedFlagshipId: 101, + fuel: 10, + ammo: 20, + steel: 30, + bauxite: 40, + buildkit: 3, + remodelkit: 4, + certainBuildkit: 5, + certainRemodelkit: 6, + reqSlotItems: [{ id: 90, count: 3 }], + reqUseItems: [ + { id: 65, count: 1 }, + { id: 66, count: 2 }, + ], + changeFlag: 0, + detailObserved: true, + }) + expect(report).toHaveBeenCalledTimes(2) + }) + + it('reports successful remodel recipes with cached recipe cost and day data', () => { + window._slotitems[501] = { api_slotitem_id: 700, api_level: 6 } + const reporter = new RemodelRecipeReporter() + const report = attachReportSpy(reporter) + + reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', listBody, {}, detailTime) + reporter.handle( + 'POST', + '/kcsapi/api_req_kousyou/remodel_slotlist_detail', + detailBody, + { + api_id: '33', + api_slot_id: 501, + }, + detailTime, ) reporter.handle( 'POST', @@ -51,13 +147,14 @@ describe('RemodelRecipeReporter', () => { { api_id: '33', }, + executionTime, ) expect(report).toHaveBeenCalledWith('/api/report/v2/remodel_recipe', { recipeId: 33, itemId: 700, stage: 1, - day: 3, + day: 6, secretary: 99, fuel: 10, ammo: 20, @@ -71,16 +168,69 @@ describe('RemodelRecipeReporter', () => { certainRemodelkit: 6, upgradeToItemId: 701, upgradeToItemLevel: 0, - key: 'r33-i700-s1-d3-s99', + key: 'r33-i700-s1-d6-s99', + }) + expect(report).toHaveBeenCalledWith('/api/report/v3/item_improvement_recipe', { + schemaVersion: 1, + source: 'execution', + clientObservedAt: executionTime, + recipeId: 33, + itemId: 700, + itemLevel: 6, + day: 0, + observedSecondShipId: 102, + observedFlagshipId: 101, + upgradeObserved: true, + upgradeToItemId: 701, + upgradeToItemLevel: 0, + }) + }) + + it('does not report v3 update facts for failed or non-converting improvements', () => { + window._slotitems[501] = { api_slotitem_id: 700, api_level: 6 } + const reporter = new RemodelRecipeReporter() + const report = attachReportSpy(reporter) + + reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', listBody) + reporter.handle('POST', '/kcsapi/api_req_kousyou/remodel_slotlist_detail', detailBody, { + api_id: '33', + api_slot_id: 501, }) + reporter.handle( + 'POST', + '/kcsapi/api_req_kousyou/remodel_slot', + { + api_remodel_flag: false, + api_remodel_id: [700, 700], + }, + { + api_id: '33', + }, + ) + reporter.handle( + 'POST', + '/kcsapi/api_req_kousyou/remodel_slot', + { + api_remodel_flag: true, + api_remodel_id: [700, 700], + }, + { + api_id: '33', + }, + ) + + expect(report).not.toHaveBeenCalledWith( + '/api/report/v3/item_improvement_recipe', + expect.objectContaining({ source: 'execution' }), + ) }) - it('does not report failed remodel recipes or unknown item stages', () => { - window._slotitems[501] = { api_slotitem_id: 700, api_level: -1 } + it('does not cache detail state before recipe list or when upgrade responses mismatch', () => { + window._slotitems[501] = { api_slotitem_id: 700, api_level: 6 } const reporter = new RemodelRecipeReporter() const report = attachReportSpy(reporter) + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) - reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', [{ api_id: 33 }]) reporter.handle( 'POST', '/kcsapi/api_req_kousyou/remodel_slotlist_detail', @@ -94,34 +244,100 @@ describe('RemodelRecipeReporter', () => { 'POST', '/kcsapi/api_req_kousyou/remodel_slot', { - api_remodel_flag: false, - api_remodel_id: [700, 700], + api_remodel_flag: true, + api_remodel_id: [700, 701], }, { api_id: '33', }, ) + reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', listBody) + reporter.handle('POST', '/kcsapi/api_req_kousyou/remodel_slotlist_detail', detailBody, { + api_id: '33', + api_slot_id: 501, + }) reporter.handle( 'POST', '/kcsapi/api_req_kousyou/remodel_slot', { api_remodel_flag: true, - api_remodel_id: [700, 700], + api_remodel_id: [999, 701], }, { api_id: '33', }, ) + reporter.handle( + 'POST', + '/kcsapi/api_req_kousyou/remodel_slot', + { + api_remodel_flag: true, + api_remodel_id: [700, 701], + }, + { + api_id: '34', + }, + ) + + expect(report).not.toHaveBeenCalledWith('/api/report/v2/remodel_recipe', expect.anything()) + expect(report).not.toHaveBeenCalledWith( + '/api/report/v3/item_improvement_recipe', + expect.objectContaining({ source: 'execution' }), + ) + expect(consoleError).toHaveBeenCalledTimes(1) + consoleError.mockRestore() + }) + + it('skips v3 reporting when fleet context is unknown', () => { + window._decks = [] + const reporter = new RemodelRecipeReporter() + const report = attachReportSpy(reporter) + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + + reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', listBody) expect(report).not.toHaveBeenCalled() + expect(consoleError).toHaveBeenCalledWith('Invalid remodel recipe fleet context') + consoleError.mockRestore() }) - it('does not cache detail state before recipe list or when upgrade responses mismatch', () => { + it('rejects malformed required item pairs', () => { window._slotitems[501] = { api_slotitem_id: 700, api_level: 6 } const reporter = new RemodelRecipeReporter() const report = attachReportSpy(reporter) const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', listBody) + reporter.handle( + 'POST', + '/kcsapi/api_req_kousyou/remodel_slotlist_detail', + { + ...detailBody, + api_req_useitem_id: 65, + api_req_useitem_num: 0, + }, + { + api_id: '33', + api_slot_id: 501, + }, + ) + + expect(report).toHaveBeenCalledTimes(1) + expect(consoleError).toHaveBeenCalledWith('Invalid required item pair: 65/0') + consoleError.mockRestore() + }) + + it('rejects detail observations without exact costs or slot item state', () => { + const reporter = new RemodelRecipeReporter() + const report = attachReportSpy(reporter) + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + + reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', listBody) + reporter.handle('POST', '/kcsapi/api_req_kousyou/remodel_slotlist_detail', detailBody, { + api_id: '33', + api_slot_id: 501, + }) + window._slotitems[501] = { api_slotitem_id: 700, api_level: 6 } reporter.handle( 'POST', '/kcsapi/api_req_kousyou/remodel_slotlist_detail', @@ -131,52 +347,170 @@ describe('RemodelRecipeReporter', () => { api_slot_id: 501, }, ) + + expect(report).toHaveBeenCalledTimes(1) + expect(consoleError).toHaveBeenCalledWith('Invalid remodel recipe slot item data') + expect(consoleError).toHaveBeenCalledWith('Invalid remodel recipe detail data') + consoleError.mockRestore() + }) + + it('rejects placeholder item ids in detail observations', () => { + window._slotitems[501] = { api_slotitem_id: 0, api_level: 6 } + const reporter = new RemodelRecipeReporter() + const report = attachReportSpy(reporter) + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + + reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', listBody) + reporter.handle('POST', '/kcsapi/api_req_kousyou/remodel_slotlist_detail', detailBody, { + api_id: '33', + api_slot_id: 501, + }) + + expect(report).toHaveBeenCalledTimes(1) + expect(consoleError).toHaveBeenCalledWith('Invalid remodel recipe slot item data') + consoleError.mockRestore() + }) + + it('rejects execution when the request slot mismatches cached detail state', () => { + window._slotitems[501] = { api_slotitem_id: 700, api_level: 6 } + const reporter = new RemodelRecipeReporter() + const report = attachReportSpy(reporter) + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + + reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', listBody) + reporter.handle('POST', '/kcsapi/api_req_kousyou/remodel_slotlist_detail', detailBody, { + api_id: '33', + api_slot_id: 501, + }) reporter.handle( 'POST', '/kcsapi/api_req_kousyou/remodel_slot', { api_remodel_flag: true, api_remodel_id: [700, 701], + api_after_slot: { api_level: 0 }, + api_voice_ship_id: 99, }, { api_id: '33', + api_slot_id: 999, }, ) - reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', [{ api_id: 33 }]) + + expect(report).not.toHaveBeenCalledWith('/api/report/v2/remodel_recipe', expect.anything()) + expect(consoleError).toHaveBeenCalledWith('Inconsistent remodel item data: 501, 999') + consoleError.mockRestore() + }) + + it('logs item id mismatches without mixing item and slot identifiers', () => { + window._slotitems[501] = { api_slotitem_id: 700, api_level: 6 } + const reporter = new RemodelRecipeReporter() + const report = attachReportSpy(reporter) + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + + reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', listBody) + reporter.handle('POST', '/kcsapi/api_req_kousyou/remodel_slotlist_detail', detailBody, { + api_id: '33', + api_slot_id: 501, + }) reporter.handle( 'POST', - '/kcsapi/api_req_kousyou/remodel_slotlist_detail', - {}, + '/kcsapi/api_req_kousyou/remodel_slot', + { + api_remodel_flag: true, + api_remodel_id: [999, 701], + }, { api_id: '33', api_slot_id: 501, }, ) + + expect(report).not.toHaveBeenCalledWith('/api/report/v2/remodel_recipe', expect.anything()) + expect(consoleError).toHaveBeenCalledWith( + 'Inconsistent remodel item data: expected item 700, got 999', + ) + consoleError.mockRestore() + }) + + it('preserves v2 execution reporting when v3 fleet context is unavailable', () => { + window._slotitems[501] = { api_slotitem_id: 700, api_level: 6 } + window._decks = [] + const reporter = new RemodelRecipeReporter() + const report = attachReportSpy(reporter) + const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) + + reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', listBody) + reporter.handle('POST', '/kcsapi/api_req_kousyou/remodel_slotlist_detail', detailBody, { + api_id: '33', + api_slot_id: 501, + }) reporter.handle( 'POST', '/kcsapi/api_req_kousyou/remodel_slot', { api_remodel_flag: true, - api_remodel_id: [999, 701], + api_remodel_id: [700, 701], + api_after_slot: { api_level: 0 }, + api_voice_ship_id: 99, }, { api_id: '33', }, ) + + expect(report).toHaveBeenCalledTimes(1) + expect(report).toHaveBeenCalledWith( + '/api/report/v2/remodel_recipe', + expect.objectContaining({ + recipeId: 33, + itemId: 700, + upgradeToItemId: 701, + }), + ) + expect(consoleError).toHaveBeenCalledWith('Invalid remodel recipe fleet context') + consoleError.mockRestore() + }) + + it('uses api_after_slot item id for conversion targets when remodel id is unchanged', () => { + window._slotitems[501] = { api_slotitem_id: 700, api_level: 10 } + const reporter = new RemodelRecipeReporter() + const report = attachReportSpy(reporter) + + reporter.handle('GET', '/kcsapi/api_req_kousyou/remodel_slotlist', listBody) + reporter.handle( + 'POST', + '/kcsapi/api_req_kousyou/remodel_slotlist_detail', + { + ...detailBody, + api_change_flag: 1, + }, + { + api_id: '33', + api_slot_id: 501, + }, + ) reporter.handle( 'POST', '/kcsapi/api_req_kousyou/remodel_slot', { api_remodel_flag: true, - api_remodel_id: [700, 701], + api_remodel_id: [700, 700], + api_after_slot: { api_slotitem_id: 701, api_level: 0 }, + api_voice_ship_id: 99, }, { - api_id: '34', + api_id: '33', }, ) - expect(report).not.toHaveBeenCalled() - expect(consoleError).toHaveBeenCalledTimes(2) - consoleError.mockRestore() + expect(report).toHaveBeenCalledWith( + '/api/report/v3/item_improvement_recipe', + expect.objectContaining({ + source: 'execution', + upgradeToItemId: 701, + upgradeToItemLevel: 0, + }), + ) }) }) diff --git a/tsconfig.json b/tsconfig.json index 92988fa..97450cc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,9 +8,10 @@ "resolveJsonModule": true, "skipLibCheck": true, "strict": true, + "jsx": "react-jsx", "baseUrl": ".", "types": ["node"] }, - "include": ["src/**/*.ts", "src/**/*.d.ts", "tests/**/*.ts"], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts", "tests/**/*.ts", "tests/**/*.tsx"], "exclude": ["node_modules"] } diff --git a/tsdown.config.ts b/tsdown.config.ts index 31dd629..f3857d0 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -1,26 +1,16 @@ const { defineConfig } = require('tsdown') +const packageMeta = require('./package.json') module.exports = defineConfig({ entry: { index: 'src/index.ts', - 'reporters/aaci': 'src/reporters/aaci.ts', - 'reporters/base': 'src/reporters/base.ts', - 'reporters/create-item': 'src/reporters/create-item.ts', - 'reporters/create-ship': 'src/reporters/create-ship.ts', - 'reporters/drop-ship': 'src/reporters/drop-ship.ts', - 'reporters/index': 'src/reporters/index.ts', - 'reporters/night-battle-ci': 'src/reporters/night-battle-ci.ts', - 'reporters/night-contact': 'src/reporters/night-contact.ts', - 'reporters/quest': 'src/reporters/quest.ts', - 'reporters/remodel-item': 'src/reporters/remodel-item.ts', - 'reporters/remodel-recipe': 'src/reporters/remodel-recipe.ts', - 'reporters/ship-stat': 'src/reporters/ship-stat.ts', - 'reporters/utils': 'src/reporters/utils.ts', - sentry: 'src/sentry.ts', }, outDir: '.', outExtensions: () => ({ js: '.js' }), format: ['cjs'], + define: { + __REPORTER_VERSION__: JSON.stringify(packageMeta.version), + }, deps: { neverBundle: [ '@electron/remote', @@ -29,17 +19,19 @@ module.exports = defineConfig({ 'lodash', 'moment-timezone', 'node-fetch', + 'react', + 'react-dom', + 'react/jsx-runtime', + 'react/jsx-dev-runtime', + 'react-i18next', 'semver', /^views\//, ], }, dts: false, - clean: ['index.js', 'sentry.js', 'reporters', 'chunks'], + clean: ['index.js'], sourcemap: false, hash: false, - outputOptions: { - chunkFileNames: 'chunks/[name].js', - }, treeshake: false, minify: false, shims: false, diff --git a/vitest.config.mjs b/vitest.config.mjs index 5a7c2e8..8dc5d66 100644 --- a/vitest.config.mjs +++ b/vitest.config.mjs @@ -5,8 +5,8 @@ export default defineConfig({ coverage: { provider: 'v8', reporter: ['text', 'json-summary'], - include: ['reporters/*.js', 'chunks/*.js'], - exclude: ['reporters/index.js', 'reporters/base.js', 'chunks/rolldown-runtime.js'], + include: ['src/reporters/**/*.ts', 'src/remodel-debug-recorder.ts'], + exclude: ['src/reporters/index.ts'], all: true, thresholds: { lines: 95,