diff --git a/MCP.md b/MCP.md index d6db9fa6..cbd924ad 100644 --- a/MCP.md +++ b/MCP.md @@ -9,6 +9,10 @@ structured API. ## Table of Contents - [Setup](#setup) + - [Option A: OAuth 2.1 (recommended)](#option-a-oauth-21-recommended) + - [Option B: Static API Token (deprecated)](#option-b-static-api-token-deprecated) +- [Authentication](#authentication) +- [OAuth Scopes](#oauth-scopes) - [Limitations & Important Notes](#limitations--important-notes) - [Resources (read-only)](#resources-read-only) - [Tools (read-write)](#tools-read-write) @@ -22,22 +26,51 @@ structured API. ### 1. Enable the MCP addon (admin) An administrator must first enable the MCP addon from the **Admin Panel > Addons** page. Until enabled, the `/mcp` -endpoint returns `403 Forbidden` and the MCP section does not appear in user settings. +endpoint returns `404` and the MCP section does not appear in user settings. -### 2. Create an API token +### 2. Connect your MCP client -Once MCP is enabled, go to **Settings > MCP Configuration** and create an API token: +#### Option A: OAuth 2.1 (recommended) -1. Click **Create New Token** -2. Give it a descriptive name (e.g. "Claude Desktop", "Work laptop") -3. **Copy the token immediately** — it is shown only once and cannot be recovered +MCP clients that support OAuth 2.1 (such as Claude Desktop via `mcp-remote`) authenticate automatically. No token +management required — just provide the server URL: -Each user can create up to **10 tokens**. +```json +{ + "mcpServers": { + "trek": { + "command": "npx", + "args": [ + "mcp-remote", + "https://your-trek-instance.com/mcp" + ] + } + } +} +``` -### 3. Configure your MCP client +> The path to `npx` may need to be adjusted for your system (e.g. `C:\PROGRA~1\nodejs\npx.cmd` on Windows). -The Settings page shows a ready-to-copy client configuration snippet. For **Claude Desktop**, add the following to your -`claude_desktop_config.json`: +**What happens automatically:** +1. The client fetches `/.well-known/oauth-authorization-server` to discover the TREK authorization server. +2. The client registers itself via [Dynamic Client Registration (RFC 7591)](https://www.rfc-editor.org/rfc/rfc7591). +3. Your browser opens TREK's consent screen, where you choose which scopes (permissions) to grant. +4. The client receives a short-lived access token and a rotating refresh token — no re-authorization needed. + +> **Requirement:** The `APP_URL` environment variable must be set to your TREK instance's public URL for OAuth +> discovery to work correctly. + +**For more control over scopes or to use confidential client mode**, pre-create an OAuth client in +**Settings > Integrations > MCP > OAuth Clients** before connecting. Clients created there have a client secret +(`trekcs_` prefix) and fixed scopes that you define up front. + +#### Option B: Static API Token (deprecated) + +> **Deprecated:** Static API tokens will stop working in a future version. Migrate to OAuth 2.1 above. + +1. Go to **Settings > Integrations > MCP** and create an API token. +2. Click **Create New Token**, give it a name, and **copy the token immediately** — it is shown only once. +3. Add it to your `claude_desktop_config.json`: ```json { @@ -55,7 +88,65 @@ The Settings page shows a ready-to-copy client configuration snippet. For **Clau } ``` -> The path to `npx` may need to be adjusted for your system (e.g. `C:\PROGRA~1\nodejs\npx.cmd` on Windows). +Static tokens grant full access to all tools and resources (no scope restrictions). Sessions authenticated with a +static token will receive deprecation warnings in the AI client via server instructions and tool results. + +Each user can create up to **10 static tokens**. + +--- + +## Authentication + +TREK's MCP server supports three authentication methods. OAuth 2.1 is the recommended path for all external clients. + +| Method | Token prefix | Access level | TTL | Notes | +|--------|-------------|-------------|-----|-------| +| **OAuth 2.1** | `trekoa_` | Scoped (per-consent) | 1 hour | Recommended. Automatically refreshed via 30-day rolling refresh tokens (`trekrf_` prefix). Replay-detected rotation — replayed tokens cascade-revoke the entire chain. | +| **Static API token** | `trek_` | Full access | No expiry | **Deprecated.** Triggers deprecation warnings in AI clients. Will be removed in a future release. | +| **Web session JWT** | — | Full access | Session-based | Used internally by the TREK web UI. Not intended for external clients. | + +All methods require the `Authorization: Bearer ` header (strict scheme enforcement — `Bearer` required). + +--- + +## OAuth Scopes + +When connecting via OAuth 2.1, you grant specific scopes during the consent step. TREK registers only the MCP tools +that match your granted scopes for that session. + +| Scope | Permission | Group | +|-------|-----------|-------| +| `trips:read` | View trips & itineraries | Trips | +| `trips:write` | Edit trips & itineraries | Trips | +| `trips:delete` | Delete trips (irreversible) | Trips | +| `trips:share` | Manage share links | Trips | +| `places:read` | View places & map data | Places | +| `places:write` | Manage places | Places | +| `atlas:read` | View Atlas | Atlas | +| `atlas:write` | Manage Atlas | Atlas | +| `packing:read` | View packing lists | Packing | +| `packing:write` | Manage packing lists | Packing | +| `todos:read` | View to-do lists | To-dos | +| `todos:write` | Manage to-do lists | To-dos | +| `budget:read` | View budget | Budget | +| `budget:write` | Manage budget | Budget | +| `reservations:read` | View reservations | Reservations | +| `reservations:write` | Manage reservations | Reservations | +| `collab:read` | View collaboration | Collaboration | +| `collab:write` | Manage collaboration | Collaboration | +| `notifications:read` | View notifications | Notifications | +| `notifications:write` | Manage notifications | Notifications | +| `vacay:read` | View vacation plans | Vacation | +| `vacay:write` | Manage vacation plans | Vacation | +| `geo:read` | Maps & geocoding | Geo | +| `weather:read` | Weather forecasts | Weather | + +**Scope rules:** +- A `:write` scope implies `:read` access for the same group (e.g. `budget:write` also grants budget read access). +- Any `trips:*` scope (`trips:read`, `trips:write`, `trips:delete`, or `trips:share`) grants trip read access. +- `list_trips` and `get_trip_summary` are **always available** regardless of scopes — they are navigation tools. +- Static tokens and web session JWTs have full access to all tools (equivalent to all scopes). +- Addon-gated tools (Atlas Extended, Collab, Vacay) require both the relevant scope **and** the addon to be enabled. --- @@ -68,10 +159,13 @@ The Settings page shows a ready-to-copy client configuration snippet. For **Clau | **No image uploads** | Cover images cannot be set through MCP. Use the web UI to upload trip covers. | | **Reservations are created as pending** | When the AI creates a reservation, it starts with `pending` status. You must confirm it manually or ask the AI to set the status to `confirmed`. | | **Demo mode restrictions** | If TREK is running in demo mode, all write operations through MCP are blocked. | -| **Rate limiting** | 60 requests per minute per user. Exceeding this returns a `429` error. | -| **Session limits** | Maximum 5 concurrent MCP sessions per user. Sessions expire after 1 hour of inactivity. | -| **Token limits** | Maximum 10 API tokens per user. | -| **Token revocation** | Deleting a token immediately terminates all active MCP sessions for that user. | +| **Rate limiting** | 300 requests per minute per user (configurable via `MCP_RATE_LIMIT`). Exceeding this returns a `429` error. | +| **Per-client rate limiting** | Rate limits are tracked per user-client pair, so each OAuth client has its own independent rate limit window. | +| **Session limits** | Maximum 20 concurrent MCP sessions per user (configurable via `MCP_MAX_SESSION_PER_USER`). Sessions expire after 1 hour of inactivity. | +| **Token limits** | Maximum 10 static API tokens per user. Maximum 10 OAuth clients per user. | +| **Token revocation** | Deleting a static token or revoking an OAuth session immediately terminates all active MCP sessions for that token/client. | +| **OAuth scope enforcement** | Only tools matching your granted OAuth scopes are registered in the session. Calling an out-of-scope tool returns an error. | +| **Addon toggle invalidation** | When an admin enables or disables an addon, all active MCP sessions are invalidated and must be re-established. | | **Real-time sync** | Changes made through MCP are broadcast to all connected clients in real-time via WebSocket, just like changes made through the web UI. | | **Addon-gated features** | Some resources and tools are only available when the corresponding addon (Atlas, Collab, Vacay) is enabled by an admin. | @@ -356,11 +450,12 @@ trip in a single call. MCP prompts are pre-built context loaders your AI client can invoke to get a structured starting point for common tasks. -| Prompt | Description | -|-------------------|---------------------------------------------------------------------------------| -| `trip-summary` | Load a formatted summary of a trip (dates, members, days, budget, packing, reservations) before planning or modifying it. | -| `packing-list` | Get a formatted packing checklist for a trip, grouped by category. | -| `budget-overview` | Get a formatted budget summary with totals by category and per-person cost. | +| Prompt | Description | +|----------------------|---------------------------------------------------------------------------------| +| `trip-summary` | Load a formatted summary of a trip (dates, members, days, budget, packing, reservations) before planning or modifying it. | +| `packing-list` | Get a formatted packing checklist for a trip, grouped by category. | +| `budget-overview` | Get a formatted budget summary with totals by category and per-person cost. | +| `token_auth_notice` | Static token deprecation notice and migration guide. Only available in sessions authenticated with a legacy `trek_` token. | --- diff --git a/README.md b/README.md index e0983450..d2749e16 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,8 @@ - **Dashboard Widgets** — Currency converter and timezone clock, toggleable per user ### AI / MCP Integration -- **MCP Server** — Built-in [Model Context Protocol](MCP.md) server exposes 80+ tools and 27 resources so AI assistants (Claude, Cursor, etc.) can read and modify your trips +- **MCP Server** — Built-in [Model Context Protocol](MCP.md) server with OAuth 2.1 authentication exposes 80+ tools and 27 resources so AI assistants (Claude, Cursor, etc.) can read and modify your trips +- **Granular Scopes** — 24 OAuth scopes across 13 permission groups let you control exactly what data your AI client can access - **Full Trip Automation** — AI can create trips, plan itineraries, build packing lists, manage budgets, send collab messages, mark countries visited, and more in a single conversation - **Prompts** — Pre-built `trip-summary`, `packing-list`, and `budget-overview` prompts give AI clients instant structured context - **Addon-Aware** — Atlas, Collab, and Vacay features are exposed automatically when those addons are enabled @@ -97,7 +98,7 @@ - **PWA**: vite-plugin-pwa + Workbox - **Real-Time**: WebSocket (`ws`) - **State**: Zustand -- **Auth**: JWT + OIDC + TOTP (MFA) +- **Auth**: JWT + OAuth 2.1 + OIDC + TOTP (MFA) - **Maps**: Leaflet + react-leaflet-cluster + Google Places API (optional) - **Weather**: Open-Meteo API (free, no key required) - **Icons**: lucide-react @@ -166,8 +167,8 @@ services: # - DEMO_MODE=false # Enable demo mode (resets data hourly) # - ADMIN_EMAIL=admin@trek.local # Initial admin e-mail — only used on first boot when no users exist # - ADMIN_PASSWORD=changeme # Initial admin password — only used on first boot when no users exist - # - MCP_RATE_LIMIT=60 # Max MCP API requests per user per minute (default: 60) - # - MCP_MAX_SESSION_PER_USER=5 # Max concurrent MCP sessions per user (default: 5) + # - MCP_RATE_LIMIT=300 # Max MCP API requests per user per minute (default: 300) + # - MCP_MAX_SESSION_PER_USER=20 # Max concurrent MCP sessions per user (default: 20) volumes: - ./data:/app/data - ./uploads:/app/uploads @@ -311,8 +312,8 @@ trek.yourdomain.com { | `ADMIN_PASSWORD` | Password for the first admin account created on initial boot. Must be set together with `ADMIN_EMAIL`. | random | | **Other** | | | | `DEMO_MODE` | Enable demo mode (hourly data resets) | `false` | -| `MCP_RATE_LIMIT` | Max MCP API requests per user per minute | `60` | -| `MCP_MAX_SESSION_PER_USER` | Max concurrent MCP sessions per user | `5` | +| `MCP_RATE_LIMIT` | Max MCP API requests per user per minute | `300` | +| `MCP_MAX_SESSION_PER_USER` | Max concurrent MCP sessions per user | `20` | ## Optional API Keys diff --git a/chart/values.yaml b/chart/values.yaml index 47a941c7..35758aa9 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -51,10 +51,10 @@ env: # Override the OIDC discovery endpoint for providers with non-standard paths (e.g. Authentik). # DEMO_MODE: "false" # Enable demo mode (hourly data resets). - # MCP_RATE_LIMIT: "60" - # Max MCP API requests per user per minute. Defaults to 60. - # MCP_MAX_SESSION_PER_USER: "5" - # Max concurrent MCP sessions per user. Defaults to 5. + # MCP_RATE_LIMIT: "300" + # Max MCP API requests per user per minute. Defaults to 300. + # MCP_MAX_SESSION_PER_USER: "20" + # Max concurrent MCP sessions per user. Defaults to 20. # Secret environment variables stored in a Kubernetes Secret. diff --git a/client/package-lock.json b/client/package-lock.json index f1ac488c..df72f135 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -33,7 +33,7 @@ "@types/react-dom": "^18.2.19", "@types/react-window": "^1.8.8", "@vitejs/plugin-react": "^4.2.1", - "@vitest/coverage-v8": "^4.1.2", + "@vitest/coverage-v8": "^3.2.4", "autoprefixer": "^10.4.18", "jsdom": "^29.0.1", "msw": "^2.13.0", @@ -43,7 +43,7 @@ "typescript": "^6.0.2", "vite": "^5.1.4", "vite-plugin-pwa": "^0.21.0", - "vitest": "^4.1.2" + "vitest": "^3.2.4" } }, "node_modules/@adobe/css-tools": { @@ -66,15 +66,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@apideck/better-ajv-errors": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", - "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.7.tgz", + "integrity": "sha512-TajUJwGWbDwkCx/CZi7tRE8PVB7simCvKJfHUsSdvps+aTM/PDPP4gkLmKnc+x3CE//y9i/nj74GqdL/hwk7Iw==", "dev": true, "license": "MIT", "dependencies": { - "json-schema": "^0.4.0", - "jsonpointer": "^5.0.0", + "jsonpointer": "^5.0.1", "leven": "^3.1.0" }, "engines": { @@ -85,9 +98,9 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.6.tgz", - "integrity": "sha512-BXWCh8dHs9GOfpo/fWGDJtDmleta2VePN9rn6WQt3GjEbxzutVF4t0x2pmH+7dbMCLtuv3MlwqRsAuxlzFXqFg==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.9.tgz", + "integrity": "sha512-zd9c/Wdso6v1U7v6w3i/hbAr4K7NaSHImdpvmLt+Y9ea5BhilnIGNkfhOJ7FEIuPipAnE9tZeDOll05WDT0kgg==", "dev": true, "license": "MIT", "dependencies": { @@ -101,9 +114,9 @@ } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.7.tgz", - "integrity": "sha512-d2BgqDUOS1Hfp4IzKUZqCNz+Kg3Y88AkaBvJK/ZVSQPU1f7OpPNi7nQTH6/oI47Dkdg+Z3e8Yp6ynOu4UMINAQ==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.9.tgz", + "integrity": "sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg==", "dev": true, "license": "MIT", "dependencies": { @@ -1841,23 +1854,10 @@ "node": ">=20.19.0" } }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/runtime": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz", - "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "dev": true, "license": "MIT", "optional": true, @@ -1865,16 +1865,395 @@ "tslib": "^2.4.0" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.4.0" + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, "node_modules/@exodus/bytes": { @@ -2043,6 +2422,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2080,6 +2462,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2175,6 +2560,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2224,6 +2612,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "Apache-2.0", "optional": true, "os": [ @@ -2388,13 +2779,62 @@ } }, "node_modules/@isaacs/cliui": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", - "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, "engines": { - "node": ">=18" + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, "node_modules/@jridgewell/gen-mapping": { @@ -2476,23 +2916,28 @@ "node": ">=18" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", - "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", - "dev": true, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "engines": { + "node": "^14.21.3 || >=16" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "funding": { + "url": "https://paulmillr.com/funding/" } }, "node_modules/@nodelib/fs.scandir": { @@ -2558,14 +3003,15 @@ "dev": true, "license": "MIT" }, - "node_modules/@oxc-project/types": { - "version": "0.122.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", - "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" + "optional": true, + "engines": { + "node": ">=14" } }, "node_modules/@react-leaflet/core": { @@ -2580,19 +3026,19 @@ } }, "node_modules/@react-pdf/fns": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.2.tgz", - "integrity": "sha512-qTKGUf0iAMGg2+OsUcp9ffKnKi41RukM/zYIWMDJ4hRVYSr89Q7e3wSDW/Koqx3ea3Uy/z3h2y3wPX6Bdfxk6g==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@react-pdf/fns/-/fns-3.1.3.tgz", + "integrity": "sha512-0I7pApDr1/RLAKbizuLy/IHTEa93LSPy/bEwYniboC3Xqnp6Od8xFJKbKEzGw2wh/5zKFFwl00g4t9RwgIMc3w==", "license": "MIT" }, "node_modules/@react-pdf/font": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.4.tgz", - "integrity": "sha512-8YtgGtL511txIEc9AjiilpZ7yjid8uCd8OGUl6jaL3LIHnrToUupSN4IzsMQpVTCMYiDLFnDNQzpZsOYtRS/Pg==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@react-pdf/font/-/font-4.0.6.tgz", + "integrity": "sha512-1RxR/hTyZcbgjESUjrMms574xuS9PLB4ovqQx6jvgdrIHXUyeUtSH6i3Szw1qVfUnA9MfaEm1FBuydQeJD39BQ==", "license": "MIT", "dependencies": { - "@react-pdf/pdfkit": "^4.1.0", - "@react-pdf/types": "^2.9.2", + "@react-pdf/pdfkit": "^5.0.0", + "@react-pdf/types": "^2.10.0", "fontkit": "^2.0.2", "is-url": "^1.2.4" } @@ -2608,34 +3054,36 @@ } }, "node_modules/@react-pdf/layout": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.4.2.tgz", - "integrity": "sha512-gNu2oh8MiGR+NJZYTJ4c4q0nWCESBI6rKFiodVhE7OeVAjtzZzd6l65wsN7HXdWJqOZD3ttD97iE+tf5SOd/Yg==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@react-pdf/layout/-/layout-4.5.0.tgz", + "integrity": "sha512-XlGLzcZFdEYQHK6b9z9uPOVywbjv6pQ92D4RvqRmYNjpf7lQLnhdHr63tbiF7fB5k8Lg9/lGBXkHbzkeQW5geQ==", "license": "MIT", "dependencies": { - "@react-pdf/fns": "3.1.2", + "@react-pdf/fns": "3.1.3", "@react-pdf/image": "^3.0.4", - "@react-pdf/primitives": "^4.1.1", - "@react-pdf/stylesheet": "^6.1.2", - "@react-pdf/textkit": "^6.1.0", - "@react-pdf/types": "^2.9.2", + "@react-pdf/primitives": "^4.2.0", + "@react-pdf/stylesheet": "^6.1.4", + "@react-pdf/textkit": "^6.1.1", + "@react-pdf/types": "^2.10.0", "emoji-regex-xs": "^1.0.0", "queue": "^6.0.1", "yoga-layout": "^3.2.1" } }, "node_modules/@react-pdf/pdfkit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-4.1.0.tgz", - "integrity": "sha512-Wm/IOAv0h/U5Ra94c/PltFJGcpTUd/fwVMVeFD6X9tTTPCttIwg0teRG1Lqq617J8K4W7jpL/B0HTH0mjp3QpQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@react-pdf/pdfkit/-/pdfkit-5.0.0.tgz", + "integrity": "sha512-FcQBWGtfhMGuOB0G3NcnF/cBq/JnFVs22i1tuafiT1XlmG6KjCxgTGng5bVh+b9RtTuwNpUGmCtB6CmG6B4ZVA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.13", + "@noble/ciphers": "^1.0.0", + "@noble/hashes": "^1.6.0", "@react-pdf/png-js": "^3.0.0", "browserify-zlib": "^0.2.0", - "crypto-js": "^4.2.0", "fontkit": "^2.0.2", "jay-peg": "^1.1.1", + "js-md5": "^0.8.3", "linebreak": "^1.1.0", "vite-compatible-readable-stream": "^3.6.1" } @@ -2650,9 +3098,9 @@ } }, "node_modules/@react-pdf/primitives": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.1.1.tgz", - "integrity": "sha512-IuhxYls1luJb7NUWy6q5avb1XrNaVj9bTNI40U9qGRuS6n7Hje/8H8Qi99Z9UKFV74bBP3DOf3L1wV2qZVgVrQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@react-pdf/primitives/-/primitives-4.2.0.tgz", + "integrity": "sha512-onlXLcA6SpsD7SX9HOyt55qdRRJCfauegPlo4ZNw0hA/IipaZTbT9MJliWKtEXm03ibGxAQyp/BgTuXm91fo0A==", "license": "MIT" }, "node_modules/@react-pdf/reconciler": { @@ -2668,23 +3116,17 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@react-pdf/reconciler/node_modules/scheduler": { - "version": "0.25.0-rc-603e6108-20241029", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz", - "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==", - "license": "MIT" - }, "node_modules/@react-pdf/render": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.3.2.tgz", - "integrity": "sha512-el5KYM1sH/PKcO4tRCIm8/AIEmhtraaONbwCrBhFdehoGv6JtgnXiMxHGAvZbI5kEg051GbyP+XIU6f6YbOu6Q==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@react-pdf/render/-/render-4.4.0.tgz", + "integrity": "sha512-+gGa9ymGosN6Ld3hFFSIVCV03Vva5S+asW7vmKetZY4LnhX5ea2gYNy6wL3e9VsLHqFDAefIXGtGDLH6XxQHag==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.13", - "@react-pdf/fns": "3.1.2", - "@react-pdf/primitives": "^4.1.1", - "@react-pdf/textkit": "^6.1.0", - "@react-pdf/types": "^2.9.2", + "@react-pdf/fns": "3.1.3", + "@react-pdf/primitives": "^4.2.0", + "@react-pdf/textkit": "^6.1.1", + "@react-pdf/types": "^2.10.0", "abs-svg-path": "^0.1.1", "color-string": "^1.9.1", "normalize-svg-path": "^1.1.0", @@ -2693,20 +3135,20 @@ } }, "node_modules/@react-pdf/renderer": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.3.2.tgz", - "integrity": "sha512-EhPkj35gO9rXIyyx29W3j3axemvVY5RigMmlK4/6Ku0pXB8z9PEE/sz4ZBOShu2uot6V4xiCR3aG+t9IjJJlBQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@react-pdf/renderer/-/renderer-4.4.0.tgz", + "integrity": "sha512-TtCcz1vyYD6fddhvBagwr9Aj3gRFLCqvduERQyNhTzHJi3zAKayHvJFr2PxcP4sRyIDRhibDW6ApqNhTqIVPoQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.13", - "@react-pdf/fns": "3.1.2", - "@react-pdf/font": "^4.0.4", - "@react-pdf/layout": "^4.4.2", - "@react-pdf/pdfkit": "^4.1.0", - "@react-pdf/primitives": "^4.1.1", + "@react-pdf/fns": "3.1.3", + "@react-pdf/font": "^4.0.6", + "@react-pdf/layout": "^4.5.0", + "@react-pdf/pdfkit": "^5.0.0", + "@react-pdf/primitives": "^4.2.0", "@react-pdf/reconciler": "^2.0.0", - "@react-pdf/render": "^4.3.2", - "@react-pdf/types": "^2.9.2", + "@react-pdf/render": "^4.4.0", + "@react-pdf/types": "^2.10.0", "events": "^3.3.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", @@ -2717,13 +3159,13 @@ } }, "node_modules/@react-pdf/stylesheet": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.2.tgz", - "integrity": "sha512-E3ftGRYUQGKiN3JOgtGsLDo0hGekA6dmkmi/MYACytmPTKxQRBSO3126MebmCq+t1rgU9uRlREIEawJ+8nzSbw==", + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@react-pdf/stylesheet/-/stylesheet-6.1.4.tgz", + "integrity": "sha512-jiwovO7lUwgccAh3JbVcXnh90AiSKZetdz2ETcWsKApPPLzLUzPkEs6wCVvZqh3lcGOAPFV3AfdMkFnLwv1ryg==", "license": "MIT", "dependencies": { - "@react-pdf/fns": "3.1.2", - "@react-pdf/types": "^2.9.2", + "@react-pdf/fns": "3.1.3", + "@react-pdf/types": "^2.10.0", "color-string": "^1.9.1", "hsl-to-hex": "^1.0.0", "media-engine": "^1.0.3", @@ -2731,26 +3173,26 @@ } }, "node_modules/@react-pdf/textkit": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.1.0.tgz", - "integrity": "sha512-sFlzDC9CDFrJsnL3B/+NHrk9+Advqk7iJZIStiYQDdskbow8GF/AGYrpIk+vWSnh35YxaGbHkqXq53XOxnyrjQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@react-pdf/textkit/-/textkit-6.1.1.tgz", + "integrity": "sha512-HAHoa407q0UHLzwe/oL6VwgJj2cGKs5vORSVY+cRG/GC0kt7nxUV9N+2hA6VcqJA37gSRg7BTLsVr8Tt+4l5ow==", "license": "MIT", "dependencies": { - "@react-pdf/fns": "3.1.2", + "@react-pdf/fns": "3.1.3", "bidi-js": "^1.0.2", "hyphen": "^1.6.4", "unicode-properties": "^1.4.1" } }, "node_modules/@react-pdf/types": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.9.2.tgz", - "integrity": "sha512-dufvpKId9OajLLbgn9q7VLUmyo1Jf+iyGk2ZHmCL8nIDtL8N1Ejh9TH7+pXXrR0tdie1nmnEb5Bz9U7g4hI4/g==", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@react-pdf/types/-/types-2.10.0.tgz", + "integrity": "sha512-iz0NusqQ/9ZHQirWhJqOaxY1UkpvuNkEDtH4/SPCnhZJKBO/IhlFLFHuzbHkmWByBoX6X3m8GCc2b/1QH6QNlA==", "license": "MIT", "dependencies": { - "@react-pdf/font": "^4.0.4", - "@react-pdf/primitives": "^4.1.1", - "@react-pdf/stylesheet": "^6.1.2" + "@react-pdf/font": "^4.0.6", + "@react-pdf/primitives": "^4.2.0", + "@react-pdf/stylesheet": "^6.1.4" } }, "node_modules/@remix-run/router": { @@ -2762,279 +3204,6 @@ "node": ">=14.0.0" } }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", - "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", - "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", - "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", - "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", - "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", - "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", - "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -3113,6 +3282,13 @@ } } }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/pluginutils/node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", @@ -3127,9 +3303,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", "cpu": [ "arm" ], @@ -3141,9 +3317,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", "cpu": [ "arm64" ], @@ -3155,9 +3331,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", "cpu": [ "arm64" ], @@ -3169,9 +3345,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", "cpu": [ "x64" ], @@ -3183,9 +3359,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", "cpu": [ "arm64" ], @@ -3197,9 +3373,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", "cpu": [ "x64" ], @@ -3211,9 +3387,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", "cpu": [ "arm" ], @@ -3228,9 +3404,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", "cpu": [ "arm" ], @@ -3245,9 +3421,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", "cpu": [ "arm64" ], @@ -3262,9 +3438,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", "cpu": [ "arm64" ], @@ -3279,9 +3455,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", "cpu": [ "loong64" ], @@ -3296,9 +3472,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", "cpu": [ "loong64" ], @@ -3313,9 +3489,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", "cpu": [ "ppc64" ], @@ -3330,9 +3506,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", "cpu": [ "ppc64" ], @@ -3347,9 +3523,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", "cpu": [ "riscv64" ], @@ -3364,9 +3540,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", "cpu": [ "riscv64" ], @@ -3381,9 +3557,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", "cpu": [ "s390x" ], @@ -3398,13 +3574,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -3412,13 +3591,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -3426,9 +3608,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", "cpu": [ "x64" ], @@ -3440,9 +3622,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", "cpu": [ "arm64" ], @@ -3454,9 +3636,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", "cpu": [ "arm64" ], @@ -3468,9 +3650,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", "cpu": [ "ia32" ], @@ -3482,9 +3664,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", "cpu": [ "x64" ], @@ -3496,9 +3678,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", "cpu": [ "x64" ], @@ -3509,13 +3691,6 @@ "win32" ] }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -3529,10 +3704,20 @@ "string.prototype.matchall": "^4.0.6" } }, + "node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, "node_modules/@swc/helpers": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz", - "integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", + "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" @@ -3628,17 +3813,6 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -3866,29 +4040,32 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.2.tgz", - "integrity": "sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", "dev": true, "license": "MIT", "dependencies": { + "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.2", - "ast-v8-to-istanbul": "^1.0.0", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.2", - "vitest": "4.1.2" + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -3897,96 +4074,115 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.2.tgz", - "integrity": "sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", "dev": true, "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.2.tgz", - "integrity": "sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==", + "node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^3.1.0" + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.2.tgz", - "integrity": "sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.2", - "pathe": "^2.0.3" + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.2.tgz", - "integrity": "sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.2", - "@vitest/utils": "4.1.2", - "magic-string": "^0.30.21", + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.2.tgz", - "integrity": "sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==", - "dev": true, - "license": "MIT", + "tinyspy": "^4.0.3" + }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.2.tgz", - "integrity": "sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.2", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -4140,9 +4336,9 @@ } }, "node_modules/ast-v8-to-istanbul": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", - "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", + "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", "dev": true, "license": "MIT", "dependencies": { @@ -4151,16 +4347,6 @@ "js-tokens": "^10.0.0" } }, - "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", @@ -4264,14 +4450,14 @@ } }, "node_modules/axios": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", - "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" + "proxy-from-env": "^2.1.0" } }, "node_modules/babel-plugin-polyfill-corejs2": { @@ -4357,9 +4543,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.8", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", - "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", + "version": "2.10.17", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.17.tgz", + "integrity": "sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4436,9 +4622,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -4456,11 +4642,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -4476,16 +4662,26 @@ "dev": true, "license": "MIT" }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -4536,9 +4732,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001780", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", - "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", + "version": "1.0.30001787", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", + "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", "dev": true, "funding": [ { @@ -4567,11 +4763,18 @@ } }, "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", "dev": true, "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, "engines": { "node": ">=18" } @@ -4616,6 +4819,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -4695,6 +4908,41 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -4857,12 +5105,6 @@ "node": ">= 8" } }, - "node_modules/crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", - "license": "MIT" - }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -4927,44 +5169,6 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/data-urls/node_modules/tr46": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", - "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/data-urls/node_modules/webidl-conversions": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", - "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" - } - }, - "node_modules/data-urls/node_modules/whatwg-url": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", - "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.11.0", - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -5056,6 +5260,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -5185,6 +5399,13 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -5202,16 +5423,16 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.313", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", - "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "version": "1.5.334", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.334.tgz", + "integrity": "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==", "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, @@ -5235,9 +5456,9 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", "dev": true, "license": "MIT", "dependencies": { @@ -5322,9 +5543,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, @@ -5373,6 +5594,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -5406,11 +5666,14 @@ } }, "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } }, "node_modules/esutils": { "version": "2.0.3", @@ -5846,26 +6109,23 @@ } }, "node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": "20 || >=22" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -5883,6 +6143,39 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -6738,6 +7031,21 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -6753,19 +7061,19 @@ } }, "node_modules/jackspeak": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", - "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "@isaacs/cliui": "^9.0.0" - }, - "engines": { - "node": "20 || >=22" + "@isaacs/cliui": "^8.0.2" }, "funding": { "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jake": { @@ -6805,6 +7113,12 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-md5": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz", + "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6812,14 +7126,14 @@ "license": "MIT" }, "node_modules/jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz", - "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==", + "version": "29.0.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", + "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^5.0.1", - "@asamuzakjp/dom-selector": "^7.0.3", + "@asamuzakjp/css-color": "^5.1.5", + "@asamuzakjp/dom-selector": "^7.0.6", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.1", "@exodus/bytes": "^1.15.0", @@ -6853,53 +7167,15 @@ } }, "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.1.tgz", - "integrity": "sha512-Y71HWT4hydF1IAG/2OPync4dgQ/J2iWye7eg6CuzJHI+E97tvqFPlADzxiNnjH6WSljg8ecfXMr9k6bfFuqA5w==", + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz", + "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, - "node_modules/jsdom/node_modules/tr46": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", - "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/jsdom/node_modules/webidl-conversions": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", - "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" - } - }, - "node_modules/jsdom/node_modules/whatwg-url": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", - "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.11.0", - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -6913,13 +7189,6 @@ "node": ">=6" } }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -6988,279 +7257,6 @@ "node": ">=6" } }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -7343,6 +7339,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -7374,25 +7377,25 @@ } }, "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { - "sourcemap-codec": "^1.4.8" + "@jridgewell/sourcemap-codec": "^1.5.5" } }, "node_modules/magicast": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", - "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" } }, "node_modules/make-dir": { @@ -8345,13 +8348,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -8377,9 +8380,9 @@ "license": "MIT" }, "node_modules/msw": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.13.0.tgz", - "integrity": "sha512-5PPWf7I7DBHb4ZUZ0NUI+/VBDk/eiNYDNJZGt/jZ7+rbCSIK5hRcNTGqWMnn0vT6NrHiQlb0nfpenVGz1vrqpg==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.13.2.tgz", + "integrity": "sha512-go2H1TIERKkC48pXiwec5l6sbNqYuvqOk3/vHGo1Zd+pq/H63oFawDQerH+WQdUw/flJFHDG7F+QdWMwhntA/A==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -8421,22 +8424,6 @@ } } }, - "node_modules/msw/node_modules/type-fest": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz", - "integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "dependencies": { - "tagged-tag": "^1.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mute-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", @@ -8479,9 +8466,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "dev": true, "license": "MIT" }, @@ -8567,17 +8554,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, "node_modules/outvariant": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", @@ -8678,31 +8654,28 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } + "license": "ISC" }, "node_modules/path-to-regexp": { "version": "6.3.0", @@ -8718,6 +8691,16 @@ "dev": true, "license": "MIT" }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -8769,9 +8752,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", "dev": true, "funding": [ { @@ -8959,14 +8942,6 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, - "node_modules/pretty-format/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -8978,6 +8953,12 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -8989,10 +8970,13 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/punycode": { "version": "2.3.1", @@ -9069,6 +9053,15 @@ "react": "^18.3.1" } }, + "node_modules/react-dom/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/react-dropzone": { "version": "14.4.1", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.4.1.tgz", @@ -9087,10 +9080,12 @@ } }, "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/react-leaflet": { "version": "4.2.1", @@ -9327,9 +9322,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -9469,51 +9464,10 @@ "node": ">=0.10.0" } }, - "node_modules/rolldown": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", - "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.122.0", - "@rolldown/pluginutils": "1.0.0-rc.12" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", - "@rolldown/binding-darwin-x64": "1.0.0-rc.12", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" - } - }, - "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", - "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", - "dev": true, - "license": "MIT" - }, "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", "dev": true, "license": "MIT", "dependencies": { @@ -9527,31 +9481,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" } }, @@ -9668,13 +9622,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.25.0-rc-603e6108-20241029", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0-rc-603e6108-20241029.tgz", + "integrity": "sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==", + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -9842,14 +9793,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -9981,6 +9932,35 @@ "node": ">=0.10.0" } }, + "node_modules/source-map/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/source-map/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/source-map/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "node_modules/sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", @@ -10017,9 +9997,9 @@ } }, "node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, @@ -10054,6 +10034,25 @@ } }, "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -10068,6 +10067,26 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -10185,6 +10204,23 @@ } }, "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -10197,6 +10233,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/strip-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", @@ -10220,6 +10269,26 @@ "node": ">=8" } }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -10380,6 +10449,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/terser": { "version": "5.46.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", @@ -10406,6 +10488,21 @@ "dev": true, "license": "MIT" }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -10443,24 +10540,21 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", - "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } + "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -10500,10 +10594,30 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", "dev": true, "license": "MIT", "engines": { @@ -10577,13 +10691,16 @@ } }, "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", "dev": true, "license": "MIT", "dependencies": { - "punycode": "^2.1.0" + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" } }, "node_modules/trim-lines": { @@ -10620,13 +10737,16 @@ "license": "0BSD" }, "node_modules/type-fest": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", - "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz", + "integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==", "dev": true, "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, "engines": { - "node": ">=10" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -11102,6 +11222,29 @@ "node": ">= 6" } }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/vite-plugin-pwa": { "version": "0.21.2", "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.21.2.tgz", @@ -11133,502 +11276,66 @@ } } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, "node_modules/vitest": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.2.tgz", - "integrity": "sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.2", - "@vitest/mocker": "4.1.2", - "@vitest/pretty-format": "4.1.2", - "@vitest/runner": "4.1.2", - "@vitest/snapshot": "4.1.2", - "@vitest/spy": "4.1.2", - "@vitest/utils": "4.1.2", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", "why-is-node-running": "^2.3.0" }, "bin": { "vitest": "vitest.mjs" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "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.2", - "@vitest/browser-preview": "4.1.2", - "@vitest/browser-webdriverio": "4.1.2", - "@vitest/ui": "4.1.2", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "jsdom": "*" }, "peerDependenciesMeta": { "@edge-runtime/vm": { "optional": true }, - "@opentelemetry/api": { + "@types/debug": { "optional": true }, "@types/node": { "optional": true }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { + "@vitest/browser": { "optional": true }, "@vitest/ui": { @@ -11639,59 +11346,9 @@ }, "jsdom": { "optional": true - }, - "vite": { - "optional": false } } }, - "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.2.tgz", - "integrity": "sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.2", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/vitest/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/vitest/node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, "node_modules/vitest/node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", @@ -11705,84 +11362,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vitest/node_modules/vite": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.5.tgz", - "integrity": "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.12", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -11797,11 +11376,14 @@ } }, "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, - "license": "BSD-2-Clause" + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } }, "node_modules/whatwg-mimetype": { "version": "5.0.0", @@ -11814,15 +11396,18 @@ } }, "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", "dev": true, "license": "MIT", "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/which": { @@ -12017,6 +11602,16 @@ "node": ">=20.0.0" } }, + "node_modules/workbox-build/node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/workbox-build/node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -12087,6 +11682,84 @@ "dev": true, "license": "MIT" }, + "node_modules/workbox-build/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/lru-cache": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz", + "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/workbox-build/node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/workbox-build/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/workbox-build/node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -12268,6 +11941,76 @@ "node": ">=8" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -12284,6 +12027,41 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -12347,6 +12125,41 @@ "node": ">=12" } }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yoctocolors-cjs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", diff --git a/client/package.json b/client/package.json index b4533f7d..b4e26ccd 100644 --- a/client/package.json +++ b/client/package.json @@ -40,7 +40,7 @@ "@types/react-dom": "^18.2.19", "@types/react-window": "^1.8.8", "@vitejs/plugin-react": "^4.2.1", - "@vitest/coverage-v8": "^4.1.2", + "@vitest/coverage-v8": "^3.2.4", "autoprefixer": "^10.4.18", "jsdom": "^29.0.1", "msw": "^2.13.0", @@ -50,6 +50,6 @@ "typescript": "^6.0.2", "vite": "^5.1.4", "vite-plugin-pwa": "^0.21.0", - "vitest": "^4.1.2" + "vitest": "^3.2.4" } } diff --git a/client/src/App.tsx b/client/src/App.tsx index 0ca00b63..06640508 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -12,6 +12,7 @@ import VacayPage from './pages/VacayPage' import AtlasPage from './pages/AtlasPage' import SharedTripPage from './pages/SharedTripPage' import InAppNotificationsPage from './pages/InAppNotificationsPage.tsx' +import OAuthAuthorizePage from './pages/OAuthAuthorizePage' import { ToastContainer } from './components/shared/Toast' import { TranslationProvider, useTranslation } from './i18n' import { authApi } from './api/client' @@ -163,6 +164,8 @@ export default function App() { } /> } /> } /> + {/* OAuth 2.1 consent page — intentionally outside ProtectedRoute */} + } /> apiClient.get('/oauth/authorize/validate', { params }).then(r => r.data), + + /** Submit user consent (approve or deny) */ + authorize: (body: { + client_id: string + redirect_uri: string + scope: string + state?: string + code_challenge: string + code_challenge_method: string + approved: boolean + }) => apiClient.post('/oauth/authorize', body).then(r => r.data), + + clients: { + list: () => apiClient.get('/oauth/clients').then(r => r.data), + create: (data: { name: string; redirect_uris: string[]; allowed_scopes: string[] }) => + apiClient.post('/oauth/clients', data).then(r => r.data), + rotate: (id: string) => apiClient.post(`/oauth/clients/${id}/rotate`).then(r => r.data), + delete: (id: string) => apiClient.delete(`/oauth/clients/${id}`).then(r => r.data), + }, + + sessions: { + list: () => apiClient.get('/oauth/sessions').then(r => r.data), + revoke: (id: number) => apiClient.delete(`/oauth/sessions/${id}`).then(r => r.data), + }, +} + export const tripsApi = { list: (params?: Record) => apiClient.get('/trips', { params }).then(r => r.data), create: (data: Record) => apiClient.post('/trips', data).then(r => r.data), @@ -195,6 +232,8 @@ export const adminApi = { apiClient.get('/admin/audit-log', { params }).then(r => r.data), mcpTokens: () => apiClient.get('/admin/mcp-tokens').then(r => r.data), deleteMcpToken: (id: number) => apiClient.delete(`/admin/mcp-tokens/${id}`).then(r => r.data), + oauthSessions: () => apiClient.get('/admin/oauth-sessions').then(r => r.data), + revokeOAuthSession: (id: number) => apiClient.delete(`/admin/oauth-sessions/${id}`).then(r => r.data), getPermissions: () => apiClient.get('/admin/permissions').then(r => r.data), updatePermissions: (permissions: Record) => apiClient.put('/admin/permissions', { permissions }).then(r => r.data), rotateJwtSecret: () => apiClient.post('/admin/rotate-jwt-secret').then(r => r.data), diff --git a/client/src/api/oauthScopes.test.ts b/client/src/api/oauthScopes.test.ts new file mode 100644 index 00000000..b16da606 --- /dev/null +++ b/client/src/api/oauthScopes.test.ts @@ -0,0 +1,102 @@ +// FE-OAUTH-SCOPES-001 to FE-OAUTH-SCOPES-010 +import { describe, it, expect } from 'vitest' +import { SCOPE_GROUPS, ALL_SCOPES, SCOPE_GROUP_NAMES, getScopesByGroup } from './oauthScopes' + +describe('SCOPE_GROUPS', () => { + it('FE-OAUTH-SCOPES-001: contains all expected scope keys', () => { + const expected = [ + 'trips:read', 'trips:write', 'trips:delete', 'trips:share', + 'places:read', 'places:write', + 'atlas:read', 'atlas:write', + 'packing:read', 'packing:write', + 'todos:read', 'todos:write', + 'budget:read', 'budget:write', + 'reservations:read', 'reservations:write', + 'collab:read', 'collab:write', + 'notifications:read', 'notifications:write', + 'vacay:read', 'vacay:write', + 'geo:read', 'weather:read', + ] + for (const scope of expected) { + expect(SCOPE_GROUPS).toHaveProperty(scope) + } + }) + + it('FE-OAUTH-SCOPES-002: each scope entry has labelKey, descriptionKey, groupKey', () => { + for (const [scope, keys] of Object.entries(SCOPE_GROUPS)) { + expect(keys.labelKey, `${scope} missing labelKey`).toBeTruthy() + expect(keys.descriptionKey, `${scope} missing descriptionKey`).toBeTruthy() + expect(keys.groupKey, `${scope} missing groupKey`).toBeTruthy() + } + }) +}) + +describe('ALL_SCOPES', () => { + it('FE-OAUTH-SCOPES-003: contains exactly 24 scopes', () => { + expect(ALL_SCOPES).toHaveLength(24) + }) + + it('FE-OAUTH-SCOPES-004: matches Object.keys(SCOPE_GROUPS)', () => { + expect(ALL_SCOPES).toEqual(Object.keys(SCOPE_GROUPS)) + }) +}) + +describe('SCOPE_GROUP_NAMES', () => { + it('FE-OAUTH-SCOPES-005: contains no duplicate group names', () => { + expect(SCOPE_GROUP_NAMES).toHaveLength(new Set(SCOPE_GROUP_NAMES).size) + }) + + it('FE-OAUTH-SCOPES-006: contains expected groups', () => { + const expected = [ + 'oauth.scope.group.trips', + 'oauth.scope.group.places', + 'oauth.scope.group.packing', + 'oauth.scope.group.budget', + ] + for (const g of expected) { + expect(SCOPE_GROUP_NAMES).toContain(g) + } + }) +}) + +describe('getScopesByGroup', () => { + const identity = (key: string) => key + + it('FE-OAUTH-SCOPES-007: groups all scopes under the correct group key', () => { + const groups = getScopesByGroup(identity) + // Every scope must appear exactly once across all groups + const allScopesInGroups = Object.values(groups).flat().map(s => s.scope) + expect(allScopesInGroups).toHaveLength(ALL_SCOPES.length) + for (const scope of ALL_SCOPES) { + expect(allScopesInGroups).toContain(scope) + } + }) + + it('FE-OAUTH-SCOPES-008: each item has scope, label, description, group', () => { + const groups = getScopesByGroup(identity) + for (const items of Object.values(groups)) { + for (const item of items) { + expect(item.scope).toBeTruthy() + expect(item.label).toBeTruthy() + expect(item.description).toBeTruthy() + expect(item.group).toBeTruthy() + } + } + }) + + it('FE-OAUTH-SCOPES-009: trips group contains trips:read and trips:write', () => { + const groups = getScopesByGroup(identity) + const tripsGroup = groups['oauth.scope.group.trips'] + expect(tripsGroup).toBeDefined() + const scopeNames = tripsGroup.map(s => s.scope) + expect(scopeNames).toContain('trips:read') + expect(scopeNames).toContain('trips:write') + }) + + it('FE-OAUTH-SCOPES-010: uses translated group name as key', () => { + const t = (key: string) => key === 'oauth.scope.group.trips' ? 'Trips' : key + const groups = getScopesByGroup(t) + expect(groups['Trips']).toBeDefined() + expect(groups['oauth.scope.group.trips']).toBeUndefined() + }) +}) diff --git a/client/src/api/oauthScopes.ts b/client/src/api/oauthScopes.ts new file mode 100644 index 00000000..55cc3c09 --- /dev/null +++ b/client/src/api/oauthScopes.ts @@ -0,0 +1,56 @@ +// Human-readable scope definitions for the OAuth consent page. +// Must stay in sync with server/src/mcp/scopes.ts + +export interface ScopeInfo { + label: string + description: string + group: string +} + +export interface ScopeKeys { + labelKey: string + descriptionKey: string + groupKey: string +} + +export const SCOPE_GROUPS: Record = { + 'trips:read': { labelKey: 'oauth.scope.trips:read.label', descriptionKey: 'oauth.scope.trips:read.description', groupKey: 'oauth.scope.group.trips' }, + 'trips:write': { labelKey: 'oauth.scope.trips:write.label', descriptionKey: 'oauth.scope.trips:write.description', groupKey: 'oauth.scope.group.trips' }, + 'trips:delete': { labelKey: 'oauth.scope.trips:delete.label', descriptionKey: 'oauth.scope.trips:delete.description', groupKey: 'oauth.scope.group.trips' }, + 'trips:share': { labelKey: 'oauth.scope.trips:share.label', descriptionKey: 'oauth.scope.trips:share.description', groupKey: 'oauth.scope.group.trips' }, + 'places:read': { labelKey: 'oauth.scope.places:read.label', descriptionKey: 'oauth.scope.places:read.description', groupKey: 'oauth.scope.group.places' }, + 'places:write': { labelKey: 'oauth.scope.places:write.label', descriptionKey: 'oauth.scope.places:write.description', groupKey: 'oauth.scope.group.places' }, + 'atlas:read': { labelKey: 'oauth.scope.atlas:read.label', descriptionKey: 'oauth.scope.atlas:read.description', groupKey: 'oauth.scope.group.atlas' }, + 'atlas:write': { labelKey: 'oauth.scope.atlas:write.label', descriptionKey: 'oauth.scope.atlas:write.description', groupKey: 'oauth.scope.group.atlas' }, + 'packing:read': { labelKey: 'oauth.scope.packing:read.label', descriptionKey: 'oauth.scope.packing:read.description', groupKey: 'oauth.scope.group.packing' }, + 'packing:write': { labelKey: 'oauth.scope.packing:write.label', descriptionKey: 'oauth.scope.packing:write.description', groupKey: 'oauth.scope.group.packing' }, + 'todos:read': { labelKey: 'oauth.scope.todos:read.label', descriptionKey: 'oauth.scope.todos:read.description', groupKey: 'oauth.scope.group.todos' }, + 'todos:write': { labelKey: 'oauth.scope.todos:write.label', descriptionKey: 'oauth.scope.todos:write.description', groupKey: 'oauth.scope.group.todos' }, + 'budget:read': { labelKey: 'oauth.scope.budget:read.label', descriptionKey: 'oauth.scope.budget:read.description', groupKey: 'oauth.scope.group.budget' }, + 'budget:write': { labelKey: 'oauth.scope.budget:write.label', descriptionKey: 'oauth.scope.budget:write.description', groupKey: 'oauth.scope.group.budget' }, + 'reservations:read': { labelKey: 'oauth.scope.reservations:read.label', descriptionKey: 'oauth.scope.reservations:read.description', groupKey: 'oauth.scope.group.reservations' }, + 'reservations:write': { labelKey: 'oauth.scope.reservations:write.label', descriptionKey: 'oauth.scope.reservations:write.description', groupKey: 'oauth.scope.group.reservations' }, + 'collab:read': { labelKey: 'oauth.scope.collab:read.label', descriptionKey: 'oauth.scope.collab:read.description', groupKey: 'oauth.scope.group.collab' }, + 'collab:write': { labelKey: 'oauth.scope.collab:write.label', descriptionKey: 'oauth.scope.collab:write.description', groupKey: 'oauth.scope.group.collab' }, + 'notifications:read': { labelKey: 'oauth.scope.notifications:read.label', descriptionKey: 'oauth.scope.notifications:read.description', groupKey: 'oauth.scope.group.notifications' }, + 'notifications:write': { labelKey: 'oauth.scope.notifications:write.label', descriptionKey: 'oauth.scope.notifications:write.description', groupKey: 'oauth.scope.group.notifications' }, + 'vacay:read': { labelKey: 'oauth.scope.vacay:read.label', descriptionKey: 'oauth.scope.vacay:read.description', groupKey: 'oauth.scope.group.vacay' }, + 'vacay:write': { labelKey: 'oauth.scope.vacay:write.label', descriptionKey: 'oauth.scope.vacay:write.description', groupKey: 'oauth.scope.group.vacay' }, + 'geo:read': { labelKey: 'oauth.scope.geo:read.label', descriptionKey: 'oauth.scope.geo:read.description', groupKey: 'oauth.scope.group.geo' }, + 'weather:read': { labelKey: 'oauth.scope.weather:read.label', descriptionKey: 'oauth.scope.weather:read.description', groupKey: 'oauth.scope.group.weather' }, +} + +export const ALL_SCOPES = Object.keys(SCOPE_GROUPS) + +// Group all scopes for the client registration form +export const SCOPE_GROUP_NAMES = [...new Set(Object.values(SCOPE_GROUPS).map(s => s.groupKey))] + +export function getScopesByGroup(t: (key: string) => string): Record> { + const groups: Record> = {} + for (const [scope, keys] of Object.entries(SCOPE_GROUPS)) { + const group = t(keys.groupKey) + if (!groups[group]) groups[group] = [] + groups[group].push({ scope, label: t(keys.labelKey), description: t(keys.descriptionKey), group }) + } + return groups +} diff --git a/client/src/components/Admin/AdminMcpTokensPanel.test.tsx b/client/src/components/Admin/AdminMcpTokensPanel.test.tsx index 3a5be8f7..8abcd44d 100644 --- a/client/src/components/Admin/AdminMcpTokensPanel.test.tsx +++ b/client/src/components/Admin/AdminMcpTokensPanel.test.tsx @@ -1,4 +1,4 @@ -// FE-ADMIN-MCP-001 to FE-ADMIN-MCP-010 +// FE-ADMIN-MCP-001 to FE-ADMIN-MCP-016 import { render, screen, waitFor } from '../../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; @@ -197,4 +197,127 @@ describe('AdminMcpTokensPanel', () => { render(<>); await screen.findByText('Failed to load tokens'); }); + + it('FE-ADMIN-MCP-011: OAuth sessions loading spinner shown on mount', async () => { + server.use( + http.get('/api/admin/oauth-sessions', async () => { + await new Promise(resolve => setTimeout(resolve, 200)); + return HttpResponse.json({ sessions: [] }); + }) + ); + render(); + expect(document.querySelector('.animate-spin')).toBeInTheDocument(); + }); + + it('FE-ADMIN-MCP-012: OAuth sessions empty state rendered when no sessions', async () => { + server.use( + http.get('/api/admin/oauth-sessions', () => + HttpResponse.json({ sessions: [] }) + ) + ); + render(); + await screen.findByText('No active OAuth sessions'); + }); + + it('FE-ADMIN-MCP-013: OAuth sessions list renders with scopes', async () => { + server.use( + http.get('/api/admin/oauth-sessions', () => + HttpResponse.json({ + sessions: [ + { + id: 1, + client_name: 'Claude Desktop', + username: 'alice', + scopes: ['trips:read', 'budget:read'], + created_at: '2025-01-01T00:00:00Z', + }, + ], + }) + ) + ); + render(); + await screen.findByText('Claude Desktop'); + expect(screen.getByText('alice')).toBeInTheDocument(); + expect(screen.getByText('trips:read')).toBeInTheDocument(); + }); + + it('FE-ADMIN-MCP-014: scope expand/collapse toggle shows hidden scopes', async () => { + const user = userEvent.setup(); + // 7 scopes — more than SCOPES_PREVIEW=6, so "+1 more" button appears + const scopes = ['trips:read', 'trips:write', 'places:read', 'places:write', 'budget:read', 'budget:write', 'packing:read']; + server.use( + http.get('/api/admin/oauth-sessions', () => + HttpResponse.json({ + sessions: [ + { id: 1, client_name: 'App', username: 'bob', scopes, created_at: '2025-01-01T00:00:00Z' }, + ], + }) + ) + ); + render(); + await screen.findByText('App'); + // "+1 more" button should appear + const moreBtn = await screen.findByText(/\+1 more/); + expect(moreBtn).toBeInTheDocument(); + await user.click(moreBtn); + // After expand, "show less" appears + expect(await screen.findByText('show less')).toBeInTheDocument(); + }); + + it('FE-ADMIN-MCP-015: revoke session confirmation and successful revoke', async () => { + const user = userEvent.setup(); + server.use( + http.get('/api/admin/oauth-sessions', () => + HttpResponse.json({ + sessions: [ + { id: 5, client_name: 'Revoke Me', username: 'carol', scopes: ['trips:read'], created_at: '2025-01-01T00:00:00Z' }, + ], + }) + ), + http.delete('/api/admin/oauth-sessions/5', () => + HttpResponse.json({ success: true }) + ) + ); + render(<>); + await screen.findByText('Revoke Me'); + + // Click the revoke (trash) button next to the session + const deleteBtn = screen.getAllByTitle('Delete')[0]; + await user.click(deleteBtn); + + // Confirmation modal opens + expect(screen.getByText('Revoke Session')).toBeInTheDocument(); + // Confirm — find the modal's Delete button (has no title, unlike the trash icon) + const deleteBtns = screen.getAllByRole('button', { name: 'Delete' }); + const confirmBtn = deleteBtns.find(b => !b.title); + await user.click(confirmBtn ?? deleteBtns[deleteBtns.length - 1]); + await waitFor(() => { + expect(screen.queryByText('Revoke Me')).not.toBeInTheDocument(); + }); + }); + + it('FE-ADMIN-MCP-016: revoke session error shows toast', async () => { + const user = userEvent.setup(); + server.use( + http.get('/api/admin/oauth-sessions', () => + HttpResponse.json({ + sessions: [ + { id: 6, client_name: 'Error Session', username: 'dave', scopes: ['trips:read'], created_at: '2025-01-01T00:00:00Z' }, + ], + }) + ), + http.delete('/api/admin/oauth-sessions/6', () => + HttpResponse.json({ error: 'forbidden' }, { status: 403 }) + ) + ); + render(<>); + await screen.findByText('Error Session'); + + const deleteBtn = screen.getAllByTitle('Delete')[0]; + await user.click(deleteBtn); + const deleteBtns = screen.getAllByRole('button', { name: 'Delete' }); + const confirmBtn = deleteBtns.find(b => !b.title); + await user.click(confirmBtn ?? deleteBtns[deleteBtns.length - 1]); + await screen.findByText('Failed to revoke session'); + }); }); diff --git a/client/src/components/Admin/AdminMcpTokensPanel.tsx b/client/src/components/Admin/AdminMcpTokensPanel.tsx index 8a89f92d..7173ae9c 100644 --- a/client/src/components/Admin/AdminMcpTokensPanel.tsx +++ b/client/src/components/Admin/AdminMcpTokensPanel.tsx @@ -1,9 +1,21 @@ import { useState, useEffect } from 'react' import { adminApi } from '../../api/client' import { useToast } from '../shared/Toast' -import { Key, Trash2, User, Loader2 } from 'lucide-react' +import { Key, Trash2, User, Loader2, Shield } from 'lucide-react' import { useTranslation } from '../../i18n' +interface AdminOAuthSession { + id: number + client_id: string + client_name: string + user_id: number + username: string + scopes: string[] + access_token_expires_at: string + refresh_token_expires_at: string + created_at: string +} + interface AdminMcpToken { id: number name: string @@ -14,21 +26,49 @@ interface AdminMcpToken { username: string } +const SCOPES_PREVIEW = 6 + export default function AdminMcpTokensPanel() { + const [sessions, setSessions] = useState([]) + const [sessionsLoading, setSessionsLoading] = useState(true) const [tokens, setTokens] = useState([]) - const [isLoading, setIsLoading] = useState(true) + const [tokensLoading, setTokensLoading] = useState(true) + const [expandedScopes, setExpandedScopes] = useState>(new Set()) + const [revokeConfirmId, setRevokeConfirmId] = useState(null) const [deleteConfirmId, setDeleteConfirmId] = useState(null) + + const toggleScopes = (id: number) => + setExpandedScopes(prev => { + const next = new Set(prev) + next.has(id) ? next.delete(id) : next.add(id) + return next + }) const toast = useToast() const { t, locale } = useTranslation() useEffect(() => { - setIsLoading(true) + adminApi.oauthSessions() + .then(d => setSessions(d.sessions || [])) + .catch(() => toast.error(t('admin.oauthSessions.loadError'))) + .finally(() => setSessionsLoading(false)) + adminApi.mcpTokens() .then(d => setTokens(d.tokens || [])) .catch(() => toast.error(t('admin.mcpTokens.loadError'))) - .finally(() => setIsLoading(false)) + .finally(() => setTokensLoading(false)) }, []) + const handleRevoke = async (id: number) => { + try { + await adminApi.revokeOAuthSession(id) + setSessions(prev => prev.filter(s => s.id !== id)) + setRevokeConfirmId(null) + toast.success(t('admin.oauthSessions.revokeSuccess')) + } catch { + toast.error(t('admin.oauthSessions.revokeError')) + } + } + const handleDelete = async (id: number) => { try { await adminApi.deleteMcpToken(id) @@ -47,55 +87,156 @@ export default function AdminMcpTokensPanel() {

{t('admin.mcpTokens.subtitle')}

-
- {isLoading ? ( -
- -
- ) : tokens.length === 0 ? ( -
- -

{t('admin.mcpTokens.empty')}

-
- ) : ( - <> -
- {t('admin.mcpTokens.tokenName')} - {t('admin.mcpTokens.owner')} - {t('admin.mcpTokens.created')} - {t('admin.mcpTokens.lastUsed')} - + {/* OAuth Sessions */} +
+

{t('admin.oauthSessions.sectionTitle')}

+
+ {sessionsLoading ? ( +
+
- {tokens.map((token, i) => ( -
-
-

{token.name}

-

{token.token_prefix}...

-
-
- - {token.username} -
- - {new Date(token.created_at).toLocaleDateString(locale)} - - - {token.last_used_at ? new Date(token.last_used_at).toLocaleDateString(locale) : t('admin.mcpTokens.never')} - - + ) : sessions.length === 0 ? ( +
+ +

{t('admin.oauthSessions.empty')}

+
+ ) : ( + <> +
+ {t('admin.oauthSessions.clientName')} + {t('admin.oauthSessions.owner')} + {t('admin.oauthSessions.created')} +
- ))} - - )} + {sessions.map((session, i) => { + const expanded = expandedScopes.has(session.id) + const visible = expanded ? session.scopes : session.scopes.slice(0, SCOPES_PREVIEW) + const hidden = session.scopes.length - SCOPES_PREVIEW + return ( +
+
+

{session.client_name}

+
+ {visible.map(scope => ( + + {scope} + + ))} + {!expanded && hidden > 0 && ( + + )} + {expanded && hidden > 0 && ( + + )} +
+
+
+ + {session.username} +
+ + {new Date(session.created_at).toLocaleDateString(locale)} + + +
+ ) + })} + + )} +
+ {/* MCP Tokens */} +
+

{t('admin.mcpTokens.sectionTitle')}

+
+ {tokensLoading ? ( +
+ +
+ ) : tokens.length === 0 ? ( +
+ +

{t('admin.mcpTokens.empty')}

+
+ ) : ( + <> +
+ {t('admin.mcpTokens.tokenName')} + {t('admin.mcpTokens.owner')} + {t('admin.mcpTokens.created')} + {t('admin.mcpTokens.lastUsed')} + +
+ {tokens.map((token, i) => ( +
+
+

{token.name}

+

{token.token_prefix}...

+
+
+ + {token.username} +
+ + {new Date(token.created_at).toLocaleDateString(locale)} + + + {token.last_used_at ? new Date(token.last_used_at).toLocaleDateString(locale) : t('admin.mcpTokens.never')} + + +
+ ))} + + )} +
+
+ + {/* Revoke OAuth session modal */} + {revokeConfirmId !== null && ( +
{ if (e.target === e.currentTarget) setRevokeConfirmId(null) }}> +
+

{t('admin.oauthSessions.revokeTitle')}

+

{t('admin.oauthSessions.revokeMessage')}

+
+ + +
+
+
+ )} + + {/* Delete MCP token modal */} {deleteConfirmId !== null && (
{ if (e.target === e.currentTarget) setDeleteConfirmId(null) }}> diff --git a/client/src/components/Budget/BudgetPanel.test.tsx b/client/src/components/Budget/BudgetPanel.test.tsx index 4a48d9ba..c912d651 100644 --- a/client/src/components/Budget/BudgetPanel.test.tsx +++ b/client/src/components/Budget/BudgetPanel.test.tsx @@ -1,4 +1,4 @@ -// FE-COMP-BUDGET-001 to FE-COMP-BUDGET-020 +// FE-COMP-BUDGET-001 to FE-COMP-BUDGET-040 import { render, screen, waitFor } from '../../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; @@ -6,6 +6,7 @@ import { server } from '../../../tests/helpers/msw/server'; import { useAuthStore } from '../../store/authStore'; import { useTripStore } from '../../store/tripStore'; import { useSettingsStore } from '../../store/settingsStore'; +import { usePermissionsStore } from '../../store/permissionsStore'; import { resetAllStores, seedStore } from '../../../tests/helpers/store'; import { buildUser, buildTrip, buildBudgetItem, buildSettings } from '../../../tests/helpers/factories'; import BudgetPanel from './BudgetPanel'; @@ -418,4 +419,80 @@ describe('BudgetPanel', () => { // Grand total card shows 300.00 expect(screen.getByText('300.00')).toBeInTheDocument(); }); + + it('FE-COMP-BUDGET-033: read-only mode hides add/delete/edit controls', async () => { + // Restrict budget_edit to trip owners only; user is not the owner (owner_id=1, user.id > 1) + seedStore(usePermissionsStore, { permissions: { budget_edit: 'trip_owner' } }); + // Use a user with id != 1 so they're not the owner + seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true }); + seedStore(useTripStore, { trip: buildTrip({ id: 1, owner_id: 9999 }) }); + const item = { ...buildBudgetItem({ trip_id: 1, category: 'Food', name: 'Read Only Item' }), total_price: 50 }; + server.use( + http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] })) + ); + render(); + await screen.findByText('Read Only Item'); + // In read-only mode the Delete button should not be visible + expect(screen.queryByTitle('Delete')).not.toBeInTheDocument(); + }); + + it('FE-COMP-BUDGET-034: read-only mode shows expense_date as text span', async () => { + seedStore(usePermissionsStore, { permissions: { budget_edit: 'trip_owner' } }); + seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true }); + seedStore(useTripStore, { trip: buildTrip({ id: 1, owner_id: 9999 }) }); + const item = { ...buildBudgetItem({ trip_id: 1, category: 'Transport', name: 'Train' }), total_price: 30, expense_date: '2025-06-15' }; + server.use( + http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] })) + ); + render(); + await screen.findByText('Train'); + // expense_date is rendered as plain text in read-only mode + await screen.findByText('2025-06-15'); + }); + + it('FE-COMP-BUDGET-035: settlement section with avatar renders user avatar image', async () => { + const user = userEvent.setup(); + const item = { ...buildBudgetItem({ trip_id: 1, category: 'Food', name: 'Lunch' }), total_price: 60 }; + server.use( + http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] })), + http.get('/api/trips/1/budget/settlement', () => + HttpResponse.json({ + balances: [ + { user_id: 1, username: 'alice', avatar_url: '/uploads/avatars/alice.jpg', balance: -30 }, + { user_id: 2, username: 'bob', avatar_url: null, balance: 30 }, + ], + flows: [{ from: { username: 'alice', avatar_url: '/uploads/avatars/alice.jpg' }, to: { username: 'bob', avatar_url: null }, amount: 30 }] + }) + ), + http.get('/api/trips/1/budget/per-person', () => HttpResponse.json({ summary: [] })), + ); + const tripMembers = [ + { id: 1, username: 'alice', avatar_url: '/uploads/avatars/alice.jpg' }, + { id: 2, username: 'bob', avatar_url: null }, + ]; + render(); + await screen.findByText('Lunch'); + // Trigger settlement display + const settlementBtn = await screen.findByRole('button', { name: /settlement/i }); + await user.click(settlementBtn); + await screen.findByText('alice'); + // Avatar image should be rendered for alice + const avatarImg = screen.getAllByRole('img'); + expect(avatarImg.length).toBeGreaterThan(0); + }); + + it('FE-COMP-BUDGET-036: expense_date shows dash when not set in read-only mode', async () => { + seedStore(usePermissionsStore, { permissions: { budget_edit: 'trip_owner' } }); + seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true }); + seedStore(useTripStore, { trip: buildTrip({ id: 1, owner_id: 9999 }) }); + const item = { ...buildBudgetItem({ trip_id: 1, category: 'Food', name: 'Snack' }), total_price: 5, expense_date: null }; + server.use( + http.get('/api/trips/1/budget', () => HttpResponse.json({ items: [item] })) + ); + render(); + await screen.findByText('Snack'); + // When expense_date is null, the fallback '—' is shown + const dashes = screen.getAllByText('—'); + expect(dashes.length).toBeGreaterThan(0); + }); }); diff --git a/client/src/components/Collab/CollabChat.tsx b/client/src/components/Collab/CollabChat.tsx index 251a4439..cb9711a6 100644 --- a/client/src/components/Collab/CollabChat.tsx +++ b/client/src/components/Collab/CollabChat.tsx @@ -370,6 +370,11 @@ export default function CollabChat({ tripId, currentUser }: CollabChatProps) { const [showEmoji, setShowEmoji] = useState(false) const [reactMenu, setReactMenu] = useState(null) // { msgId, x, y } const [deletingIds, setDeletingIds] = useState(new Set()) + const deleteTimersRef = useRef[]>([]) + + useEffect(() => { + return () => { deleteTimersRef.current.forEach(clearTimeout) } + }, []) const containerRef = useRef(null) const messagesRef = useRef(messages) @@ -483,13 +488,14 @@ export default function CollabChat({ tripId, currentUser }: CollabChatProps) { requestAnimationFrame(() => { setDeletingIds(prev => new Set(prev).add(msgId)) }) - setTimeout(async () => { + const t = setTimeout(async () => { try { await collabApi.deleteMessage(tripId, msgId) setMessages(prev => prev.map(m => m.id === msgId ? { ...m, _deleted: true } : m)) } catch {} setDeletingIds(prev => { const s = new Set(prev); s.delete(msgId); return s }) }, 400) + deleteTimersRef.current.push(t) }, [tripId]) const handleReact = useCallback(async (msgId, emoji) => { diff --git a/client/src/components/Collab/WhatsNextWidget.tsx b/client/src/components/Collab/WhatsNextWidget.tsx index c5fd11a2..90d39caf 100644 --- a/client/src/components/Collab/WhatsNextWidget.tsx +++ b/client/src/components/Collab/WhatsNextWidget.tsx @@ -16,12 +16,13 @@ function formatTime(timeStr, is12h) { } function formatDayLabel(date, t, locale) { - const d = new Date(date + 'T00:00:00') const now = new Date() - const tomorrow = new Date(); tomorrow.setDate(now.getDate() + 1) + const nowDate = now.toISOString().split('T')[0] + const tomorrowUtc = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)) + const tomorrowDate = tomorrowUtc.toISOString().split('T')[0] - if (d.toDateString() === now.toDateString()) return t('collab.whatsNext.today') || 'Today' - if (d.toDateString() === tomorrow.toDateString()) return t('collab.whatsNext.tomorrow') || 'Tomorrow' + if (date === nowDate) return t('collab.whatsNext.today') || 'Today' + if (date === tomorrowDate) return t('collab.whatsNext.tomorrow') || 'Tomorrow' return new Date(date + 'T00:00:00Z').toLocaleDateString(locale || undefined, { weekday: 'short', day: 'numeric', month: 'short', timeZone: 'UTC' }) } diff --git a/client/src/components/Notifications/InAppNotificationItem.test.tsx b/client/src/components/Notifications/InAppNotificationItem.test.tsx index f8ac1081..1eb024bc 100644 --- a/client/src/components/Notifications/InAppNotificationItem.test.tsx +++ b/client/src/components/Notifications/InAppNotificationItem.test.tsx @@ -1,4 +1,4 @@ -// FE-COMP-NOTIF-001 to FE-COMP-NOTIF-010 +// FE-COMP-NOTIF-001 to FE-COMP-NOTIF-016 import { render, screen, waitFor } from '../../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; import { useAuthStore } from '../../store/authStore'; @@ -99,4 +99,109 @@ describe('InAppNotificationItem', () => { // Recent notification shows "just now" expect(screen.getByText('just now')).toBeInTheDocument(); }); + + it('FE-COMP-NOTIF-011: shows avatar image when sender_avatar is provided', () => { + render( + + ); + expect(document.querySelector('img')).toBeInTheDocument(); + expect(document.querySelector('img')?.getAttribute('src')).toBe('https://example.com/avatar.png'); + }); + + it('FE-COMP-NOTIF-012: boolean notification shows Accept and Reject buttons', () => { + render( + + ); + expect(screen.getByText('Yes')).toBeInTheDocument(); + expect(screen.getByText('No')).toBeInTheDocument(); + }); + + it('FE-COMP-NOTIF-013: clicking Accept calls respondToBoolean with positive', async () => { + const user = userEvent.setup(); + const respondToBoolean = vi.fn().mockResolvedValue(undefined); + seedStore(useInAppNotificationStore, { respondToBoolean }); + render( + + ); + await user.click(screen.getByText('Yes')); + expect(respondToBoolean).toHaveBeenCalledWith(55, 'positive'); + }); + + it('FE-COMP-NOTIF-014: clicking Reject calls respondToBoolean with negative', async () => { + const user = userEvent.setup(); + const respondToBoolean = vi.fn().mockResolvedValue(undefined); + seedStore(useInAppNotificationStore, { respondToBoolean }); + render( + + ); + await user.click(screen.getByText('No')); + expect(respondToBoolean).toHaveBeenCalledWith(66, 'negative'); + }); + + it('FE-COMP-NOTIF-015: navigate notification shows action button', () => { + render( + + ); + // t('notifications.title') = "Notifications" — the navigate button renders this + const navigateBtn = document.querySelector('button[style*="pointer"]') ?? + Array.from(document.querySelectorAll('button')).find(b => b.textContent?.includes('Notifications')); + expect(navigateBtn).toBeInTheDocument(); + }); + + it('FE-COMP-NOTIF-016: clicking navigate button marks read and navigates', async () => { + const user = userEvent.setup(); + const markRead = vi.fn().mockResolvedValue(undefined); + const onClose = vi.fn(); + seedStore(useInAppNotificationStore, { markRead }); + render( + + ); + // The navigate button renders t('notifications.title') = "Notifications" + const btn = Array.from(document.querySelectorAll('button')).find( + b => b.textContent?.includes('Notifications') + ); + expect(btn).toBeTruthy(); + await user.click(btn!); + expect(markRead).toHaveBeenCalledWith(77); + expect(onClose).toHaveBeenCalled(); + }); }); diff --git a/client/src/components/OAuth/ScopeGroupPicker.test.tsx b/client/src/components/OAuth/ScopeGroupPicker.test.tsx new file mode 100644 index 00000000..1dde39e7 --- /dev/null +++ b/client/src/components/OAuth/ScopeGroupPicker.test.tsx @@ -0,0 +1,119 @@ +// FE-COMP-SCOPE-001 to FE-COMP-SCOPE-009 +import { render, screen, waitFor } from '../../../tests/helpers/render'; +import userEvent from '@testing-library/user-event'; +import { resetAllStores } from '../../../tests/helpers/store'; +import ScopeGroupPicker from './ScopeGroupPicker'; + +beforeEach(() => { + resetAllStores(); +}); + +describe('ScopeGroupPicker', () => { + it('FE-COMP-SCOPE-001: renders scope groups', () => { + render(); + // Several group headers should be visible + expect(screen.getAllByRole('button').length).toBeGreaterThan(0); + }); + + it('FE-COMP-SCOPE-002: shows Select All button when nothing selected', () => { + render(); + expect(screen.getByRole('button', { name: /select all/i })).toBeInTheDocument(); + }); + + it('FE-COMP-SCOPE-003: Select All calls onChange with all scopes', async () => { + const user = userEvent.setup(); + const onChange = vi.fn(); + render(); + await user.click(screen.getByRole('button', { name: /select all/i })); + expect(onChange).toHaveBeenCalledTimes(1); + const called = onChange.mock.calls[0][0] as string[]; + expect(called.length).toBeGreaterThan(0); + }); + + it('FE-COMP-SCOPE-004: shows Deselect All button when all selected', async () => { + // First collect all scopes by clicking Select All and capturing the callback + const user = userEvent.setup(); + const captured: string[][] = []; + const { rerender } = render( + captured.push(s)} /> + ); + await user.click(screen.getByRole('button', { name: /select all/i })); + const allScopes = captured[0]; + + // Now rerender with all scopes selected + rerender(); + expect(screen.getByRole('button', { name: /deselect all/i })).toBeInTheDocument(); + }); + + it('FE-COMP-SCOPE-005: Deselect All calls onChange with empty array', async () => { + const user = userEvent.setup(); + const captured: string[][] = []; + + // Get all scopes first + const { rerender } = render( + captured.push(s)} /> + ); + await user.click(screen.getByRole('button', { name: /select all/i })); + const allScopes = captured[0]; + + const onChange = vi.fn(); + rerender(); + await user.click(screen.getByRole('button', { name: /deselect all/i })); + expect(onChange).toHaveBeenCalledWith([]); + }); + + it('FE-COMP-SCOPE-006: expanding a group reveals individual scope checkboxes', async () => { + const user = userEvent.setup(); + render(); + + // Groups are collapsed by default — checkboxes for individual scopes not visible + const groupToggles = screen.getAllByRole('button').filter(b => + !b.textContent?.toLowerCase().includes('select all') && + !b.textContent?.toLowerCase().includes('deselect all') + ); + // Click the first group expand button + await user.click(groupToggles[0]); + // Individual scope checkboxes should now appear (more than just group-level ones) + const checkboxes = screen.getAllByRole('checkbox'); + expect(checkboxes.length).toBeGreaterThan(0); + }); + + it('FE-COMP-SCOPE-007: group checkbox selects all scopes in the group', async () => { + const user = userEvent.setup(); + const onChange = vi.fn(); + render(); + + const groupCheckboxes = screen.getAllByRole('checkbox'); + await user.click(groupCheckboxes[0]); + expect(onChange).toHaveBeenCalledTimes(1); + const called = onChange.mock.calls[0][0] as string[]; + expect(called.length).toBeGreaterThan(0); + }); + + it('FE-COMP-SCOPE-008: individual scope toggle adds/removes that scope', async () => { + const user = userEvent.setup(); + const onChange = vi.fn(); + render(); + + // Expand first group + const groupToggles = screen.getAllByRole('button').filter(b => + !b.textContent?.toLowerCase().includes('select all') && + !b.textContent?.toLowerCase().includes('deselect all') + ); + await user.click(groupToggles[0]); + + // There are now individual scope checkboxes — click the second one (first is group-level) + const checkboxes = screen.getAllByRole('checkbox'); + await user.click(checkboxes[1]); // individual scope + expect(onChange).toHaveBeenCalledTimes(1); + }); + + it('FE-COMP-SCOPE-009: count badge shown when some scopes selected in group', () => { + // Get any single scope key from the first group via Select All trick + manual slice + // We'll just select a scope by triggering group checkbox and passing it in + const firstGroupScope = 'trips:read'; // known scope from SCOPE_GROUPS + render(); + // Count badge like "(1/N)" should be visible + expect(screen.getByText(/\(\d+\/\d+\)/)).toBeInTheDocument(); + }); +}); diff --git a/client/src/components/OAuth/ScopeGroupPicker.tsx b/client/src/components/OAuth/ScopeGroupPicker.tsx new file mode 100644 index 00000000..aa69828b --- /dev/null +++ b/client/src/components/OAuth/ScopeGroupPicker.tsx @@ -0,0 +1,96 @@ +import React, { useState } from 'react' +import { ChevronDown, ChevronRight } from 'lucide-react' +import { getScopesByGroup } from '../../api/oauthScopes' +import { useTranslation } from '../../i18n' + +interface Props { + selected: string[] + onChange: (scopes: string[]) => void +} + +export default function ScopeGroupPicker({ selected, onChange }: Props): React.ReactElement { + const { t } = useTranslation() + const [open, setOpen] = useState>({}) + + const scopesByGroup = getScopesByGroup(t) + const allScopeKeys = Object.values(scopesByGroup).flat().map(s => s.scope) + const allSelected = allScopeKeys.every(s => selected.includes(s)) + + return ( +
+
+ +
+
+ {Object.entries(scopesByGroup).map(([group, groupScopes]) => { + const groupScopeKeys = groupScopes.map(s => s.scope) + const allGroupSelected = groupScopeKeys.every(s => selected.includes(s)) + const someGroupSelected = groupScopeKeys.some(s => selected.includes(s)) + return ( +
+
+ + { if (el) el.indeterminate = someGroupSelected && !allGroupSelected }} + onChange={e => onChange( + e.target.checked + ? [...new Set([...selected, ...groupScopeKeys])] + : selected.filter(s => !groupScopeKeys.includes(s)) + )} + className="rounded" + title={allGroupSelected ? `Deselect all ${group}` : `Select all ${group}`} + /> +
+ {open[group] && ( +
+ {groupScopes.map(({ scope, label, description }) => ( + + ))} +
+ )} +
+ ) + })} +
+
+ ) +} diff --git a/client/src/components/Planner/PlacesSidebar.test.tsx b/client/src/components/Planner/PlacesSidebar.test.tsx index ba1557e6..79b52c17 100644 --- a/client/src/components/Planner/PlacesSidebar.test.tsx +++ b/client/src/components/Planner/PlacesSidebar.test.tsx @@ -5,6 +5,7 @@ import { http, HttpResponse } from 'msw'; import { useAuthStore } from '../../store/authStore'; import { useTripStore } from '../../store/tripStore'; import { usePermissionsStore } from '../../store/permissionsStore'; +import { placesApi } from '../../api/client'; import { resetAllStores, seedStore } from '../../../tests/helpers/store'; import { buildUser, buildTrip, buildPlace, buildCategory, buildDay, buildAssignment } from '../../../tests/helpers/factories'; import { server } from '../../../tests/helpers/msw/server'; @@ -443,11 +444,8 @@ describe('GPX import', () => { }); it('FE-PLANNER-SIDEBAR-039: successful GPX import shows success toast', async () => { - server.use( - http.post('/api/trips/1/places/import/gpx', () => - HttpResponse.json({ count: 2, places: [{ id: 10 }, { id: 11 }] }) - ), - ); + // FormData POST hangs on CI — mock at the API boundary instead of MSW. + const importSpy = vi.spyOn(placesApi, 'importGpx').mockResolvedValueOnce({ count: 2, places: [{ id: 10 }, { id: 11 }] }); const loadTrip = vi.fn().mockResolvedValue(undefined); seedStore(useTripStore, { loadTrip }); const addToast = vi.fn(); @@ -465,6 +463,7 @@ describe('GPX import', () => { undefined, ); }); + importSpy.mockRestore(); }); }); diff --git a/client/src/components/Settings/IntegrationsTab.test.tsx b/client/src/components/Settings/IntegrationsTab.test.tsx index 84eeb161..7170da6e 100644 --- a/client/src/components/Settings/IntegrationsTab.test.tsx +++ b/client/src/components/Settings/IntegrationsTab.test.tsx @@ -1,4 +1,4 @@ -// FE-COMP-INTEGRATIONS-001 to FE-COMP-INTEGRATIONS-018 +// FE-COMP-INTEGRATIONS-001 to FE-COMP-INTEGRATIONS-032 import { render, screen, waitFor } from '../../../tests/helpers/render'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; @@ -7,6 +7,7 @@ import { useAuthStore } from '../../store/authStore'; import { useAddonStore } from '../../store/addonStore'; import { resetAllStores, seedStore } from '../../../tests/helpers/store'; import { buildUser } from '../../../tests/helpers/factories'; +import { ToastContainer } from '../shared/Toast'; import IntegrationsTab from './IntegrationsTab'; function enableMcp() { @@ -40,6 +41,8 @@ beforeEach(() => { server.use( http.get('/api/auth/mcp-tokens', () => HttpResponse.json({ tokens: [] })), http.get('/api/addons', () => HttpResponse.json({ addons: [] })), + http.get('/api/oauth/clients', () => HttpResponse.json({ clients: [] })), + http.get('/api/oauth/sessions', () => HttpResponse.json({ sessions: [] })), ); }); @@ -69,18 +72,26 @@ describe('IntegrationsTab', () => { expect(codeEl!.textContent).toContain('/mcp'); }); - it('FE-COMP-INTEGRATIONS-005: JSON config block is rendered', async () => { + it('FE-COMP-INTEGRATIONS-005: JSON config block is rendered when expanded', async () => { + const user = userEvent.setup(); enableMcp(); render(); await screen.findByText('MCP Configuration'); + // Config is collapsed by default — no
 yet
+    expect(document.querySelector('pre')).toBeNull();
+    // Expand by clicking the "Client Configuration" toggle
+    await user.click(screen.getByRole('button', { name: /Client Configuration/i }));
     const preEl = document.querySelector('pre');
     expect(preEl).not.toBeNull();
     expect(preEl!.textContent).toContain('mcpServers');
   });
 
   it('FE-COMP-INTEGRATIONS-006: "no tokens" message shown when token list is empty', async () => {
+    const user = userEvent.setup();
     enableMcp();
     render();
+    await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
     await screen.findByText('No tokens yet. Create one to connect MCP clients.');
   });
 
@@ -95,8 +106,11 @@ describe('IntegrationsTab', () => {
         }),
       ),
     );
+    const user = userEvent.setup();
     enableMcp();
     render();
+    await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
     await screen.findByText('My Token');
     await screen.findByText('Other Token');
   });
@@ -106,6 +120,7 @@ describe('IntegrationsTab', () => {
     enableMcp();
     render();
     await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
     const createBtn = screen.getByRole('button', { name: /Create New Token/i });
     await user.click(createBtn);
     await screen.findByText('Create API Token');
@@ -116,6 +131,7 @@ describe('IntegrationsTab', () => {
     enableMcp();
     render();
     await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
     await user.click(screen.getByRole('button', { name: /Create New Token/i }));
     await screen.findByText('Create API Token');
     const modalCreateBtn = screen.getByRole('button', { name: /^Create Token$/i });
@@ -127,6 +143,7 @@ describe('IntegrationsTab', () => {
     enableMcp();
     render();
     await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
     await user.click(screen.getByRole('button', { name: /Create New Token/i }));
     await screen.findByText('Create API Token');
     const input = screen.getByPlaceholderText(/Claude Desktop/i);
@@ -153,6 +170,7 @@ describe('IntegrationsTab', () => {
     enableMcp();
     render();
     await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
     await user.click(screen.getByRole('button', { name: /Create New Token/i }));
     await screen.findByText('Create API Token');
     const input = screen.getByPlaceholderText(/Claude Desktop/i);
@@ -182,6 +200,7 @@ describe('IntegrationsTab', () => {
     enableMcp();
     render();
     await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
     await user.click(screen.getByRole('button', { name: /Create New Token/i }));
     await screen.findByText('Create API Token');
     await user.type(screen.getByPlaceholderText(/Claude Desktop/i), 'test');
@@ -206,6 +225,8 @@ describe('IntegrationsTab', () => {
     const user = userEvent.setup();
     enableMcp();
     render();
+    await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
     await screen.findByText('Delete Me');
     await user.click(screen.getByTitle('Delete Token'));
     await screen.findByText('This token will stop working immediately. Any MCP client using it will lose access.');
@@ -230,6 +251,8 @@ describe('IntegrationsTab', () => {
     const user = userEvent.setup();
     enableMcp();
     render();
+    await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
     await screen.findByText('Delete Me');
     await user.click(screen.getByTitle('Delete Token'));
     // There are two "Delete Token" buttons: the trash icon (title) and the confirm button in modal
@@ -289,6 +312,8 @@ describe('IntegrationsTab', () => {
     const user = userEvent.setup();
     enableMcp();
     render();
+    await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
     await screen.findByText('Cancel Token');
     await user.click(screen.getByTitle('Delete Token'));
     await screen.findByRole('button', { name: /^Cancel$/i });
@@ -319,6 +344,7 @@ describe('IntegrationsTab', () => {
     enableMcp();
     render();
     await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
     await user.click(screen.getByRole('button', { name: /Create New Token/i }));
     await screen.findByText('Create API Token');
     const input = screen.getByPlaceholderText(/Claude Desktop/i);
@@ -328,4 +354,301 @@ describe('IntegrationsTab', () => {
       expect(postCalled).toBe(true);
     });
   });
+
+  it('FE-COMP-INTEGRATIONS-019: default tab is OAuth 2.1 Clients — OAuth hint visible, token list hidden', async () => {
+    enableMcp();
+    render();
+    await screen.findByText('MCP Configuration');
+    // OAuth hint is visible on the default tab
+    expect(screen.getByText(/Register OAuth 2\.1 clients/i)).toBeInTheDocument();
+    // API Tokens "no tokens" message is not rendered
+    expect(screen.queryByText('No tokens yet. Create one to connect MCP clients.')).toBeNull();
+  });
+
+  it('FE-COMP-INTEGRATIONS-020: switching tabs toggles content visibility', async () => {
+    const user = userEvent.setup();
+    enableMcp();
+    render();
+    await screen.findByText('MCP Configuration');
+    // Default: OAuth hint visible, token list absent
+    expect(screen.getByText(/Register OAuth 2\.1 clients/i)).toBeInTheDocument();
+    expect(screen.queryByText('No tokens yet. Create one to connect MCP clients.')).toBeNull();
+    // Switch to API Tokens tab
+    await user.click(screen.getByRole('button', { name: /API Tokens/i }));
+    await screen.findByText('No tokens yet. Create one to connect MCP clients.');
+    expect(screen.queryByText(/Register OAuth 2\.1 clients/i)).toBeNull();
+    // Switch back to OAuth tab
+    await user.click(screen.getByRole('button', { name: /OAuth 2\.1 Clients/i }));
+    await screen.findByText(/Register OAuth 2\.1 clients/i);
+    expect(screen.queryByText('No tokens yet. Create one to connect MCP clients.')).toBeNull();
+  });
+
+  it('FE-COMP-INTEGRATIONS-021: OAuth client list renders when clients exist', async () => {
+    server.use(
+      http.get('/api/oauth/clients', () =>
+        HttpResponse.json({
+          clients: [
+            {
+              id: 'client-1',
+              client_id: 'clid-abc',
+              name: 'My OAuth App',
+              redirect_uris: ['http://localhost'],
+              allowed_scopes: ['trips:read', 'places:read'],
+              created_at: '2025-01-01T00:00:00Z',
+            },
+          ],
+        })
+      )
+    );
+    enableMcp();
+    render();
+    await screen.findByText('My OAuth App');
+    expect(screen.getByText(/clid-abc/)).toBeInTheDocument();
+  });
+
+  it('FE-COMP-INTEGRATIONS-022: scope expansion toggle shows more/fewer scopes', async () => {
+    const user = userEvent.setup();
+    const scopes = ['trips:read', 'trips:write', 'places:read', 'places:write', 'budget:read', 'budget:write', 'packing:read'];
+    server.use(
+      http.get('/api/oauth/clients', () =>
+        HttpResponse.json({
+          clients: [
+            { id: 'c1', client_id: 'cid', name: 'Big App', redirect_uris: ['http://localhost'], allowed_scopes: scopes, created_at: '2025-01-01T00:00:00Z' },
+          ],
+        })
+      )
+    );
+    enableMcp();
+    render();
+    await screen.findByText('Big App');
+    // "+2 more" button visible (7 scopes, 5 shown)
+    const moreBtn = screen.getByText(/^\+\d+$/);
+    await user.click(moreBtn);
+    // Show less / collapse button now visible
+    expect(screen.getByText('−')).toBeInTheDocument();
+  });
+
+  it('FE-COMP-INTEGRATIONS-023: active OAuth sessions section renders when sessions exist', async () => {
+    server.use(
+      http.get('/api/oauth/sessions', () =>
+        HttpResponse.json({
+          sessions: [
+            {
+              id: 10,
+              client_name: 'Claude Desktop',
+              scopes: ['trips:read'],
+              access_token_expires_at: '2025-12-31T00:00:00Z',
+            },
+          ],
+        })
+      )
+    );
+    enableMcp();
+    render();
+    await screen.findByText('Claude Desktop');
+    expect(screen.getByText(/trips:read/)).toBeInTheDocument();
+  });
+
+  it('FE-COMP-INTEGRATIONS-024: Create OAuth Client modal opens and shows presets', async () => {
+    const user = userEvent.setup();
+    enableMcp();
+    render();
+    await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /New Client/i }));
+    await screen.findByText('Register OAuth Client');
+    expect(screen.getByText('Claude.ai')).toBeInTheDocument();
+    expect(screen.getByText('Claude Desktop')).toBeInTheDocument();
+  });
+
+  it('FE-COMP-INTEGRATIONS-025: clicking a preset fills form fields', async () => {
+    const user = userEvent.setup();
+    enableMcp();
+    render();
+    await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /New Client/i }));
+    await screen.findByText('Register OAuth Client');
+    // Presets render as buttons — click "Claude.ai" preset
+    const presetBtns = screen.getAllByRole('button', { name: /Claude\.ai/i });
+    await user.click(presetBtns[0]);
+    // Name field should be filled with 'Claude.ai'
+    const nameInput = screen.getByPlaceholderText(/Claude Web, My MCP App/i);
+    expect((nameInput as HTMLInputElement).value).toBe('Claude.ai');
+  });
+
+  it('FE-COMP-INTEGRATIONS-026: creating client shows success view with client_id and secret', async () => {
+    const user = userEvent.setup();
+    server.use(
+      http.post('/api/oauth/clients', () =>
+        HttpResponse.json({
+          client: {
+            id: 'new-id',
+            client_id: 'clid-new',
+            client_secret: 'secret-value',
+            name: 'Test Client',
+            redirect_uris: ['http://localhost'],
+            allowed_scopes: ['trips:read'],
+            created_at: '2025-01-01T00:00:00Z',
+          },
+        })
+      )
+    );
+    enableMcp();
+    render();
+    await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /New Client/i }));
+    await screen.findByText('Register OAuth Client');
+
+    const nameInput = screen.getByPlaceholderText(/Claude Web, My MCP App/i);
+    await user.type(nameInput, 'Test Client');
+    const uriInput = screen.getByPlaceholderText(/https:\/\/your-app/i);
+    await user.type(uriInput, 'http://localhost');
+    await user.click(screen.getByRole('button', { name: /Register Client/i }));
+    // Success view shows client credentials (there may be multiple matches in list + modal)
+    await screen.findAllByText(/clid-new/);
+    const secretEls = await screen.findAllByText(/secret-value/);
+    expect(secretEls.length).toBeGreaterThan(0);
+  });
+
+  it('FE-COMP-INTEGRATIONS-027: Done button closes created-client modal', async () => {
+    const user = userEvent.setup();
+    server.use(
+      http.post('/api/oauth/clients', () =>
+        HttpResponse.json({
+          client: {
+            id: 'n2',
+            client_id: 'clid-n2',
+            client_secret: 'secret-n2',
+            name: 'TC2',
+            redirect_uris: ['http://localhost'],
+            allowed_scopes: ['trips:read'],
+            created_at: '2025-01-01T00:00:00Z',
+          },
+        })
+      )
+    );
+    enableMcp();
+    render();
+    await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /New Client/i }));
+    await screen.findByText('Register OAuth Client');
+    await user.type(screen.getByPlaceholderText(/Claude Web, My MCP App/i), 'TC2');
+    await user.type(screen.getByPlaceholderText(/https:\/\/your-app/i), 'http://localhost');
+    await user.click(screen.getByRole('button', { name: /Register Client/i }));
+    await screen.findAllByText(/clid-n2/);
+    // Check the "Client Registered" modal title is visible before Done
+    expect(screen.getByText('Client Registered')).toBeInTheDocument();
+    await user.click(screen.getByRole('button', { name: /^Done$/i }));
+    await waitFor(() => {
+      expect(screen.queryByText('Client Registered')).toBeNull();
+    });
+  });
+
+  it('FE-COMP-INTEGRATIONS-028: delete OAuth client confirmation removes client from list', async () => {
+    const user = userEvent.setup();
+    server.use(
+      http.get('/api/oauth/clients', () =>
+        HttpResponse.json({
+          clients: [
+            { id: 'del-1', client_id: 'cid-del', name: 'Delete Me', redirect_uris: ['http://localhost'], allowed_scopes: ['trips:read'], created_at: '2025-01-01T00:00:00Z' },
+          ],
+        })
+      ),
+      http.delete('/api/oauth/clients/del-1', () => HttpResponse.json({ success: true }))
+    );
+    enableMcp();
+    render(<>);
+    await screen.findByText('Delete Me');
+    await user.click(screen.getByTitle('Delete Client'));
+    // Confirmation modal
+    await screen.findByRole('heading', { name: 'Delete Client' });
+    const confirmBtns = screen.getAllByRole('button', { name: /Delete Client/i });
+    // Modal confirm button is last in DOM (modal renders after list)
+    await user.click(confirmBtns[confirmBtns.length - 1]);
+    await waitFor(() => {
+      expect(screen.queryByText('Delete Me')).toBeNull();
+    });
+  });
+
+  it('FE-COMP-INTEGRATIONS-029: rotate secret confirmation shows new secret', async () => {
+    const user = userEvent.setup();
+    server.use(
+      http.get('/api/oauth/clients', () =>
+        HttpResponse.json({
+          clients: [
+            { id: 'rot-1', client_id: 'cid-rot', name: 'Rotate Me', redirect_uris: ['http://localhost'], allowed_scopes: ['trips:read'], created_at: '2025-01-01T00:00:00Z' },
+          ],
+        })
+      ),
+      http.post('/api/oauth/clients/rot-1/rotate', () =>
+        HttpResponse.json({ client_secret: 'new-rotated-secret' })
+      )
+    );
+    enableMcp();
+    render();
+    await screen.findByText('Rotate Me');
+    await user.click(screen.getByTitle('Rotate Secret'));
+    await screen.findByText('Rotate Secret');
+    // Confirm — button text is 'Rotate'
+    const rotateBtns = screen.getAllByRole('button', { name: /^Rotate$/i });
+    await user.click(rotateBtns[rotateBtns.length - 1]);
+    await screen.findByText(/new-rotated-secret/);
+  });
+
+  it('FE-COMP-INTEGRATIONS-030: revoke OAuth session removes it from list', async () => {
+    const user = userEvent.setup();
+    server.use(
+      http.get('/api/oauth/sessions', () =>
+        HttpResponse.json({
+          sessions: [
+            { id: 99, client_name: 'Revoke App', scopes: ['trips:read'], access_token_expires_at: '2025-12-31T00:00:00Z' },
+          ],
+        })
+      ),
+      http.delete('/api/oauth/sessions/99', () => HttpResponse.json({ success: true }))
+    );
+    enableMcp();
+    render(<>);
+    await screen.findByText('Revoke App');
+    await user.click(screen.getByText('Revoke'));
+    // Confirmation modal
+    await screen.findByText('Revoke Session');
+    const revokeBtns = screen.getAllByRole('button', { name: /^Revoke$/i });
+    // Modal confirm button is last in DOM
+    await user.click(revokeBtns[revokeBtns.length - 1]);
+    await waitFor(() => {
+      expect(screen.queryByText('Revoke App')).toBeNull();
+    });
+  });
+
+  it('FE-COMP-INTEGRATIONS-031: Register Client button disabled when name or URI is empty', async () => {
+    const user = userEvent.setup();
+    enableMcp();
+    render();
+    await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /New Client/i }));
+    await screen.findByText('Register OAuth Client');
+    const createBtn = screen.getByRole('button', { name: /Register Client/i });
+    expect(createBtn).toBeDisabled();
+    // Type only name, not URI → still disabled
+    await user.type(screen.getByPlaceholderText(/Claude Web, My MCP App/i), 'Test');
+    expect(createBtn).toBeDisabled();
+  });
+
+  it('FE-COMP-INTEGRATIONS-032: error toast shown when create OAuth client fails', async () => {
+    const user = userEvent.setup();
+    server.use(
+      http.post('/api/oauth/clients', () =>
+        HttpResponse.json({ error: 'server error' }, { status: 500 })
+      )
+    );
+    enableMcp();
+    render(<>);
+    await screen.findByText('MCP Configuration');
+    await user.click(screen.getByRole('button', { name: /New Client/i }));
+    await screen.findByText('Register OAuth Client');
+    await user.type(screen.getByPlaceholderText(/Claude Web, My MCP App/i), 'Fail Client');
+    await user.type(screen.getByPlaceholderText(/https:\/\/your-app/i), 'http://localhost');
+    await user.click(screen.getByRole('button', { name: /Register Client/i }));
+    await screen.findByText(/Failed to register/i);
+  });
 });
diff --git a/client/src/components/Settings/IntegrationsTab.tsx b/client/src/components/Settings/IntegrationsTab.tsx
index f56dbcdf..430da0f6 100644
--- a/client/src/components/Settings/IntegrationsTab.tsx
+++ b/client/src/components/Settings/IntegrationsTab.tsx
@@ -1,12 +1,87 @@
 import Section from './Section'
-import React, { useEffect, useState } from 'react'
+import React, { useEffect, useRef, useState } from 'react'
 import { useTranslation } from '../../i18n'
 import { useToast } from '../shared/Toast'
-import { Trash2, Copy, Terminal, Plus, Check } from 'lucide-react'
-import { authApi } from '../../api/client'
+import { Trash2, Copy, Terminal, Plus, Check, KeyRound, ChevronDown, ChevronRight, RefreshCw } from 'lucide-react'
+import { authApi, oauthApi } from '../../api/client'
 import { useAddonStore } from '../../store/addonStore'
 import PhotoProvidersSection from './PhotoProvidersSection'
+import { ALL_SCOPES } from '../../api/oauthScopes'
+import ScopeGroupPicker from '../OAuth/ScopeGroupPicker'
 
+interface OAuthPreset {
+  id: string
+  label: string
+  name: string
+  uris: string
+  scopes: string[]
+}
+
+const OAUTH_PRESETS: OAuthPreset[] = [
+  {
+    id: 'claude-web',
+    label: 'Claude.ai',
+    name: 'Claude.ai',
+    uris: 'https://claude.ai/api/mcp/auth_callback',
+    scopes: ALL_SCOPES.filter(s => !s.includes(':delete')),
+  },
+  {
+    id: 'claude-desktop',
+    label: 'Claude Desktop',
+    name: 'Claude Desktop',
+    uris: 'http://localhost',
+    scopes: ALL_SCOPES.filter(s => !s.includes(':delete')),
+  },
+  {
+    id: 'cursor',
+    label: 'Cursor',
+    name: 'Cursor',
+    uris: 'http://localhost',
+    scopes: ALL_SCOPES.filter(s => !s.includes(':delete')),
+  },
+  {
+    id: 'vscode',
+    label: 'VS Code',
+    name: 'VS Code / Copilot',
+    uris: 'http://localhost',
+    scopes: ALL_SCOPES.filter(s => s.endsWith(':read')),
+  },
+  {
+    id: 'windsurf',
+    label: 'Windsurf',
+    name: 'Windsurf',
+    uris: 'http://localhost',
+    scopes: ALL_SCOPES.filter(s => !s.includes(':delete')),
+  },
+  {
+    id: 'zed',
+    label: 'Zed',
+    name: 'Zed',
+    uris: 'http://localhost',
+    scopes: ALL_SCOPES.filter(s => !s.includes(':delete')),
+  },
+]
+
+
+interface OAuthClient {
+  id: string
+  name: string
+  client_id: string
+  redirect_uris: string[]
+  allowed_scopes: string[]
+  created_at: string
+  client_secret?: string // only present on create
+}
+
+interface OAuthSession {
+  id: number
+  client_id: string
+  client_name: string
+  scopes: string[]
+  access_token_expires_at: string
+  refresh_token_expires_at: string
+  created_at: string
+}
 
 interface McpToken {
   id: number
@@ -26,6 +101,28 @@ export default function IntegrationsTab(): React.ReactElement {
     loadAddons()
   }, [loadAddons])
 
+  // OAuth clients state
+  const [oauthClients, setOauthClients] = useState([])
+  const [oauthSessions, setOauthSessions] = useState([])
+  const [oauthCreateOpen, setOauthCreateOpen] = useState(false)
+  const [oauthNewName, setOauthNewName] = useState('')
+  const [oauthNewUris, setOauthNewUris] = useState('')
+  const [oauthNewScopes, setOauthNewScopes] = useState([])
+  const [oauthCreating, setOauthCreating] = useState(false)
+  const [oauthCreatedClient, setOauthCreatedClient] = useState(null)
+  const [oauthDeleteId, setOauthDeleteId] = useState(null)
+  const [oauthRevokeId, setOauthRevokeId] = useState(null)
+  const [oauthRotateId, setOauthRotateId] = useState(null)
+  const [oauthRotatedSecret, setOauthRotatedSecret] = useState(null)
+  const [oauthRotating, setOauthRotating] = useState(false)
+  // oauthScopesOpen is managed internally by ScopeGroupPicker
+  const [oauthScopesExpanded, setOauthScopesExpanded] = useState>({})
+
+  // MCP sub-tab state
+  const [activeMcpTab, setActiveMcpTab] = useState<'oauth' | 'apitokens'>('oauth')
+  const [configOpenOAuth, setConfigOpenOAuth] = useState(false)
+  const [configOpenToken, setConfigOpenToken] = useState(false)
+
   // MCP state
   const [mcpTokens, setMcpTokens] = useState([])
   const [mcpModalOpen, setMcpModalOpen] = useState(false)
@@ -34,8 +131,26 @@ export default function IntegrationsTab(): React.ReactElement {
   const [mcpCreating, setMcpCreating] = useState(false)
   const [mcpDeleteId, setMcpDeleteId] = useState(null)
   const [copiedKey, setCopiedKey] = useState(null)
+  const copyTimerRef = useRef | null>(null)
+
+  useEffect(() => {
+    return () => { if (copyTimerRef.current) clearTimeout(copyTimerRef.current) }
+  }, [])
 
   const mcpEndpoint = `${window.location.origin}/mcp`
+  const mcpJsonConfigOAuth = `{
+  "mcpServers": {
+    "trek": {
+      "command": "npx",
+      "args": [
+        "mcp-remote",
+        "${mcpEndpoint}",
+        "--static-oauth-client-info",
+        "{\\"client_id\\": \\"\\", \\"client_secret\\": \\"\\"}"
+      ]
+    }
+  }
+}`
   const mcpJsonConfig = `{
   "mcpServers": {
     "trek": {
@@ -85,10 +200,72 @@ export default function IntegrationsTab(): React.ReactElement {
   const handleCopy = (text: string, key: string) => {
     navigator.clipboard.writeText(text).then(() => {
       setCopiedKey(key)
-      setTimeout(() => setCopiedKey(null), 2000)
+      if (copyTimerRef.current) clearTimeout(copyTimerRef.current)
+      copyTimerRef.current = setTimeout(() => setCopiedKey(null), 2000)
     })
   }
 
+  // Load OAuth clients and sessions
+  useEffect(() => {
+    if (mcpEnabled) {
+      oauthApi.clients.list().then(d => setOauthClients(d.clients || [])).catch(() => {})
+      oauthApi.sessions.list().then(d => setOauthSessions(d.sessions || [])).catch(() => {})
+    }
+  }, [mcpEnabled])
+
+  const handleCreateOAuthClient = async () => {
+    if (!oauthNewName.trim() || !oauthNewUris.trim()) return
+    setOauthCreating(true)
+    try {
+      const uris = oauthNewUris.split('\n').map(u => u.trim()).filter(Boolean)
+      const d = await oauthApi.clients.create({ name: oauthNewName.trim(), redirect_uris: uris, allowed_scopes: oauthNewScopes })
+      setOauthCreatedClient(d.client)
+      setOauthClients(prev => [...prev, { ...d.client, client_secret: undefined }])
+      setOauthNewName('')
+      setOauthNewUris('')
+      setOauthNewScopes([])
+    } catch {
+      toast.error(t('settings.oauth.toast.createError'))
+    } finally {
+      setOauthCreating(false)
+    }
+  }
+
+  const handleDeleteOAuthClient = async (id: string) => {
+    try {
+      await oauthApi.clients.delete(id)
+      setOauthClients(prev => prev.filter(c => c.id !== id))
+      setOauthDeleteId(null)
+      toast.success(t('settings.oauth.toast.deleted'))
+    } catch {
+      toast.error(t('settings.oauth.toast.deleteError'))
+    }
+  }
+
+  const handleRotateSecret = async (id: string) => {
+    setOauthRotating(true)
+    try {
+      const d = await oauthApi.clients.rotate(id)
+      setOauthRotatedSecret(d.client_secret)
+      setOauthRotateId(null)
+    } catch {
+      toast.error(t('settings.oauth.toast.rotateError'))
+    } finally {
+      setOauthRotating(false)
+    }
+  }
+
+  const handleRevokeSession = async (id: number) => {
+    try {
+      await oauthApi.sessions.revoke(id)
+      setOauthSessions(prev => prev.filter(s => s.id !== id))
+      setOauthRevokeId(null)
+      toast.success(t('settings.oauth.toast.revoked'))
+    } catch {
+      toast.error(t('settings.oauth.toast.revokeError'))
+    }
+  }
+
   return (
     <>
       
@@ -109,63 +286,217 @@ export default function IntegrationsTab(): React.ReactElement {
             
- {/* JSON config box */} -
-
- - -
-
-              {mcpJsonConfig}
-            
-

{t('settings.mcp.clientConfigHint')}

+ {/* Sub-tab bar */} +
+ +
- {/* Token list */} -
-
- - -
- - {mcpTokens.length === 0 ? ( -

- {t('settings.mcp.noTokens')} -

- ) : ( + {/* OAuth 2.1 Clients tab */} + {activeMcpTab === 'oauth' && ( + <> + {/* JSON config — OAuth (collapsible) */}
- {mcpTokens.map((token, i) => ( -
-
-

{token.name}

-

- {token.token_prefix}... - {t('settings.mcp.tokenCreatedAt')} {new Date(token.created_at).toLocaleDateString(locale)} - {token.last_used_at && ( - · {t('settings.mcp.tokenUsedAt')} {new Date(token.last_used_at).toLocaleDateString(locale)} - )} -

+ + {configOpenOAuth && ( +
+
+
- +
+                      {mcpJsonConfigOAuth}
+                    
+

{t('settings.mcp.clientConfigHintOAuth')}

- ))} + )}
- )} -
+ +
+

{t('settings.oauth.clientsHint')}

+ +
+ +
+ + {oauthClients.length === 0 ? ( +

+ {t('settings.oauth.noClients')} +

+ ) : ( +
+ {oauthClients.map((client, i) => ( +
+
+ +
+

{client.name}

+

+ {t('settings.oauth.clientId')}: {client.client_id} + {t('settings.mcp.tokenCreatedAt')} {new Date(client.created_at).toLocaleDateString(locale)} +

+
+ {(oauthScopesExpanded[client.id] ? client.allowed_scopes : client.allowed_scopes.slice(0, 5)).map(s => ( + {s} + ))} + {client.allowed_scopes.length > 5 && ( + + )} +
+
+ + +
+
+ ))} +
+ )} +
+ + {/* Active OAuth Sessions */} + {oauthSessions.length > 0 && ( +
+ +
+ {oauthSessions.map((session, i) => ( +
+
+

{session.client_name}

+

+ {t('settings.oauth.sessionScopes')}: {session.scopes.join(', ')} + {t('settings.oauth.sessionExpires')} {new Date(session.access_token_expires_at).toLocaleDateString(locale)} +

+
+ +
+ ))} +
+
+ )} + + )} + + {/* API Tokens tab (deprecated) */} + {activeMcpTab === 'apitokens' && ( + <> +
+ +

{t('settings.mcp.apiTokensDeprecated')}

+
+ + {/* JSON config — API Token (collapsible) */} +
+ + {configOpenToken && ( +
+
+ +
+
+                      {mcpJsonConfig}
+                    
+

{t('settings.mcp.clientConfigHint')}

+
+ )} +
+ +
+ +
+ + {mcpTokens.length === 0 ? ( +

+ {t('settings.mcp.noTokens')} +

+ ) : ( +
+ {mcpTokens.map((token, i) => ( +
+
+

{token.name}

+

+ {token.token_prefix}... + {t('settings.mcp.tokenCreatedAt')} {new Date(token.created_at).toLocaleDateString(locale)} + {token.last_used_at && ( + · {t('settings.mcp.tokenUsedAt')} {new Date(token.last_used_at).toLocaleDateString(locale)} + )} +

+
+ +
+ ))} +
+ )} + + )} )} @@ -182,7 +513,7 @@ export default function IntegrationsTab(): React.ReactElement { setMcpNewName(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleCreateMcpToken()} placeholder={t('settings.mcp.modal.tokenNamePlaceholder')} - className="w-full px-3 py-2.5 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-indigo-300" + className="w-full px-3 py-2.5 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-400" style={{ borderColor: 'var(--border-primary)', background: 'var(--bg-secondary)', color: 'var(--text-primary)' }} autoFocus />
@@ -192,8 +523,7 @@ export default function IntegrationsTab(): React.ReactElement { {t('common.cancel')}
@@ -217,8 +547,7 @@ export default function IntegrationsTab(): React.ReactElement {
@@ -248,6 +577,216 @@ export default function IntegrationsTab(): React.ReactElement {
)} + + {/* Create OAuth Client modal */} + {oauthCreateOpen && ( +
{ if (e.target === e.currentTarget && !oauthCreatedClient) setOauthCreateOpen(false) }}> +
+ {!oauthCreatedClient ? ( + <> +

{t('settings.oauth.modal.createTitle')}

+ +
+ +
+ {OAUTH_PRESETS.map(preset => ( + + ))} +
+
+ +
+ + setOauthNewName(e.target.value)} + placeholder={t('settings.oauth.modal.clientNamePlaceholder')} + className="w-full px-3 py-2.5 border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-slate-400" + style={{ borderColor: 'var(--border-primary)', background: 'var(--bg-secondary)', color: 'var(--text-primary)' }} + autoFocus /> +
+ +
+ +