Просмотр исходного кода

feat(sentry): 接入前端错误监控与生产 sourcemap

初始化 @sentry/react,release 与 SDK 对齐;有 token 才上传 hidden sourcemap 并删除,避免打进 Tauri 包。
关闭 Logs/Metrics/Replay/Profiling。同步升级 vitest 到 5。

Co-authored-by: Cursor <cursoragent@cursor.com>
darknessomi 4 дней назад
Родитель
Сommit
0a1bc5da51
8 измененных файлов с 749 добавлено и 157 удалено
  1. 15 0
      .github/workflows/build.yml
  2. 15 0
      .github/workflows/prerelease.yml
  3. 638 146
      package-lock.json
  4. 3 1
      package.json
  5. 2 0
      src/components/error-boundary.tsx
  6. 38 0
      src/instrument.ts
  7. 16 9
      src/main.tsx
  8. 22 1
      vite.config.ts

+ 15 - 0
.github/workflows/build.yml

@@ -118,11 +118,26 @@ jobs:
           fi
           gh release edit "$tag" --notes-file "$notes_path" --draft=false
 
+      - name: Require Sentry sourcemap token
+        env:
+          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+        shell: bash
+        run: |
+          if [ -z "${SENTRY_AUTH_TOKEN}" ]; then
+            echo "SENTRY_AUTH_TOKEN is required to upload production sourcemaps." >&2
+            echo "Create an org token (project:releases, org:read) at https://qmai-c5.sentry.io/settings/auth-tokens/" >&2
+            echo "Then: gh secret set SENTRY_AUTH_TOKEN -R Mochocyang/QMAI" >&2
+            exit 1
+          fi
+
       - name: Build Tauri app
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
           TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
+          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+          SENTRY_ORG: qmai-c5
+          SENTRY_PROJECT: qmai
         run: npx tauri build ${{ matrix.tauri_args }}
 
       - name: Publish Windows updater manifest

+ 15 - 0
.github/workflows/prerelease.yml

@@ -78,11 +78,26 @@ jobs:
             cat release-assets/changelog.txt
           } > release-assets/prerelease-notes.txt
 
+      - name: Require Sentry sourcemap token
+        env:
+          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+        shell: bash
+        run: |
+          if [ -z "${SENTRY_AUTH_TOKEN}" ]; then
+            echo "SENTRY_AUTH_TOKEN is required to upload production sourcemaps." >&2
+            echo "Create an org token (project:releases, org:read) at https://qmai-c5.sentry.io/settings/auth-tokens/" >&2
+            echo "Then: gh secret set SENTRY_AUTH_TOKEN -R Mochocyang/QMAI" >&2
+            exit 1
+          fi
+
       - name: Build Tauri NSIS installer
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
           TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
+          SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+          SENTRY_ORG: qmai-c5
+          SENTRY_PROJECT: qmai
         run: npx tauri build --bundles nsis
 
       - name: Package Windows 内测 assets

+ 638 - 146
package-lock.json

@@ -17,6 +17,7 @@
         "@milkdown/react": "^7.22.1",
         "@milkdown/theme-nord": "^7.22.1",
         "@react-sigma/core": "^5.0.6",
+        "@sentry/react": "^10.74.0",
         "@sigma/edge-curve": "^3.1.0",
         "@tailwindcss/vite": "^4.3.3",
         "@tauri-apps/api": "^2.11.1",
@@ -58,6 +59,7 @@
         "zustand": "^5.0.15"
       },
       "devDependencies": {
+        "@sentry/vite-plugin": "^5.4.0",
         "@tauri-apps/cli": "^2.11.4",
         "@types/js-yaml": "^4.0.9",
         "@types/node": "^24.13.4",
@@ -68,7 +70,7 @@
         "jsdom": "^29.1.1",
         "typescript": "^5.9.3",
         "vite": "^8.3.0",
-        "vitest": "^4.1.11"
+        "vitest": "^5.0.1"
       }
     },
     "node_modules/@antfu/install-pkg": {
@@ -2672,6 +2674,428 @@
       "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
       "license": "MIT"
     },
+    "node_modules/@sentry/browser": {
+      "version": "10.74.0",
+      "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-10.74.0.tgz",
+      "integrity": "sha512-U6Zn+YcAgnR7bXZmpjMxivptPSsOeDKV+SlSqbJZqViOaCcZSG/vJiQr50UrdTccCwiU1LnwuOOoclJOvW+cLQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@sentry/browser-utils": "10.74.0",
+        "@sentry/conventions": "^0.16.0",
+        "@sentry/core": "10.74.0",
+        "@sentry/feedback": "10.74.0",
+        "@sentry/replay": "10.74.0",
+        "@sentry/replay-canvas": "10.74.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@sentry/browser-utils": {
+      "version": "10.74.0",
+      "resolved": "https://registry.npmjs.org/@sentry/browser-utils/-/browser-utils-10.74.0.tgz",
+      "integrity": "sha512-/Rh3WH4i2nStdOqHpaWocbpSMFPjKhlxKQgp9vkJBSolqjJzXlIpLYXOrldZHHZLG1iDoX3PFBQkNDYcPXCg0A==",
+      "license": "MIT",
+      "dependencies": {
+        "@sentry/conventions": "^0.16.0",
+        "@sentry/core": "10.74.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@sentry/bundler-plugins": {
+      "version": "10.74.0",
+      "resolved": "https://registry.npmjs.org/@sentry/bundler-plugins/-/bundler-plugins-10.74.0.tgz",
+      "integrity": "sha512-BEBMIlfABB0mS22/KAQXTYSpL+HXucsvvPW421hsIlh6IlBft1yx7wfrjl4uHu1BD54etOrVyZIoUr7AnXzP+A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/core": "^7.18.5",
+        "@sentry/cli": "^2.58.6",
+        "@sentry/core": "10.74.0",
+        "dotenv": "^17.4.2",
+        "find-up": "^5.0.0",
+        "glob": "^13.0.6",
+        "magic-string": "~0.30.8"
+      },
+      "engines": {
+        "node": ">= 18"
+      },
+      "peerDependencies": {
+        "rollup": ">=3.2.0",
+        "webpack": ">=5.0.0"
+      },
+      "peerDependenciesMeta": {
+        "rollup": {
+          "optional": true
+        },
+        "webpack": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@sentry/bundler-plugins/node_modules/find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@sentry/bundler-plugins/node_modules/locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "p-locate": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@sentry/bundler-plugins/node_modules/p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@sentry/bundler-plugins/node_modules/p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "p-limit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@sentry/bundler-plugins/node_modules/path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@sentry/cli": {
+      "version": "2.58.6",
+      "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.58.6.tgz",
+      "integrity": "sha512-baBcNPLLfUi9WuL+Tpri9BFaAdvugZIKelC5X0tt0Zdy+K0K+PCVSrnNmwMWU/HyaF/SEv6b6UHnXIdqanBlcg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "FSL-1.1-MIT",
+      "dependencies": {
+        "https-proxy-agent": "^5.0.0",
+        "node-fetch": "^2.6.7",
+        "progress": "^2.0.3",
+        "proxy-from-env": "^1.1.0",
+        "which": "^2.0.2"
+      },
+      "bin": {
+        "sentry-cli": "bin/sentry-cli"
+      },
+      "engines": {
+        "node": ">= 10"
+      },
+      "optionalDependencies": {
+        "@sentry/cli-darwin": "2.58.6",
+        "@sentry/cli-linux-arm": "2.58.6",
+        "@sentry/cli-linux-arm64": "2.58.6",
+        "@sentry/cli-linux-i686": "2.58.6",
+        "@sentry/cli-linux-x64": "2.58.6",
+        "@sentry/cli-win32-arm64": "2.58.6",
+        "@sentry/cli-win32-i686": "2.58.6",
+        "@sentry/cli-win32-x64": "2.58.6"
+      }
+    },
+    "node_modules/@sentry/cli-darwin": {
+      "version": "2.58.6",
+      "resolved": "https://registry.npmjs.org/@sentry/cli-darwin/-/cli-darwin-2.58.6.tgz",
+      "integrity": "sha512-udAVvcyfNa0R+95GvPz/+43/N3TC0TYKdkQ7D7jhPSzbcMc7l2fxRNN5yB3UpCA5fWFnW4toeaqwDBhb/Wh3LA==",
+      "dev": true,
+      "license": "FSL-1.1-MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@sentry/cli-linux-arm": {
+      "version": "2.58.6",
+      "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm/-/cli-linux-arm-2.58.6.tgz",
+      "integrity": "sha512-pD0LAt5PcUzAinBwvDqc66x9+2CabHEv486yP0gRjWO7SakbaxmfVq/EXd8VLq/Tzi39LAu422UYK1lpW3MILw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "FSL-1.1-MIT",
+      "optional": true,
+      "os": [
+        "linux",
+        "freebsd",
+        "android"
+      ],
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@sentry/cli-linux-arm64": {
+      "version": "2.58.6",
+      "resolved": "https://registry.npmjs.org/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.58.6.tgz",
+      "integrity": "sha512-q8mEcNNmeXMy5i+jWT30TVpH7LcP4HD21CD5XRSPAd/a912HF6EpK0ybf/1USO14WOhoXbAGi9txwaWabSe33g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "FSL-1.1-MIT",
+      "optional": true,
+      "os": [
+        "linux",
+        "freebsd",
+        "android"
+      ],
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@sentry/cli-linux-i686": {
+      "version": "2.58.6",
+      "resolved": "https://registry.npmjs.org/@sentry/cli-linux-i686/-/cli-linux-i686-2.58.6.tgz",
+      "integrity": "sha512-q8vNJi1eOV/4vxAFWBsEwLHoSYapaZHIf4j76KJGJXFKTkEbsjCOOsKbwUIBTQQhRgV4DFWh3ryfsPS/que4Kg==",
+      "cpu": [
+        "x86",
+        "ia32"
+      ],
+      "dev": true,
+      "license": "FSL-1.1-MIT",
+      "optional": true,
+      "os": [
+        "linux",
+        "freebsd",
+        "android"
+      ],
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@sentry/cli-linux-x64": {
+      "version": "2.58.6",
+      "resolved": "https://registry.npmjs.org/@sentry/cli-linux-x64/-/cli-linux-x64-2.58.6.tgz",
+      "integrity": "sha512-DZu956Mhi3ZRjTBe1WdbGV46ldVbA8d2rgp/fh51GsI25zjBHah4wZnPTSzpc+YqxU6pJpg579B/r3jrIK530Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "FSL-1.1-MIT",
+      "optional": true,
+      "os": [
+        "linux",
+        "freebsd",
+        "android"
+      ],
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@sentry/cli-win32-arm64": {
+      "version": "2.58.6",
+      "resolved": "https://registry.npmjs.org/@sentry/cli-win32-arm64/-/cli-win32-arm64-2.58.6.tgz",
+      "integrity": "sha512-nj0Ff/kmAB73EPDhR8B4O9r+NUHK5GkPCkGWC+kXVemqAJWL5jcJ5KdxG0l/S0z6RoEoltID8/43/B+TaMlT7A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "FSL-1.1-MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@sentry/cli-win32-i686": {
+      "version": "2.58.6",
+      "resolved": "https://registry.npmjs.org/@sentry/cli-win32-i686/-/cli-win32-i686-2.58.6.tgz",
+      "integrity": "sha512-WNZiDzPbgsEMQWq4avsQ391v/xWKJDIWWWo9GYl+N/w5qcYKkoDW7wQG7T9FasI6ENn68phChTOAPXXxbfAdOg==",
+      "cpu": [
+        "x86",
+        "ia32"
+      ],
+      "dev": true,
+      "license": "FSL-1.1-MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@sentry/cli-win32-x64": {
+      "version": "2.58.6",
+      "resolved": "https://registry.npmjs.org/@sentry/cli-win32-x64/-/cli-win32-x64-2.58.6.tgz",
+      "integrity": "sha512-R35WJ17oF4D2eqI1DR2sQQqr0fjRTt5xoP16WrTu91XM2lndRMFsnjh+/GttbxapLCBNlrjzia99MJ0PZHZpgA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "FSL-1.1-MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@sentry/cli/node_modules/isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/@sentry/cli/node_modules/which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "isexe": "^2.0.0"
+      },
+      "bin": {
+        "node-which": "bin/node-which"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/@sentry/conventions": {
+      "version": "0.16.0",
+      "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz",
+      "integrity": "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14"
+      }
+    },
+    "node_modules/@sentry/core": {
+      "version": "10.74.0",
+      "resolved": "https://registry.npmjs.org/@sentry/core/-/core-10.74.0.tgz",
+      "integrity": "sha512-u9rY8vcZfktccwm6LznfCZlqP5C9A+p76r4/pFS1grqpuTO0m21Cl8rosnlESrDGP/Xd9tfr91rWYk0jPH8jeQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@sentry/conventions": "^0.16.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@sentry/feedback": {
+      "version": "10.74.0",
+      "resolved": "https://registry.npmjs.org/@sentry/feedback/-/feedback-10.74.0.tgz",
+      "integrity": "sha512-WwUao9K2XgRIxTUTaBhm7aglhsyP7LQTGi2lxvFbXzlSagUzwew/EL2c5oqQ23Mjn94i5SY+rM4wMdgyWdtpAw==",
+      "license": "MIT",
+      "dependencies": {
+        "@sentry/core": "10.74.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@sentry/react": {
+      "version": "10.74.0",
+      "resolved": "https://registry.npmjs.org/@sentry/react/-/react-10.74.0.tgz",
+      "integrity": "sha512-eKSNCbLyvc/qk4rIrm1IQZnC1zYPMJBqOVTX4VQCbK+d6YnzLc8JuZsw3PaGeB2x1g3CGe4qtBd9HUvsvBD9aw==",
+      "license": "MIT",
+      "dependencies": {
+        "@sentry/browser": "10.74.0",
+        "@sentry/conventions": "^0.16.0",
+        "@sentry/core": "10.74.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "react": "^16.14.0 || 17.x || 18.x || 19.x"
+      }
+    },
+    "node_modules/@sentry/replay": {
+      "version": "10.74.0",
+      "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-10.74.0.tgz",
+      "integrity": "sha512-amIu0zKzhQJZoWmlwfFnn/uKRGyF+Tikph3lBgYwj5XbHv4U94PQXi9Ll8CYzzLVS7BkrCVc2Hl7RvpRg5M2Yg==",
+      "license": "MIT",
+      "dependencies": {
+        "@sentry/browser-utils": "10.74.0",
+        "@sentry/core": "10.74.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@sentry/replay-canvas": {
+      "version": "10.74.0",
+      "resolved": "https://registry.npmjs.org/@sentry/replay-canvas/-/replay-canvas-10.74.0.tgz",
+      "integrity": "sha512-HAdu4ViRsuOD5ifphQR24HugL0geZXV8LnGQVkA8vlzZWiuYgGNqDRoUWI75IgVcCYLYSaVBoKN+jTimdzG/sQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@sentry/core": "10.74.0",
+        "@sentry/replay": "10.74.0"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@sentry/vite-plugin": {
+      "version": "5.4.0",
+      "resolved": "https://registry.npmjs.org/@sentry/vite-plugin/-/vite-plugin-5.4.0.tgz",
+      "integrity": "sha512-fFJgCxs5hDyAm9BbZJ+LbA+LK2tjX5OoD0v0ARU4StR6KQmGUduoPs69yJ9AfqZ0om3Rlp5JDliiwFcNkasORA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@sentry/bundler-plugins": "^10.64.0"
+      },
+      "engines": {
+        "node": ">= 18"
+      }
+    },
     "node_modules/@sigma/edge-curve": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/@sigma/edge-curve/-/edge-curve-3.1.0.tgz",
@@ -2693,13 +3117,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/@standard-schema/spec": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
-      "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/@tailwindcss/node": {
       "version": "4.3.3",
       "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
@@ -3735,34 +4152,17 @@
         }
       }
     },
-    "node_modules/@vitest/expect": {
-      "version": "4.1.11",
-      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz",
-      "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@standard-schema/spec": "^1.1.0",
-        "@types/chai": "^5.2.2",
-        "@vitest/spy": "4.1.11",
-        "@vitest/utils": "4.1.11",
-        "chai": "^6.2.2",
-        "tinyrainbow": "^3.1.0"
-      },
-      "funding": {
-        "url": "https://opencollective.com/vitest"
-      }
-    },
     "node_modules/@vitest/mocker": {
-      "version": "4.1.11",
-      "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz",
-      "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==",
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.1.tgz",
+      "integrity": "sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vitest/spy": "4.1.11",
+        "@jridgewell/trace-mapping": "0.3.31",
+        "@vitest/spy": "5.0.1",
         "estree-walker": "^3.0.3",
-        "magic-string": "^0.30.21"
+        "magic-string": "^1.2.3"
       },
       "funding": {
         "url": "https://opencollective.com/vitest"
@@ -3780,81 +4180,26 @@
         }
       }
     },
-    "node_modules/@vitest/pretty-format": {
-      "version": "4.1.11",
-      "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz",
-      "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "tinyrainbow": "^3.1.0"
-      },
-      "funding": {
-        "url": "https://opencollective.com/vitest"
-      }
-    },
-    "node_modules/@vitest/runner": {
-      "version": "4.1.11",
-      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz",
-      "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@vitest/utils": "4.1.11",
-        "pathe": "^2.0.3"
-      },
-      "funding": {
-        "url": "https://opencollective.com/vitest"
-      }
-    },
-    "node_modules/@vitest/snapshot": {
-      "version": "4.1.11",
-      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz",
-      "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==",
+    "node_modules/@vitest/mocker/node_modules/magic-string": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.1.tgz",
+      "integrity": "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vitest/pretty-format": "4.1.11",
-        "@vitest/utils": "4.1.11",
-        "magic-string": "^0.30.21",
-        "pathe": "^2.0.3"
-      },
-      "funding": {
-        "url": "https://opencollective.com/vitest"
+        "@jridgewell/sourcemap-codec": "^1.6.0"
       }
     },
     "node_modules/@vitest/spy": {
-      "version": "4.1.11",
-      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz",
-      "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==",
-      "dev": true,
-      "license": "MIT",
-      "funding": {
-        "url": "https://opencollective.com/vitest"
-      }
-    },
-    "node_modules/@vitest/utils": {
-      "version": "4.1.11",
-      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz",
-      "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==",
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.1.tgz",
+      "integrity": "sha512-rbto/mF/SGERxEgYOek7Xm6B9b+y+mVoo+f4b2LymYO8zM1b7uB5nHuhVMTP2hxdzgxvGiZYGxGIaMvL5y180Q==",
       "dev": true,
       "license": "MIT",
-      "dependencies": {
-        "@vitest/pretty-format": "4.1.11",
-        "convert-source-map": "^2.0.0",
-        "tinyrainbow": "^3.1.0"
-      },
       "funding": {
         "url": "https://opencollective.com/vitest"
       }
     },
-    "node_modules/@vitest/utils/node_modules/convert-source-map": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
-      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/@vue/compiler-core": {
       "version": "3.5.42",
       "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.42.tgz",
@@ -3990,6 +4335,19 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/agent-base": {
+      "version": "6.0.2",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+      "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 6.0.0"
+      }
+    },
     "node_modules/ajv": {
       "version": "8.20.0",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
@@ -6102,6 +6460,24 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/glob": {
+      "version": "13.0.6",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
+      "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
+      "dev": true,
+      "license": "BlueOak-1.0.0",
+      "dependencies": {
+        "minimatch": "^10.2.2",
+        "minipass": "^7.1.3",
+        "path-scurry": "^2.0.2"
+      },
+      "engines": {
+        "node": "18 || 20 || >=22"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/glob-parent": {
       "version": "5.1.2",
       "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
@@ -6492,6 +6868,20 @@
         "url": "https://opencollective.com/express"
       }
     },
+    "node_modules/https-proxy-agent": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+      "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "agent-base": "6",
+        "debug": "4"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/human-signals": {
       "version": "8.0.1",
       "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
@@ -8510,6 +8900,16 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/minipass": {
+      "version": "7.1.3",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+      "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+      "dev": true,
+      "license": "BlueOak-1.0.0",
+      "engines": {
+        "node": ">=16 || 14 >=14.17"
+      }
+    },
     "node_modules/mnemonist": {
       "version": "0.39.8",
       "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz",
@@ -8572,6 +8972,52 @@
         "url": "https://opencollective.com/express"
       }
     },
+    "node_modules/node-fetch": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/node-fetch/node_modules/tr46": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/node-fetch/node_modules/webidl-conversions": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+      "dev": true,
+      "license": "BSD-2-Clause"
+    },
+    "node_modules/node-fetch/node_modules/whatwg-url": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "tr46": "~0.0.3",
+        "webidl-conversions": "^3.0.0"
+      }
+    },
     "node_modules/node-releases": {
       "version": "2.0.55",
       "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz",
@@ -8975,6 +9421,23 @@
       "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
       "license": "MIT"
     },
+    "node_modules/path-scurry": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
+      "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
+      "dev": true,
+      "license": "BlueOak-1.0.0",
+      "dependencies": {
+        "lru-cache": "^11.0.0",
+        "minipass": "^7.1.2"
+      },
+      "engines": {
+        "node": "18 || 20 || >=22"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
     "node_modules/path-to-regexp": {
       "version": "8.4.2",
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
@@ -8994,13 +9457,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/pathe": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
-      "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/picocolors": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -9148,6 +9604,16 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/progress": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/prompts": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -9389,6 +9855,13 @@
         "url": "https://opencollective.com/express"
       }
     },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/punycode": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -10577,11 +11050,14 @@
       "license": "MIT"
     },
     "node_modules/tinybench": {
-      "version": "2.9.0",
-      "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
-      "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+      "version": "6.1.4",
+      "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz",
+      "integrity": "sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==",
       "dev": true,
-      "license": "MIT"
+      "license": "MIT",
+      "engines": {
+        "node": ">=20.0.0"
+      }
     },
     "node_modules/tinyexec": {
       "version": "1.3.1",
@@ -10608,16 +11084,6 @@
         "url": "https://github.com/sponsors/SuperchupuDev"
       }
     },
-    "node_modules/tinyrainbow": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz",
-      "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=14.0.0"
-      }
-    },
     "node_modules/tldts": {
       "version": "7.4.13",
       "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.13.tgz",
@@ -11416,38 +11882,31 @@
       }
     },
     "node_modules/vitest": {
-      "version": "4.1.11",
-      "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz",
-      "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==",
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.1.tgz",
+      "integrity": "sha512-iA95lQbKEkvrtTkdAgnWbXfbipWiiWe/hDl2P5tMi6WFwD76G0NxXAGp/M9EOcYupeGJRr6wppMc7CoA41TQjg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vitest/expect": "4.1.11",
-        "@vitest/mocker": "4.1.11",
-        "@vitest/pretty-format": "4.1.11",
-        "@vitest/runner": "4.1.11",
-        "@vitest/snapshot": "4.1.11",
-        "@vitest/spy": "4.1.11",
-        "@vitest/utils": "4.1.11",
-        "es-module-lexer": "^2.0.0",
-        "expect-type": "^1.3.0",
-        "magic-string": "^0.30.21",
-        "obug": "^2.1.1",
-        "pathe": "^2.0.3",
-        "picomatch": "^4.0.3",
-        "std-env": "^4.0.0-rc.1",
-        "tinybench": "^2.9.0",
-        "tinyexec": "^1.0.2",
-        "tinyglobby": "^0.2.15",
-        "tinyrainbow": "^3.1.0",
-        "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+        "@types/chai": "^5.2.2",
+        "@vitest/mocker": "5.0.1",
+        "chai": "^6.2.2",
+        "es-module-lexer": "^2.3.2",
+        "expect-type": "^1.4.0",
+        "magic-string": "^1.2.3",
+        "obug": "^2.1.4",
+        "picomatch": "^4.0.7",
+        "std-env": "^4.2.0",
+        "tinybench": "6.1.4",
+        "tinyexec": "1.3.0",
+        "tinyglobby": "^0.2.17",
         "why-is-node-running": "^2.3.0"
       },
       "bin": {
         "vitest": "vitest.mjs"
       },
       "engines": {
-        "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+        "node": "^22.12.0 || ^24.0.0 || >=26.0.0"
       },
       "funding": {
         "url": "https://opencollective.com/vitest"
@@ -11455,16 +11914,16 @@
       "peerDependencies": {
         "@edge-runtime/vm": "*",
         "@opentelemetry/api": "^1.9.0",
-        "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
-        "@vitest/browser-playwright": "4.1.11",
-        "@vitest/browser-preview": "4.1.11",
-        "@vitest/browser-webdriverio": "4.1.11",
-        "@vitest/coverage-istanbul": "4.1.11",
-        "@vitest/coverage-v8": "4.1.11",
-        "@vitest/ui": "4.1.11",
+        "@types/node": "^22.0.0 || >=24.0.0",
+        "@vitest/browser-playwright": "5.0.1",
+        "@vitest/browser-preview": "5.0.1",
+        "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0",
+        "@vitest/coverage-istanbul": "5.0.1",
+        "@vitest/coverage-v8": "5.0.1",
+        "@vitest/ui": "5.0.1",
         "happy-dom": "*",
         "jsdom": "*",
-        "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+        "vite": "^6.4.0 || ^7.0.0 || ^8.0.0"
       },
       "peerDependenciesMeta": {
         "@edge-runtime/vm": {
@@ -11505,6 +11964,26 @@
         }
       }
     },
+    "node_modules/vitest/node_modules/magic-string": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.1.tgz",
+      "integrity": "sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.6.0"
+      }
+    },
+    "node_modules/vitest/node_modules/tinyexec": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz",
+      "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/vue": {
       "version": "3.5.42",
       "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz",
@@ -11679,6 +12158,19 @@
       "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
       "license": "ISC"
     },
+    "node_modules/yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/yocto-spinner": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.2.2.tgz",

+ 3 - 1
package.json

@@ -26,6 +26,7 @@
     "@milkdown/react": "^7.22.1",
     "@milkdown/theme-nord": "^7.22.1",
     "@react-sigma/core": "^5.0.6",
+    "@sentry/react": "^10.74.0",
     "@sigma/edge-curve": "^3.1.0",
     "@tailwindcss/vite": "^4.3.3",
     "@tauri-apps/api": "^2.11.1",
@@ -67,6 +68,7 @@
     "zustand": "^5.0.15"
   },
   "devDependencies": {
+    "@sentry/vite-plugin": "^5.4.0",
     "@tauri-apps/cli": "^2.11.4",
     "@types/js-yaml": "^4.0.9",
     "@types/node": "^24.13.4",
@@ -77,6 +79,6 @@
     "jsdom": "^29.1.1",
     "typescript": "^5.9.3",
     "vite": "^8.3.0",
-    "vitest": "^4.1.11"
+    "vitest": "^5.0.1"
   }
 }

+ 2 - 0
src/components/error-boundary.tsx

@@ -1,4 +1,5 @@
 import { Component, type ReactNode } from "react"
+import { captureAppException } from "@/instrument"
 import { isTauri } from "@/lib/platform"
 
 interface Props {
@@ -24,6 +25,7 @@ export class ErrorBoundary extends Component<Props, State> {
 
   componentDidCatch(error: Error, info: React.ErrorInfo) {
     console.error("ErrorBoundary caught:", error, info.componentStack)
+    captureAppException(error)
     this.setState({ componentStack: info.componentStack ?? null })
     if (isTauri()) {
       try {

+ 38 - 0
src/instrument.ts

@@ -0,0 +1,38 @@
+import * as Sentry from "@sentry/react"
+
+const SENTRY_DSN =
+  import.meta.env.VITE_SENTRY_DSN ??
+  "https://21511c3bd5d4d5cd36153b550347aea4@o4512089544720384.ingest.us.sentry.io/4512089608093696"
+
+if (import.meta.env.MODE !== "test") {
+  Sentry.init({
+    dsn: SENTRY_DSN,
+    environment: import.meta.env.PROD ? "production" : "development",
+    release: `qmai@${__APP_VERSION__}`,
+    // Developer includes errors + tracing. Logs/metrics APIs default on in SDK 10
+    // but we do not use them; profiling is PAYG-only; replay is unused.
+    enableLogs: false,
+    enableMetrics: false,
+    profilesSampleRate: 0,
+    replaysSessionSampleRate: 0,
+    replaysOnErrorSampleRate: 0,
+    integrations: [Sentry.browserTracingIntegration()],
+    tracesSampleRate: import.meta.env.PROD ? 0.2 : 1.0,
+    // Do not attach sentry-trace headers to LLM / third-party fetches.
+    tracePropagationTargets: [],
+    dataCollection: {
+      userInfo: false,
+      httpBodies: [],
+    },
+    beforeBreadcrumb(breadcrumb) {
+      if (typeof breadcrumb.message === "string" && breadcrumb.message.length > 200) {
+        breadcrumb.message = `${breadcrumb.message.slice(0, 200)}…`
+      }
+      return breadcrumb
+    },
+  })
+}
+
+export function captureAppException(error: unknown): void {
+  Sentry.captureException(error)
+}

+ 16 - 9
src/main.tsx

@@ -1,14 +1,21 @@
-import React from "react";
-import ReactDOM from "react-dom/client";
-import App from "./App";
-import "./index.css";
-import "@/i18n";
-import { ToastProvider } from "@/lib/toast";
+import "./instrument"
 
-ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
+import React from "react"
+import ReactDOM from "react-dom/client"
+import * as Sentry from "@sentry/react"
+import App from "./App"
+import "./index.css"
+import "@/i18n"
+import { ToastProvider } from "@/lib/toast"
+
+ReactDOM.createRoot(document.getElementById("root") as HTMLElement, {
+  onUncaughtError: Sentry.reactErrorHandler(),
+  onCaughtError: Sentry.reactErrorHandler(),
+  onRecoverableError: Sentry.reactErrorHandler(),
+}).render(
   <React.StrictMode>
     <ToastProvider>
       <App />
     </ToastProvider>
-  </React.StrictMode>
-);
+  </React.StrictMode>,
+)

+ 22 - 1
vite.config.ts

@@ -3,16 +3,35 @@ import { readFileSync } from "fs"
 import { defineConfig } from "vite"
 import react from "@vitejs/plugin-react"
 import tailwindcss from "@tailwindcss/vite"
+import { sentryVitePlugin } from "@sentry/vite-plugin"
 
 const host = process.env.TAURI_DEV_HOST
 
 // Read version from package.json at config-load time so the Settings
 // UI can show the running app version without duplicating the string.
 const pkgJson = JSON.parse(readFileSync(path.resolve(import.meta.dirname, "package.json"), "utf-8"))
+const sentryRelease = `qmai@${pkgJson.version}`
+const sentryAuthToken = process.env.SENTRY_AUTH_TOKEN
+const uploadSentrySourcemaps = Boolean(sentryAuthToken)
 
 // https://vitejs.dev/config/
 export default defineConfig(async () => ({
-  plugins: [react(), tailwindcss()],
+  plugins: [
+    react(),
+    tailwindcss(),
+    // Last plugin. No token → no maps, no upload (local / CI check).
+    sentryVitePlugin({
+      disable: !uploadSentrySourcemaps,
+      org: process.env.SENTRY_ORG || "qmai-c5",
+      project: process.env.SENTRY_PROJECT || "qmai",
+      authToken: sentryAuthToken,
+      telemetry: false,
+      release: { name: sentryRelease },
+      sourcemaps: {
+        filesToDeleteAfterUpload: ["./dist/**/*.map"],
+      },
+    }),
+  ],
 
   resolve: {
     alias: { "@": path.resolve(import.meta.dirname, "./src") },
@@ -28,6 +47,8 @@ export default defineConfig(async () => ({
   // 1. prevent vite from obscuring rust errors
   clearScreen: false,
   build: {
+    // Hidden maps only when uploading; never ship *.map in the Tauri bundle.
+    sourcemap: uploadSentrySourcemaps ? "hidden" : false,
     chunkSizeWarningLimit: 700,
     modulePreload: {
       resolveDependencies(_filename: string, deps: string[]) {