From 816e3875c2c2a5a5d48897ee0de1389190b4a4ca Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sat, 5 Sep 2026 19:53:09 +0200 Subject: [PATCH] feat(admin): add production error center and refresh CMS dependencies --- .gitattributes | 2 + .gitea/workflows/ci.yaml | 3 + .gitignore | 2 + docs/operations/cms-error-center.md | 65 ++ knip.json | 2 +- next.config.ts | 4 + package.json | 40 +- pnpm-lock.yaml | 892 +++++++++++------- pnpm-workspace.yaml | 75 +- renovate.json | 41 +- scripts/copy-editor-assets.mjs | 22 + scripts/prepare-hooks.mjs | 5 + src/app/(site)/error.tsx | 11 +- .../admin/devops/cms-errors/actions.test.ts | 39 + src/app/admin/devops/cms-errors/actions.ts | 17 + src/app/admin/devops/cms-errors/page.test.ts | 32 + src/app/admin/devops/cms-errors/page.tsx | 181 ++++ src/app/admin/devops/errors/page.tsx | 3 + src/app/admin/devops/page.tsx | 3 + src/app/admin/error.tsx | 11 +- src/app/admin/permissions/[id]/error.tsx | 11 +- src/app/api/diagnostics/errors/route.test.ts | 79 ++ src/app/api/diagnostics/errors/route.ts | 66 ++ src/app/error.tsx | 11 +- src/app/global-error.tsx | 11 +- src/app/layout.tsx | 2 - src/components/admin/rich-text.tsx | 6 +- src/components/smooth-scroll.tsx | 34 - .../housekeeping/domains/system/manifest.ts | 1 + .../foundation-source-contract.test.ts | 4 +- .../housekeeping/foundation/registry.test.ts | 1 + .../migration/discover-legacy-pages.test.ts | 2 +- .../housekeeping/migration/matrix.test.ts | 4 +- .../housekeeping/migration/system.test.ts | 4 +- src/features/housekeeping/migration/system.ts | 12 + src/instrumentation-client.ts | 8 + src/lib/api-handler.ts | 4 +- src/lib/browser-errors.test.ts | 28 + src/lib/browser-errors.ts | 34 + src/lib/error-monitor.test.ts | 56 ++ src/lib/error-monitor.ts | 209 ++++ src/lib/logger.ts | 19 +- src/lib/observability-contract.test.ts | 20 +- src/lib/report-error.ts | 3 +- src/lib/server-log.ts | 7 +- src/lib/services/furni-job-worker.ts | 4 +- vitest.config.ts | 2 + 47 files changed, 1545 insertions(+), 547 deletions(-) create mode 100644 .gitattributes create mode 100644 docs/operations/cms-error-center.md create mode 100644 scripts/copy-editor-assets.mjs create mode 100644 scripts/prepare-hooks.mjs create mode 100644 src/app/admin/devops/cms-errors/actions.test.ts create mode 100644 src/app/admin/devops/cms-errors/actions.ts create mode 100644 src/app/admin/devops/cms-errors/page.test.ts create mode 100644 src/app/admin/devops/cms-errors/page.tsx create mode 100644 src/app/api/diagnostics/errors/route.test.ts create mode 100644 src/app/api/diagnostics/errors/route.ts delete mode 100644 src/components/smooth-scroll.tsx create mode 100644 src/instrumentation-client.ts create mode 100644 src/lib/browser-errors.test.ts create mode 100644 src/lib/browser-errors.ts create mode 100644 src/lib/error-monitor.test.ts create mode 100644 src/lib/error-monitor.ts diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..570a2edb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +.husky/* text eol=lf +*.sh text eol=lf diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index 26892add..0e641550 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -31,6 +31,9 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Dependency security audit + run: pnpm deps:audit + - name: Lint run: pnpm biome:lint diff --git a/.gitignore b/.gitignore index fa734ee4..1dbed918 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ test-results/ playwright-report/ blob-report/ .aider* + +/public/vendor/tinymce/ diff --git a/docs/operations/cms-error-center.md b/docs/operations/cms-error-center.md new file mode 100644 index 00000000..71d086d2 --- /dev/null +++ b/docs/operations/cms-error-center.md @@ -0,0 +1,65 @@ +# CMS error center and dependency maintenance + +Open **HK > DevOps > CMS error center** (`/admin/devops/cms-errors`). +The previous `/admin/devops/errors` page still displays emulator errors. + +## Access and workflow + +- Read: existing `admin.devops.view` permission, checked on the server. +- Mark resolved: `admin.devops.edit`, checked again in the server action; recorded in the staff audit log. +- Search by event reference, Next.js digest, route, message or deployment version. +- Expand a group for its latest stack, sanitized context and occurrence references. +- Marking a group resolved does not delete evidence. A later occurrence reopens it. +- Refresh is manual so inspecting an expanded error is not interrupted by polling. + +## What is collected + +Pino `logger.error`, `logServerError`, unexpected action errors, Next.js request +failures, React error boundaries, uncaught browser errors and unhandled browser +promise rejections. API wrapper failures return an `errorId`; Next.js boundary +errors can be correlated by their digest. Release identifiers come from the build. +Browser reports retain their own client release separately from the receiving server. + +Reports are stored in `storage/cms-errors`, using the existing persistent storage +mount. No third-party service, token or new database table is required. +Storage does not depend on database availability; viewing the HK and its permission +checks still require authentication/database availability. Server console logs +remain the fallback when the CMS itself cannot serve requests. + +Retention: seven days; approximately 10 MB/day, 100 queued server writes, and the +latest 1,000 events in the viewer. Limits are per CMS process/storage volume; +this is intended for the existing single-instance deployment. Daily expiry runs +on the next write. The browser endpoint is same-origin, body-size bounded and +rate-limited. Browser reports are untrusted observations and cannot grant access. +Known credential patterns and URL parameters are redacted; arbitrary object +metadata and request bodies are excluded. Avoid putting personal data in error +messages: this is pattern-based redaction, not a universal data-loss filter. + +This does not collect historical console logs, process crashes before framework +startup, nginx failures, all `console.error` calls, or every handled business +validation error. A browser stack may point at minified chunks; private source-map +symbolication and distributed traces are not part of this local viewer. A recorded +stack is diagnostic evidence, not an automatic root-cause determination. + +## Dependencies + +`pnpm install --frozen-lockfile`, `pnpm deps:audit`, `pnpm typecheck`, `pnpm test`, +`pnpm knip`, `pnpm biome:lint`, and `pnpm build` are the verification sequence. +`pnpm analyze --output` writes a Next.js bundle analysis (not an application build). + +TinyMCE 8.9 is pinned in pnpm. `pnpm assets:editor` copies its runtime files and +license notices to ignored `public/vendor/tinymce`; dev/build run this first. +The Docker build includes the generated assets. The editor retains its existing +HTML fields and toolbar; validate saved content and preview when upgrading it. + +Lenis has been removed; the public site now uses native scrolling. Other used +runtime libraries remain. Vitest and its coverage provider are upgraded together; +`clearMocks: false` preserves initialization-time permission contract assertions. + +Renovate uses separate development/UI/Vitest groups with manual merge and a +three-day release age. The existing external Gitea bot configuration is retained. +Node/pnpm upgrades remain coordinated with Docker and the runner toolchain. +Obsolete global overrides were removed; a scoped esbuild override remains because +Drizzle Kit's loader still resolves a vulnerable legacy development-server build. +The two deprecated esbuild-kit packages remain upstream dependencies of Drizzle +Kit; replacing the ORM is not warranted for this tooling issue. diff --git a/knip.json b/knip.json index f7774b60..2b614c66 100644 --- a/knip.json +++ b/knip.json @@ -12,6 +12,6 @@ "scripts/furni-diagnose-now.ts" ], "project": ["src/**/*.{ts,tsx,css}", "scripts/**/*.{ts,js}"], - "ignoreDependencies": ["@sentry/nextjs", "pino-pretty", "husky"], + "ignoreDependencies": ["pino-pretty"], "ignoreBinaries": ["sendmail"] } diff --git a/next.config.ts b/next.config.ts index 618d221b..201d4d3d 100644 --- a/next.config.ts +++ b/next.config.ts @@ -27,6 +27,10 @@ const securityHeaders = [ const nextConfig: NextConfig = { output: "standalone", + env: { + NEXT_PUBLIC_CMS_RELEASE: + process.env.NEXT_DEPLOYMENT_ID?.trim() || getGitCommit() || "unknown", + }, deploymentId: process.env.NEXT_DEPLOYMENT_ID?.trim() || getGitCommit(), distDir: process.env.NEXT_DIST_DIR?.trim() || ".next", reactStrictMode: true, diff --git a/package.json b/package.json index 6468d2bb..920bb7db 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ }, "packageManager": "pnpm@11.25.0+sha512.5cde925b4f075f725eb71fbae18a42ffe784524789f19b61c731cb8721ec28aaee160e01a8d5af4fedb2a42cdbf300efe23db356b0d4a17b4d63e11f8ab7c956", "scripts": { - "dev": "next dev", - "build": "next build", + "dev": "pnpm assets:editor && next dev", + "build": "pnpm assets:editor && next build", "start": "next start", "toolchain:check": "node scripts/check-node-toolchain.mjs", "lint": "biome check .", @@ -25,14 +25,18 @@ "db:migrate:status": "tsx scripts/apply-migrations.ts --status", "db:studio": "drizzle-kit studio", "hk:matrix:check": "tsx scripts/verify-housekeeping-matrix.ts", - "test:housekeeping": "vitest run --coverage.enabled=false src/features/housekeeping src/lib/admin-theme-source-audit.test.ts src/lib/admin/authorization-contract.test.ts" + "test:housekeeping": "vitest run --coverage.enabled=false src/features/housekeeping src/lib/admin-theme-source-audit.test.ts src/lib/admin/authorization-contract.test.ts", + "prepare": "node scripts/prepare-hooks.mjs", + "assets:editor": "node scripts/copy-editor-assets.mjs", + "deps:audit": "pnpm audit --audit-level=high", + "analyze": "next experimental-analyze" }, "lint-staged": { "*.{js,ts,jsx,tsx,json}": "biome check --write --no-errors-on-unmatched", "*.{ts,tsx}": "node scripts/check-admin-colors.mjs" }, "dependencies": { - "@base-ui/react": "1.7.0", + "@base-ui/react": "1.8.0", "@dnd-kit/core": "6.3.1", "@dnd-kit/sortable": "10.0.0", "@dnd-kit/utilities": "3.2.2", @@ -49,46 +53,46 @@ "jpeg-js": "0.4.4", "jsonc-parser": "3.3.1", "jszip": "3.10.1", - "lenis": "1.3.26", - "lucide-react": "1.38.0", + "lucide-react": "1.41.0", "lzma-wasm": "1.0.7", - "motion": "13.1.1", + "motion": "13.2.0", "music-metadata": "11.15.0", - "mysql2": "3.24.2", + "mysql2": "3.24.3", "next": "16.3.4", "next-auth": "5.0.0-beta.32", - "next-intl": "4.14.1", + "next-intl": "4.14.2", "otplib": "13.5.0", "pino": "10.3.1", "react": "19.2.8", "react-dom": "19.2.8", "react-hook-form": "7.87.0", - "resend": "6.25.0", + "resend": "6.26.0", "server-only": "0.0.1", "sharp": "^0.35.4", "sonner": "2.0.8", "tailwind-merge": "3.6.0", - "zod": "4.5.4" + "zod": "4.5.4", + "tinymce": "8.9.0" }, "devDependencies": { - "@biomejs/biome": "2.5.11", + "@biomejs/biome": "2.5.12", "@playwright/test": "^1.62.1", "@tailwindcss/forms": "0.5.11", "@tailwindcss/postcss": "4.3.3", "@tailwindcss/typography": "0.5.20", - "@types/node": "26.4.0", + "@types/node": "26.4.1", "@types/react": "19.2.18", - "@types/react-dom": "19.2.5", - "@vitest/coverage-v8": "4.1.11", + "@types/react-dom": "19.2.7", + "@vitest/coverage-v8": "5.0.0", "drizzle-kit": "0.31.10", "husky": "9.1.7", - "knip": "6.33.0", + "knip": "6.34.0", "lint-staged": "17.4.1", "pino-pretty": "13.1.3", - "postcss": "^8.5.26", + "postcss": "8.5.28", "tailwindcss": "4.3.3", "tsx": "4.23.13", "typescript": "7.0.2", - "vitest": "4.1.11" + "vitest": "5.0.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dd86480b..a54e2485 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,26 +5,17 @@ settings: excludeLinksFromLockfile: false overrides: - glob: ^11.0.3 - rimraf: ^6.0.1 - uuid: ^11.1.0 - fast-uri: ^3.1.3 - '@hono/node-server': ^1.19.13 - postcss: ^8.5.26 - tmp: ^0.2.6 - sharp: ^0.35.4 - brace-expansion: ^5.0.8 + '@esbuild-kit/core-utils>esbuild': ^0.25.9 '@types/react': 19.2.18 - '@types/react-dom': 19.2.5 - esbuild: ^0.25.9 + '@types/react-dom': 19.2.7 importers: .: dependencies: '@base-ui/react': - specifier: 1.7.0 - version: 1.7.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + specifier: 1.8.0 + version: 1.8.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@dnd-kit/core': specifier: 6.3.1 version: 6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) @@ -48,13 +39,13 @@ importers: version: 2.1.1 cmdk: specifier: 1.1.1 - version: 1.1.1(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 1.1.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) croner: specifier: 10.0.1 version: 10.0.1 drizzle-orm: specifier: 0.45.2 - version: 0.45.2(mysql2@3.24.2(@types/node@26.4.0)) + version: 0.45.2(mysql2@3.24.3(@types/node@26.4.1)) hash-wasm: specifier: 4.12.0 version: 4.12.0 @@ -73,33 +64,30 @@ importers: jszip: specifier: 3.10.1 version: 3.10.1 - lenis: - specifier: 1.3.26 - version: 1.3.26(react@19.2.8) lucide-react: - specifier: 1.38.0 - version: 1.38.0(react@19.2.8) + specifier: 1.41.0 + version: 1.41.0(react@19.2.8) lzma-wasm: specifier: 1.0.7 version: 1.0.7 motion: - specifier: 13.1.1 - version: 13.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + specifier: 13.2.0 + version: 13.2.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) music-metadata: specifier: 11.15.0 version: 11.15.0(supports-color@7.2.0) mysql2: - specifier: 3.24.2 - version: 3.24.2(@types/node@26.4.0) + specifier: 3.24.3 + version: 3.24.3(@types/node@26.4.1) next: specifier: 16.3.4 - version: 16.3.4(@playwright/test@1.62.1)(@types/node@26.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + version: 16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) next-auth: specifier: 5.0.0-beta.32 - version: 5.0.0-beta.32(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) + version: 5.0.0-beta.32(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) next-intl: - specifier: 4.14.1 - version: 4.14.1(@swc/helpers@0.5.23)(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) + specifier: 4.14.2 + version: 4.14.2(@swc/helpers@0.5.23)(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8) otplib: specifier: 13.5.0 version: 13.5.0 @@ -116,27 +104,30 @@ importers: specifier: 7.87.0 version: 7.87.0(react@19.2.8) resend: - specifier: 6.25.0 - version: 6.25.0 + specifier: 6.26.0 + version: 6.26.0 server-only: specifier: 0.0.1 version: 0.0.1 sharp: specifier: ^0.35.4 - version: 0.35.4(@types/node@26.4.0) + version: 0.35.4(@types/node@26.4.1) sonner: specifier: 2.0.8 version: 2.0.8(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) tailwind-merge: specifier: 3.6.0 version: 3.6.0 + tinymce: + specifier: 8.9.0 + version: 8.9.0 zod: specifier: 4.5.4 version: 4.5.4 devDependencies: '@biomejs/biome': - specifier: 2.5.11 - version: 2.5.11 + specifier: 2.5.12 + version: 2.5.12 '@playwright/test': specifier: ^1.62.1 version: 1.62.1 @@ -150,17 +141,17 @@ importers: specifier: 0.5.20 version: 0.5.20(tailwindcss@4.3.3) '@types/node': - specifier: 26.4.0 - version: 26.4.0 + specifier: 26.4.1 + version: 26.4.1 '@types/react': specifier: 19.2.18 version: 19.2.18 '@types/react-dom': - specifier: 19.2.5 - version: 19.2.5(@types/react@19.2.18) + specifier: 19.2.7 + version: 19.2.7(@types/react@19.2.18) '@vitest/coverage-v8': - specifier: 4.1.11 - version: 4.1.11(vitest@4.1.11) + specifier: 5.0.0 + version: 5.0.0(vitest@5.0.0) drizzle-kit: specifier: 0.31.10 version: 0.31.10 @@ -168,8 +159,8 @@ importers: specifier: 9.1.7 version: 9.1.7 knip: - specifier: 6.33.0 - version: 6.33.0 + specifier: 6.34.0 + version: 6.34.0 lint-staged: specifier: 17.4.1 version: 17.4.1 @@ -177,8 +168,8 @@ importers: specifier: 13.1.3 version: 13.1.3 postcss: - specifier: ^8.5.26 - version: 8.5.26 + specifier: 8.5.28 + version: 8.5.28 tailwindcss: specifier: 4.3.3 version: 4.3.3 @@ -189,8 +180,8 @@ importers: specifier: 7.0.2 version: 7.0.2 vitest: - specifier: 4.1.11 - version: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(jsdom@30.0.1(@noble/hashes@2.4.0))(vite@8.2.2(@types/node@26.4.0)(esbuild@0.25.12)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)) + specifier: 5.0.0 + version: 5.0.0(@types/node@26.4.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1(@noble/hashes@2.4.0))(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)) packages: @@ -241,8 +232,8 @@ packages: resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} - '@base-ui/react@1.7.0': - resolution: {integrity: sha512-j+8QjX44C32jrXD/qyEAGpFr70FRpGL2CY61mQd9nBPWN737CK0xxD1ceJ055rW4RtdvFDT1e7otzdlfxvsYug==} + '@base-ui/react@1.8.0': + resolution: {integrity: sha512-P0/1sxo6SBVZOklKMIedvTWqw2s2IQzi9x5bIVsXu980cuSOD4NeuRSs+/L7LZQfDkZP/uRZyGPyfFl/B1oH+Q==} engines: {node: '>=14.0.0'} peerDependencies: '@date-fns/tz': ^1.2.0 @@ -258,8 +249,8 @@ packages: date-fns: optional: true - '@base-ui/utils@0.3.2': - resolution: {integrity: sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ==} + '@base-ui/utils@0.4.0': + resolution: {integrity: sha512-bO9fz25kKtPf+aZVyfQrC0PDmJdmVni31W2hCS5/Owb+inwdIL3XU26pCPRPlt4LSxZrBgLwubXQXQlKaFEZzw==} peerDependencies: '@types/react': 19.2.18 react: ^17 || ^18 || ^19 @@ -272,59 +263,59 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@biomejs/biome@2.5.11': - resolution: {integrity: sha512-Tj0dnkLPdW0ASjHfj2D/ZkkvPU2wrFmnE1jWTD2xzV1ycapV1DutbYXk4NDnR3rYTi1ZCbNFD4G2gRMEY65WaA==} + '@biomejs/biome@2.5.12': + resolution: {integrity: sha512-Lw4VHZRebrReBBnlHa12JQjnIBm3JJAA55PDB9LbBVBF0q4RYphm6KfmIjqtPhf61MxZ5Q9KoK8R8x+7per5Aw==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.5.11': - resolution: {integrity: sha512-6SGZxoKbXvUjMn1t6A98HqWISPnGNbYs0R/Rt2JarmXBSev+lva4QxUMWEBX9lX1Wo1XTJ78uk5xVDtG58SRZg==} + '@biomejs/cli-darwin-arm64@2.5.12': + resolution: {integrity: sha512-lCRY1rwgNeWNgTr4DI/u6ZwXTRwRLHAvbaio1YLLGS+4r1nhvB2ssyPqIpfUSmRveNfv0fn/N58C7CAdK2XVrg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.5.11': - resolution: {integrity: sha512-nYkXY7tLBEgnGbYapDKAyKzgt44ZEyG+AKalvTXtCWKYgepI9dw327q+cVgedxm+Udi1ZzHKUyZrIusHi/KQbw==} + '@biomejs/cli-darwin-x64@2.5.12': + resolution: {integrity: sha512-vhPgwnh+6tN3ArdAXuET99xaNbFt7CG82Bqn+omHVLC5xdVx45JsYjGPmUIGNzjDek5XdNCP1HKksK7fn8+3bQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.5.11': - resolution: {integrity: sha512-qhyZUMyCbWYFV2bAwRNVvfMVZ+hv7WYl6mossGrxC+uiQQXhvsuWWU8zz6jYX0mChZd9MgQZbm4vozTmG/5iGw==} + '@biomejs/cli-linux-arm64-musl@2.5.12': + resolution: {integrity: sha512-couHYjFLL5uuI8ne6zhT7KwEsXo5YP7ry/2xmEqah7qanu0YmfDi3mwJg47YXSuv/NpZj22CZzcRH/5c4gjPSQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [musl] - '@biomejs/cli-linux-arm64@2.5.11': - resolution: {integrity: sha512-3PVLSTD9RR73rvVPt5G3T1gc+ycggWEGfTD7RvzzbtcDPD27NxgxBbAFfpm7DXJKW6VLHWE1lLMGvFt2Qxjcow==} + '@biomejs/cli-linux-arm64@2.5.12': + resolution: {integrity: sha512-2gp8aVwXYKdAtmBfRFCUuyDMcfN1ahHqUkGfLYrZlNRFmryMATLVvJgWKvyA8wu4Rwn5OSxM1UcUmOuOFNGeBQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] libc: [glibc] - '@biomejs/cli-linux-x64-musl@2.5.11': - resolution: {integrity: sha512-oRRlrchG5EfrEL/EmtT1qUjSNHk3/5LGeZhQqADBBAJF1b1ET6964xEKe7aGlGARzDfza8H/seEsFJl7S6Ql9w==} + '@biomejs/cli-linux-x64-musl@2.5.12': + resolution: {integrity: sha512-8A0oDW58/w9f/PQNYuq0sGUZtGtGrkNF4Z6n0PUoXpLCshi85vtKTv1XSznQawhdE4MXJ8ufpzHXyLFe87M/+w==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [musl] - '@biomejs/cli-linux-x64@2.5.11': - resolution: {integrity: sha512-JOytptlsgM33B2MMFUg8iBrb4IKpbD5JnJrSeYiaFEeAj4vuXx0iQSQZ4qK7sqyMtfjZxxPdNdMZZVL4y/mFyA==} + '@biomejs/cli-linux-x64@2.5.12': + resolution: {integrity: sha512-SnvOs3TSTiuia4SQOUNe1aWC9RT4+YkjcKnOhL/nsKOV0k5ycgBkDzF0lUxKn1V7Q8CLTRq6iV23ZAivHomRoA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] libc: [glibc] - '@biomejs/cli-win32-arm64@2.5.11': - resolution: {integrity: sha512-e49E6K9hzH/ohJNx8Y26mY8HaV4I4ZViIeoqhKsmoXLKHhQnMeBAVqCgsGf2Wa3lXlS7RkporDXMHHWkzvZzFw==} + '@biomejs/cli-win32-arm64@2.5.12': + resolution: {integrity: sha512-b9vtoZFsuZt1pdjNwJvXl0f+BpayRzV008uS2+JpmwIKdSE2qdu4A/l04FESwLoou5g2E/Qlec0xwJydZplH+A==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.5.11': - resolution: {integrity: sha512-QSQr/KjOgXA7OzXJUWS+oguKyAZ3Q0l/lnlDGbu397eKo83atuWUjBPJrsqbKNF6CARGw8XXJLGzpHC8Ryhd4Q==} + '@biomejs/cli-win32-x64@2.5.12': + resolution: {integrity: sha512-B1R/l+CwEpKFSuqiwePzPNRk1EiJN8kc0UhdafNz6MZN9v5OFP9HYP1irptvWzHrwVI4blVNGMbxc5zt70m3IA==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -397,14 +388,14 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} - '@eloqnt/config@0.0.2': - resolution: {integrity: sha512-xz86UU1TJQPSzb0XJ3PkoyDlv3wdIiHfuu2wD/8m9ponHx0uSasYRDj4S+dpwQkIQaPYibN1oRr5HBll2GQkog==} + '@eloqnt/config@0.1.0': + resolution: {integrity: sha512-SLR6ZSHxu6XdZtmOz3xRmC3gsY/sCzqtLMD1gxbW2I3XXPBbupwL5RZbkLU/NcZm74AKZrIUPfz3CtEJfxUKqQ==} - '@eloqnt/format-json@0.0.3': - resolution: {integrity: sha512-zgDUVryBYQyM0WZGAoVr+FXjrzRjKkBGJran0E3i5LBcvTycl9ScFVhkF7aRBg8mHrU9xxvclUtSDIYg2mRTQg==} + '@eloqnt/format-json@0.1.0': + resolution: {integrity: sha512-4Pkb+HyzgWNJXoU48aJhUyNZSjZvMGooPCNG1ZFmBm55xTiNNPQCNXH66Ob6sXS6RvbWZrRpCf+Uo/Jz9K9g+A==} - '@eloqnt/format-po@0.0.3': - resolution: {integrity: sha512-4r36qEd7KJFV+IMKUv5czNXbGVys8ahxlQKWoUDV20eGl4Oxr+YZ2O5IEQU09S+d7FvPJ1USnyc/wtCLNCo1Ng==} + '@eloqnt/format-po@0.1.0': + resolution: {integrity: sha512-odgtuICWs67GdkNqY51/d2vaIukuOdlGalKASUGkI5c4cyWOmv/WIVyk5gZyn3xr614RzwWp57s9WB40lRLlBw==} '@emnapi/core@1.11.2': resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} @@ -432,156 +423,312 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.2': + resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.2': + resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.2': + resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.2': + resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.2': + resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.2': + resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.2': + resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.2': + resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.2': + resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.2': + resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.2': + resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.2': + resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.2': + resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.2': + resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.2': + resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.2': + resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.2': + resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.12': resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.2': + resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.2': + resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.2': + resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.2': + resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.2': + resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.2': + resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.2': + resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.2': + resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.2': + resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@exodus/bytes@1.15.1': resolution: {integrity: sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -1310,7 +1457,7 @@ packages: resolution: {integrity: sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==} peerDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5 + '@types/react-dom': 19.2.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -1323,7 +1470,7 @@ packages: resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==} peerDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5 + '@types/react-dom': 19.2.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -1345,7 +1492,7 @@ packages: resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==} peerDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5 + '@types/react-dom': 19.2.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -1367,7 +1514,7 @@ packages: resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==} peerDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5 + '@types/react-dom': 19.2.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -1380,7 +1527,7 @@ packages: resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==} peerDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5 + '@types/react-dom': 19.2.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -1393,7 +1540,7 @@ packages: resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==} peerDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5 + '@types/react-dom': 19.2.7 react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -1787,11 +1934,11 @@ packages: '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - '@types/node@26.4.0': - resolution: {integrity: sha512-faiGnoIrLH/V8cibOMEAZ8pMw6oXqSukl29ra4mN8GdaB2ZewzeaLj+INpV5N+Z1eKWzY+IzaIZH2EIR6YZRNQ==} + '@types/node@26.4.1': + resolution: {integrity: sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==} - '@types/react-dom@19.2.5': - resolution: {integrity: sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==} + '@types/react-dom@19.2.7': + resolution: {integrity: sha512-I8bPpDLcHBv1qiIiXDCy71Rt8eQDKJP0sMSWJphDdAcdqiJ1sGpZamavoEIRZmYzjia9LuEb2HlYdDpmoENpvQ==} peerDependencies: '@types/react': 19.2.18 @@ -1921,20 +2068,25 @@ packages: cpu: [x64] os: [win32] - '@vitest/coverage-v8@4.1.11': - resolution: {integrity: sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==} + '@vitest/coverage-v8@5.0.0': + resolution: {integrity: sha512-toMg6PZGCIa/lQNCDoASrfb1ly4hsUKXFtFYC9kD4t78o5Y6LyNJU7AENt8eHPr3quYdxaxK7hj2mnbFfUk9NA==} peerDependencies: - '@vitest/browser': 4.1.11 - vitest: 4.1.11 + '@vitest/browser': 5.0.0 + vitest: 5.0.0 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.1.11': - resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + '@vitest/istanbul-lib-coverage@1.0.1': + resolution: {integrity: sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==} + engines: {node: '>=22'} - '@vitest/mocker@4.1.11': - resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + '@vitest/istanbul-lib-report@1.0.1': + resolution: {integrity: sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==} + engines: {node: '>=22'} + + '@vitest/mocker@5.0.0': + resolution: {integrity: sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1944,20 +2096,8 @@ packages: vite: optional: true - '@vitest/pretty-format@4.1.11': - resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} - - '@vitest/runner@4.1.11': - resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} - - '@vitest/snapshot@4.1.11': - resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} - - '@vitest/spy@4.1.11': - resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} - - '@vitest/utils@4.1.11': - resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + '@vitest/spy@5.0.0': + resolution: {integrity: sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==} aria-hidden@1.2.6: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} @@ -2023,9 +2163,6 @@ packages: resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} engines: {node: '>=18'} - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -2193,6 +2330,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.2: + resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} + engines: {node: '>=18'} + hasBin: true + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -2230,8 +2372,8 @@ packages: engines: {node: '>=18.3.0'} hasBin: true - framer-motion@13.1.1: - resolution: {integrity: sha512-B/xn2TPS4f61cEBLFjiYlQFnBZUW1YVj/LM+C+N4OP8Rs95VLEI2ot/RlfBg111la/EiyECFaJJi/A3FWA8MUA==} + framer-motion@13.2.0: + resolution: {integrity: sha512-9E33ebgMaO33w1nN/jEdW8z3/GO483fMi4rqbMG9rt83XgW9QLKRe4NcmJ8s+fQ3O34++UHrIQwlIWGIWTITjA==} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 @@ -2278,9 +2420,6 @@ packages: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - husky@9.1.7: resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} @@ -2290,8 +2429,8 @@ packages: resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} engines: {node: '>=0.10.0'} - icu-minify@4.14.1: - resolution: {integrity: sha512-I8lz1epVvPmTSCfElYrOVZPMlxXYO6u/HDAd8bAksU0WmL8774lqg/h4PSHIisEYbqYcxXGU2y5b5sbhzCIi0A==} + icu-minify@4.14.2: + resolution: {integrity: sha512-2eJ9ooR8xXWbe0IivG58TFL96AUp5ts8FJ4qSMzn8YRJXpLmtPNWcUkIZ8t3iz0+eaVzG+mN+rdNIigHDvYa2Q==} ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -2330,18 +2469,6 @@ packages: resolution: {integrity: sha512-vjQwQSxyEkJHO6g+KIDlWj8swAzUB01pJu9GU9cLGHjE10d59GFzJkgtB2Lj5WTOoq9JeHlYjR/FFuqeopPO0Q==} engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-reports@3.2.0: - resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} - engines: {node: '>=8'} - jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -2374,25 +2501,11 @@ packages: jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} - knip@6.33.0: - resolution: {integrity: sha512-gMXWV2bqgJcdZKsaRgl1oH5V2J0VumhJ2ujfP4M6b9ftpca2BNpvlX3Dq++vVtEey7sU67EXCvZGNkQfG2w1RQ==} + knip@6.34.0: + resolution: {integrity: sha512-bbHIrnGspYwe4EBPjjx+lvkUor0F2qfKQc5BPzPI4SOAImYA+k2ueVpIcF7d/W1LEVT7XoJUPt6zELeGZhXBgA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - lenis@1.3.26: - resolution: {integrity: sha512-s/xTCZCxTFvHbAN1OzuhNaN5YPJH2ail0XAkctKW1b+RUAG4nUL5UHLXwNko1h8aEeT2jspBXegMgPJd8zcuag==} - peerDependencies: - '@nuxt/kit': '>=3.0.0' - react: '>=17.0.0' - vue: '>=3.0.0' - peerDependenciesMeta: - '@nuxt/kit': - optional: true - react: - optional: true - vue: - optional: true - lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -2560,8 +2673,8 @@ packages: resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} - lucide-react@1.38.0: - resolution: {integrity: sha512-xZCyBd/wiVUDactoCc+42TjL0aB7EBOXsuX+tjz+W/sGzw2KhHpL1NOH3FIaVUcpimvUBpIYfz34Ofj9S5JEzQ==} + lucide-react@1.41.0: + resolution: {integrity: sha512-6lksP35l6KszDKUeRTi4LV7i6DEe0Yzl2ALJm9j4c5xEYN91GdW1xGsawGMOg2mgjF5GHBVX8pKX9kP+cWsP3Q==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2572,13 +2685,12 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.2.3: + resolution: {integrity: sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==} + magicast@0.5.4: resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} @@ -2593,14 +2705,14 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - motion-dom@13.1.1: - resolution: {integrity: sha512-XSf8VYWSB6G/0IY3rWVbyLcxWXtAVHkN1PQE2agTaCv3u8RGvbwu56TyyR/MNzBqqNavEBTZzErcxI1TxBrjcA==} + motion-dom@13.2.0: + resolution: {integrity: sha512-N6gdSoWRDk0Rh/fVtlqUtLs+fEN3ELFZI3cn3IQE9Mnf3E+Mh8wjO6MstzCOPFh4Yf0L1as5m2eUyYWj8ylVSQ==} motion-utils@13.0.0: resolution: {integrity: sha512-7DnN7TmbLcYXcG4RVadXIihWlyuM9afoUww8Y5Agg431kGKiuL2/OMyP4mJ5wLz+pvN3t5ySClLOaVXJ+wekRQ==} - motion@13.1.1: - resolution: {integrity: sha512-WNZoK6xiF+kkTqkZ5K7FDDh6A8BG4i5Hc7KXtW8gtTxkpJFds+hIOrDaQGKjQj/AE/i4hJqAaUHEqp/Qo02y6Q==} + motion@13.2.0: + resolution: {integrity: sha512-4Hrb5vD6HhjFstLUiCmWvtpsw+WTpP4R+QXfSDYZBz7+uxE/LrRg3aV0ReJxHrTRffHhbIE6svEqnotngXvesQ==} peerDependencies: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 @@ -2617,8 +2729,8 @@ packages: resolution: {integrity: sha512-TN+kO1/oOc8UzDW5N3vSDncBcv9WyNnQQe/NFMcFWq6G1+zVeYUkGvkYpQ/S8wb8vKtUD7i78dIaY0cv+cmPRA==} engines: {node: '>=18'} - mysql2@3.24.2: - resolution: {integrity: sha512-l9kXeKGwd6VCbSjmpO/bLWb+YCYpbvE4whte8ec6InXebvCNyEEydyRCnRU+TSHNqRLJ8B+Tk1uWb+vMCJgJzA==} + mysql2@3.24.3: + resolution: {integrity: sha512-OKfWHkMAg9v06neq8FmSyhbxPQKABN9PAW5G9/bDTXzJBO5xXtkKL0V27vju7HQWk9UD4Od5BZsvCtBTB1CPEw==} engines: {node: '>= 8.0'} peerDependencies: '@types/node': '>= 8' @@ -2652,11 +2764,11 @@ packages: nodemailer: optional: true - next-intl-swc-plugin-extractor@4.14.1: - resolution: {integrity: sha512-ixZCS/z1Nx0sto4rWFfoCqahzqXjdhVuQzYrITVwXsrVzYi1ZKrWP8evoXTB5+F+j9FpLQIMIYB6gp3k+zePWA==} + next-intl-swc-plugin-extractor@4.14.2: + resolution: {integrity: sha512-R+i9Xw6XKyn1QVx+I5IDZQNGQBwT79ugGwzbuJudgejob/IYnXXrRKMlLhmMxLdKH8jUWh92nqwNnp+5rtSVJA==} - next-intl@4.14.1: - resolution: {integrity: sha512-7o7ZyYIWoB0w87n13sezA8r1uLH0ipZCGrMHFKVhwFnwUI1/tym04P/T5NiyULaXUQ276m60xsmInE+HnfAq3A==} + next-intl@4.14.2: + resolution: {integrity: sha512-rIq8me1uOa2qXnttJ6dUGYnHHL63Rh1FAIIDTJP5av5KSBmUzNBS17QAi07QDrUMY6KxFNeoCTRzSZRPUc97lg==} peerDependencies: next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 @@ -2718,9 +2830,6 @@ packages: parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2762,8 +2871,12 @@ packages: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} engines: {node: '>=4'} - postcss@8.5.26: - resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + postcss@8.5.23: + resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} preact-render-to-string@6.5.11: @@ -2856,8 +2969,8 @@ packages: reselect@5.3.0: resolution: {integrity: sha512-XGoLeRAVzUTcJ1qkxPQhDJyIZ5d6zzZD9nT7AEZOaaU9UbWclhycElmhO+VD5bFeLuzhPBaOV2oXC8uG35ZSpg==} - resend@6.25.0: - resolution: {integrity: sha512-iptUEycs+6Hu+W8mExK708LrDiMYOuGgnCP+wTD5L4Zrqqd2B86h1oyAmNe0khZWQw0ccI7jz8OgAaplEsyaIA==} + resend@6.26.0: + resolution: {integrity: sha512-vkrULozV5nGF8o7tyun/t0+qFgtPpn+cyRhVqeorhOsPUNDiSc/9p/pn8AprdHqJpYaTvftpnQgWurW++FkCdw==} engines: {node: '>=20'} peerDependencies: '@react-email/render': '*' @@ -3013,8 +3126,9 @@ packages: resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} engines: {node: '>=20'} - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinybench@6.1.4: + resolution: {integrity: sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==} + engines: {node: '>=20.0.0'} tinyexec@1.3.0: resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} @@ -3024,6 +3138,9 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinymce@8.9.0: + resolution: {integrity: sha512-zh9NKpdF89vnqOB3VEkTHiPtltrY6i3vTEVF5+l7vLxMX6lBju7d0UV9wWV7Vm1NyO1PccDCwa6qxAb2pWSvfg==} + tinyrainbow@3.1.1: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} @@ -3085,8 +3202,8 @@ packages: '@types/react': optional: true - use-intl@4.14.1: - resolution: {integrity: sha512-p6Ggq6AZJDJQpjj4WEjByfid1IEifH7OYpL283y29dzVT2M6vbMoHmYA9KQ/L1A9jHED8uzCNvWic0mJ23kU5A==} + use-intl@4.14.2: + resolution: {integrity: sha512-XffvbD4q4uZjRbuO8CgK6Ei8hvYn7aegoSad2YPho1ZRjYEs+k4erhYq0vfm5ttF4lnja3riXCeJrtUdNdQG3g==} peerDependencies: react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0 @@ -3115,7 +3232,7 @@ packages: peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 '@vitejs/devtools': ^0.4.0 || ^0.5.0 - esbuild: ^0.25.9 + esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 sass: ^1.70.0 @@ -3151,23 +3268,23 @@ packages: yaml: optional: true - vitest@4.1.11: - resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + vitest@5.0.0: + resolution: {integrity: sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==} + engines: {node: ^22.12.0 || ^24.0.0 || >=26.0.0} hasBin: true 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.0 + '@vitest/browser-preview': 5.0.0 + '@vitest/browser-webdriverio': ^5.0.0-beta.5 || >=5.0.0 + '@vitest/coverage-istanbul': 5.0.0 + '@vitest/coverage-v8': 5.0.0 + '@vitest/ui': 5.0.0 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': optional: true @@ -3284,10 +3401,10 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@base-ui/react@1.7.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@base-ui/react@1.8.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@babel/runtime': 7.29.7 - '@base-ui/utils': 0.3.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@base-ui/utils': 0.4.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@floating-ui/utils': 0.2.12 react: 19.2.8 @@ -3296,7 +3413,7 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 - '@base-ui/utils@0.3.2(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@base-ui/utils@0.4.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@babel/runtime': 7.29.7 '@floating-ui/utils': 0.2.12 @@ -3309,39 +3426,39 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} - '@biomejs/biome@2.5.11': + '@biomejs/biome@2.5.12': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.5.11 - '@biomejs/cli-darwin-x64': 2.5.11 - '@biomejs/cli-linux-arm64': 2.5.11 - '@biomejs/cli-linux-arm64-musl': 2.5.11 - '@biomejs/cli-linux-x64': 2.5.11 - '@biomejs/cli-linux-x64-musl': 2.5.11 - '@biomejs/cli-win32-arm64': 2.5.11 - '@biomejs/cli-win32-x64': 2.5.11 + '@biomejs/cli-darwin-arm64': 2.5.12 + '@biomejs/cli-darwin-x64': 2.5.12 + '@biomejs/cli-linux-arm64': 2.5.12 + '@biomejs/cli-linux-arm64-musl': 2.5.12 + '@biomejs/cli-linux-x64': 2.5.12 + '@biomejs/cli-linux-x64-musl': 2.5.12 + '@biomejs/cli-win32-arm64': 2.5.12 + '@biomejs/cli-win32-x64': 2.5.12 - '@biomejs/cli-darwin-arm64@2.5.11': + '@biomejs/cli-darwin-arm64@2.5.12': optional: true - '@biomejs/cli-darwin-x64@2.5.11': + '@biomejs/cli-darwin-x64@2.5.12': optional: true - '@biomejs/cli-linux-arm64-musl@2.5.11': + '@biomejs/cli-linux-arm64-musl@2.5.12': optional: true - '@biomejs/cli-linux-arm64@2.5.11': + '@biomejs/cli-linux-arm64@2.5.12': optional: true - '@biomejs/cli-linux-x64-musl@2.5.11': + '@biomejs/cli-linux-x64-musl@2.5.12': optional: true - '@biomejs/cli-linux-x64@2.5.11': + '@biomejs/cli-linux-x64@2.5.12': optional: true - '@biomejs/cli-win32-arm64@2.5.11': + '@biomejs/cli-win32-arm64@2.5.12': optional: true - '@biomejs/cli-win32-x64@2.5.11': + '@biomejs/cli-win32-x64@2.5.12': optional: true '@borewit/text-codec@0.2.2': {} @@ -3401,15 +3518,15 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} - '@eloqnt/config@0.0.2': {} + '@eloqnt/config@0.1.0': {} - '@eloqnt/format-json@0.0.3': + '@eloqnt/format-json@0.1.0': dependencies: - '@eloqnt/config': 0.0.2 + '@eloqnt/config': 0.1.0 - '@eloqnt/format-po@0.0.3': + '@eloqnt/format-po@0.1.0': dependencies: - '@eloqnt/config': 0.0.2 + '@eloqnt/config': 0.1.0 po-parser: 2.2.0 '@emnapi/core@1.11.2': @@ -3446,81 +3563,159 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true + '@esbuild/aix-ppc64@0.28.2': + optional: true + '@esbuild/android-arm64@0.25.12': optional: true + '@esbuild/android-arm64@0.28.2': + optional: true + '@esbuild/android-arm@0.25.12': optional: true + '@esbuild/android-arm@0.28.2': + optional: true + '@esbuild/android-x64@0.25.12': optional: true + '@esbuild/android-x64@0.28.2': + optional: true + '@esbuild/darwin-arm64@0.25.12': optional: true + '@esbuild/darwin-arm64@0.28.2': + optional: true + '@esbuild/darwin-x64@0.25.12': optional: true + '@esbuild/darwin-x64@0.28.2': + optional: true + '@esbuild/freebsd-arm64@0.25.12': optional: true + '@esbuild/freebsd-arm64@0.28.2': + optional: true + '@esbuild/freebsd-x64@0.25.12': optional: true + '@esbuild/freebsd-x64@0.28.2': + optional: true + '@esbuild/linux-arm64@0.25.12': optional: true + '@esbuild/linux-arm64@0.28.2': + optional: true + '@esbuild/linux-arm@0.25.12': optional: true + '@esbuild/linux-arm@0.28.2': + optional: true + '@esbuild/linux-ia32@0.25.12': optional: true + '@esbuild/linux-ia32@0.28.2': + optional: true + '@esbuild/linux-loong64@0.25.12': optional: true + '@esbuild/linux-loong64@0.28.2': + optional: true + '@esbuild/linux-mips64el@0.25.12': optional: true + '@esbuild/linux-mips64el@0.28.2': + optional: true + '@esbuild/linux-ppc64@0.25.12': optional: true + '@esbuild/linux-ppc64@0.28.2': + optional: true + '@esbuild/linux-riscv64@0.25.12': optional: true + '@esbuild/linux-riscv64@0.28.2': + optional: true + '@esbuild/linux-s390x@0.25.12': optional: true + '@esbuild/linux-s390x@0.28.2': + optional: true + '@esbuild/linux-x64@0.25.12': optional: true + '@esbuild/linux-x64@0.28.2': + optional: true + '@esbuild/netbsd-arm64@0.25.12': optional: true + '@esbuild/netbsd-arm64@0.28.2': + optional: true + '@esbuild/netbsd-x64@0.25.12': optional: true + '@esbuild/netbsd-x64@0.28.2': + optional: true + '@esbuild/openbsd-arm64@0.25.12': optional: true + '@esbuild/openbsd-arm64@0.28.2': + optional: true + '@esbuild/openbsd-x64@0.25.12': optional: true + '@esbuild/openbsd-x64@0.28.2': + optional: true + '@esbuild/openharmony-arm64@0.25.12': optional: true + '@esbuild/openharmony-arm64@0.28.2': + optional: true + '@esbuild/sunos-x64@0.25.12': optional: true + '@esbuild/sunos-x64@0.28.2': + optional: true + '@esbuild/win32-arm64@0.25.12': optional: true + '@esbuild/win32-arm64@0.28.2': + optional: true + '@esbuild/win32-ia32@0.25.12': optional: true + '@esbuild/win32-ia32@0.28.2': + optional: true + '@esbuild/win32-x64@0.25.12': optional: true + '@esbuild/win32-x64@0.28.2': + optional: true + '@exodus/bytes@1.15.1(@noble/hashes@2.4.0)': optionalDependencies: '@noble/hashes': 2.4.0 @@ -3951,18 +4146,18 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) '@radix-ui/react-context': 1.2.2(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.18)(react@19.2.8) '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) @@ -3972,20 +4167,20 @@ snapshots: react-remove-scroll: 2.7.2(@types/react@19.2.18)(react@19.2.8) optionalDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5(@types/react@19.2.18) + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/primitive': 1.1.7 '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5(@types/react@19.2.18) + '@types/react-dom': 19.2.7(@types/react@19.2.18) '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.18)(react@19.2.8)': dependencies: @@ -3993,16 +4188,16 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5(@types/react@19.2.18) + '@types/react-dom': 19.2.7(@types/react@19.2.18) '@radix-ui/react-id@1.1.4(@types/react@19.2.18)(react@19.2.8)': dependencies: @@ -4011,33 +4206,33 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 - '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5(@types/react@19.2.18) + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5(@types/react@19.2.18) + '@types/react-dom': 19.2.7(@types/react@19.2.18) - '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': + '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)': dependencies: '@radix-ui/react-slot': 1.3.3(@types/react@19.2.18)(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) optionalDependencies: '@types/react': 19.2.18 - '@types/react-dom': 19.2.5(@types/react@19.2.18) + '@types/react-dom': 19.2.7(@types/react@19.2.18) '@radix-ui/react-slot@1.3.3(@types/react@19.2.18)(react@19.2.8)': dependencies: @@ -4127,7 +4322,8 @@ snapshots: '@stablelib/base64@1.0.1': {} - '@standard-schema/spec@1.1.0': {} + '@standard-schema/spec@1.1.0': + optional: true '@standard-schema/utils@0.3.0': {} @@ -4267,7 +4463,7 @@ snapshots: '@alloc/quick-lru': 5.2.0 '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 - postcss: 8.5.26 + postcss: 8.5.28 tailwindcss: 4.3.3 '@tailwindcss/typography@0.5.20(tailwindcss@4.3.3)': @@ -4306,11 +4502,11 @@ snapshots: '@types/estree@1.0.9': {} - '@types/node@26.4.0': + '@types/node@26.4.1': dependencies: undici-types: 8.3.0 - '@types/react-dom@19.2.5(@types/react@19.2.18)': + '@types/react-dom@19.2.7(@types/react@19.2.18)': dependencies: '@types/react': 19.2.18 @@ -4381,60 +4577,34 @@ snapshots: '@typescript/typescript-win32-x64@7.0.2': optional: true - '@vitest/coverage-v8@4.1.11(vitest@4.1.11)': + '@vitest/coverage-v8@5.0.0(vitest@5.0.0)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.1.11 + '@vitest/istanbul-lib-coverage': 1.0.1 + '@vitest/istanbul-lib-report': 1.0.1 ast-v8-to-istanbul: 1.0.5 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.2.0 magicast: 0.5.4 obug: 2.1.4 std-env: 4.2.0 tinyrainbow: 3.1.1 - vitest: 4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(jsdom@30.0.1(@noble/hashes@2.4.0))(vite@8.2.2(@types/node@26.4.0)(esbuild@0.25.12)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)) + vitest: 5.0.0(@types/node@26.4.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1(@noble/hashes@2.4.0))(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)) - '@vitest/expect@4.1.11': - dependencies: - '@standard-schema/spec': 1.1.0 - '@types/chai': 5.2.3 - '@vitest/spy': 4.1.11 - '@vitest/utils': 4.1.11 - chai: 6.2.2 - tinyrainbow: 3.1.1 + '@vitest/istanbul-lib-coverage@1.0.1': {} - '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@26.4.0)(esbuild@0.25.12)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0))': + '@vitest/istanbul-lib-report@1.0.1': dependencies: - '@vitest/spy': 4.1.11 + '@vitest/istanbul-lib-coverage': 1.0.1 + + '@vitest/mocker@5.0.0(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0))': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@vitest/spy': 5.0.0 estree-walker: 3.0.3 - magic-string: 0.30.21 + magic-string: 1.2.3 optionalDependencies: - vite: 8.2.2(@types/node@26.4.0)(esbuild@0.25.12)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0) + vite: 8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0) - '@vitest/pretty-format@4.1.11': - dependencies: - tinyrainbow: 3.1.1 - - '@vitest/runner@4.1.11': - dependencies: - '@vitest/utils': 4.1.11 - pathe: 2.0.3 - - '@vitest/snapshot@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - '@vitest/utils': 4.1.11 - magic-string: 0.30.21 - pathe: 2.0.3 - - '@vitest/spy@4.1.11': {} - - '@vitest/utils@4.1.11': - dependencies: - '@vitest/pretty-format': 4.1.11 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.1 + '@vitest/spy@5.0.0': {} aria-hidden@1.2.6: dependencies: @@ -4474,12 +4644,12 @@ snapshots: cluster-key-slot@1.1.1: {} - cmdk@1.1.1(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + cmdk@1.1.1(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) '@radix-ui/react-id': 1.1.4(@types/react@19.2.18)(react@19.2.8) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.5(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.7(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 react-dom: 19.2.8(react@19.2.8) transitivePeerDependencies: @@ -4490,8 +4660,6 @@ snapshots: content-type@2.1.0: {} - convert-source-map@2.0.0: {} - core-util-is@1.0.3: {} croner@10.0.1: {} @@ -4539,9 +4707,9 @@ snapshots: esbuild: 0.25.12 tsx: 4.23.13 - drizzle-orm@0.45.2(mysql2@3.24.2(@types/node@26.4.0)): + drizzle-orm@0.45.2(mysql2@3.24.3(@types/node@26.4.1)): optionalDependencies: - mysql2: 3.24.2(@types/node@26.4.0) + mysql2: 3.24.3(@types/node@26.4.1) end-of-stream@1.4.5: dependencies: @@ -4585,6 +4753,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 + esbuild@0.28.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.2 + '@esbuild/android-arm': 0.28.2 + '@esbuild/android-arm64': 0.28.2 + '@esbuild/android-x64': 0.28.2 + '@esbuild/darwin-arm64': 0.28.2 + '@esbuild/darwin-x64': 0.28.2 + '@esbuild/freebsd-arm64': 0.28.2 + '@esbuild/freebsd-x64': 0.28.2 + '@esbuild/linux-arm': 0.28.2 + '@esbuild/linux-arm64': 0.28.2 + '@esbuild/linux-ia32': 0.28.2 + '@esbuild/linux-loong64': 0.28.2 + '@esbuild/linux-mips64el': 0.28.2 + '@esbuild/linux-ppc64': 0.28.2 + '@esbuild/linux-riscv64': 0.28.2 + '@esbuild/linux-s390x': 0.28.2 + '@esbuild/linux-x64': 0.28.2 + '@esbuild/netbsd-arm64': 0.28.2 + '@esbuild/netbsd-x64': 0.28.2 + '@esbuild/openbsd-arm64': 0.28.2 + '@esbuild/openbsd-x64': 0.28.2 + '@esbuild/openharmony-arm64': 0.28.2 + '@esbuild/sunos-x64': 0.28.2 + '@esbuild/win32-arm64': 0.28.2 + '@esbuild/win32-ia32': 0.28.2 + '@esbuild/win32-x64': 0.28.2 + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -4619,9 +4816,9 @@ snapshots: fd-package-json: 2.0.0 package-manager-detector: 1.8.0 - framer-motion@13.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + framer-motion@13.2.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - motion-dom: 13.1.1 + motion-dom: 13.2.0 motion-utils: 13.0.0 tslib: 2.8.1 optionalDependencies: @@ -4646,7 +4843,8 @@ snapshots: graceful-fs@4.2.11: {} - has-flag@4.0.0: {} + has-flag@4.0.0: + optional: true hash-wasm@4.12.0: {} @@ -4658,15 +4856,13 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' - html-escaper@2.0.2: {} - husky@9.1.7: {} iconv-lite@0.7.3: dependencies: safer-buffer: 2.1.2 - icu-minify@4.14.1: + icu-minify@4.14.2: dependencies: '@formatjs/icu-messageformat-parser': 3.5.17 @@ -4712,19 +4908,6 @@ snapshots: - '@noble/hashes' - canvas - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-reports@3.2.0: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - jiti@2.7.0: {} jose@6.2.10: {} @@ -4770,7 +4953,7 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 - knip@6.33.0: + knip@6.34.0: dependencies: fdir: 6.5.0(picomatch@4.0.7) formatly: 0.7.0 @@ -4786,10 +4969,6 @@ snapshots: yaml: 2.9.0 zod: 4.5.4 - lenis@1.3.26(react@19.2.8): - optionalDependencies: - react: 19.2.8 - lie@3.3.0: dependencies: immediate: 3.0.6 @@ -4906,7 +5085,7 @@ snapshots: lru.min@1.1.4: {} - lucide-react@1.38.0(react@19.2.8): + lucide-react@1.41.0(react@19.2.8): dependencies: react: 19.2.8 @@ -4916,16 +5095,16 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 + magic-string@1.2.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + magicast@0.5.4: dependencies: '@babel/parser': 7.29.8 '@babel/types': 7.29.8 source-map-js: 1.2.1 - make-dir@4.0.0: - dependencies: - semver: 7.8.5 - mdn-data@2.27.1: {} media-typer@2.0.0: {} @@ -4934,15 +5113,15 @@ snapshots: minimist@1.2.8: {} - motion-dom@13.1.1: + motion-dom@13.2.0: dependencies: motion-utils: 13.0.0 motion-utils@13.0.0: {} - motion@13.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + motion@13.2.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - framer-motion: 13.1.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + framer-motion: 13.2.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8) tslib: 2.8.1 optionalDependencies: react: 19.2.8 @@ -4965,9 +5144,9 @@ snapshots: transitivePeerDependencies: - supports-color - mysql2@3.24.2(@types/node@26.4.0): + mysql2@3.24.3(@types/node@26.4.1): dependencies: - '@types/node': 26.4.0 + '@types/node': 26.4.1 aws-ssl-profiles: 1.1.2 generate-function: 2.3.1 iconv-lite: 0.7.3 @@ -4986,38 +5165,38 @@ snapshots: dependencies: content-type: 2.1.0 - next-auth@5.0.0-beta.32(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8): + next-auth@5.0.0-beta.32(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8): dependencies: '@auth/core': 0.41.3 - next: 16.3.4(@playwright/test@1.62.1)(@types/node@26.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + next: 16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) react: 19.2.8 - next-intl-swc-plugin-extractor@4.14.1: {} + next-intl-swc-plugin-extractor@4.14.2: {} - next-intl@4.14.1(@swc/helpers@0.5.23)(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8): + next-intl@4.14.2(@swc/helpers@0.5.23)(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8): dependencies: - '@eloqnt/config': 0.0.2 - '@eloqnt/format-json': 0.0.3 - '@eloqnt/format-po': 0.0.3 + '@eloqnt/config': 0.1.0 + '@eloqnt/format-json': 0.1.0 + '@eloqnt/format-po': 0.1.0 '@formatjs/intl-localematcher': 0.8.13 '@parcel/watcher': 2.6.0 '@swc/core': 1.16.1(@swc/helpers@0.5.23) - icu-minify: 4.14.1 + icu-minify: 4.14.2 negotiator: 1.1.0 - next: 16.3.4(@playwright/test@1.62.1)(@types/node@26.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) - next-intl-swc-plugin-extractor: 4.14.1 + next: 16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8) + next-intl-swc-plugin-extractor: 4.14.2 react: 19.2.8 - use-intl: 4.14.1(react@19.2.8) + use-intl: 4.14.2(react@19.2.8) transitivePeerDependencies: - '@swc/helpers' - next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): + next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: '@next/env': 16.3.4 '@swc/helpers': 0.5.23 baseline-browser-mapping: 2.11.20 caniuse-lite: 1.0.30001810 - postcss: 8.5.26 + postcss: 8.5.23 react: 19.2.8 react-dom: 19.2.8(react@19.2.8) styled-jsx: 5.1.6(react@19.2.8) @@ -5031,7 +5210,7 @@ snapshots: '@next/swc-win32-arm64-msvc': 16.3.4 '@next/swc-win32-x64-msvc': 16.3.4 '@playwright/test': 1.62.1 - sharp: 0.35.4(@types/node@26.4.0) + sharp: 0.35.4(@types/node@26.4.1) transitivePeerDependencies: - '@babel/core' - '@types/node' @@ -5112,8 +5291,6 @@ snapshots: dependencies: entities: 8.0.0 - pathe@2.0.3: {} - picocolors@1.1.1: {} picomatch@4.0.7: {} @@ -5171,7 +5348,13 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss@8.5.26: + postcss@8.5.23: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.28: dependencies: nanoid: 3.3.18 picocolors: 1.1.1 @@ -5254,7 +5437,7 @@ snapshots: reselect@5.3.0: {} - resend@6.25.0: + resend@6.26.0: dependencies: postal-mime: 2.7.5 standardwebhooks: 1.0.0 @@ -5302,7 +5485,7 @@ snapshots: setimmediate@1.0.5: {} - sharp@0.35.4(@types/node@26.4.0): + sharp@0.35.4(@types/node@26.4.1): dependencies: '@img/colour': 1.1.0 detect-libc: 2.1.2 @@ -5333,7 +5516,7 @@ snapshots: '@img/sharp-win32-arm64': 0.35.4 '@img/sharp-win32-ia32': 0.35.4 '@img/sharp-win32-x64': 0.35.4 - '@types/node': 26.4.0 + '@types/node': 26.4.1 siginfo@2.0.0: {} @@ -5394,6 +5577,7 @@ snapshots: supports-color@7.2.0: dependencies: has-flag: 4.0.0 + optional: true symbol-tree@3.2.4: {} @@ -5407,7 +5591,7 @@ snapshots: dependencies: real-require: 1.0.0 - tinybench@2.9.0: {} + tinybench@6.1.4: {} tinyexec@1.3.0: {} @@ -5416,6 +5600,8 @@ snapshots: fdir: 6.5.0(picomatch@4.0.7) picomatch: 4.0.7 + tinymce@8.9.0: {} + tinyrainbow@3.1.1: {} tldts-core@7.4.11: {} @@ -5442,7 +5628,7 @@ snapshots: tsx@4.23.13: dependencies: - esbuild: 0.25.12 + esbuild: 0.28.2 optionalDependencies: fsevents: 2.3.3 @@ -5484,11 +5670,11 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 - use-intl@4.14.1(react@19.2.8): + use-intl@4.14.2(react@19.2.8): dependencies: '@formatjs/fast-memoize': 3.1.7 '@schummar/icu-type-parser': 1.21.5 - icu-minify: 4.14.1 + icu-minify: 4.14.2 intl-messageformat: 11.2.14 react: 19.2.8 @@ -5506,46 +5692,40 @@ snapshots: util-deprecate@1.0.2: {} - vite@8.2.2(@types/node@26.4.0)(esbuild@0.25.12)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0): + vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 - postcss: 8.5.26 + postcss: 8.5.28 rolldown: 1.2.5 tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 26.4.0 - esbuild: 0.25.12 + '@types/node': 26.4.1 + esbuild: 0.28.2 fsevents: 2.3.3 jiti: 2.7.0 tsx: 4.23.13 yaml: 2.9.0 - vitest@4.1.11(@types/node@26.4.0)(@vitest/coverage-v8@4.1.11)(jsdom@30.0.1(@noble/hashes@2.4.0))(vite@8.2.2(@types/node@26.4.0)(esbuild@0.25.12)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)): + vitest@5.0.0(@types/node@26.4.1)(@vitest/coverage-v8@5.0.0)(jsdom@30.0.1(@noble/hashes@2.4.0))(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)): dependencies: - '@vitest/expect': 4.1.11 - '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@26.4.0)(esbuild@0.25.12)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)) - '@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 + '@types/chai': 5.2.3 + '@vitest/mocker': 5.0.0(vite@8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0)) + chai: 6.2.2 es-module-lexer: 2.3.2 expect-type: 1.4.0 - magic-string: 0.30.21 + magic-string: 1.2.3 obug: 2.1.4 - pathe: 2.0.3 picomatch: 4.0.7 std-env: 4.2.0 - tinybench: 2.9.0 + tinybench: 6.1.4 tinyexec: 1.3.0 tinyglobby: 0.2.17 - tinyrainbow: 3.1.1 - vite: 8.2.2(@types/node@26.4.0)(esbuild@0.25.12)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0) + vite: 8.2.2(@types/node@26.4.1)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.13)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 26.4.0 - '@vitest/coverage-v8': 4.1.11(vitest@4.1.11) + '@types/node': 26.4.1 + '@vitest/coverage-v8': 5.0.0(vitest@5.0.0) jsdom: 30.0.1(@noble/hashes@2.4.0) transitivePeerDependencies: - msw diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1a7b3f49..27a66bd5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,80 +1,13 @@ -# pnpm-workspace.yaml - allowBuilds: esbuild: true sharp: true "@parcel/watcher": true "@swc/core": true - bcrypt: true -minimumReleaseAgeExclude: - - "@base-ui/react@1.7.0" - - "@base-ui/utils@0.3.2" - - "@biomejs/biome@2.5.7" - - "@biomejs/cli-darwin-arm64@2.5.7" - - "@biomejs/cli-darwin-x64@2.5.7" - - "@biomejs/cli-linux-arm64-musl@2.5.7" - - "@biomejs/cli-linux-arm64@2.5.7" - - "@biomejs/cli-linux-x64-musl@2.5.7" - - "@biomejs/cli-linux-x64@2.5.7" - - "@biomejs/cli-win32-arm64@2.5.7" - - "@biomejs/cli-win32-x64@2.5.7" - - "@hookform/resolvers@5.8.0" - - "@next/bundle-analyzer@16.3.0" - - "@next/bundle-analyzer@16.3.1" - - "@next/env@16.3.0" - - "@next/env@16.3.1" - - "@next/swc-darwin-arm64@16.3.0" - - "@next/swc-darwin-arm64@16.3.1" - - "@next/swc-darwin-x64@16.3.0" - - "@next/swc-darwin-x64@16.3.1" - - "@next/swc-linux-arm64-gnu@16.3.0" - - "@next/swc-linux-arm64-gnu@16.3.1" - - "@next/swc-linux-arm64-musl@16.3.0" - - "@next/swc-linux-arm64-musl@16.3.1" - - "@next/swc-linux-x64-gnu@16.3.0" - - "@next/swc-linux-x64-gnu@16.3.1" - - "@next/swc-linux-x64-musl@16.3.0" - - "@next/swc-linux-x64-musl@16.3.1" - - "@next/swc-win32-arm64-msvc@16.3.0" - - "@next/swc-win32-arm64-msvc@16.3.1" - - "@next/swc-win32-x64-msvc@16.3.0" - - "@next/swc-win32-x64-msvc@16.3.1" - - "icu-minify@4.13.5" - - "lenis@1.3.26" - - "next-intl-swc-plugin-extractor@4.13.5" - - "next-intl@4.13.5" - - "next@16.3.0" - - "next@16.3.1" - - "resend@6.20.0" - - "tsx@4.23.8" - - "use-intl@4.13.5" +minimumReleaseAge: 1440 overrides: - glob: "^11.0.3" - rimraf: "^6.0.1" - uuid: "^11.1.0" - fast-uri: "^3.1.3" - "@hono/node-server": "^1.19.13" - postcss: "^8.5.26" - tmp: "^0.2.6" - sharp: "^0.35.4" - brace-expansion: "^5.0.8" + # drizzle-kit still uses an obsolete development-server dependency. + "@esbuild-kit/core-utils>esbuild": "^0.25.9" "@types/react": "19.2.18" - "@types/react-dom": "19.2.5" - # Tijdelijke mitigatie voor drizzle-kit audit - esbuild: "^0.25.9" - -allowedDeprecatedVersions: - "@esbuild-kit/esm-loader": "*" - "@esbuild-kit/core-utils": "*" - -ignoredBuiltDependencies: - - "@prisma/engines" - - "prisma" - -peerDependencyRules: - allowedVersions: - nodemailer: "9.0.3" - ignoreMissing: - - nodemailer \ No newline at end of file + "@types/react-dom": "19.2.7" diff --git a/renovate.json b/renovate.json index b18a9dd3..ebf662cc 100644 --- a/renovate.json +++ b/renovate.json @@ -14,29 +14,36 @@ "prConcurrentLimit": 5, "packageRules": [ { - "description": "Group all dependency updates into a single PR", - "matchPackagePatterns": ["*"], - "groupName": "All dependencies", - "groupSlug": "all", - "automerge": true + "matchDepTypes": ["devDependencies"], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "Development tools", + "automerge": false + }, + { + "matchPackageNames": [ + "@base-ui/react", + "lucide-react", + "motion", + "@dnd-kit/**" + ], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "Interface libraries", + "automerge": false + }, + { + "matchPackageNames": ["vitest", "@vitest/coverage-v8"], + "groupName": "Vitest", + "automerge": false }, { - "description": "Major updates need manual review", "matchUpdateTypes": ["major"], - "labels": ["dependencies", "major"], - "automerge": false, - "assignees": [], - "reviewers": [] + "automerge": false }, { - "description": "Disable updates for engine pins", "matchPackageNames": ["node", "pnpm"], "enabled": false - }, - { - "description": "Disable server-only (abandoned, pinned at 0.0.1)", - "matchPackageNames": ["server-only"], - "enabled": false } - ] + ], + "minimumReleaseAge": "3 days", + "automerge": false } diff --git a/scripts/copy-editor-assets.mjs b/scripts/copy-editor-assets.mjs new file mode 100644 index 00000000..f4edd9f3 --- /dev/null +++ b/scripts/copy-editor-assets.mjs @@ -0,0 +1,22 @@ +import { cp, mkdir } from "node:fs/promises"; +import { createRequire } from "node:module"; +import path from "node:path"; + +const require = createRequire(import.meta.url); +const source = path.dirname(require.resolve("tinymce/package.json")); +const target = path.resolve("public/vendor/tinymce"); +await mkdir(target, { recursive: true }); +for (const name of [ + "tinymce.min.js", + "icons", + "models", + "plugins", + "skins", + "themes", + "license.md", + "notices.txt", +]) { + await cp(path.join(source, name), path.join(target, name), { + recursive: true, + }); +} diff --git a/scripts/prepare-hooks.mjs b/scripts/prepare-hooks.mjs new file mode 100644 index 00000000..8a7856c5 --- /dev/null +++ b/scripts/prepare-hooks.mjs @@ -0,0 +1,5 @@ +// Production builds and CI do not need Git hooks or dev-only executables. +if (process.env.NODE_ENV !== "production" && !process.env.CI) { + const { default: husky } = await import("husky"); + husky(); +} diff --git a/src/app/(site)/error.tsx b/src/app/(site)/error.tsx index 696637f5..38816451 100644 --- a/src/app/(site)/error.tsx +++ b/src/app/(site)/error.tsx @@ -2,9 +2,10 @@ import { Home, RefreshCw } from "lucide-react"; import { useTranslations } from "next-intl"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { ErrorScreen } from "@/components/error-screen"; import Link from "@/components/link"; +import { reportBrowserError } from "@/lib/browser-errors"; /** * Site route-segment error boundary. Renders inside the site shell layout. @@ -19,8 +20,10 @@ export default function SiteErrorPage({ }) { const t = useTranslations("pages.error"); + const [reference, setReference] = useState(error.digest); useEffect(() => { - console.error(error); + setReference(error.digest ?? reportBrowserError(error, "browser.boundary")); + if (error.digest) reportBrowserError(error, "browser.boundary"); }, [error]); return ( @@ -46,9 +49,9 @@ export default function SiteErrorPage({ } footer={ - error.digest ? ( + reference ? (

- {t("reference", { digest: error.digest })} + {t("reference", { digest: reference })}

) : null } diff --git a/src/app/admin/devops/cms-errors/actions.test.ts b/src/app/admin/devops/cms-errors/actions.test.ts new file mode 100644 index 00000000..161ae070 --- /dev/null +++ b/src/app/admin/devops/cms-errors/actions.test.ts @@ -0,0 +1,39 @@ +import { expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ + guard: vi.fn(), + resolve: vi.fn(), + audit: vi.fn(), +})); +vi.mock("@/lib/admin/guard", () => ({ requirePermission: mocks.guard })); +vi.mock("@/lib/error-monitor", () => ({ + ErrorStore: class { + resolve = mocks.resolve; + }, +})); +vi.mock("@/lib/permissions", () => ({ + PERMS: { DEVOPS_EDIT: "admin.devops.edit" }, +})); +vi.mock("@/lib/services/audit", () => ({ logAudit: mocks.audit })); +vi.mock("next/cache", () => ({ revalidatePath: vi.fn() })); + +import { resolveCmsError } from "./actions"; + +it("requires edit permission before resolving or auditing", async () => { + mocks.guard.mockRejectedValue(new Error("denied")); + await expect(resolveCmsError(new FormData())).rejects.toThrow("denied"); + expect(mocks.resolve).not.toHaveBeenCalled(); + expect(mocks.audit).not.toHaveBeenCalled(); +}); +it("records who resolved the problem", async () => { + mocks.guard.mockResolvedValue({ id: 42 }); + mocks.resolve.mockResolvedValue(undefined); + mocks.audit.mockResolvedValue(undefined); + const data = new FormData(); + data.set("fingerprint", "aabbccddeeff0011"); + await resolveCmsError(data); + expect(mocks.guard).toHaveBeenCalledWith("admin.devops.edit"); + expect(mocks.audit).toHaveBeenCalledWith( + expect.objectContaining({ userId: 42, action: "cms.error.resolve" }), + ); +}); diff --git a/src/app/admin/devops/cms-errors/actions.ts b/src/app/admin/devops/cms-errors/actions.ts new file mode 100644 index 00000000..ae8edacc --- /dev/null +++ b/src/app/admin/devops/cms-errors/actions.ts @@ -0,0 +1,17 @@ +"use server"; +import { revalidatePath } from "next/cache"; +import { requirePermission } from "@/lib/admin/guard"; +import { ErrorStore } from "@/lib/error-monitor"; +import { PERMS } from "@/lib/permissions"; +import { logAudit } from "@/lib/services/audit"; +export async function resolveCmsError(data: FormData) { + const staff = await requirePermission(PERMS.DEVOPS_EDIT); + await new ErrorStore().resolve(String(data.get("fingerprint") ?? "")); + await logAudit({ + userId: staff.id, + action: "cms.error.resolve", + target: "cms-error", + after: { fingerprint: String(data.get("fingerprint")) }, + }); + revalidatePath("/admin/devops/cms-errors"); +} diff --git a/src/app/admin/devops/cms-errors/page.test.ts b/src/app/admin/devops/cms-errors/page.test.ts new file mode 100644 index 00000000..a10f3091 --- /dev/null +++ b/src/app/admin/devops/cms-errors/page.test.ts @@ -0,0 +1,32 @@ +import { expect, it, vi } from "vitest"; + +const mocks = vi.hoisted(() => ({ read: vi.fn(), access: vi.fn() })); +vi.mock("@/lib/permissions", () => ({ + getAdminContext: async () => ({ + session: { user: { rank: 1 } }, + permissions: {}, + }), + canAccess: mocks.access, + PERMS: { DEVOPS_VIEW: "devops.view" }, +})); +vi.mock("next/navigation", () => ({ + redirect: () => { + throw new Error("redirect"); + }, +})); +vi.mock("@/lib/error-monitor", () => ({ + ErrorStore: class { + read = mocks.read; + }, +})); +vi.mock("@/components/link", () => ({ default: () => null })); + +import Page from "./page"; + +it("rejects access before reading diagnostic data", async () => { + mocks.access.mockReturnValue(false); + await expect(Page({ searchParams: Promise.resolve({}) })).rejects.toThrow( + "redirect", + ); + expect(mocks.read).not.toHaveBeenCalled(); +}); diff --git a/src/app/admin/devops/cms-errors/page.tsx b/src/app/admin/devops/cms-errors/page.tsx new file mode 100644 index 00000000..ea194223 --- /dev/null +++ b/src/app/admin/devops/cms-errors/page.tsx @@ -0,0 +1,181 @@ +import { redirect } from "next/navigation"; +import Link from "@/components/link"; +import { ErrorStore } from "@/lib/error-monitor"; +import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; +import { resolveCmsError } from "./actions"; +export default async function CmsErrorsPage({ + searchParams, +}: { + searchParams: Promise>; +}) { + const { session, permissions } = await getAdminContext(); + if (!canAccess(permissions, PERMS.DEVOPS_VIEW, session.user.rank)) + redirect("/admin"); + const params = await searchParams; + const query = (params.q ?? "").trim().slice(0, 200).toLowerCase(); + let result: Awaited>; + try { + result = await new ErrorStore().read(); + } catch { + return ( +
+ Error storage could not be read. Check the CMS storage volume and + permissions. +
+ ); + } + const filtered = result.records.filter( + (r) => + (!params.source || r.source === params.source) && + (!query || JSON.stringify(r).toLowerCase().includes(query)), + ); + const groups = new Map(); + for (const r of filtered) { + const list = groups.get(r.fingerprint) ?? []; + list.push(r); + groups.set(r.fingerprint, list); + } + const page = Math.max( + 1, + Math.min( + Number(params.page) || 1, + Math.max(1, Math.ceil(groups.size / 25)), + ), + ); + const pageHref = (next: number) => + `?${new URLSearchParams({ q: params.q ?? "", source: params.source ?? "", page: String(next) })}`; + return ( +
+
+

CMS error center

+

+ Server and browser errors from the last 7 days. Browser reports are + unverified client observations. +

+
+
+ + DevOps + + + Emulator errors + + + Refresh / clear filters + +
+
+ + + +
+

+ {filtered.length} occurrences · {groups.size} groups.{" "} + {result.truncated + ? "Showing the latest 1,000 events. Narrowing filters searches this retained window." + : ""}{" "} + Storage is limited to 10 MB per day; excess events remain in server logs + where available. +

+ {groups.size === 0 ? ( +
+ No captured errors match these filters. Monitoring starts with this + deployment; older console logs are not imported. +
+ ) : null} + {[...groups.entries()] + .slice((page - 1) * 25, page * 25) + .map(([fingerprint, events]) => { + const r = events[0]; + return ( +
+ + + {r.resolved ? "Resolved" : "Open"} · {r.source} ·{" "} + {events.length} occurrences · {r.at} + +

{r.event}

+

{r.message}

+
+
+

+ Reference: {r.id} +

+

+ Release: {r.release} +

+
+									{JSON.stringify(r.context, null, 2)}
+								
+
+									{r.stack ||
+										"No stack supplied. Use the reference or digest to correlate server logs."}
+								
+ {!r.resolved && + canAccess(permissions, PERMS.DEVOPS_EDIT, session.user.rank) ? ( +
+ + +

+ A new occurrence reopens the group automatically. +

+
+ ) : null} +
+ + Occurrence references + +
    + {events.map((e) => ( +
  • + {e.at} · {e.id} +
  • + ))} +
+
+

+ Browser stack traces may reference compiled chunks. This view + does not infer a root cause or include request bodies. +

+
+
+ ); + })} + +
+ ); +} diff --git a/src/app/admin/devops/errors/page.tsx b/src/app/admin/devops/errors/page.tsx index 75a5dfcd..a3b490cd 100644 --- a/src/app/admin/devops/errors/page.tsx +++ b/src/app/admin/devops/errors/page.tsx @@ -35,6 +35,9 @@ export default async function EmulatorErrorsPage({ return (
+ + CMS error center +

{total} total errors — showing page {page} of {lastPage}

diff --git a/src/app/admin/devops/page.tsx b/src/app/admin/devops/page.tsx index 062ef1d2..e28e1d58 100644 --- a/src/app/admin/devops/page.tsx +++ b/src/app/admin/devops/page.tsx @@ -55,6 +55,9 @@ export default async function DevOpsPage() { return (
+ + CMS error center + {/* Status cards */}
diff --git a/src/app/admin/error.tsx b/src/app/admin/error.tsx index c9a63d62..6f9158f8 100644 --- a/src/app/admin/error.tsx +++ b/src/app/admin/error.tsx @@ -2,9 +2,10 @@ import { LayoutDashboard, RefreshCw } from "lucide-react"; import { useTranslations } from "next-intl"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import Link from "@/components/link"; import { Button } from "@/components/ui/button"; +import { reportBrowserError } from "@/lib/browser-errors"; /** * Admin route-segment error boundary. Renders inside the admin layout shell. @@ -19,8 +20,10 @@ export default function AdminErrorPage({ const t = useTranslations("pages.error"); const tNav = useTranslations("admin.nav"); + const [reference, setReference] = useState(error.digest); useEffect(() => { - console.error(error); + setReference(error.digest ?? reportBrowserError(error, "browser.boundary")); + if (error.digest) reportBrowserError(error, "browser.boundary"); }, [error]); return ( @@ -63,12 +66,12 @@ export default function AdminErrorPage({
- {error.digest ? ( + {reference ? (

- {t("reference", { digest: error.digest })} + {t("reference", { digest: reference })}

) : null}
diff --git a/src/app/admin/permissions/[id]/error.tsx b/src/app/admin/permissions/[id]/error.tsx index 614ffae2..29100ab9 100644 --- a/src/app/admin/permissions/[id]/error.tsx +++ b/src/app/admin/permissions/[id]/error.tsx @@ -1,8 +1,9 @@ "use client"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { reportBrowserError } from "@/lib/browser-errors"; export default function PermissionRankError({ error, @@ -11,8 +12,10 @@ export default function PermissionRankError({ error: Error & { digest?: string }; reset: () => void; }) { + const [reference, setReference] = useState(error.digest); useEffect(() => { - console.error(error); + setReference(error.digest ?? reportBrowserError(error, "browser.boundary")); + if (error.digest) reportBrowserError(error, "browser.boundary"); }, [error]); return ( @@ -28,9 +31,9 @@ export default function PermissionRankError({
 					{error.stack ?? error.message}
 				
- {error.digest ? ( + {reference ? (

- Reference: {error.digest} + Reference: {reference}

) : null} - {error.digest ? ( -

Reference: {error.digest}

+ {reference ? ( +

Reference: {reference}

) : null}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index cd4feeae..ab875498 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -9,7 +9,6 @@ import type { ReactNode } from "react"; import { Toaster } from "sonner"; import { PwaRegister } from "@/components/pwa-register"; import { ScopedThemeVars } from "@/components/scoped-theme-vars"; -import { SmoothScroll } from "@/components/smooth-scroll"; import { ThemeScopeDetector } from "@/components/theme-scope-detector"; import { ThemeVars } from "@/components/theme-vars"; import { enforceSiteAccess } from "@/lib/access-guard"; @@ -102,7 +101,6 @@ export default async function RootLayout({ {children} - ) => Promise; diff --git a/src/components/smooth-scroll.tsx b/src/components/smooth-scroll.tsx deleted file mode 100644 index c779e829..00000000 --- a/src/components/smooth-scroll.tsx +++ /dev/null @@ -1,34 +0,0 @@ -"use client"; - -import { Lenis } from "lenis/react"; -import { usePathname } from "next/navigation"; -import { useEffect, useState } from "react"; - -export function SmoothScroll() { - const pathname = usePathname(); - const isAdmin = pathname === "/admin" || pathname?.startsWith("/admin/"); - const [prefersReduced, setPrefersReduced] = useState(true); - - useEffect(() => { - const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); - setPrefersReduced(mq.matches); - const onChange = (e: MediaQueryListEvent) => setPrefersReduced(e.matches); - mq.addEventListener("change", onChange); - return () => mq.removeEventListener("change", onChange); - }, []); - - // Admin panels own their scroll containers; root wheel smoothing consumes their events. - if (prefersReduced || isAdmin) return null; - - return ( - - ); -} diff --git a/src/features/housekeeping/domains/system/manifest.ts b/src/features/housekeeping/domains/system/manifest.ts index f4ba2345..48d4a879 100644 --- a/src/features/housekeeping/domains/system/manifest.ts +++ b/src/features/housekeeping/domains/system/manifest.ts @@ -15,6 +15,7 @@ export const systemManifest = { PERMS.LOGS_VIEW, PERMS.ANALYTICS_VIEW, PERMS.DEVOPS_VIEW, + PERMS.DEVOPS_EDIT, PERMS.NOTIFICATIONS_VIEW, PERMS.PERMISSIONS_MANAGE, PERMS.RCON_EXECUTE, diff --git a/src/features/housekeeping/foundation/foundation-source-contract.test.ts b/src/features/housekeeping/foundation/foundation-source-contract.test.ts index f3ad7eb0..f93db2e4 100644 --- a/src/features/housekeeping/foundation/foundation-source-contract.test.ts +++ b/src/features/housekeeping/foundation/foundation-source-contract.test.ts @@ -612,8 +612,8 @@ describe("housekeeping foundation completion contracts", () => { HOUSEKEEPING_MIGRATION_MATRIX, ); - expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(138); - expect(discovered).toHaveLength(138); + expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(139); + expect(discovered).toHaveLength(139); expect(issues).toEqual([]); }); }); diff --git a/src/features/housekeeping/foundation/registry.test.ts b/src/features/housekeeping/foundation/registry.test.ts index 5bbc528d..a57ce82d 100644 --- a/src/features/housekeeping/foundation/registry.test.ts +++ b/src/features/housekeeping/foundation/registry.test.ts @@ -173,6 +173,7 @@ const expectedManifests = [ PERMS.LOGS_VIEW, PERMS.ANALYTICS_VIEW, PERMS.DEVOPS_VIEW, + PERMS.DEVOPS_EDIT, PERMS.NOTIFICATIONS_VIEW, PERMS.PERMISSIONS_MANAGE, PERMS.RCON_EXECUTE, diff --git a/src/features/housekeeping/migration/discover-legacy-pages.test.ts b/src/features/housekeeping/migration/discover-legacy-pages.test.ts index 8138cacc..6482d4f5 100644 --- a/src/features/housekeeping/migration/discover-legacy-pages.test.ts +++ b/src/features/housekeeping/migration/discover-legacy-pages.test.ts @@ -29,7 +29,7 @@ describe("discoverLegacyPages", () => { it("discovers the exact legacy administration inventory", () => { const pages = discoverLegacyPages(); - expect(pages).toHaveLength(138); + expect(pages).toHaveLength(139); expect(pages).toContainEqual({ surface: "admin", legacyPath: "/admin/users/:id/edit", diff --git a/src/features/housekeeping/migration/matrix.test.ts b/src/features/housekeeping/migration/matrix.test.ts index 1badc4e0..3db71421 100644 --- a/src/features/housekeeping/migration/matrix.test.ts +++ b/src/features/housekeeping/migration/matrix.test.ts @@ -4,10 +4,10 @@ import { HOUSEKEEPING_MIGRATION_MATRIX } from "./matrix"; import { validateMigrationEntries } from "./validate-matrix"; describe("HOUSEKEEPING_MIGRATION_MATRIX", () => { - it("covers all 138 legacy pages exactly once", () => { + it("covers all 139 legacy pages exactly once", () => { const discovered = discoverLegacyPages(); - expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(138); + expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(139); expect( validateMigrationEntries(discovered, HOUSEKEEPING_MIGRATION_MATRIX), ).toEqual([]); diff --git a/src/features/housekeeping/migration/system.test.ts b/src/features/housekeeping/migration/system.test.ts index da8b3244..76029d30 100644 --- a/src/features/housekeeping/migration/system.test.ts +++ b/src/features/housekeeping/migration/system.test.ts @@ -19,8 +19,8 @@ const SYSTEM_PREFIXES = [ ] as const; describe("systemMigrationEntries", () => { - it("covers all 19 System pages exactly once", () => { - expect(systemMigrationEntries).toHaveLength(19); + it("covers all 20 System pages exactly once", () => { + expect(systemMigrationEntries).toHaveLength(20); expect( validateMigrationEntries( ownedLegacyPages(SYSTEM_PREFIXES), diff --git a/src/features/housekeeping/migration/system.ts b/src/features/housekeeping/migration/system.ts index d990c1d6..548bba6d 100644 --- a/src/features/housekeeping/migration/system.ts +++ b/src/features/housekeeping/migration/system.ts @@ -193,6 +193,18 @@ export const systemMigrationEntries: readonly MigrationEntry[] = [ localization: "PARTIAL", accessibility: "PARTIAL", }), + plannedSystemEntry({ + surface: "admin", + legacyPath: "/admin/devops/cms-errors", + sourceFile: "src/app/admin/devops/cms-errors/page.tsx", + targetPath: "/admin/system/observability/devops/cms-errors", + decision: "REBUILD", + capabilities: { read: [PERMS.DEVOPS_VIEW], mutate: [PERMS.DEVOPS_EDIT] }, + dependencies: { queries: ["ErrorStore"], mutations: ["resolveCmsError"] }, + auditRequirement: "MUTATION", + localization: "PARTIAL", + accessibility: "PARTIAL", + }), plannedSystemEntry({ surface: "admin", legacyPath: "/admin/emulator", diff --git a/src/instrumentation-client.ts b/src/instrumentation-client.ts new file mode 100644 index 00000000..19849dad --- /dev/null +++ b/src/instrumentation-client.ts @@ -0,0 +1,8 @@ +import { reportBrowserError } from "@/lib/browser-errors"; + +window.addEventListener("error", (event) => { + if (event.error) reportBrowserError(event.error); +}); +window.addEventListener("unhandledrejection", (event) => + reportBrowserError(event.reason, "browser.unhandled_rejection"), +); diff --git a/src/lib/api-handler.ts b/src/lib/api-handler.ts index fcaf74d1..3e925cfc 100644 --- a/src/lib/api-handler.ts +++ b/src/lib/api-handler.ts @@ -122,12 +122,12 @@ export function withAdmin( return response; } catch (error) { await finishExport?.().catch(() => undefined); - logServerError("admin.api_failed", error, { + const errorId = logServerError("admin.api_failed", error, { path: request.nextUrl.pathname, userId: context.session.user.id, }); return NextResponse.json( - { ok: false, error: "Internal server error" }, + { ok: false, error: "Internal server error", errorId }, { status: 500 }, ); } diff --git a/src/lib/browser-errors.test.ts b/src/lib/browser-errors.test.ts new file mode 100644 index 00000000..68e8639c --- /dev/null +++ b/src/lib/browser-errors.test.ts @@ -0,0 +1,28 @@ +import { afterEach, expect, it, vi } from "vitest"; +import { reportBrowserError } from "./browser-errors"; + +afterEach(() => vi.unstubAllGlobals()); +it("returns a reusable reference, deduplicates reports and omits URL parameters", () => { + const fetchMock = vi.fn().mockResolvedValue({ ok: true }); + vi.stubGlobal("window", {}); + vi.stubGlobal("location", { + pathname: "/admin/studio", + search: "?token=secret", + }); + vi.stubGlobal("fetch", fetchMock); + const error = new Error("browser-test-unique"); + const id = reportBrowserError(error); + expect(reportBrowserError(error)).toBe(id); + expect(fetchMock).toHaveBeenCalledTimes(1); + const payload = JSON.parse(fetchMock.mock.calls[0][1].body); + expect(payload.clientReference).toBe(id); + expect(payload.path).toBe("/admin/studio"); + expect(JSON.stringify(payload)).not.toContain("token=secret"); +}); +it("never throws again when reporting fails on the network", async () => { + vi.stubGlobal("window", {}); + vi.stubGlobal("location", { pathname: "/" }); + vi.stubGlobal("fetch", vi.fn().mockRejectedValue(new Error("offline"))); + expect(reportBrowserError(new Error("offline-test"))).toBeTruthy(); + await Promise.resolve(); +}); diff --git a/src/lib/browser-errors.ts b/src/lib/browser-errors.ts new file mode 100644 index 00000000..8a8b602f --- /dev/null +++ b/src/lib/browser-errors.ts @@ -0,0 +1,34 @@ +const seen = new Map(); +export function reportBrowserError( + error: unknown, + event = "browser.exception", +) { + if (typeof window === "undefined") return; + const candidate = + error instanceof Error + ? error + : new Error(typeof error === "string" ? error : "Unknown browser error"); + const key = `${event}:${location.pathname}:${"digest" in candidate ? candidate.digest : ""}:${candidate.message}`; + const now = Date.now(); + const prior = seen.get(key); + if (prior && now - prior.at < 60000) return prior.id; + const reference = crypto.randomUUID(); + if (seen.size >= 100) seen.clear(); + seen.set(key, { at: now, id: reference }); + const digest = "digest" in candidate ? String(candidate.digest) : undefined; + void fetch("/api/diagnostics/errors", { + method: "POST", + headers: { "Content-Type": "application/json" }, + keepalive: true, + body: JSON.stringify({ + event, + clientReference: reference, + message: candidate.message.slice(0, 1000), + stack: candidate.stack?.slice(0, 6000), + path: location.pathname, + digest, + release: process.env.NEXT_PUBLIC_CMS_RELEASE, + }), + }).catch(() => {}); + return reference; +} diff --git a/src/lib/error-monitor.test.ts b/src/lib/error-monitor.test.ts new file mode 100644 index 00000000..a9fa2e2a --- /dev/null +++ b/src/lib/error-monitor.test.ts @@ -0,0 +1,56 @@ +import { mkdtemp, rm } from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { expect, it } from "vitest"; +import { createErrorRecord, ErrorStore } from "./error-monitor"; + +it("redacts secrets in messages, stack and context while retaining a reference", () => { + const r = createErrorRecord( + "server", + "payment.failed", + new Error("https://user:password@host/a?token=abc password=hello"), + { + token: "secret", + path: "/a?session=hidden", + nested: { password: "never" }, + }, + ); + const text = JSON.stringify(r); + for (const secret of ["password@", "token=abc", "hello", "hidden", "never"]) + expect(text).not.toContain(secret); + expect(r.id).toBeTruthy(); + expect(r.stack).toContain("Error"); +}); +it("persists across instances and groups repeated errors without losing references", async () => { + const dir = await mkdtemp(path.join(os.tmpdir(), "cms-errors-")); + try { + const store = new ErrorStore(dir); + const a = createErrorRecord("server", "test", new Error("failed")); + await store.append(a); + await store.append({ ...a, id: "second" }); + const result = await new ErrorStore(dir).read(); + expect(result.records).toHaveLength(2); + expect(result.records[0].fingerprint).toBe(result.records[1].fingerprint); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); +it("resolves a group and reopens it when a later occurrence arrives", async () => { + const dir = await mkdtemp(path.join(os.tmpdir(), "cms-errors-")); + try { + const store = new ErrorStore(dir); + const a = createErrorRecord("server", "test", new Error("failed")); + await store.append(a); + await store.resolve(a.fingerprint); + expect((await store.read()).records[0].resolved).toBe(true); + await store.append({ + ...a, + id: "later", + at: new Date(Date.now() + 1000).toISOString(), + }); + expect((await store.read()).records[0].resolved).toBe(false); + await expect(store.resolve("../escape")).rejects.toThrow(); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); diff --git a/src/lib/error-monitor.ts b/src/lib/error-monitor.ts new file mode 100644 index 00000000..6a4dc7c2 --- /dev/null +++ b/src/lib/error-monitor.ts @@ -0,0 +1,209 @@ +import { createHash, randomUUID } from "node:crypto"; +import { + appendFile, + mkdir, + readdir, + readFile, + stat, + unlink, + writeFile, +} from "node:fs/promises"; +import path from "node:path"; + +export function redactErrorText(value: string): string { + return value + .slice(0, 12000) + .replace( + /(https?:\/\/|mysql:\/\/|redis:\/\/)[^\s/@]+:[^\s/@]+@/gi, + "$1[REDACTED]@", + ) + .replace(/([?&])[^\s#)]+/g, "$1[REDACTED]") + .replace( + /((?:password|secret|token|authorization|cookie|api[_-]?key)\s*[:=]\s*)(?:"[^"]*"|'[^']*'|[^\s,;]+)/gi, + "$1[REDACTED]", + ) + .replace(/Bearer\s+[^\s,;]+/gi, "Bearer [REDACTED]"); +} +export interface CmsErrorRecord { + id: string; + resolved?: boolean; + at: string; + source: "server" | "browser"; + event: string; + message: string; + stack: string; + release: string; + fingerprint: string; + context: Record; +} +export function createErrorRecord( + source: CmsErrorRecord["source"], + event: string, + error: unknown, + context: Record = {}, +): CmsErrorRecord { + const message = redactErrorText( + error instanceof Error + ? error.message + : typeof error === "string" + ? error + : "Unknown error", + ); + const stack = + error instanceof Error ? redactErrorText(error.stack ?? "") : ""; + const safeContext = Object.fromEntries( + Object.entries(context) + .slice(0, 24) + .map(([key, value]) => [ + key, + /password|secret|token|cookie|authorization|body|query|email|api[_-]?key/i.test( + key, + ) + ? "[REDACTED]" + : typeof value === "string" + ? redactErrorText(value).slice(0, 1000) + : typeof value === "number" || + typeof value === "boolean" || + value === null + ? value + : "[NON_SCALAR]", + ]), + ); + return { + id: randomUUID(), + at: new Date().toISOString(), + source, + event: redactErrorText(event).slice(0, 160), + message, + stack, + release: process.env.NEXT_PUBLIC_CMS_RELEASE ?? "unknown", + fingerprint: createHash("sha256") + .update(`${source}:${event}:${message}:${stack.split("\n")[1] ?? ""}`) + .digest("hex") + .slice(0, 16), + context: safeContext, + }; +} +const DAY_LIMIT = 10 * 1024 * 1024; +const RETENTION_DAYS = 7; +export class ErrorStore { + private static queue: Promise = Promise.resolve(); + private static pending = 0; + constructor( + private directory = path.join(process.cwd(), "storage", "cms-errors"), + ) {} + async append(record: CmsErrorRecord) { + if (ErrorStore.pending >= 100) throw new Error("Error storage queue full"); + ErrorStore.pending++; + const write = ErrorStore.queue.then(() => this.writeRecord(record)); + ErrorStore.queue = write.catch(() => {}); + try { + await write; + } finally { + ErrorStore.pending--; + } + } + private async writeRecord(record: CmsErrorRecord) { + await mkdir(this.directory, { recursive: true }); + const files = await readdir(this.directory); + const cutoff = new Date(Date.now() - RETENTION_DAYS * 86400000) + .toISOString() + .slice(0, 10); + for (const name of files) + if ( + /^\d{4}-\d{2}-\d{2}(\.jsonl|\.[a-f0-9]{16}\.resolved)$/.test(name) && + name.slice(0, 10) < cutoff + ) + await unlink(path.join(this.directory, name)).catch(() => {}); + const file = path.join(this.directory, record.at.slice(0, 10) + ".jsonl"); + const size = await stat(file) + .then((s) => s.size) + .catch(() => 0); + if (size >= DAY_LIMIT) throw new Error("Daily error storage limit reached"); + await appendFile(file, JSON.stringify(record) + "\n", { mode: 0o600 }); + } + async resolve(fingerprint: string) { + if (!/^[a-f0-9]{16}$/.test(fingerprint)) + throw new Error("Invalid fingerprint"); + const { records } = await this.read(); + if (!records.some((r) => r.fingerprint === fingerprint)) + throw new Error("Error group no longer available"); + const now = new Date().toISOString(); + await writeFile( + path.join(this.directory, `${now.slice(0, 10)}.${fingerprint}.resolved`), + now, + { mode: 0o600 }, + ); + } + async read(): Promise<{ records: CmsErrorRecord[]; truncated: boolean }> { + const files = await readdir(this.directory).catch( + (e: NodeJS.ErrnoException) => { + if (e.code === "ENOENT") return []; + throw e; + }, + ); + const resolved = new Map(); + for (const name of files + .filter((f) => /^\d{4}-\d{2}-\d{2}\.[a-f0-9]{16}\.resolved$/.test(f)) + .sort()) { + const at = await readFile(path.join(this.directory, name), "utf8"); + resolved.set(name.split(".")[1], at); + } + const records: CmsErrorRecord[] = []; + const cutoff = new Date(Date.now() - RETENTION_DAYS * 86400000) + .toISOString() + .slice(0, 10); + for (const name of files + .filter( + (f) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(f) && f.slice(0, 10) >= cutoff, + ) + .sort() + .reverse()) { + const file = path.join(this.directory, name); + if ((await stat(file)).size > DAY_LIMIT + 100000) continue; + const lines = (await readFile(file, "utf8")).trim().split("\n").reverse(); + for (const line of lines) { + try { + const r = JSON.parse(line); + if (r.id && r.fingerprint) + records.push({ + ...r, + resolved: (resolved.get(r.fingerprint) ?? "") >= r.at, + }); + } catch { + /* Ignore an interrupted final write. */ + } + if (records.length >= 1000) return { records, truncated: true }; + } + } + return { records, truncated: false }; + } +} +const store = new ErrorStore(); +let pending = 0; +let lastFailure = 0; +export function captureCmsError( + source: CmsErrorRecord["source"], + event: string, + error: unknown, + context: Record = {}, +) { + const record = createErrorRecord(source, event, error, context); + if (process.env.NODE_ENV === "test" || process.env.VITEST || pending >= 100) + return record.id; + pending++; + void store + .append(record) + .catch(() => { + if (Date.now() - lastFailure > 60000) { + lastFailure = Date.now(); + process.stderr.write( + "CMS error monitor could not persist an event; check storage permissions or daily quota.\n", + ); + } + }) + .finally(() => { + pending--; + }); + return record.id; +} diff --git a/src/lib/logger.ts b/src/lib/logger.ts index 8600ef41..9aee6a10 100644 --- a/src/lib/logger.ts +++ b/src/lib/logger.ts @@ -1,5 +1,6 @@ import pino from "pino"; import { env } from "@/env"; +import { captureCmsError } from "./error-monitor"; type LogLevel = "debug" | "info" | "warn" | "error"; @@ -41,7 +42,21 @@ export const logger = { warn(message: string, meta: Record = {}): void { pinoLogger.warn(meta, message); }, - error(message: string, meta: Record = {}): void { - pinoLogger.error(meta, message); + error(message: string, meta: Record = {}): string { + const error = + meta.err instanceof Error + ? meta.err + : meta.error instanceof Error + ? meta.error + : new Error( + typeof meta.err === "string" + ? meta.err + : typeof meta.error === "string" + ? meta.error + : message, + ); + const errorId = captureCmsError("server", message, error, meta); + pinoLogger.error({ ...meta, errorId }, message); + return errorId; }, }; diff --git a/src/lib/observability-contract.test.ts b/src/lib/observability-contract.test.ts index 2ee292d9..448998e6 100644 --- a/src/lib/observability-contract.test.ts +++ b/src/lib/observability-contract.test.ts @@ -1,10 +1,14 @@ -import { createRequire } from "node:module"; -import { describe, expect, it } from "vitest"; +import { expect, it } from "vitest"; +import { createErrorRecord } from "./error-monitor"; -describe("observability dependency contract", () => { - it("does not ship the Sentry SDK", () => { - const require = createRequire(import.meta.url); - - expect(() => require.resolve("@sentry/nextjs")).toThrow(); - }); +it("captures diagnostic data without serializing arbitrary request bodies", () => { + const record = createErrorRecord( + "server", + "request.failed", + new Error("failure"), + { body: { password: "secret" }, path: "/admin/studio" }, + ); + expect(record.context.body).toBe("[REDACTED]"); + expect(record.context.path).toBe("/admin/studio"); + expect(record.stack).toContain("failure"); }); diff --git a/src/lib/report-error.ts b/src/lib/report-error.ts index 74f4f267..1d28fa5c 100644 --- a/src/lib/report-error.ts +++ b/src/lib/report-error.ts @@ -5,9 +5,8 @@ import { logger } from "@/lib/logger"; * Domain errors (validation, auth, etc.) should NOT go through here. */ export function reportError(error: unknown, context = "Unhandled error"): void { - const message = error instanceof Error ? error.message : String(error); logger.error(context, { - err: message, + err: error, name: error instanceof Error ? error.name : undefined, }); } diff --git a/src/lib/server-log.ts b/src/lib/server-log.ts index 7f3f7409..52b5e268 100644 --- a/src/lib/server-log.ts +++ b/src/lib/server-log.ts @@ -38,8 +38,11 @@ export function logServerError( event: string, error: unknown, context: LogContext = {}, -): void { - logger.error(JSON.stringify(serverErrorRecord(event, error, context))); +): string { + return logger.error(event, { + ...serverErrorRecord(event, error, context).context, + err: error, + }); } function logScalar(value: unknown): LogScalar { diff --git a/src/lib/services/furni-job-worker.ts b/src/lib/services/furni-job-worker.ts index ccc483c9..11eb7e41 100644 --- a/src/lib/services/furni-job-worker.ts +++ b/src/lib/services/furni-job-worker.ts @@ -17,7 +17,9 @@ let running: Promise | undefined; export function drainFurnitureImports(): Promise { if (running) return running; running = drain() - .catch((error) => logServerError("furni.worker_failed", error)) + .catch((error) => { + logServerError("furni.worker_failed", error); + }) .finally(() => { running = undefined; }); diff --git a/vitest.config.ts b/vitest.config.ts index ec90cee7..7b0ea77c 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -15,6 +15,8 @@ export default defineConfig({ }, test: { environment: "node", + // Preserve module-initialization calls used by route permission contract tests. + clearMocks: false, // Modules under test transitively import @/env; skip its strict parse so // unit tests run without a populated .env. env: {