diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..08053a8
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,32 @@
+.git
+.env
+.env.*
+!.env.example
+.venv
+__pycache__
+.pytest_cache
+.ruff_cache
+.mypy_cache
+*.py[cod]
+
+# Node modules (re-installed in Docker)
+**/node_modules
+
+# Build outputs (re-built in Docker)
+frontend/dist
+packages/*/dist
+*.tsbuildinfo
+
+# Dev/test artifacts
+coverage
+*.log
+.DS_Store
+.idea
+.vscode
+
+# Docker-in-Docker
+docker-compose.yml
+**/Dockerfile
+
+# Backend (not needed for bridge/frontend containers)
+backend/
diff --git a/.env.example b/.env.example
index 897ec4a..872740b 100644
--- a/.env.example
+++ b/.env.example
@@ -1,9 +1,21 @@
+# ─── Bridge Configuration ──────────────────────────────
+BRIDGE_HOST=127.0.0.1
+BRIDGE_PORT=8787
+BRIDGE_TOKEN=dev-secret-token
+AUTH_MODE=dev
+BRIDGE_ALLOWED_BOARDS=*
+COMMAND_TIMEOUT_SECONDS=30
+LOG_TAIL_MAX_LINES=500
+
+# ─── Hermes CLI ────────────────────────────────────────
HERMES_BIN=hermes
-HERMES_HOME=/home/your-user/.hermes
-HERMES_KANBAN_BOARD=default
-KANBAN_UI_AUTH_MODE=dev
+HERMES_HOME=/home/kisskin/.hermes
+
+# ─── Telegram (optional, for production) ───────────────
TELEGRAM_BOT_TOKEN=
-TELEGRAM_ALLOWED_USER_IDS=
-KANBAN_UI_ALLOWED_ORIGINS=http://localhost:5173
-KANBAN_UI_COMMAND_TIMEOUT_SECONDS=30
-VITE_API_BASE_URL=http://localhost:8000
+
+# ─── Frontend (Vite) ──────────────────────────────────
+# In dev mode the vite proxy handles /bridge → localhost:8787
+# Set VITE_BRIDGE_URL only if frontend talks to bridge directly
+# VITE_BRIDGE_URL=http://localhost:8787/bridge
+# VITE_AUTH_TOKEN=dev-secret-token
diff --git a/Dockerfile b/Dockerfile
index 1b6919d..3d0fd91 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,17 @@
+FROM node:22-alpine AS frontend-build
+WORKDIR /app/frontend
+COPY frontend/package*.json ./
+RUN npm ci
+COPY frontend/ ./
+RUN npm run build
+
FROM python:3.11-slim AS backend
WORKDIR /app
COPY backend/pyproject.toml /app/backend/pyproject.toml
COPY backend/app /app/backend/app
+COPY --from=frontend-build /app/frontend/dist /app/frontend/dist
RUN pip install --no-cache-dir -e /app/backend
-ENV KANBAN_UI_AUTH_MODE=telegram
+ENV KANBAN_UI_AUTH_MODE=telegram \
+ KANBAN_UI_FRONTEND_DIST_DIR=/app/frontend/dist
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--app-dir", "backend", "--host", "0.0.0.0", "--port", "8000"]
diff --git a/README.md b/README.md
index 4a78b8a..01ecbe3 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,7 @@ Important settings:
- `TELEGRAM_BOT_TOKEN`: bot token used only by backend for `initData` validation.
- `TELEGRAM_ALLOWED_USER_IDS`: optional comma-separated Telegram user id allowlist.
- `KANBAN_UI_ALLOWED_ORIGINS`: CORS allowlist.
+- `KANBAN_UI_FRONTEND_DIST_DIR`: optional built frontend directory for single-container serving.
- `VITE_API_BASE_URL`: frontend API base URL for production builds.
## Telegram Mini App setup
@@ -126,6 +127,9 @@ npm ci
npm run build
```
+The included Dockerfile builds the frontend and serves it from the FastAPI container on port `8000`.
+For same-origin Docker deployment, leave `VITE_API_BASE_URL` empty at build time and expose only the backend container.
+
## Security model
- Backend accepts only fixed API actions mapped to fixed Hermes Kanban CLI subcommands.
diff --git a/backend/app/config.py b/backend/app/config.py
index 30d76a5..a345f19 100644
--- a/backend/app/config.py
+++ b/backend/app/config.py
@@ -1,8 +1,9 @@
+import json
from functools import lru_cache
-from typing import Literal
+from typing import Annotated, Literal
from pydantic import Field, field_validator
-from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic_settings import BaseSettings, NoDecode, SettingsConfigDict
class Settings(BaseSettings):
@@ -13,17 +14,27 @@ class Settings(BaseSettings):
default_board: str = Field(default="default", alias="HERMES_KANBAN_BOARD")
auth_mode: Literal["telegram", "none", "dev"] = Field(default="dev", alias="KANBAN_UI_AUTH_MODE")
telegram_bot_token: str | None = Field(default=None, alias="TELEGRAM_BOT_TOKEN")
- telegram_allowed_user_ids: list[int] = Field(default_factory=list, alias="TELEGRAM_ALLOWED_USER_IDS")
- allowed_origins: list[str] = Field(default_factory=lambda: ["http://localhost:5173"], alias="KANBAN_UI_ALLOWED_ORIGINS")
+ telegram_allowed_user_ids: Annotated[list[int], NoDecode] = Field(default_factory=list, alias="TELEGRAM_ALLOWED_USER_IDS")
+ allowed_origins: Annotated[list[str], NoDecode] = Field(default_factory=lambda: ["http://localhost:5173"], alias="KANBAN_UI_ALLOWED_ORIGINS")
command_timeout_seconds: int = Field(default=30, alias="KANBAN_UI_COMMAND_TIMEOUT_SECONDS")
+ frontend_dist_dir: str | None = Field(default=None, alias="KANBAN_UI_FRONTEND_DIST_DIR")
@field_validator("telegram_allowed_user_ids", mode="before")
@classmethod
def parse_user_ids(cls, value):
if value is None or value == "":
return []
+ if isinstance(value, str):
+ value = value.strip()
+ if value.startswith("["):
+ try:
+ value = json.loads(value)
+ except json.JSONDecodeError:
+ pass
if isinstance(value, str):
return [int(part.strip()) for part in value.split(",") if part.strip()]
+ if isinstance(value, (list, tuple)):
+ return [int(v) for v in value]
return value
@field_validator("allowed_origins", mode="before")
@@ -31,8 +42,17 @@ class Settings(BaseSettings):
def parse_origins(cls, value):
if value is None or value == "":
return []
+ if isinstance(value, str):
+ value = value.strip()
+ if value.startswith("["):
+ try:
+ value = json.loads(value)
+ except json.JSONDecodeError:
+ pass
if isinstance(value, str):
return [part.strip() for part in value.split(",") if part.strip()]
+ if isinstance(value, (list, tuple)):
+ return [str(v) for v in value]
return value
diff --git a/backend/app/main.py b/backend/app/main.py
index d1682e3..39366c2 100644
--- a/backend/app/main.py
+++ b/backend/app/main.py
@@ -128,6 +128,14 @@ def create_app(settings: Settings | None = None, adapter: KanbanAdapter | None =
def dispatch(board: str | None = Query(default=None), user=Depends(require_user)):
return safe_call(adapter.dispatch, selected_board(board))
+ if settings.frontend_dist_dir:
+ from pathlib import Path
+ from fastapi.staticfiles import StaticFiles
+
+ frontend_dir = Path(settings.frontend_dist_dir)
+ if frontend_dir.is_dir():
+ app.mount("/", StaticFiles(directory=frontend_dir, html=True), name="frontend")
+
return app
diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py
index 1da498a..1047d93 100644
--- a/backend/tests/test_api.py
+++ b/backend/tests/test_api.py
@@ -118,6 +118,35 @@ def test_allowed_user_filter_rejects_unauthorized(fake_adapter):
assert response.status_code == 403
+def test_env_list_settings_accept_json_strings():
+ settings = Settings(
+ telegram_allowed_user_ids='[123, "456"]',
+ allowed_origins='["https://miniapp.example", "http://localhost:5173"]',
+ )
+ assert settings.telegram_allowed_user_ids == [123, 456]
+ assert settings.allowed_origins == ["https://miniapp.example", "http://localhost:5173"]
+
+
+def test_env_list_settings_accept_comma_separated_strings():
+ settings = Settings(
+ telegram_allowed_user_ids="123,456",
+ allowed_origins="https://miniapp.example,http://localhost:5173",
+ )
+ assert settings.telegram_allowed_user_ids == [123, 456]
+ assert settings.allowed_origins == ["https://miniapp.example", "http://localhost:5173"]
+
+
+def test_static_frontend_can_be_served_from_backend(tmp_path, fake_adapter):
+ (tmp_path / "index.html").write_text("
Kanban Mini App
", encoding="utf-8")
+ settings = Settings(auth_mode="none", frontend_dist_dir=str(tmp_path))
+ app = create_app(settings, fake_adapter)
+
+ response = TestClient(app).get("/")
+
+ assert response.status_code == 200
+ assert "Kanban Mini App" in response.text
+
+
def test_task_list_returns_normalized_tasks_grouped_by_status(client):
data = client.get("/api/tasks").json()
assert data["groups"]["ready"][0]["id"] == "t_1"
diff --git a/docker-compose.yml b/docker-compose.yml
index 8bc370e..38760b7 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,22 +1,48 @@
+# ─────────────────────────────────────────────────────────────
+# docker-compose.yml — Hermes Kanban MiniApp deployment
+# Services: bridge (Express API) + frontend (Nginx SPA)
+# ─────────────────────────────────────────────────────────────
+
services:
- api:
- build: .
- env_file: .env
+ # ── Bridge API ───────────────────────────────────────────
+ bridge:
+ build:
+ context: .
+ dockerfile: packages/bridge/Dockerfile
ports:
- - "8000:8000"
+ - "3456:3456"
+ env_file: .env
+ environment:
+ # Override bind to 0.0.0.0 so container is reachable
+ BRIDGE_HOST: "0.0.0.0"
+ BRIDGE_PORT: "3456"
volumes:
- - ${HERMES_HOME:-/home/your-user/.hermes}:${HERMES_HOME:-/home/your-user/.hermes}:ro
+ # Mount hermes profile directory (read-only)
+ - ${HERMES_HOME:-/home/kisskin/.hermes}:/root/.hermes:ro
+ networks:
+ - kanban
+ restart: unless-stopped
+ healthcheck:
+ test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3456/bridge/v1/health"]
+ interval: 30s
+ timeout: 5s
+ retries: 3
+ start_period: 10s
+
+ # ── Frontend (Nginx SPA) ─────────────────────────────────
+ frontend:
+ build:
+ context: .
+ dockerfile: frontend/Dockerfile
+ ports:
+ - "8080:80"
+ depends_on:
+ bridge:
+ condition: service_healthy
+ networks:
+ - kanban
restart: unless-stopped
- frontend:
- image: node:22-alpine
- working_dir: /app/frontend
- command: sh -c "npm ci && npm run dev -- --host 0.0.0.0"
- environment:
- VITE_API_BASE_URL: http://localhost:8000
- volumes:
- - .:/app
- ports:
- - "5173:5173"
- depends_on:
- - api
+networks:
+ kanban:
+ driver: bridge
diff --git a/docs/architecture-adr.md b/docs/architecture-adr.md
new file mode 100644
index 0000000..dc9eeb0
--- /dev/null
+++ b/docs/architecture-adr.md
@@ -0,0 +1,665 @@
+# ADR-001: Community architecture for Hermes Kanban Telegram Mini App
+
+Status: Proposed for implementation
+Date: 2026-07-10
+Decision owner: project maintainer
+Scope: open-source community release, before rewriting the current local FastAPI prototype
+
+## 1. Context
+
+The current repository contains an uncommitted prototype with a Vite/React frontend and a Python/FastAPI backend that shells out to `hermes kanban`. The target changed: the public release should be best-practice, secure, easy to install, documented in English and Russian, pleasant as a Telegram Mini App and normal browser/mobile app, and work well on Vercel.
+
+Hard constraint: a static Vercel frontend cannot safely execute a user's local `hermes` CLI or read local `~/.hermes/kanban.db`. Any architecture that claims otherwise is untrustworthy. A remote browser UI must talk to a trusted server-side component that is already authorized to reach the user's Hermes instance.
+
+## 2. Verified facts used for this ADR
+
+### Hermes Agent
+
+From the official Hermes docs and local checked source at `/home/kisskin/.hermes/hermes-agent`:
+
+- Hermes API Server exposes Hermes as an OpenAI-compatible HTTP endpoint for external frontends (`POST /v1/chat/completions`, `POST /v1/responses`, `POST /v1/runs`, sessions, jobs, skills/toolsets discovery, health). Source: `/home/kisskin/.hermes/hermes-agent/website/docs/user-guide/features/api-server.md`.
+- API Server is enabled with env vars including `API_SERVER_ENABLED=true`, `API_SERVER_KEY`, `API_SERVER_HOST`, `API_SERVER_PORT`, and optional `API_SERVER_CORS_ORIGINS`. Default bind host is `127.0.0.1`; bearer auth is required. Source: same file, Configuration and Authentication sections.
+- Hermes API Server is powerful: it gives access to the agent's toolset including terminal commands, so the key must never be exposed to a browser. Source: same file, Security warning.
+- Current API Server capabilities do not advertise first-class Kanban CRUD endpoints. The capabilities list includes chat/responses/runs, sessions, jobs, skills, and toolsets; searching `gateway/platforms/api_server.py` found no `/api/kanban` route.
+- Hermes Kanban is a durable SQLite-backed board. Tasks, comments, links, workspaces, logs, dispatcher behavior, boards, and board isolation are documented in `/home/kisskin/.hermes/hermes-agent/website/docs/user-guide/features/kanban.md`.
+- Hermes Kanban has two official surfaces today: agents use `kanban_*` tools, while humans/scripts use `hermes kanban ...` CLI, slash command, or the Hermes dashboard. Source: Kanban docs section "Two surfaces".
+
+### Telegram Mini Apps
+
+From official Telegram Mini Apps docs and Telegram core docs:
+
+- Mini App init data is intended to be used as an authentication/authorization factor.
+- Server validation requires excluding `hash`, sorting key-value pairs, joining them with `\n`, deriving a secret with HMAC-SHA256 over the bot token using `WebAppData`, then HMAC-SHA256 over the data-check string and comparing hex digest to `hash`.
+- The bot token must remain server-side. A frontend may forward raw init data to its backend, but must not validate with the bot token in the browser.
+- The official React ecosystem template for Telegram Mini Apps uses React, TypeScript, Vite, `@tma.js/sdk-react` / Telegram Apps SDK packages, React Router HashRouter, and Telegram UI components.
+
+### Vercel / browser deployment
+
+- Vercel is a good fit for Vite static frontend hosting and for lightweight Node.js serverless API routes.
+- Vercel serverless functions can call HTTPS APIs, but they cannot be trusted to execute a user's private local `hermes` CLI or access a local SQLite board unless the user separately exposes a backend/bridge over HTTPS.
+- Browser-side direct calls to Hermes API Server are unsafe for a community default because they would require exposing `API_SERVER_KEY` to JavaScript. If direct browser mode is documented, it must be explicitly marked local/dev only.
+
+### Comparable open-source Telegram Mini Apps
+
+GitHub search verified active public projects/templates:
+
+- `Telegram-Mini-Apps/reactjs-template`: React, TypeScript, Vite, Telegram Mini Apps SDK, Telegram UI; suitable baseline for project structure.
+- `telegram-mini-apps-dev/TelegramUI`: React component library inspired by Telegram interface; suitable UI dependency.
+- Other public Telegram Mini App examples commonly combine React/TypeScript with Next.js or Vite and place secrets in a backend/server component, not in static browser code.
+
+## 3. Decision
+
+Use this stack decisively:
+
+1. Frontend: React + TypeScript + Vite, hosted as static assets on Vercel or any static host.
+2. Telegram SDK/UI: `@telegram-apps/sdk-react` (or current successor package from Telegram Mini Apps docs) + `@telegram-apps/telegram-ui` for native-feeling Telegram UX.
+3. API layer for Vercel/community default: TypeScript serverless BFF (Backend-for-Frontend) API routes deployed on Vercel.
+4. Local integration: optional self-hosted Hermes Kanban Bridge, implemented in TypeScript/Node.js, running on the same trusted machine as Hermes.
+5. Data source: the bridge uses stable `hermes kanban ... --json` CLI commands first, with fixed argv and `shell: false`. It may later switch to official Hermes Kanban HTTP endpoints if Hermes adds them.
+6. Authentication: Vercel BFF validates Telegram `initData` server-side and issues a short-lived app session. The BFF then calls the self-hosted bridge using a separate bridge token. The bridge never trusts the browser directly.
+7. Documentation: docs must include English and Russian install paths for Vercel + bridge, Docker Compose, and local dev.
+
+Short version: `Vercel static React Mini App + Vercel TS BFF + optional self-hosted TS bridge near Hermes`.
+
+## 4. Why not keep the prototype stack as-is?
+
+The Python/FastAPI prototype proved the shape, but it is not the best public target because:
+
+- The user prefers TypeScript/JS and Vercel.
+- A Vercel-centered release is simpler if frontend and BFF share TypeScript types and validation schemas.
+- A Python backend on Vercel is possible but less ergonomic for this use case than a small Node BFF plus a separate self-hosted bridge.
+- The current prototype mixes concerns: Telegram auth, browser CORS, Hermes CLI subprocess, and static serving in one backend. For community release, the trust boundary must be explicit.
+
+Python/FastAPI is not rejected forever; it becomes a migration source and optional legacy adapter, not the recommended architecture.
+
+## 5. Target architecture
+
+```text
+Telegram client / mobile browser / desktop browser
+ |
+ | HTTPS, static assets, no secrets
+ v
+Vercel-hosted React + TypeScript + Vite app
+ |
+ | Authorization: tma
+ | or app session cookie/JWT after validation
+ v
+Vercel TypeScript BFF API routes
+ | - validates Telegram initData with TELEGRAM_BOT_TOKEN
+ | - enforces allowed Telegram user IDs / roles
+ | - rate limits and normalizes errors
+ | - holds no Hermes CLI access
+ |
+ | HTTPS + Bridge token, server-to-server only
+ v
+Optional self-hosted Hermes Kanban Bridge (Node.js)
+ | - runs on trusted host near Hermes
+ | - validates bridge token/mTLS or reverse-proxy auth
+ | - maps narrow REST endpoints to fixed Hermes Kanban operations
+ | - uses child_process.spawn/execFile with shell=false
+ v
+Hermes install / profile
+ |
+ v
+Hermes Kanban board SQLite + dispatcher + logs/workspaces
+```
+
+### Local-only shortcut
+
+For local development only:
+
+```text
+Vite dev server -> local bridge on 127.0.0.1 -> hermes kanban CLI
+```
+
+This mode may use `AUTH_MODE=dev` and a fake Telegram user fixture, but it must be impossible to enable in production without an explicit env var.
+
+## 6. Trust boundaries
+
+### Public browser / Mini App
+
+Trusted for display only. Not trusted for identity, permissions, board slug, task IDs, or action payloads. It must not receive:
+
+- Telegram bot token.
+- Hermes API Server key.
+- Bridge token.
+- Local filesystem paths beyond what the bridge explicitly exposes as display-safe metadata.
+- Raw command output containing secrets.
+
+### Vercel BFF
+
+Trusted to authenticate Telegram users and authorize UI actions. It has:
+
+- `TELEGRAM_BOT_TOKEN`.
+- `SESSION_SECRET` or JWT signing secret.
+- `BRIDGE_BASE_URL` and `BRIDGE_TOKEN` if a bridge is configured.
+
+It does not have direct local CLI access.
+
+### Self-hosted bridge
+
+Trusted local integration process. It has:
+
+- Access to `hermes` binary and `HERMES_HOME`.
+- Optional `API_SERVER_KEY` only if a future bridge mode calls Hermes API Server for non-Kanban agent actions.
+- A bridge token accepted only from the BFF/reverse proxy.
+
+### Hermes API Server
+
+Powerful agent API. Do not expose directly to the Mini App browser for community default. If users expose it, they must keep bearer auth server-side and set narrow CORS only when absolutely needed.
+
+## 7. Authentication model
+
+### Telegram initData validation
+
+Recommended request from frontend to BFF:
+
+```http
+Authorization: tma
+```
+
+Server validation steps:
+
+1. Parse query-string-like init data.
+2. Remove and store `hash`.
+3. Sort remaining key-value pairs alphabetically.
+4. Join as `key=value` lines separated by `\n`.
+5. Derive secret key: HMAC-SHA256 with key `WebAppData` and message `TELEGRAM_BOT_TOKEN`.
+6. Compute HMAC-SHA256 of the data-check string using the derived key.
+7. Compare hex digest to received `hash` using constant-time comparison.
+8. Reject missing/invalid `auth_date`, expired data, missing user, or malformed JSON.
+9. Apply allowlist/RBAC: `TELEGRAM_ALLOWED_USER_IDS`, optional `TELEGRAM_ADMIN_USER_IDS`.
+10. Issue a short-lived app session cookie/JWT or validate `initData` on every API request.
+
+Recommended TTL: 1 hour for write actions, configurable up to 24 hours for read-only dashboards.
+
+### App roles
+
+Minimum roles:
+
+- `viewer`: boards/tasks read-only, logs redacted/truncated.
+- `operator`: create/comment/assign/promote/block/unblock/archive/dispatch.
+- `admin`: configure bridge target, manage board allowlist, view diagnostics.
+
+Initial open-source release may implement allowlist-only authorization with `operator` for all allowed users and document RBAC as planned, but the API contract should reserve `role` fields now.
+
+### Bridge authentication
+
+Recommended:
+
+- `Authorization: Bearer ` between BFF and bridge.
+- HTTPS at the public edge if bridge is reachable from Vercel.
+- Optional reverse-proxy IP allowlist, Cloudflare Access, Tailscale, or mTLS for advanced users.
+- Bridge should reject requests without `X-Request-Id` and should log audit metadata without storing Telegram raw initData.
+
+## 8. Threat model
+
+| Threat | Risk | Mitigation |
+|---|---|---|
+| Browser steals Hermes API key | Full agent/tool compromise | Never put `API_SERVER_KEY` or bridge token in frontend env. Use BFF. |
+| Forged Telegram user | Unauthorized board control | Validate initData server-side using official HMAC flow and max age. |
+| Replay of old initData | Unauthorized action reuse | Enforce `auth_date` TTL; short-lived session; optional nonce/idempotency for writes. |
+| CLI injection | Arbitrary local commands | Bridge maps endpoints to fixed argv; use `execFile`/`spawn` with `shell:false`; validate task IDs/board slugs. |
+| Path traversal via board/workspace | Filesystem escape | Accept only Hermes-valid board slugs; never pass raw paths from browser except documented workspace kind with strict validation. |
+| Leaking secrets in logs/errors | Token disclosure | Redact `token`, `secret`, `password`, `api_key`; truncate logs; avoid returning raw stderr. |
+| CSRF against BFF | Actions from malicious site | Use SameSite cookies or Authorization header; verify Origin; require Telegram/session auth for writes. |
+| CORS overexposure | Browser exfiltration | BFF CORS only for app origin; bridge CORS disabled by default. |
+| SSRF from BFF to arbitrary bridge URL | Internal network probing | `BRIDGE_BASE_URL` env only; no user-provided target URLs. |
+| Bridge public exposure | Local Hermes takeover | Strong token, HTTPS, rate limiting, reverse-proxy auth, optional local-only mode. |
+| Denial of service by refresh/log tail | Resource exhaustion | Pagination, `limit` caps, debounce refresh, server-side rate limits, timeouts. |
+| Confused deputy across boards | User controls wrong board | Server-side board allowlist; all write endpoints include normalized board slug and audit actor. |
+| Supply-chain compromise | Public package risk | Pin lockfile, Dependabot/Renovate, CI tests, minimal dependencies. |
+
+## 9. Deploy matrix
+
+| Mode | Frontend | BFF | Bridge | Hermes access | Recommended for | Notes |
+|---|---|---|---|---|---|---|
+| Vercel + self-hosted bridge | Vercel static Vite | Vercel Node serverless routes | Node bridge on user's server/Pi/VPS | Bridge runs local `hermes kanban` | Community default | Best UX for Telegram HTTPS and simple updates. Requires exposing bridge securely. |
+| Docker Compose single host | Containerized static frontend served by BFF or Nginx | Node BFF container | Node bridge container or same process | Bind-mount Hermes home / host `hermes` | Self-hosters | Strong docs needed for volumes and user permissions. Avoid mounting Docker socket. |
+| Local dev | Vite dev server | Local Node BFF | Local bridge | Local `hermes kanban` | Contributors | Supports Telegram mock/dev auth. No public exposure. |
+| Fully static Vercel only | Vercel static | None | None | None | Demo/readme only | Cannot control real Hermes. Can show mock data only. |
+| Browser -> Hermes API direct | Static or local | None | None | Hermes API Server over HTTPS | Not recommended | Exposes bearer token to browser unless using a trusted private network. Document as unsafe for public/community default. |
+| Future native Hermes Kanban API | Vercel static | Vercel BFF | Optional/none | BFF calls official `/api/kanban` | Future | Adopt only when official Hermes exposes least-privilege Kanban CRUD separate from full agent API. |
+
+## 10. API contract for v1
+
+All public frontend calls go to the BFF under `/api/*`. The BFF calls the bridge under `/bridge/v1/*` or equivalent. The frontend should not know whether data came from a bridge or future native Hermes API.
+
+### Common headers
+
+Frontend -> BFF:
+
+```http
+Authorization: tma
+Content-Type: application/json
+X-Request-Id:
+```
+
+BFF -> Bridge:
+
+```http
+Authorization: Bearer
+Content-Type: application/json
+X-Request-Id:
+X-Actor-Telegram-Id:
+X-Actor-Role: viewer|operator|admin
+```
+
+### Common response envelope
+
+```ts
+type ApiOk = {
+ ok: true;
+ data: T;
+ requestId: string;
+};
+
+type ApiError = {
+ ok: false;
+ error: {
+ code: string;
+ message: string;
+ details?: unknown;
+ };
+ requestId: string;
+};
+```
+
+### Core models
+
+```ts
+type Board = {
+ slug: string;
+ name?: string;
+ description?: string;
+ icon?: string;
+ current?: boolean;
+};
+
+type TaskStatus = 'triage' | 'todo' | 'ready' | 'running' | 'blocked' | 'done' | 'archived';
+
+type TaskSummary = {
+ id: string;
+ title: string;
+ status: TaskStatus;
+ assignee?: string | null;
+ priority?: number | null;
+ createdAt?: string;
+ updatedAt?: string;
+ blockedReason?: string | null;
+ parentIds?: string[];
+ childIds?: string[];
+};
+
+type TaskDetail = TaskSummary & {
+ body?: string | null;
+ comments?: TaskComment[];
+ runs?: TaskRun[];
+ events?: TaskEvent[];
+ attachments?: TaskAttachment[];
+};
+
+type TaskComment = {
+ id?: string;
+ author?: string;
+ body: string;
+ createdAt?: string;
+};
+
+type TaskRun = {
+ id?: string;
+ status?: string;
+ startedAt?: string;
+ finishedAt?: string;
+ logAvailable?: boolean;
+};
+
+type TaskEvent = {
+ id?: string;
+ type: string;
+ message?: string;
+ createdAt?: string;
+};
+
+type TaskAttachment = {
+ id: string;
+ filename: string;
+ sizeBytes?: number;
+ mimeType?: string;
+};
+```
+
+### BFF endpoints
+
+Read:
+
+- `GET /api/health` -> `{ status, version, bridge: { configured, reachable } }`
+- `GET /api/me` -> current Telegram user + role + auth expiry
+- `GET /api/boards` -> `Board[]`
+- `GET /api/boards/:board/tasks?status=&assignee=&q=&limit=&cursor=` -> paginated `TaskSummary[]` grouped client-side or server-side
+- `GET /api/boards/:board/tasks/:taskId` -> `TaskDetail`
+- `GET /api/boards/:board/tasks/:taskId/log?lines=200` -> redacted log tail
+- `GET /api/boards/:board/events/stream` -> SSE for future live updates; polling fallback required
+
+Write:
+
+- `POST /api/boards/:board/tasks`
+- `POST /api/boards/:board/tasks/:taskId/comment`
+- `POST /api/boards/:board/tasks/:taskId/assign`
+- `POST /api/boards/:board/tasks/:taskId/promote`
+- `POST /api/boards/:board/tasks/:taskId/block`
+- `POST /api/boards/:board/tasks/:taskId/unblock`
+- `POST /api/boards/:board/tasks/:taskId/archive`
+- `POST /api/boards/:board/dispatch`
+
+Write payload examples:
+
+```json
+{
+ "title": "Implement mobile task drawer",
+ "body": "Acceptance criteria...",
+ "assignee": "developer",
+ "priority": 3,
+ "workspaceKind": "worktree",
+ "workspacePath": null,
+ "goalMode": false,
+ "maxRuntimeSeconds": 3600,
+ "idempotencyKey": "optional-client-generated-key"
+}
+```
+
+```json
+{ "body": "Looks good, unblock after config is set." }
+```
+
+```json
+{ "assignee": "reviewer" }
+```
+
+```json
+{ "reason": "Needs maintainer decision on API shape." }
+```
+
+### Bridge endpoint behavior
+
+Bridge can mirror BFF paths under `/bridge/v1/boards/...`, but it must be internal-only. It should return the same envelope. It must:
+
+- Validate all slugs and IDs before spawning Hermes CLI.
+- Use a command allowlist, not arbitrary pass-through.
+- Set `HERMES_HOME` from env, not request payload.
+- Set `HERMES_KANBAN_BOARD` or `--board` from validated board slug.
+- Enforce timeouts per command.
+- Redact and normalize errors.
+
+## 11. UX requirements
+
+Telegram Mini App:
+
+- Respect Telegram theme variables and safe areas.
+- Use Telegram-style components for lists, section headers, buttons, badges, bottom sheets/drawers, destructive confirmations.
+- Use haptic feedback only for meaningful actions if SDK supports it.
+- `MainButton`/bottom action area for primary task action on mobile.
+- HashRouter or equivalent router compatible with Telegram webview and static hosting.
+
+Browser/mobile:
+
+- Responsive board: columns on desktop, status tabs/list on mobile.
+- Fast read path: skeleton loading, optimistic comment append only after server accepts writes or clearly mark pending.
+- Polling fallback; SSE enhancement later.
+- Accessible color contrast and keyboard navigation in browser.
+
+Community docs/quality:
+
+- `.env.example` only placeholders.
+- No real bot tokens, Hermes keys, domains, local IPs, or passwords.
+- README in English plus Russian quickstart or `README.ru.md`.
+- `SECURITY.md`, `CONTRIBUTING.md`, CI, tests, build checks.
+
+## 12. Documentation outline
+
+### English docs
+
+1. `README.md`
+ - What is Hermes Kanban Mini App?
+ - Screenshots/GIFs
+ - Architecture summary
+ - Quickstart matrix: Vercel + bridge, Docker Compose, local dev
+ - Security warning: static frontend cannot run Hermes CLI
+ - Links to full docs
+2. `docs/installation.en.md`
+ - Prerequisites: Node.js, Hermes Agent, Telegram bot, HTTPS URL
+ - Create Telegram bot and configure Mini App domain
+ - Vercel frontend/BFF deploy
+ - Self-hosted bridge setup
+ - Docker Compose setup
+ - Local dev setup with mock Telegram auth
+3. `docs/configuration.en.md`
+ - All env vars
+ - Auth modes
+ - Board allowlist
+ - Rate limit settings
+ - Reverse proxy examples
+4. `docs/security.en.md`
+ - Threat model
+ - Token handling
+ - Telegram initData validation
+ - Bridge exposure hardening
+ - Responsible disclosure
+5. `docs/api.en.md`
+ - BFF and bridge endpoint contracts
+ - Error codes
+ - SSE/polling behavior
+6. `docs/migration-from-fastapi-prototype.en.md`
+ - Mapping current prototype endpoints to new TS BFF/bridge
+ - Removed assumptions
+ - Compatibility notes
+
+### Russian docs
+
+1. `README.ru.md`
+ - Что это и кому нужно
+ - Рекомендуемая схема установки
+ - Главное предупреждение про Vercel/static и локальный Hermes CLI
+2. `docs/installation.ru.md`
+ - Telegram BotFather
+ - Vercel
+ - self-hosted bridge
+ - Docker Compose
+ - локальный dev
+3. `docs/configuration.ru.md`
+ - Переменные окружения
+ - режимы авторизации
+ - allowlist пользователей/досок
+4. `docs/security.ru.md`
+ - модель угроз
+ - где хранить токены
+ - как безопасно открыть bridge
+5. `docs/api.ru.md`
+ - контракт API
+ - ошибки
+6. `docs/migration-from-fastapi-prototype.ru.md`
+ - план миграции с текущего прототипа
+
+## 13. Environment variables for the new stack
+
+Frontend build-time public vars:
+
+```bash
+VITE_APP_NAME=Hermes Kanban
+VITE_BFF_BASE_URL= # empty for same-origin Vercel deployment
+VITE_TELEGRAM_BOT_USERNAME= # optional display/link only, not token
+```
+
+Vercel BFF server vars:
+
+```bash
+TELEGRAM_BOT_TOKEN= # secret, server-only
+TELEGRAM_ALLOWED_USER_IDS= # comma-separated Telegram IDs
+TELEGRAM_ADMIN_USER_IDS= # optional
+SESSION_SECRET= # random 32+ bytes
+BRIDGE_BASE_URL= # https://bridge.example.com
+BRIDGE_TOKEN= # secret, server-to-server
+ALLOWED_ORIGINS= # exact frontend origins
+AUTH_MAX_AGE_SECONDS=3600
+READ_AUTH_MAX_AGE_SECONDS=86400
+RATE_LIMIT_PER_MINUTE=60
+```
+
+Bridge server vars:
+
+```bash
+BRIDGE_TOKEN= # must match BFF
+HERMES_BIN=hermes
+HERMES_HOME= # target Hermes profile home
+HERMES_KANBAN_BOARD=default
+BRIDGE_HOST=127.0.0.1
+BRIDGE_PORT=8787
+BRIDGE_ALLOWED_BOARDS=default
+COMMAND_TIMEOUT_SECONDS=30
+LOG_TAIL_MAX_LINES=500
+```
+
+Docker Compose vars should mirror the same names. `.env` must stay gitignored; `.env.example` must contain placeholders only.
+
+## 14. Migration plan from current FastAPI prototype
+
+Do not modify the current Python implementation until this ADR is accepted.
+
+Phase 0: Freeze and document
+
+- Keep current prototype uncommitted as reference only.
+- Add this ADR.
+- Record endpoint inventory and data shapes from `backend/app/main.py`, `adapter.py`, `auth.py`, and frontend calls.
+
+Phase 1: Extract contracts
+
+- Create shared TypeScript schemas for Board, TaskSummary, TaskDetail, API envelope, and write payloads.
+- Write contract tests against fixtures copied from current prototype responses.
+- Define error code taxonomy.
+
+Phase 2: Build TypeScript bridge
+
+- Implement Node bridge with fixed command allowlist matching current `HermesCliKanbanAdapter` behavior.
+- Port Telegram-independent validation helpers: board slug, task ID, safe log limits, error redaction.
+- Add tests that assert `shell:false` / `execFile` usage and reject invalid IDs/slugs.
+- Verify against local `hermes kanban` commands.
+
+Phase 3: Build Vercel BFF
+
+- Implement Telegram initData validation using a maintained Node package such as `@tma.js/init-data-node` or a small audited HMAC helper with tests from Telegram examples.
+- Add allowlist auth and bridge proxy endpoints.
+- Add rate limiting and CORS/origin checks.
+
+Phase 4: Rebuild frontend UX
+
+- Keep React + TypeScript + Vite.
+- Replace ad-hoc Telegram integration with Telegram Apps SDK React package.
+- Add Telegram UI components, mobile task drawer, board status tabs, desktop columns.
+- Use BFF API only; no direct bridge/Hermes calls from browser.
+
+Phase 5: Docs and release hardening
+
+- Write EN/RU docs from outline.
+- Add `.env.example`, `SECURITY.md`, `CONTRIBUTING.md`, CI.
+- Add unit tests, contract tests, frontend tests, build check.
+- Add Docker Compose example.
+- Add Vercel deployment guide.
+
+Phase 6: Optional legacy compatibility
+
+- Either remove Python prototype from the public release or move it to `legacy/fastapi-prototype` with clear unsupported status.
+- Do not publish both as equal recommended paths; it confuses users.
+
+## 15. Consequences
+
+Positive:
+
+- Clear Vercel-first story without lying about local CLI access.
+- Secrets stay server-side.
+- TypeScript types can be shared across frontend, BFF, and bridge.
+- Bridge is optional but honest: users who want real Hermes control must run a trusted server near Hermes.
+- Future Hermes native Kanban HTTP API can replace bridge internals without changing frontend UX.
+
+Negative/tradeoffs:
+
+- Requires two deploy targets for real use: Vercel app and bridge.
+- Public bridge exposure needs careful docs.
+- Slightly more moving parts than a single FastAPI container.
+- Until Hermes exposes first-class Kanban HTTP endpoints, the bridge still depends on CLI JSON behavior.
+
+## 16. Rejected alternatives
+
+### Static Vercel app directly runs Hermes CLI
+
+Rejected. Browsers/static hosting cannot execute the user's local CLI. Any workaround would require unsafe local agents or browser extensions and is not community-ready.
+
+### Static app directly calls Hermes API Server with API key
+
+Rejected for public/community default. Hermes API Server bearer key controls a full agent toolset including terminal; exposing it to JavaScript is a critical security issue.
+
+### Keep single Python/FastAPI backend as recommended public stack
+
+Rejected as recommended stack. It works locally and may be a useful prototype, but it conflicts with TypeScript/Vercel preference and mixes trust boundaries.
+
+### Full Next.js monolith
+
+Not selected. Next.js could host frontend+BFF on Vercel, but Vite static + small serverless BFF is simpler, lighter, and closer to official Telegram Mini Apps React template patterns. If future SSR/auth needs grow, migration to Next.js remains possible.
+
+### Bridge reads `kanban.db` directly
+
+Rejected for v1. Direct DB reads couple the project to Hermes internals and risk schema drift. Use official CLI behavior first; switch to official HTTP API when available.
+
+## 17. Precise next implementation milestones
+
+M1. Contract foundation
+
+- Add `packages/shared` or `src/shared` TypeScript schemas.
+- Add API envelope and error code tests.
+- No UI rewrite yet.
+
+M2. Bridge MVP
+
+- Create `apps/bridge` TypeScript service.
+- Implement `GET /health`, `GET /bridge/v1/boards`, `GET /bridge/v1/boards/:board/tasks`, `GET /bridge/v1/boards/:board/tasks/:taskId`.
+- Use fixed `execFile`/`spawn` calls to `hermes kanban` with tests.
+
+M3. BFF auth MVP
+
+- Create Vercel API routes.
+- Validate Telegram initData in server code.
+- Proxy read endpoints to bridge.
+- Add auth fixtures/tests.
+
+M4. Frontend read-only UX
+
+- Migrate current React UI to new BFF contract.
+- Add Telegram SDK initialization and Telegram UI theme handling.
+- Ship read-only boards/tasks/log tail.
+
+M5. Write actions
+
+- Add create/comment/assign/promote/block/unblock/archive/dispatch.
+- Add confirmations and role checks.
+- Add audit log fields passed to bridge.
+
+M6. Release docs and CI
+
+- EN/RU install docs.
+- `.env.example` for frontend, BFF, bridge, Docker Compose.
+- `SECURITY.md`, `CONTRIBUTING.md`.
+- CI: typecheck, lint, unit tests, frontend build, bridge tests.
+
+M7. Optional live updates
+
+- SSE endpoint from BFF to frontend.
+- Bridge polling or future Hermes dashboard/events integration.
+- Graceful fallback to polling.
+
+## 18. Acceptance criteria for starting app-code rewrite
+
+Do not write implementation code until these are accepted:
+
+- This ADR stack decision is approved.
+- Maintainer confirms whether bridge is a separate `apps/bridge` service or same package as BFF with a runtime mode switch.
+- Maintainer confirms whether the public repo should delete the Python prototype or keep it under `legacy/`.
+- First milestone is limited to contracts + tests + scaffold, not feature-complete UI.
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
new file mode 100644
index 0000000..6a84cef
--- /dev/null
+++ b/frontend/Dockerfile
@@ -0,0 +1,41 @@
+# ─────────────────────────────────────────────────────────────
+# Frontend — Multi-stage Docker build
+# Stage 1: install deps + build with Vite
+# Stage 2: serve static with nginx:alpine
+# ─────────────────────────────────────────────────────────────
+
+# ── Stage 1: Build ─────────────────────────────────────────
+FROM node:22-alpine AS build
+RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
+WORKDIR /app
+
+# Copy workspace scaffolding
+COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
+COPY packages/contracts/package.json packages/contracts/
+COPY frontend/package.json frontend/
+
+# Install all dependencies
+RUN pnpm install --frozen-lockfile
+
+# Copy source
+COPY packages/contracts/ packages/contracts/
+COPY frontend/ frontend/
+
+# Build contracts (frontend depends on @kanban/contracts), then frontend
+RUN pnpm --filter @kanban/contracts build && \
+ pnpm --filter frontend build
+
+# ── Stage 2: Serve ─────────────────────────────────────────
+FROM nginx:alpine AS runtime
+
+# Remove default nginx config
+RUN rm -f /etc/nginx/conf.d/default.conf
+
+# Copy custom nginx config
+COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
+
+# Copy built frontend
+COPY --from=build /app/frontend/dist /usr/share/nginx/html
+
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/frontend/index.html b/frontend/index.html
index f7aa318..67bf7ba 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1 +1,12 @@
-Hermes Kanban
+
+
+
+
+
+ Hermes Kanban
+
+
+
+
+
+
diff --git a/frontend/nginx.conf b/frontend/nginx.conf
new file mode 100644
index 0000000..b904727
--- /dev/null
+++ b/frontend/nginx.conf
@@ -0,0 +1,39 @@
+# ─────────────────────────────────────────────────────────────
+# nginx.conf — Frontend container configuration
+# Serves Vite SPA and proxies /bridge to the bridge service
+# ─────────────────────────────────────────────────────────────
+
+server {
+ listen 80;
+ server_name _;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ # Proxy /bridge requests to the bridge Express service
+ location /bridge/ {
+ proxy_pass http://bridge:3456/bridge/;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Connection "";
+
+ # Timeouts for slow hermes CLI calls
+ proxy_read_timeout 60s;
+ proxy_send_timeout 60s;
+ proxy_connect_timeout 10s;
+ }
+
+ # SPA fallback — serve index.html for client-side routes
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+
+ # Cache static assets aggressively (Vite hashes filenames)
+ location /assets/ {
+ expires 1y;
+ add_header Cache-Control "public, immutable";
+ }
+}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
deleted file mode 100644
index 5c119dd..0000000
--- a/frontend/package-lock.json
+++ /dev/null
@@ -1,2525 +0,0 @@
-{
- "name": "frontend",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "dependencies": {
- "@vitejs/plugin-react": "latest",
- "lucide-react": "latest",
- "react": "latest",
- "react-dom": "latest",
- "typescript": "latest",
- "vite": "latest"
- },
- "devDependencies": {
- "@testing-library/jest-dom": "latest",
- "@testing-library/react": "latest",
- "@testing-library/user-event": "latest",
- "@types/react": "latest",
- "@types/react-dom": "latest",
- "jsdom": "latest",
- "vitest": "latest"
- }
- },
- "node_modules/@adobe/css-tools": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz",
- "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@asamuzakjp/css-color": {
- "version": "5.1.11",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz",
- "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@asamuzakjp/generational-cache": "^1.0.1",
- "@csstools/css-calc": "^3.2.0",
- "@csstools/css-color-parser": "^4.1.0",
- "@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0"
- },
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- }
- },
- "node_modules/@asamuzakjp/dom-selector": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz",
- "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@asamuzakjp/generational-cache": "^1.0.1",
- "@asamuzakjp/nwsapi": "^2.3.9",
- "bidi-js": "^1.0.3",
- "css-tree": "^3.2.1",
- "is-potential-custom-element-name": "^1.0.1"
- },
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- }
- },
- "node_modules/@asamuzakjp/generational-cache": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz",
- "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- }
- },
- "node_modules/@asamuzakjp/nwsapi": {
- "version": "2.3.9",
- "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
- "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@babel/code-frame": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
- "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/helper-validator-identifier": "^7.29.7",
- "js-tokens": "^4.0.0",
- "picocolors": "^1.1.1"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
- "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/runtime": {
- "version": "7.29.7",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
- "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@bramus/specificity": {
- "version": "2.4.2",
- "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
- "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "css-tree": "^3.0.0"
- },
- "bin": {
- "specificity": "bin/cli.js"
- }
- },
- "node_modules/@csstools/color-helpers": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz",
- "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT-0",
- "engines": {
- "node": ">=20.19.0"
- }
- },
- "node_modules/@csstools/css-calc": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz",
- "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=20.19.0"
- },
- "peerDependencies": {
- "@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0"
- }
- },
- "node_modules/@csstools/css-color-parser": {
- "version": "4.1.9",
- "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.9.tgz",
- "integrity": "sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@csstools/color-helpers": "^6.1.0",
- "@csstools/css-calc": "^3.2.1"
- },
- "engines": {
- "node": ">=20.19.0"
- },
- "peerDependencies": {
- "@csstools/css-parser-algorithms": "^4.0.0",
- "@csstools/css-tokenizer": "^4.0.0"
- }
- },
- "node_modules/@csstools/css-parser-algorithms": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
- "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=20.19.0"
- },
- "peerDependencies": {
- "@csstools/css-tokenizer": "^4.0.0"
- }
- },
- "node_modules/@csstools/css-syntax-patches-for-csstree": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.6.tgz",
- "integrity": "sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT-0",
- "peerDependencies": {
- "css-tree": "^3.2.1"
- },
- "peerDependenciesMeta": {
- "css-tree": {
- "optional": true
- }
- }
- },
- "node_modules/@csstools/css-tokenizer": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
- "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=20.19.0"
- }
- },
- "node_modules/@emnapi/core": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
- "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/wasi-threads": "1.2.2",
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/runtime": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
- "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@emnapi/wasi-threads": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
- "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/@exodus/bytes": {
- "version": "1.15.1",
- "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz",
- "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- },
- "peerDependencies": {
- "@noble/hashes": "^1.8.0 || ^2.0.0"
- },
- "peerDependenciesMeta": {
- "@noble/hashes": {
- "optional": true
- }
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
- "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@napi-rs/wasm-runtime": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
- "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@tybys/wasm-util": "^0.10.3"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/Brooooooklyn"
- },
- "peerDependencies": {
- "@emnapi/core": "^1.7.1",
- "@emnapi/runtime": "^1.7.1"
- }
- },
- "node_modules/@oxc-project/types": {
- "version": "0.139.0",
- "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
- "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/Boshen"
- }
- },
- "node_modules/@rolldown/binding-android-arm64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
- "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-darwin-arm64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
- "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-darwin-x64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
- "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-freebsd-x64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
- "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
- "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
- "cpu": [
- "arm"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-linux-arm64-gnu": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
- "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
- "cpu": [
- "arm64"
- ],
- "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.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
- "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
- "cpu": [
- "arm64"
- ],
- "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.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
- "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
- "cpu": [
- "ppc64"
- ],
- "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.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
- "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
- "cpu": [
- "s390x"
- ],
- "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.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
- "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
- "cpu": [
- "x64"
- ],
- "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.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
- "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
- "cpu": [
- "x64"
- ],
- "libc": [
- "musl"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-openharmony-arm64": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
- "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "openharmony"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-wasm32-wasi": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
- "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
- "cpu": [
- "wasm32"
- ],
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/core": "1.11.1",
- "@emnapi/runtime": "1.11.1",
- "@napi-rs/wasm-runtime": "^1.1.6"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-win32-arm64-msvc": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
- "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
- "cpu": [
- "arm64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/binding-win32-x64-msvc": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
- "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
- "cpu": [
- "x64"
- ],
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- }
- },
- "node_modules/@rolldown/pluginutils": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
- "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
- "license": "MIT"
- },
- "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/@testing-library/dom": {
- "version": "10.4.1",
- "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
- "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/code-frame": "^7.10.4",
- "@babel/runtime": "^7.12.5",
- "@types/aria-query": "^5.0.1",
- "aria-query": "5.3.0",
- "dom-accessibility-api": "^0.5.9",
- "lz-string": "^1.5.0",
- "picocolors": "1.1.1",
- "pretty-format": "^27.0.2"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@testing-library/jest-dom": {
- "version": "6.9.1",
- "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
- "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@adobe/css-tools": "^4.4.0",
- "aria-query": "^5.0.0",
- "css.escape": "^1.5.1",
- "dom-accessibility-api": "^0.6.3",
- "picocolors": "^1.1.1",
- "redent": "^3.0.0"
- },
- "engines": {
- "node": ">=14",
- "npm": ">=6",
- "yarn": ">=1"
- }
- },
- "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
- "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@testing-library/react": {
- "version": "16.3.2",
- "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
- "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@babel/runtime": "^7.12.5"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@testing-library/dom": "^10.0.0",
- "@types/react": "^18.0.0 || ^19.0.0",
- "@types/react-dom": "^18.0.0 || ^19.0.0",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- },
- "peerDependenciesMeta": {
- "@types/react": {
- "optional": true
- },
- "@types/react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/@testing-library/user-event": {
- "version": "14.6.1",
- "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz",
- "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12",
- "npm": ">=6"
- },
- "peerDependencies": {
- "@testing-library/dom": ">=7.21.4"
- }
- },
- "node_modules/@tybys/wasm-util": {
- "version": "0.10.3",
- "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
- "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
- "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",
- "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/@types/chai": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
- "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/deep-eql": "*",
- "assertion-error": "^2.0.1"
- }
- },
- "node_modules/@types/deep-eql": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
- "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/estree": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
- "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/react": {
- "version": "19.2.17",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
- "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "csstype": "^3.2.2"
- }
- },
- "node_modules/@types/react-dom": {
- "version": "19.2.3",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
- "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "@types/react": "^19.2.0"
- }
- },
- "node_modules/@typescript/typescript-aix-ppc64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz",
- "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==",
- "cpu": [
- "ppc64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-darwin-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz",
- "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-darwin-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz",
- "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-freebsd-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz",
- "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-freebsd-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz",
- "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-arm": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz",
- "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==",
- "cpu": [
- "arm"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz",
- "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-loong64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz",
- "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==",
- "cpu": [
- "loong64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-mips64el": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz",
- "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==",
- "cpu": [
- "mips64el"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-ppc64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz",
- "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==",
- "cpu": [
- "ppc64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-riscv64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz",
- "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==",
- "cpu": [
- "riscv64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-s390x": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz",
- "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==",
- "cpu": [
- "s390x"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-linux-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz",
- "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-netbsd-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz",
- "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-netbsd-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz",
- "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-openbsd-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz",
- "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-openbsd-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz",
- "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-sunos-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz",
- "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-win32-arm64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz",
- "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@typescript/typescript-win32-x64": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz",
- "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=16.20.0"
- }
- },
- "node_modules/@vitejs/plugin-react": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz",
- "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==",
- "license": "MIT",
- "dependencies": {
- "@rolldown/pluginutils": "^1.0.1"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "peerDependencies": {
- "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
- "babel-plugin-react-compiler": "^1.0.0",
- "vite": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "@rolldown/plugin-babel": {
- "optional": true
- },
- "babel-plugin-react-compiler": {
- "optional": true
- }
- }
- },
- "node_modules/@vitest/expect": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
- "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@standard-schema/spec": "^1.1.0",
- "@types/chai": "^5.2.2",
- "@vitest/spy": "4.1.10",
- "@vitest/utils": "4.1.10",
- "chai": "^6.2.2",
- "tinyrainbow": "^3.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/mocker": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz",
- "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/spy": "4.1.10",
- "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/pretty-format": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz",
- "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tinyrainbow": "^3.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/runner": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz",
- "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/utils": "4.1.10",
- "pathe": "^2.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/snapshot": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz",
- "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/pretty-format": "4.1.10",
- "@vitest/utils": "4.1.10",
- "magic-string": "^0.30.21",
- "pathe": "^2.0.3"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/spy": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz",
- "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==",
- "dev": true,
- "license": "MIT",
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/utils": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz",
- "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/pretty-format": "4.1.10",
- "convert-source-map": "^2.0.0",
- "tinyrainbow": "^3.1.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/aria-query": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
- "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "dequal": "^2.0.3"
- }
- },
- "node_modules/assertion-error": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
- "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/bidi-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
- "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "require-from-string": "^2.0.2"
- }
- },
- "node_modules/chai": {
- "version": "6.2.2",
- "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
- "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/css-tree": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
- "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mdn-data": "2.27.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
- }
- },
- "node_modules/css.escape": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
- "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/csstype": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
- "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/data-urls": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
- "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "whatwg-mimetype": "^5.0.0",
- "whatwg-url": "^16.0.0"
- },
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- }
- },
- "node_modules/decimal.js": {
- "version": "10.6.0",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
- "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/dequal": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
- "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/detect-libc": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
- "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/dom-accessibility-api": {
- "version": "0.5.16",
- "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
- "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/entities": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
- "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==",
- "dev": true,
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=20.19.0"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/es-module-lexer": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz",
- "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==",
- "dev": true,
- "license": "MIT"
- },
- "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/expect-type": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
- "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/fdir": {
- "version": "6.5.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
- "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "license": "MIT",
- "engines": {
- "node": ">=12.0.0"
- },
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/html-encoding-sniffer": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
- "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@exodus/bytes": "^1.6.0"
- },
- "engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
- }
- },
- "node_modules/indent-string": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
- "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-potential-custom-element-name": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
- "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
- "license": "MIT",
- "peer": true
- },
- "node_modules/jsdom": {
- "version": "29.1.1",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz",
- "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@asamuzakjp/css-color": "^5.1.11",
- "@asamuzakjp/dom-selector": "^7.1.1",
- "@bramus/specificity": "^2.4.2",
- "@csstools/css-syntax-patches-for-csstree": "^1.1.3",
- "@exodus/bytes": "^1.15.0",
- "css-tree": "^3.2.1",
- "data-urls": "^7.0.0",
- "decimal.js": "^10.6.0",
- "html-encoding-sniffer": "^6.0.0",
- "is-potential-custom-element-name": "^1.0.1",
- "lru-cache": "^11.3.5",
- "parse5": "^8.0.1",
- "saxes": "^6.0.0",
- "symbol-tree": "^3.2.4",
- "tough-cookie": "^6.0.1",
- "undici": "^7.25.0",
- "w3c-xmlserializer": "^5.0.0",
- "webidl-conversions": "^8.0.1",
- "whatwg-mimetype": "^5.0.0",
- "whatwg-url": "^16.0.1",
- "xml-name-validator": "^5.0.0"
- },
- "engines": {
- "node": "^20.19.0 || ^22.13.0 || >=24.0.0"
- },
- "peerDependencies": {
- "canvas": "^3.0.0"
- },
- "peerDependenciesMeta": {
- "canvas": {
- "optional": true
- }
- }
- },
- "node_modules/lightningcss": {
- "version": "1.32.0",
- "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
- "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "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"
- ],
- "license": "MPL-2.0",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 12.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/parcel"
- }
- },
- "node_modules/lru-cache": {
- "version": "11.5.2",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
- "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "engines": {
- "node": "20 || >=22"
- }
- },
- "node_modules/lucide-react": {
- "version": "1.24.0",
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.24.0.tgz",
- "integrity": "sha512-YT6mBD8lGKkg4nM39enlm94/sfJIiW0YKUT60fBy4YK8tai31ylg1VhGNWxkpSKHo9UagfnZqwIff3HTDQwXeA==",
- "license": "ISC",
- "peerDependencies": {
- "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/lz-string": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
- "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "bin": {
- "lz-string": "bin/bin.js"
- }
- },
- "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/mdn-data": {
- "version": "2.27.1",
- "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
- "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
- "dev": true,
- "license": "CC0-1.0"
- },
- "node_modules/min-indent": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
- "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/nanoid": {
- "version": "3.3.15",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
- "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/obug": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
- "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
- "dev": true,
- "funding": [
- "https://github.com/sponsors/sxzz",
- "https://opencollective.com/debug"
- ],
- "license": "MIT",
- "engines": {
- "node": ">=12.20.0"
- }
- },
- "node_modules/parse5": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
- "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "entities": "^8.0.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/pathe": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
- "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
- "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/postcss": {
- "version": "8.5.16",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
- "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "nanoid": "^3.3.12",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/pretty-format": {
- "version": "27.5.1",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
- "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
- "dev": true,
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "ansi-regex": "^5.0.1",
- "ansi-styles": "^5.0.0",
- "react-is": "^17.0.1"
- },
- "engines": {
- "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
- }
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/react": {
- "version": "19.2.7",
- "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
- "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "19.2.7",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
- "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
- "license": "MIT",
- "dependencies": {
- "scheduler": "^0.27.0"
- },
- "peerDependencies": {
- "react": "^19.2.7"
- }
- },
- "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/redent": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
- "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "indent-string": "^4.0.0",
- "strip-indent": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
- "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/rolldown": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
- "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
- "license": "MIT",
- "dependencies": {
- "@oxc-project/types": "=0.139.0",
- "@rolldown/pluginutils": "^1.0.0"
- },
- "bin": {
- "rolldown": "bin/cli.mjs"
- },
- "engines": {
- "node": "^20.19.0 || >=22.12.0"
- },
- "optionalDependencies": {
- "@rolldown/binding-android-arm64": "1.1.5",
- "@rolldown/binding-darwin-arm64": "1.1.5",
- "@rolldown/binding-darwin-x64": "1.1.5",
- "@rolldown/binding-freebsd-x64": "1.1.5",
- "@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
- "@rolldown/binding-linux-arm64-gnu": "1.1.5",
- "@rolldown/binding-linux-arm64-musl": "1.1.5",
- "@rolldown/binding-linux-ppc64-gnu": "1.1.5",
- "@rolldown/binding-linux-s390x-gnu": "1.1.5",
- "@rolldown/binding-linux-x64-gnu": "1.1.5",
- "@rolldown/binding-linux-x64-musl": "1.1.5",
- "@rolldown/binding-openharmony-arm64": "1.1.5",
- "@rolldown/binding-wasm32-wasi": "1.1.5",
- "@rolldown/binding-win32-arm64-msvc": "1.1.5",
- "@rolldown/binding-win32-x64-msvc": "1.1.5"
- }
- },
- "node_modules/saxes": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
- "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "xmlchars": "^2.2.0"
- },
- "engines": {
- "node": ">=v12.22.7"
- }
- },
- "node_modules/scheduler": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
- "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
- "license": "MIT"
- },
- "node_modules/siginfo": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
- "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/stackback": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
- "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/std-env": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz",
- "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/strip-indent": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
- "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "min-indent": "^1.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/symbol-tree": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
- "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tinybench": {
- "version": "2.9.0",
- "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
- "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tinyexec": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
- "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/tinyglobby": {
- "version": "0.2.17",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
- "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
- "license": "MIT",
- "dependencies": {
- "fdir": "^6.5.0",
- "picomatch": "^4.0.4"
- },
- "engines": {
- "node": ">=12.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/SuperchupuDev"
- }
- },
- "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==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/tldts": {
- "version": "7.4.7",
- "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.7.tgz",
- "integrity": "sha512-56L0/9HELHSsG1bFCzay8UoLxzRL7kpFf7Wl5q/kSYwiSJGACvro61xnKzPNM+SadxllzdtXsKDSXE7HPeqIAw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "tldts-core": "^7.4.7"
- },
- "bin": {
- "tldts": "bin/cli.js"
- }
- },
- "node_modules/tldts-core": {
- "version": "7.4.7",
- "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.7.tgz",
- "integrity": "sha512-rNlAI8fKn/JckBMUSbNL/ES2kmDiurWaE49l+ikwEc9A6lFR7gMx9AhgQMQKBK4H5w4pKLH64JzZfB99uRsGNQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tough-cookie": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz",
- "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "tldts": "^7.0.5"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "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/tslib": {
- "version": "2.8.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
- "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "license": "0BSD",
- "optional": true
- },
- "node_modules/typescript": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
- "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==",
- "license": "Apache-2.0",
- "bin": {
- "tsc": "bin/tsc"
- },
- "engines": {
- "node": ">=16.20.0"
- },
- "optionalDependencies": {
- "@typescript/typescript-aix-ppc64": "7.0.2",
- "@typescript/typescript-darwin-arm64": "7.0.2",
- "@typescript/typescript-darwin-x64": "7.0.2",
- "@typescript/typescript-freebsd-arm64": "7.0.2",
- "@typescript/typescript-freebsd-x64": "7.0.2",
- "@typescript/typescript-linux-arm": "7.0.2",
- "@typescript/typescript-linux-arm64": "7.0.2",
- "@typescript/typescript-linux-loong64": "7.0.2",
- "@typescript/typescript-linux-mips64el": "7.0.2",
- "@typescript/typescript-linux-ppc64": "7.0.2",
- "@typescript/typescript-linux-riscv64": "7.0.2",
- "@typescript/typescript-linux-s390x": "7.0.2",
- "@typescript/typescript-linux-x64": "7.0.2",
- "@typescript/typescript-netbsd-arm64": "7.0.2",
- "@typescript/typescript-netbsd-x64": "7.0.2",
- "@typescript/typescript-openbsd-arm64": "7.0.2",
- "@typescript/typescript-openbsd-x64": "7.0.2",
- "@typescript/typescript-sunos-x64": "7.0.2",
- "@typescript/typescript-win32-arm64": "7.0.2",
- "@typescript/typescript-win32-x64": "7.0.2"
- }
- },
- "node_modules/undici": {
- "version": "7.28.0",
- "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
- "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=20.18.1"
- }
- },
- "node_modules/vite": {
- "version": "8.1.4",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz",
- "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==",
- "license": "MIT",
- "dependencies": {
- "lightningcss": "^1.32.0",
- "picomatch": "^4.0.5",
- "postcss": "^8.5.16",
- "rolldown": "~1.1.4",
- "tinyglobby": "^0.2.17"
- },
- "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.3.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/vitest": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
- "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@vitest/expect": "4.1.10",
- "@vitest/mocker": "4.1.10",
- "@vitest/pretty-format": "4.1.10",
- "@vitest/runner": "4.1.10",
- "@vitest/snapshot": "4.1.10",
- "@vitest/spy": "4.1.10",
- "@vitest/utils": "4.1.10",
- "es-module-lexer": "^2.0.0",
- "expect-type": "^1.3.0",
- "magic-string": "^0.30.21",
- "obug": "^2.1.1",
- "pathe": "^2.0.3",
- "picomatch": "^4.0.3",
- "std-env": "^4.0.0-rc.1",
- "tinybench": "^2.9.0",
- "tinyexec": "^1.0.2",
- "tinyglobby": "^0.2.15",
- "tinyrainbow": "^3.1.0",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
- "why-is-node-running": "^2.3.0"
- },
- "bin": {
- "vitest": "vitest.mjs"
- },
- "engines": {
- "node": "^20.0.0 || ^22.0.0 || >=24.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.10",
- "@vitest/browser-preview": "4.1.10",
- "@vitest/browser-webdriverio": "4.1.10",
- "@vitest/coverage-istanbul": "4.1.10",
- "@vitest/coverage-v8": "4.1.10",
- "@vitest/ui": "4.1.10",
- "happy-dom": "*",
- "jsdom": "*",
- "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
- },
- "peerDependenciesMeta": {
- "@edge-runtime/vm": {
- "optional": true
- },
- "@opentelemetry/api": {
- "optional": true
- },
- "@types/node": {
- "optional": true
- },
- "@vitest/browser-playwright": {
- "optional": true
- },
- "@vitest/browser-preview": {
- "optional": true
- },
- "@vitest/browser-webdriverio": {
- "optional": true
- },
- "@vitest/coverage-istanbul": {
- "optional": true
- },
- "@vitest/coverage-v8": {
- "optional": true
- },
- "@vitest/ui": {
- "optional": true
- },
- "happy-dom": {
- "optional": true
- },
- "jsdom": {
- "optional": true
- },
- "vite": {
- "optional": false
- }
- }
- },
- "node_modules/w3c-xmlserializer": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
- "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "xml-name-validator": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "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/whatwg-mimetype": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
- "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=20"
- }
- },
- "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/why-is-node-running": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
- "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "siginfo": "^2.0.0",
- "stackback": "0.0.2"
- },
- "bin": {
- "why-is-node-running": "cli.js"
- },
- "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",
- "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
- "dev": true,
- "license": "Apache-2.0",
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/xmlchars": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
- "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
- "dev": true,
- "license": "MIT"
- }
- }
-}
diff --git a/frontend/package.json b/frontend/package.json
index daec159..d3c0ac5 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1 +1,29 @@
-{"scripts":{"dev":"vite --host 0.0.0.0","build":"tsc -b && vite build","preview":"vite preview --host 0.0.0.0","test":"vitest run --environment jsdom"},"dependencies":{"@vitejs/plugin-react":"latest","vite":"latest","typescript":"latest","react":"latest","react-dom":"latest","lucide-react":"latest"},"devDependencies":{"vitest":"latest","jsdom":"latest","@testing-library/react":"latest","@testing-library/jest-dom":"latest","@testing-library/user-event":"latest","@types/react":"latest","@types/react-dom":"latest"}}
\ No newline at end of file
+{
+ "name": "frontend",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite --host 0.0.0.0",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview --host 0.0.0.0",
+ "typecheck": "tsc --noEmit",
+ "clean": "rm -rf dist"
+ },
+ "dependencies": {
+ "@kanban/contracts": "workspace:*",
+ "@telegram-apps/sdk-react": "^3.3.9",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.4",
+ "autoprefixer": "^10.4.20",
+ "postcss": "^8.4.49",
+ "tailwindcss": "^3.4.16",
+ "typescript": "^5.6.3",
+ "vite": "^6.0.5"
+ }
+}
diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js
new file mode 100644
index 0000000..2e7af2b
--- /dev/null
+++ b/frontend/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 78d7efc..f482ef1 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,186 +1,253 @@
-import { useEffect, useMemo, useState } from 'react'
-import { RefreshCw, Plus, Send } from 'lucide-react'
-import { api } from './api'
-import { applyTelegramTheme } from './telegram'
-import type { Event, Run, Task, TaskGroups } from './types'
+import { useCallback, useEffect, useState } from 'react'
+import type { Board, TaskSummary, TaskDetail, TaskListData, CreateTaskPayload, CommentPayload } from '@kanban/contracts'
+import { DEFAULT_COLUMNS } from '@kanban/contracts'
+import { initTelegram, applyTheme, hapticFeedback, showBackButton, hideBackButton } from './telegram'
+import { fetchBoards, fetchTasks, fetchTask, createTask, addComment } from './api/client'
+import BoardSelector from './components/BoardSelector'
+import KanbanBoard from './components/KanbanBoard'
+import TaskDetailModal from './components/TaskDetailModal'
+import CreateTaskForm from './components/CreateTaskForm'
-const STATUSES = ['triage', 'todo', 'ready', 'running', 'blocked', 'done', 'archived']
-
-type Detail = {
- task?: Task
- events: Event[]
- runs: Run[]
- log: string
- missingLog: boolean
-}
-
-function ErrorBanner({ message }: { message: string }) {
- return {message}
-}
-
-function TaskCard({ task, onOpen }: { task: Task; onOpen: (task: Task) => void }) {
- return (
-
- )
-}
-
-function CreateTask({ onCreated }: { onCreated: () => void }) {
- const [open, setOpen] = useState(false)
- const [title, setTitle] = useState('')
- const [body, setBody] = useState('')
- const [assignee, setAssignee] = useState('')
- const [priority, setPriority] = useState(0)
- const [workspaceKind, setWorkspaceKind] = useState('scratch')
- const [goalMode, setGoalMode] = useState(false)
- const [maxRuntime, setMaxRuntime] = useState('')
- const [error, setError] = useState('')
-
- async function submit() {
- if (!title.trim()) {
- setError('Title is required')
- return
- }
- setError('')
- await api.create({
- title,
- body,
- assignee: assignee || null,
- priority,
- workspace_kind: workspaceKind,
- goal_mode: goalMode,
- max_runtime_seconds: maxRuntime ? Number(maxRuntime) : null,
- })
- setOpen(false)
- setTitle('')
- setBody('')
- onCreated()
- }
-
- if (!open) return
-
- return (
-
-
Create task
- {error &&
}
-
setTitle(event.target.value)} />
-
- )
-}
-
-function DetailDrawer({ selected, detail, onClose, onRefresh }: { selected: Task | null; detail: Detail | null; onClose: () => void; onRefresh: () => void }) {
- const [comment, setComment] = useState('')
- const [assign, setAssign] = useState('')
- const [blockReason, setBlockReason] = useState('')
- if (!selected) return null
- const task = detail?.task || selected
- async function addComment() {
- if (!comment.trim()) return
- await api.comment(selected!.id, comment)
- setComment('')
- onRefresh()
- }
- return (
-
- )
-}
+type View = 'boards' | 'board' | 'task' | 'create'
export default function App() {
- const [groups, setGroups] = useState({})
- const [error, setError] = useState('')
+ const [view, setView] = useState('boards')
+ const [boards, setBoards] = useState([])
+ const [selectedBoard, setSelectedBoard] = useState(null)
+ const [tasks, setTasks] = useState(null)
+ const [selectedTask, setSelectedTask] = useState(null)
const [loading, setLoading] = useState(true)
- const [selected, setSelected] = useState(null)
- const [detail, setDetail] = useState(null)
- const hasRunningTasks = useMemo(() => (groups.running || []).length > 0, [groups])
+ const [error, setError] = useState(null)
- async function loadBoard() {
+ // Initialize Telegram on mount
+ useEffect(() => {
+ initTelegram()
+ applyTheme()
+ }, [])
+
+ // Load boards
+ const loadBoards = useCallback(async () => {
try {
- const data = await api.tasks()
- setGroups(data.groups)
- setError('')
+ setLoading(true)
+ setError(null)
+ const data = await fetchBoards()
+ setBoards(data)
+ // Auto-select if only one board
+ if (data.length === 1) {
+ setSelectedBoard(data[0])
+ setView('board')
+ } else {
+ setView('boards')
+ }
} catch (err) {
- setError(err instanceof Error ? err.message : 'Failed to load board')
+ setError(err instanceof Error ? err.message : 'Failed to load boards')
} finally {
setLoading(false)
}
- }
-
- async function openTask(task: Task) {
- setSelected(task)
- const [taskData, events, runs, log] = await Promise.all([api.task(task.id), api.events(task.id), api.runs(task.id), api.log(task.id)])
- setDetail({ task: taskData.task, events: events.events, runs: runs.runs, log: log.log, missingLog: log.missing })
- }
-
- async function refreshSelected() {
- await loadBoard()
- if (selected) await openTask(selected)
- }
-
- useEffect(() => {
- applyTelegramTheme()
- loadBoard()
}, [])
+ // Load tasks for board
+ const loadTasks = useCallback(async (board: string) => {
+ try {
+ setLoading(true)
+ setError(null)
+ const data = await fetchTasks(board)
+ setTasks(data)
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to load tasks')
+ } finally {
+ setLoading(false)
+ }
+ }, [])
+
+ // Load task detail
+ const loadTaskDetail = useCallback(async (board: string, taskId: string) => {
+ try {
+ const detail = await fetchTask(board, taskId)
+ setSelectedTask(detail)
+ setView('task')
+ showBackButton(() => {
+ setSelectedTask(null)
+ setView('board')
+ hideBackButton()
+ })
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to load task')
+ }
+ }, [])
+
+ // Initial load
useEffect(() => {
- const interval = window.setInterval(loadBoard, hasRunningTasks ? 2500 : 5000)
- return () => window.clearInterval(interval)
- }, [hasRunningTasks])
+ loadBoards()
+ }, [loadBoards])
+
+ // Load tasks when board changes
+ useEffect(() => {
+ if (selectedBoard) {
+ loadTasks(selectedBoard.slug)
+ }
+ }, [selectedBoard, loadTasks])
+
+ // Handle board selection
+ const handleSelectBoard = (board: Board) => {
+ hapticFeedback('light')
+ setSelectedBoard(board)
+ setView('board')
+ }
+
+ // Handle task tap
+ const handleTaskTap = (task: TaskSummary) => {
+ hapticFeedback('light')
+ if (selectedBoard) {
+ loadTaskDetail(selectedBoard.slug, task.id)
+ }
+ }
+
+ // Handle create task
+ const handleCreateTask = async (payload: CreateTaskPayload) => {
+ if (!selectedBoard) return
+ try {
+ hapticFeedback('medium')
+ await createTask(selectedBoard.slug, payload)
+ setView('board')
+ loadTasks(selectedBoard.slug)
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to create task')
+ }
+ }
+
+ // Handle add comment
+ const handleAddComment = async (taskId: string, payload: CommentPayload) => {
+ if (!selectedBoard) return
+ try {
+ hapticFeedback('light')
+ await addComment(selectedBoard.slug, taskId, payload)
+ // Reload task detail
+ const detail = await fetchTask(selectedBoard.slug, taskId)
+ setSelectedTask(detail)
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to add comment')
+ }
+ }
+
+ // Handle back
+ const handleBack = () => {
+ hapticFeedback('light')
+ if (view === 'task') {
+ setSelectedTask(null)
+ setView('board')
+ hideBackButton()
+ } else if (view === 'create') {
+ setView('board')
+ hideBackButton()
+ } else if (view === 'board' && boards.length > 1) {
+ setSelectedBoard(null)
+ setView('boards')
+ }
+ }
+
+ // Handle open create form
+ const handleOpenCreate = () => {
+ hapticFeedback('medium')
+ setView('create')
+ showBackButton(handleBack)
+ }
+
+ // Loading state
+ if (loading && !tasks) {
+ return (
+
+ )
+ }
+
+ // Error state
+ if (error && !tasks) {
+ return (
+
+ )
+ }
return (
-
-