From 77754d411926f3d9855d06d6ce4c02e747bfdb47 Mon Sep 17 00:00:00 2001 From: zendaya Date: Sat, 2 May 2026 20:31:46 +0200 Subject: [PATCH 1/2] feat: add pricing warnings for missing model entries in pricing tables - Enhanced `DiffOutcome` to include `pricing_warnings`, a tuple of strings indicating when a model is not found in the baseline or candidate pricing tables. - Updated `compute_diff` function to populate `pricing_warnings` based on the presence of entries in the pricing tables. - Modified the `post_diff` route to return `pricing_warnings` in the response. - Introduced a new `MetricsPayload` type in the API to reflect the updated response structure. - Updated the web UI to display pricing warnings when applicable, enhancing user awareness of potential issues. - Added tests to verify the correct behavior of pricing warnings in various scenarios, including when models are missing from pricing tables. This change improves the diagnostic capabilities of the system, allowing users to better understand pricing discrepancies during release diffs. --- .github/workflows/release-pypi.yml | 2 +- CHANGELOG.md | 24 +++++ DEVELOPMENT.md | 2 +- RELEASE_NOTES.md | 8 ++ ROADMAP.md | 19 ++-- docs/cli.md | 13 ++- docs/http-api.md | 9 +- docs/operations-and-policy.md | 17 +++- docs/web-ui.md | 9 +- examples/ci/README.md | 2 +- .../ci/github-actions/policy-gate-pypi.yml | 2 +- examples/deploy/Dockerfile | 2 +- examples/deploy/README.md | 13 ++- examples/integration/README.md | 41 +++++++++ pyproject.toml | 2 +- src/flightdeck/__init__.py | 2 +- src/flightdeck/cli/main.py | 76 +++++++++++++++- src/flightdeck/operations.py | 14 +++ src/flightdeck/server/routes/actions.py | 5 ++ src/flightdeck/storage.py | 22 +++++ tests/test_cli.py | 22 +++++ tests/test_cli_contract.py | 35 ++++++++ tests/test_server_actions.py | 54 +++++++++++ tests/test_spine.py | 90 +++++++++++++++++++ web/e2e/smoke.spec.ts | 1 + web/src/api.ts | 22 +++++ web/src/pages/DiffPage.tsx | 13 +++ web/src/pages/OverviewPage.tsx | 80 +++++++++++++++-- 28 files changed, 571 insertions(+), 30 deletions(-) diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index 54c547e..808e17f 100644 --- a/.github/workflows/release-pypi.yml +++ b/.github/workflows/release-pypi.yml @@ -1,4 +1,4 @@ -# Publish sdist + wheel to PyPI when a SemVer tag is pushed (e.g. v1.0.4). +# Publish sdist + wheel to PyPI when a SemVer tag is pushed (e.g. v1.0.6). # Configure "trusted publishing" on PyPI for this workflow + repository + optional GitHub environment. # https://docs.pypi.org/trusted-publishers/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 841cfb3..5ac375c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,30 @@ This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0** ## Unreleased +## 1.0.6 - 2026-05-02 + +### Added + +- **CLI `flightdeck doctor --backup PATH`:** SQLite online backup of the workspace database to **`PATH`** (parent directories created; file overwritten if present), then the usual doctor checks. +- **Examples:** **[examples/integration/emit_sample_events.node.mjs](examples/integration/emit_sample_events.node.mjs)** — **`POST /v1/events`** sample using built-in **`fetch`** (Node 18+); **[examples/integration/README.md](examples/integration/README.md)** adds **`curl`** + **`jq`** example. +- **Docs:** **[examples/deploy/README.md](examples/deploy/README.md)** — Compose **`/health`** healthcheck and **`doctor --backup`** / cron scheduling notes. +- **Roadmap:** **Phase 0** declared **closed**; **catalog-level** multi-provider pricing normalization called out under **Phase 1** build items. +- **Tests:** **`test_doctor_backup_writes_valid_sqlite`** in **`tests/test_cli.py`**. + +### Changed + +- **Examples / CI snippets:** **`flightdeck-ai>=1.0.6`** in Docker and PyPI gate samples. + +## 1.0.5 - 2026-05-02 + +### Added + +- **CLI `release diff --output json`:** prints the same JSON object as **`POST /v1/diff`** (sorted keys) for **`jq`** / CI parsers; works with **`--fail-on-policy`** (JSON to stdout, then exit **1** on policy failure). +- **`POST /v1/diff`:** **`pricing.warnings`** — string list when baseline or candidate **`spec.runtime.model`** has no row in that side's imported pricing table (diagnostic only; **`policy`** unchanged). CLI prints matching **`WARNING:`** lines in text mode. +- **Web UI:** **Run diff** shows pricing warnings above the pricing/model-change banner; **Overview** adds a **Ledger metrics** card (**`GET /v1/metrics`**). +- **Docs:** **[docs/cli.md](docs/cli.md)** and **[docs/http-api.md](docs/http-api.md)** document **`--output json`** and **`pricing.warnings`**. +- **Tests:** **`test_release_diff_output_json_shape`**, **`test_release_diff_pricing_warnings_when_model_not_in_table`** in **`tests/test_spine.py`**; **`test_release_diff_fail_on_policy_with_json_output`** in **`tests/test_cli_contract.py`**; **`test_http_v1_diff_pricing_warnings_when_model_missing`** and **`pricing.warnings`** assertion on the happy path in **`tests/test_server_actions.py`**. + ## 1.0.4 - 2026-05-03 ### Added diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 8d19aaa..ebd052c 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -131,7 +131,7 @@ Merging to **`main` does not publish packages** — PyPI uploads are **tag-drive 1. **PyPI:** add a **trusted publisher** for **[github.com/flightdeckdev/flightdeck](https://github.com/flightdeckdev/flightdeck)** — workflow **`release-pypi.yml`**. If PyPI offers **Environment name: (Any)**, you can still use a GitHub **Environment** named **`pypi`** for approval gates; otherwise match whatever you register on PyPI ([trusted publishers](https://docs.pypi.org/trusted-publishers/)). 2. **GitHub:** Settings → **Environments** → create **`pypi`** (optional: required reviewers / wait timer before OIDC publish). 3. Bump **`version`** in **`pyproject.toml`** and **`src/flightdeck/__init__.py`**, update **`CHANGELOG.md`**, merge to **`main`**. -4. **`git tag vX.Y.Z`** (must match **`pyproject.toml`** exactly, e.g. **`v1.0.4`**) then **`git push origin vX.Y.Z`**. +4. **`git tag vX.Y.Z`** (must match **`pyproject.toml`** exactly, e.g. **`v1.0.6`**) then **`git push origin vX.Y.Z`**. The workflow runs **ruff**, **pytest**, schema drift, **`uv build`**, publishes **sdist + wheel** to **PyPI** via **OIDC** (no long-lived API token in repo secrets), enables **publish attestations**, and creates a **GitHub Release** with generated notes and **`dist/*`** assets. diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c0bc3c1..835a237 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,14 @@ High-level notes for **shipping FlightDeck**. Detailed history: **[CHANGELOG.md] Narrative docs (including the CLI reference) are maintained on **[github.com/flightdeckdev/flightdeck](https://github.com/flightdeckdev/flightdeck)** `main`; this file and **`schemas/`** ship in minimal clones. +## v1.0.6 — Phase 0 closure (backup, cross-language emitters, roadmap) + +Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): **`flightdeck doctor --backup PATH`** performs a SQLite online backup of the workspace DB; **[examples/integration/](examples/integration/README.md)** gains **`curl`** and a **Node** **`emit_sample_events.node.mjs`** path for **`POST /v1/events`**; **[examples/deploy/README.md](examples/deploy/README.md)** documents the Compose **`/health`** healthcheck and backup scheduling. **ROADMAP:** **Phase 0** is **closed**; **catalog-level** multi-provider pricing normalization is an explicit **Phase 1** build item. **Stable contracts:** additive CLI flag and HTTP field **`pricing.warnings`** (from **v1.0.5**) remain backward-compatible. + +## v1.0.5 — Diff JSON output, pricing warnings, metrics in Overview + +Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): **`flightdeck release diff --output json`** matches **`POST /v1/diff`** for machine consumers; **`pricing.warnings`** surfaces missing pricing-table rows for a release's resolved model (CLI **`WARNING:`** lines + web); **Overview** shows **`GET /v1/metrics`** counters. **Stable contracts:** additive only. + ## v1.0.4 — Phase 0 closing slice (pricing diagnostic, examples index, metrics) Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): **`GET /v1/metrics`** exposes additive JSON counters for operators; **`POST /v1/diff`** and **`flightdeck release diff`** add **`pricing.prices`** / a **Per-1k token prices** line when pricing or model differs, so cost deltas are easier to interpret; **[examples/README.md](examples/README.md)** ties **integration**, **CI**, and **deploy** examples into one loop; web **Run diff** shows the same unit-price deltas when present. **Stable contracts:** additive HTTP and CLI output only; no **`v1`** payload or schema removals. diff --git a/ROADMAP.md b/ROADMAP.md index 69821ab..cb8b440 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -14,14 +14,14 @@ This roadmap is meant to be clear from **what is already shipped** to **near-ter - **Economic + operational governance:** immutable pricing imports, trusted `release diff`, policy-gated `promote` and `rollback`. - **Audit trail:** promotion/rollback history with stable sequencing (`audit_seq`) and integrity checks via `doctor`. - **Evidence ingestion:** `runs ingest` from JSONL/JSON arrays plus stable `POST /v1/events` contracts (`schemas/v1/`). -- **Local API + UI:** `flightdeck serve` routes and web UI (Overview, Diff, Promote) in `src/flightdeck/server/static/`. +- **Local API + UI:** `flightdeck serve` routes and web UI (Overview with ledger metrics, Diff, Promote) in `src/flightdeck/server/static/`. - **SDK and tooling:** Python sync/async clients with retries/batching and `flightdeck-quickstart-verify`. --- ## Next release -**v1.0.4** (patch): Phase 0 closing slice — **`GET /v1/metrics`** (JSON ledger counters); **`pricing.prices`** on **`POST /v1/diff`** plus CLI **Per-1k token prices** line and matching web diff detail when pricing/model changes; **[examples/README.md](examples/README.md)** end-to-end walkthrough linking **integration**, **CI**, and **deploy** examples. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. No breaking changes to stable CLI, HTTP, or **`api_version` `v1`** contracts. +**v1.0.6** (patch): Phase 0 closure — **`flightdeck release diff --output json`** (same shape as **`POST /v1/diff`**); **`pricing.warnings`** when a release model has no row in its pricing table (CLI **`WARNING:`** lines + web Diff); **Overview** ledger metrics card (**`GET /v1/metrics`**); **`curl`** + **Node** samples under **[examples/integration/](examples/integration/README.md)**; **`flightdeck doctor --backup PATH`** (SQLite online backup); **[examples/deploy/](examples/deploy/README.md)** documents Compose **`/health`** healthcheck and backup scheduling. **Phase 0** is declared **closed**; **catalog-level** multi-provider normalization moves to **Phase 1**. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. No breaking changes to stable CLI, HTTP, or **`api_version` `v1`** contracts. --- @@ -49,11 +49,11 @@ Goal: prove the wedge with real teams using FlightDeck as release governance sou - Harden CLI/schema contracts and edge-case policy coverage (sample windows, sparse traffic, error paths). - Add concrete integration references: app runtime event emitters, CI pipeline examples, and deployment recipes for `flightdeck serve`. -- Improve pricing normalization for multiple provider inputs while keeping diff semantics stable. +- **Catalog-level cross-vendor pricing normalization** — deferred to **Phase 1** (see Phase 1 build list). **v1.0.4–v1.0.6** ship per-side **`pricing.prices`** and **`pricing.warnings`** diagnostics only. - Strengthen local security ergonomics: explicit token/env status in UI, mutation guardrails, optional read-only UX. - Continue UI productization for current scope (structured views over raw JSON where stable). -### Phase 0 progress (v1.0.3–v1.0.4) +### Phase 0 progress (v1.0.3–v1.0.6) Shipped on **`main`**: @@ -64,8 +64,14 @@ Shipped on **`main`**: - **Pricing diagnostics (v1.0.4):** **`pricing.prices`** on **`POST /v1/diff`** and matching CLI / web lines for per-1k input/output unit prices when pricing or model differs. - **Operating narrative (v1.0.4):** **[examples/README.md](examples/README.md)** index tying emit → ingest → verify → diff/gate → promote → serve. - **Observability foundation (v1.0.4):** **`GET /v1/metrics`** JSON counters over the local ledger (not Prometheus/OTel; longer arc stays mid term). +- **Diff ergonomics (v1.0.5):** **`flightdeck release diff --output json`**; **`pricing.warnings`** on **`POST /v1/diff`** / CLI / web when the release model is missing from the imported pricing table; **Overview** shows key **`GET /v1/metrics`** counters. +- **Operator + pipeline breadth (v1.0.6):** **`curl`** and **Node** **`emit_sample_events.node.mjs`** under **[examples/integration/](examples/integration/README.md)**; **`flightdeck doctor --backup`**; deploy README covers healthcheck + backup scheduling. -**Still open in Phase 0** (see gaps table and Phase 1): **catalog-level** multi-provider normalization (single comparable unit across vendors), deeper **event pipeline** and **fleet** ergonomics, and **OTLP-oriented** telemetry remain beyond this patch. +### Phase 0 status + +**Phase 0 is closed** as of **v1.0.6** for the local-first wedge (immutable releases, evidence ingest, diff + policy gate, promote/rollback, audit, CI/deploy/integration references, metrics, diagnostics, and operator backup ergonomics). + +**Carried forward to Phase 1** (see gaps table): **catalog-level** multi-provider pricing normalization (single comparable unit across vendors), deeper **fleet** ergonomics, and **OTLP-oriented** telemetry — not blocking further patch releases on the Phase 0 spine. ### Phase-0 success signals @@ -83,7 +89,8 @@ Goal: move from solid local tooling to repeatable production usage patterns. ### Build in this phase - Human-in-the-loop approval workflow on top of policy gates (without requiring a hosted control plane). -- Stronger multi-provider pricing normalization and clearer mismatch diagnostics. +- **Catalog-level multi-provider pricing normalization** — single comparable tariff unit across vendors; additive to today's per-provider **`pricing import`** tables and **`pricing.prices`** / **`pricing.warnings`** diagnostics. +- Stronger mismatch diagnostics beyond table row presence (for example version skew hints) as needed for the catalog work. - Incident forensics improvements (replay/trace-style analysis over ingested evidence) as governance support tooling. - Deployment hardening artifacts (for example Helm or equivalent) if a blessed server topology is chosen. - Multi-workspace operator ergonomics (naming, templates, reproducible setup patterns). diff --git a/docs/cli.md b/docs/cli.md index 6f2deb7..2de7a59 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -71,10 +71,14 @@ in a shared repo. See [release-artifact.md § Workspace config](release-artifact Run read-only health checks on the local SQLite ledger. ```bash -flightdeck doctor +flightdeck doctor [--backup PATH] ``` -No flags. Calls `Storage.migrate()` at start (idempotent) and then checks: +Calls `Storage.migrate()` at start (idempotent). With **`--backup PATH`**, runs an SQLite +online backup of the workspace database to **`PATH`** (parent directories are created; +an existing file is overwritten), then runs the checks below. + +Without **`--backup`**, only the checks run. In both cases **`migrate()`** runs first. | Check | What it verifies | |-------|-----------------| @@ -216,11 +220,16 @@ flightdeck release diff BASELINE_ID CANDIDATE_ID --window WINDOW [OPTIONS] | `--tenant` | Filter events by `tenant_id` | | `--task` | Filter events by `task_id` | | `--fail-on-policy` | After printing the diff, exit **1** when the active policy does not pass (for CI gates). | +| `--output` | `text` (default) or `json`. **`json`**: same JSON object as **`POST /v1/diff`** (stable keys for `jq` / CI parsers). With **`--fail-on-policy`**, JSON is still printed to stdout before exit **1**. | Both releases must have the same `agent_id`. Cross-agent diffs are rejected with exit 1. **Exit codes:** invalid input, missing pricing, or other `OperationError` → non-zero. With **`--fail-on-policy`**, a computed diff whose policy result is **FAIL** also exits **1** (after the usual stdout). +When a release's resolved model has **no row** in its pricing table, the diff still completes +(if rollups do not need that rate for ingested events), and the CLI prints **`WARNING:`** lines +and JSON includes **`pricing.warnings`** — diagnostic only; policy is unchanged. + The diff is a **read-only computation** — it does not write to the audit ledger or update any promoted pointers. diff --git a/docs/http-api.md b/docs/http-api.md index c05b438..77ba6d8 100644 --- a/docs/http-api.md +++ b/docs/http-api.md @@ -330,7 +330,8 @@ the audit ledger. "candidate_input_usd_per_1k_tokens": 0.0045, "candidate_output_usd_per_1k_tokens": 0.0135, "candidate_cached_input_usd_per_1k_tokens": null - } + }, + "warnings": [] }, "samples": { "baseline_runs": 1200, @@ -358,6 +359,12 @@ the audit ledger. } ``` +**`pricing.warnings`** — array of human-readable strings when the baseline or candidate +release's **`spec.runtime.model`** has no matching entry in that side's imported pricing +table. Per-side **`prices.*`** fields are **`null`** in that case. Warnings are **informational +only** and do not change **`policy`**. If ingested run events reference a model that cannot +be priced, the diff request still fails with HTTP 400 as before. + **Confidence levels** | Label | Meaning | diff --git a/docs/operations-and-policy.md b/docs/operations-and-policy.md index eda5583..e4a7ffc 100644 --- a/docs/operations-and-policy.md +++ b/docs/operations-and-policy.md @@ -168,6 +168,11 @@ following differ between baseline and candidate: These fields are populated by `pricing_entry_for(table, model)` in `flightdeck.ledger` after `diff_releases` returns and before the `DiffOutcome` is constructed. +`DiffOutcome.pricing_warnings` is a tuple of human-readable strings when the release artifact's +`spec.runtime.model` has **no** matching row in that side's imported pricing table. Warnings are +**diagnostic only** (they do not change `policy`). If ingested events reference a model that +cannot be priced, `compute_rollup` still raises and `compute_diff` surfaces that as before. + **CLI output** — when `pricing_or_model_changed` is `True`, the CLI prints: ``` @@ -178,8 +183,12 @@ Per-1k token prices: input 0.005000 -> 0.004500, output 0.015000 -> 0.013500 The **Per-1k token prices** line is only printed when both input and output rates are present for both sides. If any rate is `None`, that line is omitted. +When `pricing_warnings` is non-empty, the CLI also prints one **`WARNING:`** line per string +before the `NOTE:` / per-1k lines. + **HTTP API** — `/v1/diff` includes a `pricing.prices` object alongside the existing -`pricing_or_model_changed` flag: +`pricing_or_model_changed` flag and a `pricing.warnings` string array (empty when both models +resolve to a table row): ```json "pricing": { @@ -197,14 +206,16 @@ for both sides. If any rate is `None`, that line is omitted. "candidate_input_usd_per_1k_tokens": 0.0045, "candidate_output_usd_per_1k_tokens": 0.0135, "candidate_cached_input_usd_per_1k_tokens": null - } + }, + "warnings": [] } ``` `pricing.prices` is always present in the response (not gated on `pricing_or_model_changed`). Fields are `null` when the rate is not set in the pricing table. -**Web UI** — the `DiffPage` `fd-alert--warn` banner shows the per-1k input/output price deltas +**Web UI** — the `DiffPage` shows `pricing.warnings` as a warning list when non-empty, then the +`fd-alert--warn` banner for `pricing_or_model_changed` when applicable, and the per-1k input/output price deltas (baseline → candidate) when all four rates are present. See [web-ui.md § DiffPage](web-ui.md). This is an informational signal — the diff still computes and the policy still evaluates; cost diff --git a/docs/web-ui.md b/docs/web-ui.md index bfbfdb0..7501fe0 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -16,7 +16,7 @@ The app uses **HashRouter** (`react-router-dom`) so all navigation stays within | Hash path | Component | HTTP calls | Notes | |-----------|-----------|-----------|-------| -| `#/` | `OverviewPage` | `GET /v1/releases`, `GET /v1/promoted`, `GET /v1/actions` (parallel) | | +| `#/` | `OverviewPage` | `GET /v1/releases`, `GET /v1/promoted`, `GET /v1/actions`, `GET /v1/metrics` (parallel where applicable) | Ledger metrics card is read-only counters | | `#/diff` | `DiffPage` | `POST /v1/diff` | | | `#/actions` | `ActionsPage` | `POST /v1/promote` or `POST /v1/rollback` | Redirects to `#/` when `VITE_FLIGHTDECK_UI_READ_ONLY=true` | | `#/*` (any other) | — | Redirects to `#/` | | @@ -121,10 +121,11 @@ fail. This is a configuration hint only — the server enforces the actual gate. ## `OverviewPage` (`web/src/pages/OverviewPage.tsx`) -Read-only dashboard. Renders three tables from `loadTimeline()` output: +Read-only dashboard. Renders a **Ledger metrics** card from `fetchMetrics()` plus three tables from `loadTimeline()` output: -| Table | Source | Columns | +| Block | Source | Content | |-------|--------|---------| +| Ledger metrics | `GET /v1/metrics` | Releases, pricing tables, run events, promoted pointers, and actions totals (plus `actions_by_action` breakdown), `schema_version`, `generated_at` | | Releases | `GET /v1/releases` | Release ID, Agent, Version, Environment, Checksum, Created | | Promoted | `GET /v1/promoted` | Agent, Environment, Active release | | Recent actions | `GET /v1/actions` | When, Action, Policy (PASS/FAIL badge), Release, Environment, Reason | @@ -153,6 +154,8 @@ On submit, the raw diff response is parsed and rendered as: - **Summary card:** policy badge (PASS / FAIL), failure reasons list, sample counts and confidence label (including `confidence_reason` when present). +- **Pricing table warnings:** when `pricing.warnings` is a non-empty string array, a + `fd-alert--warn` list is shown above the pricing/model-change banner (diagnostic only). - **Pricing change warning:** when the diff response includes a `pricing` block with `pricing_or_model_changed: true`, a `fd-alert--warn` banner is shown in the summary card. It names the baseline and candidate provider/version/model so the user knows the diff --git a/examples/ci/README.md b/examples/ci/README.md index 745c0c4..c53cd18 100644 --- a/examples/ci/README.md +++ b/examples/ci/README.md @@ -37,7 +37,7 @@ uv run python examples/ci/ledger_gate.py Example (**PyPI** install): ```bash -pip install "flightdeck-ai>=1.0.4" +pip install "flightdeck-ai>=1.0.6" export WORKSPACE="$(mktemp -d)" export QUICKSTART_ROOT=/path/to/flightdeck/examples/quickstart python /path/to/flightdeck/examples/ci/ledger_gate.py diff --git a/examples/ci/github-actions/policy-gate-pypi.yml b/examples/ci/github-actions/policy-gate-pypi.yml index 2dcb2a1..5a43c1b 100644 --- a/examples/ci/github-actions/policy-gate-pypi.yml +++ b/examples/ci/github-actions/policy-gate-pypi.yml @@ -11,7 +11,7 @@ on: env: # Pin to a tag or SHA that matches your installed flightdeck-ai version when possible. FLIGHTDECK_REF: main - FLIGHTDECK_AI_SPEC: ">=1.0.4" + FLIGHTDECK_AI_SPEC: ">=1.0.6" jobs: ledger-gate: diff --git a/examples/deploy/Dockerfile b/examples/deploy/Dockerfile index e98f49a..e193bdd 100644 --- a/examples/deploy/Dockerfile +++ b/examples/deploy/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.14-slim RUN pip install --no-cache-dir --upgrade pip \ - && pip install --no-cache-dir "flightdeck-ai>=1.0.4" + && pip install --no-cache-dir "flightdeck-ai>=1.0.6" WORKDIR /workspace diff --git a/examples/deploy/README.md b/examples/deploy/README.md index 0878277..6544095 100644 --- a/examples/deploy/README.md +++ b/examples/deploy/README.md @@ -23,8 +23,19 @@ docker compose up --build - **UI + API:** `http://127.0.0.1:8765/` (static UI + `/v1/*`). - **Health:** `GET http://127.0.0.1:8765/health`. +- **Compose healthcheck:** `docker-compose.yml` probes **`/health`** so orchestrators can mark the service ready (see `healthcheck:` in that file). - **Data:** named Docker volume **`fd_workspace`** (SQLite under **`.flightdeck/`** inside the volume). Remove with `docker compose down -v` when you want a clean ledger. +### SQLite backups + +FlightDeck stores the ledger in **`.flightdeck/flightdeck.db`** under the workspace root. For a **hot copy** while the server is stopped or idle, run from the workspace directory: + +```bash +flightdeck doctor --backup ./backups/flightdeck-$(date -u +%Y%m%dT%H%M%SZ).db +``` + +Inside the Compose stack, **`exec`** into the running container with **`/workspace`** as cwd (same layout as local **`flightdeck init`**), or run a one-shot sidecar that mounts the same volume and invokes **`flightdeck doctor --backup /workspace/backups/snapshot.db`**. Schedule with **cron** or your platform scheduler; keep backups off the primary volume when possible. + ### Optional mutation token Set **`FLIGHTDECK_LOCAL_API_TOKEN`** in your environment before `docker compose up` (or in an `.env` file beside `docker-compose.yml`). Clients must send **`Authorization: Bearer …`** for **`POST /v1/promote`** and **`POST /v1/rollback`**. Ingest and diff are **not** behind this Bearer gate by default—treat network placement accordingly. @@ -42,7 +53,7 @@ Use an absolute path on Linux/macOS; on Windows Docker Desktop, use a path Docke ## Process supervision -Compose provides restart policies; for systemd/Kubernetes, reuse the same image and run **`/entrypoint.sh`** (or invoke **`flightdeck serve`** directly with a prepared workspace directory). +Compose sets a **`healthcheck`** on **`/health`** plus restart policies; for systemd/Kubernetes, reuse the same image and run **`/entrypoint.sh`** (or invoke **`flightdeck serve`** directly with a prepared workspace directory). ## Related diff --git a/examples/integration/README.md b/examples/integration/README.md index e96115a..fef3160 100644 --- a/examples/integration/README.md +++ b/examples/integration/README.md @@ -31,6 +31,47 @@ python examples/integration/emit_sample_events.py --release-id rel_abc123 --agen The HTTP body is `{"events": [, ...]}` with **`api_version`: `"v1"`**. Field reference: **[docs/http-api.md](../../docs/http-api.md)** and **`schemas/v1/run_event.schema.json`**. +### `curl` (no SDK) + +Replace **`REL_ID`**, **`AGENT`**, and optionally **`BASE`** (default `http://127.0.0.1:8765`): + +```bash +BASE=http://127.0.0.1:8765 +REL_ID=rel_yourregisteredid +AGENT=agent_support +RUN_ID=emit-curl-$(date +%s) +TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + +curl -sS -X POST "$BASE/v1/events" \ + -H 'Content-Type: application/json' \ + -d "$(jq -n \ + --arg rid "$REL_ID" \ + --arg aid "$AGENT" \ + --arg run "$RUN_ID" \ + --arg ts "$TS" \ + '{events:[{ + api_version:"v1", type:"run_end", timestamp:$ts, + workspace_id:"ws_local", agent_id:$aid, release_id:$rid, run_id:$run, + tenant_id:"tenant_example", task_id:"task_example", environment:"local", + metrics:{latency_ms:250, success:true, error_type:null}, + usage:{model:{provider:"openai", model:"gpt-4.1-mini", input_tokens:400, output_tokens:120, cached_input_tokens:0}, tools:[]}, + labels:{source:"curl-example"} + }]}')" +``` + +Without **`jq`**, paste a static JSON file or use the Node script below. + +### Node (`emit_sample_events.node.mjs`) + +Uses built-in **`fetch`** (Node **18+**). No npm dependencies: + +```bash +node examples/integration/emit_sample_events.node.mjs \ + --base-url http://127.0.0.1:8765 \ + --release-id rel_yourregisteredid \ + --agent-id agent_support +``` + ### Trust boundaries Anyone who can reach **`POST /v1/events`** can append ledger rows for a `release_id` they guess. Keep **`serve`** on loopback or a private network, or front it with your own controls. See **[SECURITY.md](../../SECURITY.md)**. diff --git a/pyproject.toml b/pyproject.toml index 1b07eee..e21eda7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "flightdeck-ai" -version = "1.0.4" +version = "1.0.6" description = "AI Release Governance for production agents." readme = "README.md" license = "Apache-2.0" diff --git a/src/flightdeck/__init__.py b/src/flightdeck/__init__.py index 5abc00e..f8979f4 100644 --- a/src/flightdeck/__init__.py +++ b/src/flightdeck/__init__.py @@ -1,3 +1,3 @@ """FlightDeck - AI Release Governance for production agents.""" -__version__ = "1.0.4" +__version__ = "1.0.6" diff --git a/src/flightdeck/cli/main.py b/src/flightdeck/cli/main.py index bad85a6..073b812 100644 --- a/src/flightdeck/cli/main.py +++ b/src/flightdeck/cli/main.py @@ -79,10 +79,20 @@ def init(path_: str) -> None: @cli.command("doctor") -def doctor_cmd() -> None: +@click.option( + "--backup", + "backup_path", + type=click.Path(path_type=Path, dir_okay=False), + default=None, + help="Copy the workspace SQLite database to PATH (online backup), then run checks.", +) +def doctor_cmd(backup_path: Path | None) -> None: """Run read-only health checks on the local ledger (migrations, promoted pointers).""" cfg = load_config() storage = Storage(cfg.db_path) + if backup_path is not None: + storage.backup_to(backup_path) + click.echo(f"Backed up database to {backup_path}") checks = run_doctor(storage) failed = False for c in checks: @@ -327,6 +337,14 @@ def runs_ingest(path: Path) -> None: default=False, help="Exit with code 1 when the active policy does not pass (after printing the diff).", ) +@click.option( + "--output", + "output_format", + type=click.Choice(["text", "json"]), + default="text", + show_default=True, + help="Output format. 'json' emits the same shape as POST /v1/diff for machine consumers.", +) def release_diff( baseline_release_id: str, candidate_release_id: str, @@ -335,6 +353,7 @@ def release_diff( task_id: str | None, environment: str | None, fail_on_policy: bool, + output_format: str, ) -> None: """Compare two releases over a time window and print a confidence-labeled safety diff.""" cfg = load_config() @@ -354,6 +373,59 @@ def release_diff( except OperationError as e: raise click.ClickException(str(e)) from e + if output_format == "json": + body = { + "window": result.window, + "since": result.since.isoformat(), + "until": result.until.isoformat(), + "filters": { + "environment": result.environment, + "tenant_id": result.tenant_id, + "task_id": result.task_id, + }, + "pricing": { + "baseline_provider": result.baseline_pricing_provider, + "baseline_version": result.baseline_pricing_version, + "baseline_model": result.baseline_model, + "candidate_provider": result.candidate_pricing_provider, + "candidate_version": result.candidate_pricing_version, + "candidate_model": result.candidate_model, + "pricing_or_model_changed": result.pricing_or_model_changed, + "prices": { + "baseline_input_usd_per_1k_tokens": result.baseline_input_usd_per_1k_tokens, + "baseline_output_usd_per_1k_tokens": result.baseline_output_usd_per_1k_tokens, + "baseline_cached_input_usd_per_1k_tokens": result.baseline_cached_input_usd_per_1k_tokens, + "candidate_input_usd_per_1k_tokens": result.candidate_input_usd_per_1k_tokens, + "candidate_output_usd_per_1k_tokens": result.candidate_output_usd_per_1k_tokens, + "candidate_cached_input_usd_per_1k_tokens": result.candidate_cached_input_usd_per_1k_tokens, + }, + "warnings": list(result.pricing_warnings), + }, + "samples": { + "baseline_runs": result.baseline_runs, + "candidate_runs": result.candidate_runs, + "confidence": result.confidence, + "confidence_reason": result.confidence_reason, + }, + "metrics": { + "baseline_cost_per_run_usd": result.baseline_cost_per_run_usd, + "candidate_cost_per_run_usd": result.candidate_cost_per_run_usd, + "delta_cost_per_run_usd": result.delta_cost_per_run_usd, + "delta_cost_per_run_pct": result.delta_cost_per_run_pct, + "baseline_latency_ms_avg": result.baseline_latency_ms_avg, + "candidate_latency_ms_avg": result.candidate_latency_ms_avg, + "delta_latency_ms_avg": result.delta_latency_ms_avg, + "baseline_error_rate": result.baseline_error_rate, + "candidate_error_rate": result.candidate_error_rate, + "delta_error_rate": result.delta_error_rate, + }, + "policy": result.policy.model_dump(mode="json"), + } + click.echo(json.dumps(body, indent=2, sort_keys=True)) + if fail_on_policy and not result.policy.passed: + raise click.ClickException("Policy gate: diff blocked by active policy (see policy.reasons in JSON output).") + return + click.echo(f"Window: {window} ({result.since.isoformat()} .. {result.until.isoformat()})") click.echo(f"Filters: env={result.environment} tenant={tenant_id or '*'} task={task_id or '*'}") click.echo( @@ -364,6 +436,8 @@ def release_diff( f"Candidate pricing: {result.candidate_pricing_provider}/{result.candidate_pricing_version} " f"(model={result.candidate_model})" ) + for w in result.pricing_warnings: + click.echo(f"WARNING: {w}") if result.pricing_or_model_changed: click.echo("NOTE: cost delta includes pricing/model assumption changes (pricing reference and/or model differ).") if ( diff --git a/src/flightdeck/operations.py b/src/flightdeck/operations.py index 274c5d7..0bc7efd 100644 --- a/src/flightdeck/operations.py +++ b/src/flightdeck/operations.py @@ -58,6 +58,7 @@ class DiffOutcome: candidate_error_rate: float delta_error_rate: float policy: PolicyResult + pricing_warnings: tuple[str, ...] @dataclass(frozen=True) @@ -193,6 +194,18 @@ def compute_diff( base_entry = pricing_entry_for(base_table, base_artifact.spec.runtime.model) cand_entry = pricing_entry_for(cand_table, cand_artifact.spec.runtime.model) + pricing_warnings: list[str] = [] + if base_entry is None: + pricing_warnings.append( + f"baseline pricing table {base_ref.provider}/{base_ref.pricing_version} " + f"has no entry for model {base_artifact.spec.runtime.model!r}" + ) + if cand_entry is None: + pricing_warnings.append( + f"candidate pricing table {cand_ref.provider}/{cand_ref.pricing_version} " + f"has no entry for model {cand_artifact.spec.runtime.model!r}" + ) + return DiffOutcome( window=window, since=since, @@ -236,6 +249,7 @@ def compute_diff( candidate_error_rate=diff.candidate.error_rate, delta_error_rate=diff.delta_error_rate, policy=diff.policy, + pricing_warnings=tuple(pricing_warnings), ) diff --git a/src/flightdeck/server/routes/actions.py b/src/flightdeck/server/routes/actions.py index 28020be..f2632ae 100644 --- a/src/flightdeck/server/routes/actions.py +++ b/src/flightdeck/server/routes/actions.py @@ -117,6 +117,11 @@ def post_diff(request: Request, req: DiffRequest) -> dict[str, object]: "candidate_output_usd_per_1k_tokens": result.candidate_output_usd_per_1k_tokens, "candidate_cached_input_usd_per_1k_tokens": result.candidate_cached_input_usd_per_1k_tokens, }, + # Diagnostic strings when a release's resolved model has no entry + # in its pricing table; the cost rollup still raises if such a + # model appears in events. These are informational only and do + # not flip policy. + "warnings": list(result.pricing_warnings), }, "samples": { "baseline_runs": result.baseline_runs, diff --git a/src/flightdeck/storage.py b/src/flightdeck/storage.py index 6dc7dcf..7f36282 100644 --- a/src/flightdeck/storage.py +++ b/src/flightdeck/storage.py @@ -204,6 +204,28 @@ def apply(version: int, statements: list[str]) -> None: conn.execute("INSERT INTO schema_migrations (version) VALUES (?)", (3,)) applied.add(3) + def backup_to(self, dest: Path) -> None: + """Copy the workspace SQLite file to ``dest`` using SQLite's online backup API. + + Creates parent directories. Overwrites ``dest`` if it already exists. + ``dest`` must not be the same path as :attr:`db_path`. + """ + dest_path = dest.expanduser().resolve() + dest_path.parent.mkdir(parents=True, exist_ok=True) + source_path = Path(self.db_path).expanduser().resolve() + if dest_path == source_path: + msg = "backup destination must not be the same path as the workspace database" + raise ValueError(msg) + ensure_parent_dir(self.db_path) + self.migrate() + with self.connect() as src: + dst = sqlite3.connect(str(dest_path)) + try: + src.backup(dst) + dst.commit() + finally: + dst.close() + def list_applied_migrations(self) -> list[int]: """Return applied schema migration versions (requires tables to exist; call `migrate()` first).""" with self.connect() as conn: diff --git a/tests/test_cli.py b/tests/test_cli.py index 78cdf52..62eafbe 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,3 +1,6 @@ +import sqlite3 + +import pytest from click.testing import CliRunner from flightdeck import __version__ @@ -17,3 +20,22 @@ def test_cli_version() -> None: assert result.exit_code == 0 assert __version__ in result.output + + +def test_doctor_backup_writes_valid_sqlite(tmp_path, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.chdir(tmp_path) + runner = CliRunner() + assert runner.invoke(cli, ["init"]).exit_code == 0 + dest = tmp_path / "snap" / "ledger.db" + res = runner.invoke(cli, ["doctor", "--backup", str(dest)]) + assert res.exit_code == 0 + assert dest.is_file() + assert "Backed up database" in res.output + con = sqlite3.connect(str(dest)) + try: + row = con.execute( + "SELECT name FROM sqlite_master WHERE type='table' AND name='releases' LIMIT 1" + ).fetchone() + assert row is not None + finally: + con.close() diff --git a/tests/test_cli_contract.py b/tests/test_cli_contract.py index fc6922c..2b76cb4 100644 --- a/tests/test_cli_contract.py +++ b/tests/test_cli_contract.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json from datetime import datetime, timezone from pathlib import Path @@ -77,6 +78,40 @@ def test_release_diff_fail_on_policy_exits_1(tmp_path: Path, monkeypatch) -> Non assert res_gate.exit_code != 0 assert "Policy gate: diff blocked by active policy" in res_gate.output + res_json_gate = runner.invoke( + cli, + [ + "release", + "diff", + baseline_id, + candidate_id, + "--window", + "7d", + "--output", + "json", + "--fail-on-policy", + ], + ) + assert res_json_gate.exit_code != 0 + out = res_json_gate.output + start = out.find("{") + assert start != -1 + depth = 0 + end = -1 + for i in range(start, len(out)): + c = out[i] + if c == "{": + depth += 1 + elif c == "}": + depth -= 1 + if depth == 0: + end = i + 1 + break + assert end != -1 + body = json.loads(out[start:end]) + assert body["policy"]["passed"] is False + assert "Policy gate: diff blocked by active policy" in out + def test_release_diff_contract_invalid_window_is_nonzero(tmp_path: Path, monkeypatch) -> None: monkeypatch.chdir(tmp_path) diff --git a/tests/test_server_actions.py b/tests/test_server_actions.py index 25e9a17..7bd4a66 100644 --- a/tests/test_server_actions.py +++ b/tests/test_server_actions.py @@ -93,6 +93,60 @@ def test_http_routes_expose_read_and_diff(tmp_path: Path) -> None: body = diff_resp.json() assert body["samples"]["baseline_runs"] == 5 assert body["samples"]["candidate_runs"] == 5 + assert body["pricing"]["warnings"] == [] + + +def test_http_v1_diff_pricing_warnings_when_model_missing(tmp_path: Path) -> None: + ws = tmp_path / "ws_warn" + ws.mkdir(parents=True, exist_ok=True) + runner = CliRunner() + with _cwd(ws): + assert runner.invoke(cli, ["init"]).exit_code == 0 + policy = write_policy( + ws, + max_cost_per_run_usd=10.0, + min_candidate_runs=0, + min_baseline_runs=0, + min_low_runs=0, + require_high_diff_confidence=False, + ) + assert runner.invoke(cli, ["policy", "set", str(policy)]).exit_code == 0 + pricing = write_pricing(ws, provider="openai", pricing_version="openai-2026-04-30", model="gpt-4.1-mini") + assert runner.invoke(cli, ["pricing", "import", str(pricing)]).exit_code == 0 + b_dir = write_release( + ws, + agent_id="agent_warn", + version="1", + pricing_provider="openai", + pricing_version="openai-2026-04-30", + model="unlisted-model-x", + ) + c_dir = write_release( + ws, + agent_id="agent_warn", + version="2", + pricing_provider="openai", + pricing_version="openai-2026-04-30", + model="unlisted-model-x", + ) + baseline_id = runner.invoke(cli, ["release", "register", str(b_dir)]).output.strip() + candidate_id = runner.invoke(cli, ["release", "register", str(c_dir)]).output.strip() + + with TestClient(create_app()) as client: + diff_resp = client.post( + "/v1/diff", + json={ + "baseline_release_id": baseline_id, + "candidate_release_id": candidate_id, + "window": "7d", + "environment": "local", + }, + ) + assert diff_resp.status_code == 200 + body = diff_resp.json() + w = body["pricing"]["warnings"] + assert len(w) == 2 + assert all("unlisted-model-x" in s for s in w) def test_http_promote_parity_with_cli_outcome(tmp_path: Path) -> None: diff --git a/tests/test_spine.py b/tests/test_spine.py index 2b6a1da..0108bdc 100644 --- a/tests/test_spine.py +++ b/tests/test_spine.py @@ -112,11 +112,20 @@ def write_policy( *, max_cost_per_run_usd: float | None = None, require_high_diff_confidence: bool = False, + min_candidate_runs: int | None = None, + min_baseline_runs: int | None = None, + min_low_runs: int | None = None, ) -> Path: p = tmp_path / "policy.yaml" data: dict[str, object] = {"policy_id": "test-policy", "require_high_diff_confidence": require_high_diff_confidence} if max_cost_per_run_usd is not None: data["max_cost_per_run_usd"] = max_cost_per_run_usd + if min_candidate_runs is not None: + data["min_candidate_runs"] = min_candidate_runs + if min_baseline_runs is not None: + data["min_baseline_runs"] = min_baseline_runs + if min_low_runs is not None: + data["min_low_runs"] = min_low_runs p.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8") return p @@ -1086,3 +1095,84 @@ def test_diff_cross_model_same_provider(tmp_path: Path, monkeypatch) -> None: assert prices["candidate_input_usd_per_1k_tokens"] == 5.0 assert prices["baseline_output_usd_per_1k_tokens"] == 2.0 assert prices["candidate_output_usd_per_1k_tokens"] == 10.0 + + +def test_release_diff_output_json_shape(tmp_path: Path, monkeypatch) -> None: + monkeypatch.chdir(tmp_path) + runner = CliRunner() + assert runner.invoke(cli, ["init"]).exit_code == 0 + policy = write_policy( + tmp_path, + min_candidate_runs=0, + min_baseline_runs=0, + min_low_runs=0, + require_high_diff_confidence=False, + ) + assert runner.invoke(cli, ["policy", "set", str(policy)]).exit_code == 0 + pricing = write_pricing(tmp_path, provider="openai", pricing_version="openai-2026-04-30") + assert runner.invoke(cli, ["pricing", "import", str(pricing)]).exit_code == 0 + r1 = write_release(tmp_path, agent_id="agent_support", version="1", pricing_provider="openai", pricing_version="openai-2026-04-30") + r2 = write_release(tmp_path, agent_id="agent_support", version="2", pricing_provider="openai", pricing_version="openai-2026-04-30") + rel1 = runner.invoke(cli, ["release", "register", str(r1)]).output.strip() + rel2 = runner.invoke(cli, ["release", "register", str(r2)]).output.strip() + + res = runner.invoke( + cli, + ["release", "diff", rel1, rel2, "--window", "7d", "--output", "json"], + ) + assert res.exit_code == 0 + body = json.loads(res.output) + assert body["window"] == "7d" + assert set(body.keys()) >= {"filters", "metrics", "policy", "pricing", "samples", "since", "until"} + assert body["pricing"]["warnings"] == [] + assert body["policy"]["passed"] is True + + +def test_release_diff_pricing_warnings_when_model_not_in_table(tmp_path: Path, monkeypatch) -> None: + monkeypatch.chdir(tmp_path) + runner = CliRunner() + assert runner.invoke(cli, ["init"]).exit_code == 0 + policy = write_policy( + tmp_path, + min_candidate_runs=0, + min_baseline_runs=0, + min_low_runs=0, + require_high_diff_confidence=False, + ) + assert runner.invoke(cli, ["policy", "set", str(policy)]).exit_code == 0 + pricing = write_pricing(tmp_path, provider="openai", pricing_version="openai-2026-04-30", model="gpt-4.1-mini") + assert runner.invoke(cli, ["pricing", "import", str(pricing)]).exit_code == 0 + r1 = write_release( + tmp_path, + agent_id="agent_support", + version="1", + pricing_provider="openai", + pricing_version="openai-2026-04-30", + model="gpt-4.99-unknown", + ) + r2 = write_release( + tmp_path, + agent_id="agent_support", + version="2", + pricing_provider="openai", + pricing_version="openai-2026-04-30", + model="gpt-4.99-unknown", + ) + rel1 = runner.invoke(cli, ["release", "register", str(r1)]).output.strip() + rel2 = runner.invoke(cli, ["release", "register", str(r2)]).output.strip() + + res_text = runner.invoke(cli, ["release", "diff", rel1, rel2, "--window", "7d"]) + assert res_text.exit_code == 0 + assert "WARNING: baseline pricing table" in res_text.output + assert "WARNING: candidate pricing table" in res_text.output + assert "gpt-4.99-unknown" in res_text.output + + res_json = runner.invoke( + cli, + ["release", "diff", rel1, rel2, "--window", "7d", "--output", "json"], + ) + assert res_json.exit_code == 0 + body = json.loads(res_json.output) + w = body["pricing"]["warnings"] + assert len(w) == 2 + assert all("gpt-4.99-unknown" in x for x in w) diff --git a/web/e2e/smoke.spec.ts b/web/e2e/smoke.spec.ts index 4f29c31..1bcd6e4 100644 --- a/web/e2e/smoke.spec.ts +++ b/web/e2e/smoke.spec.ts @@ -6,6 +6,7 @@ test("home loads FlightDeck shell and overview tables", async ({ page }) => { await expect(page.getByRole("navigation", { name: "Primary" })).toBeVisible(); await expect(page.getByRole("link", { name: "Overview" })).toBeVisible(); await expect(page.getByRole("heading", { name: "Overview", level: 2 })).toBeVisible(); + await expect(page.getByRole("region", { name: "Ledger metrics" })).toBeVisible({ timeout: 30_000 }); await expect(page.getByRole("columnheader", { name: "Release ID" })).toBeVisible({ timeout: 30_000 }); await expect(page.getByText("No releases yet.")).toBeVisible(); }); diff --git a/web/src/api.ts b/web/src/api.ts index 4fb1160..cbd719f 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -39,6 +39,24 @@ export type HealthPayload = { mutation_auth?: "bearer" | "loopback"; }; +/** + * Response shape for `GET /v1/metrics`. Mirrors `routes/metrics.py` and + * `Storage.get_ledger_counters()`. Counters are non-negative integers; the + * `actions_by_action` map is keyed by action name (e.g. `promote`, `rollback`). + */ +export type MetricsPayload = { + counters: { + releases_total: number; + pricing_tables_total: number; + run_events_total: number; + promoted_pointers_total: number; + actions_total: number; + actions_by_action: Record; + }; + schema_version: number; + generated_at: string; +}; + export type PolicyResultPayload = { passed: boolean; reasons: string[]; @@ -85,6 +103,10 @@ export async function fetchHealth(): Promise { return fetchJson("/health"); } +export async function fetchMetrics(): Promise { + return fetchJson("/v1/metrics"); +} + export async function loadTimeline(): Promise { const [releases, promoted, actions] = await Promise.all([ fetchJson<{ releases: ReleaseRow[] }>("/v1/releases"), diff --git a/web/src/pages/DiffPage.tsx b/web/src/pages/DiffPage.tsx index d683705..4b05106 100644 --- a/web/src/pages/DiffPage.tsx +++ b/web/src/pages/DiffPage.tsx @@ -29,6 +29,7 @@ type PricingInfo = { candidateModel: string; changed: boolean; prices: PricingPrices | null; + warnings: string[]; }; type PricingPrices = { @@ -59,6 +60,10 @@ function pickPricing(data: DiffJson): PricingInfo | null { const p = data.pricing; if (!isRecord(p)) return null; const get = (k: string): string => (typeof p[k] === "string" ? (p[k] as string) : ""); + const rawWarnings = p.warnings; + const warnings = Array.isArray(rawWarnings) + ? rawWarnings.filter((x): x is string => typeof x === "string") + : []; return { baselineProvider: get("baseline_provider"), baselineVersion: get("baseline_version"), @@ -68,6 +73,7 @@ function pickPricing(data: DiffJson): PricingInfo | null { candidateModel: get("candidate_model"), changed: p.pricing_or_model_changed === true, prices: pickPrices(p), + warnings, }; } @@ -225,6 +231,13 @@ export function DiffPage() { {typeof samples.confidence_reason === "string" ? ` — ${samples.confidence_reason}` : null}

) : null} + {pricing && pricing.warnings.length > 0 ? ( +
    + {pricing.warnings.map((w) => ( +
  • Pricing warning: {w}
  • + ))} +
+ ) : null} {pricing && pricing.changed ? (

Pricing/model changed:{" "} diff --git a/web/src/pages/OverviewPage.tsx b/web/src/pages/OverviewPage.tsx index 581bd20..d4fbbf0 100644 --- a/web/src/pages/OverviewPage.tsx +++ b/web/src/pages/OverviewPage.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useId, useState, type ReactNode } from "react"; -import type { ActionRow, PromotedRow, ReleaseRow, TimelinePayload } from "../api"; -import { loadTimeline } from "../api"; +import type { ActionRow, MetricsPayload, PromotedRow, ReleaseRow, TimelinePayload } from "../api"; +import { fetchMetrics, loadTimeline } from "../api"; import { useTimelineRefresh } from "../context/TimelineRefreshContext"; import { Badge } from "../components/Badge"; import { JsonPanel } from "../components/JsonPanel"; @@ -36,17 +36,29 @@ function TableShell({ export function OverviewPage() { const { generation } = useTimelineRefresh(); const [data, setData] = useState(null); + const [metrics, setMetrics] = useState(null); + const [metricsError, setMetricsError] = useState(null); const [error, setError] = useState(null); const [loading, setLoading] = useState(true); const refresh = useCallback(async () => { setLoading(true); setError(null); + setMetricsError(null); try { - setData(await loadTimeline()); - } catch (e) { - setError(String(e)); - setData(null); + const [timeline, m] = await Promise.allSettled([loadTimeline(), fetchMetrics()]); + if (timeline.status === "fulfilled") { + setData(timeline.value); + } else { + setError(String(timeline.reason)); + setData(null); + } + if (m.status === "fulfilled") { + setMetrics(m.value); + } else { + setMetricsError(String(m.reason)); + setMetrics(null); + } } finally { setLoading(false); } @@ -80,6 +92,62 @@ export function OverviewPage() { {error && !loading ?

{error}

: null} {loading ?

Loading…

: null} + {metrics ? ( +
+
+

Ledger metrics

+

+ Read-only counters from{" "} + GET /v1/metrics (schema v + {metrics.schema_version}, generated{" "} + {new Date(metrics.generated_at).toLocaleString()}). +

+
+
+
+
Releases
+
+ {metrics.counters.releases_total} +
+
+
+
Pricing tables
+
+ {metrics.counters.pricing_tables_total} +
+
+
+
Run events
+
+ {metrics.counters.run_events_total} +
+
+
+
Promoted pointers
+
+ {metrics.counters.promoted_pointers_total} +
+
+
+
Actions
+
+ {metrics.counters.actions_total} +
+ {Object.keys(metrics.counters.actions_by_action).length > 0 ? ( +
+ {Object.entries(metrics.counters.actions_by_action) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([k, v]) => `${k}=${v}`) + .join(" · ")} +
+ ) : null} +
+
+
+ ) : metricsError && !loading ? ( +

Ledger metrics unavailable: {metricsError}

+ ) : null} + {data ? ( <> From 8f5880b380ca8bd2890da4d4ca231c7d5a41ac93 Mon Sep 17 00:00:00 2001 From: zendaya Date: Sat, 2 May 2026 20:32:16 +0200 Subject: [PATCH 2/2] Remove obsolete JavaScript asset file `index-FUgPH5k2.js` from the static assets directory. --- .../integration/emit_sample_events.node.mjs | 70 +++++++++++++++++++ .../server/static/assets/index-92annNc3.js | 11 +++ .../server/static/assets/index-FUgPH5k2.js | 11 --- src/flightdeck/server/static/index.html | 2 +- uv.lock | 2 +- 5 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 examples/integration/emit_sample_events.node.mjs create mode 100644 src/flightdeck/server/static/assets/index-92annNc3.js delete mode 100644 src/flightdeck/server/static/assets/index-FUgPH5k2.js diff --git a/examples/integration/emit_sample_events.node.mjs b/examples/integration/emit_sample_events.node.mjs new file mode 100644 index 0000000..3736fb6 --- /dev/null +++ b/examples/integration/emit_sample_events.node.mjs @@ -0,0 +1,70 @@ +/** + * Minimal POST /v1/events sample (Node 18+ with global fetch, or Node 20+). + * + * node examples/integration/emit_sample_events.node.mjs \ + * --base-url http://127.0.0.1:8765 \ + * --release-id rel_yourregisteredid \ + * --agent-id agent_support + * + * Same envelope as emit_sample_events.py: { "events": [ RunEvent, ... ] }. + */ +function arg(name, fallback = null) { + const i = process.argv.indexOf(name); + if (i === -1 || i + 1 >= process.argv.length) return fallback; + return process.argv[i + 1]; +} + +const baseUrl = (arg("--base-url", "http://127.0.0.1:8765") ?? "http://127.0.0.1:8765").replace(/\/$/, ""); +const releaseId = arg("--release-id"); +const agentId = arg("--agent-id"); +const environment = arg("--environment", "local") ?? "local"; + +if (!releaseId || !agentId) { + console.error("Usage: node emit_sample_events.node.mjs --release-id REL --agent-id AGENT [--base-url URL] [--environment ENV]"); + process.exit(2); +} + +const rid = Math.random().toString(16).slice(2, 12); +const ts = new Date().toISOString(); + +const event = { + api_version: "v1", + type: "run_end", + timestamp: ts, + workspace_id: "ws_local", + agent_id: agentId, + release_id: releaseId, + run_id: `emit-sample-node-${rid}`, + tenant_id: "tenant_example", + task_id: "task_example", + environment, + metrics: { latency_ms: 250, success: true, error_type: null }, + usage: { + model: { + provider: "openai", + model: "gpt-4.1-mini", + input_tokens: 400, + output_tokens: 120, + cached_input_tokens: 0, + }, + tools: [], + }, + labels: { source: "examples/integration/emit_sample_events.node.mjs" }, +}; + +const body = JSON.stringify({ events: [event] }); + +const url = `${baseUrl}/v1/events`; +const res = await fetch(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body, +}); + +const text = await res.text(); +if (!res.ok) { + console.error(`HTTP ${res.status}: ${text}`); + process.exit(1); +} +console.log(`POST ${url} -> ${res.status}`); +console.log(text); diff --git a/src/flightdeck/server/static/assets/index-92annNc3.js b/src/flightdeck/server/static/assets/index-92annNc3.js new file mode 100644 index 0000000..85ff6f3 --- /dev/null +++ b/src/flightdeck/server/static/assets/index-92annNc3.js @@ -0,0 +1,11 @@ +(function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))f(d);new MutationObserver(d=>{for(const h of d)if(h.type==="childList")for(const b of h.addedNodes)b.tagName==="LINK"&&b.rel==="modulepreload"&&f(b)}).observe(document,{childList:!0,subtree:!0});function o(d){const h={};return d.integrity&&(h.integrity=d.integrity),d.referrerPolicy&&(h.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?h.credentials="include":d.crossOrigin==="anonymous"?h.credentials="omit":h.credentials="same-origin",h}function f(d){if(d.ep)return;d.ep=!0;const h=o(d);fetch(d.href,h)}})();var Cf={exports:{}},Mn={};var im;function yy(){if(im)return Mn;im=1;var i=Symbol.for("react.transitional.element"),s=Symbol.for("react.fragment");function o(f,d,h){var b=null;if(h!==void 0&&(b=""+h),d.key!==void 0&&(b=""+d.key),"key"in d){h={};for(var N in d)N!=="key"&&(h[N]=d[N])}else h=d;return d=h.ref,{$$typeof:i,type:f,key:b,ref:d!==void 0?d:null,props:h}}return Mn.Fragment=s,Mn.jsx=o,Mn.jsxs=o,Mn}var cm;function py(){return cm||(cm=1,Cf.exports=yy()),Cf.exports}var m=py(),Uf={exports:{}},P={};var fm;function gy(){if(fm)return P;fm=1;var i=Symbol.for("react.transitional.element"),s=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),f=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),h=Symbol.for("react.consumer"),b=Symbol.for("react.context"),N=Symbol.for("react.forward_ref"),S=Symbol.for("react.suspense"),y=Symbol.for("react.memo"),j=Symbol.for("react.lazy"),A=Symbol.for("react.activity"),q=Symbol.iterator;function H(g){return g===null||typeof g!="object"?null:(g=q&&g[q]||g["@@iterator"],typeof g=="function"?g:null)}var Z={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},G=Object.assign,B={};function Q(g,U,Y){this.props=g,this.context=U,this.refs=B,this.updater=Y||Z}Q.prototype.isReactComponent={},Q.prototype.setState=function(g,U){if(typeof g!="object"&&typeof g!="function"&&g!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,g,U,"setState")},Q.prototype.forceUpdate=function(g){this.updater.enqueueForceUpdate(this,g,"forceUpdate")};function V(){}V.prototype=Q.prototype;function $(g,U,Y){this.props=g,this.context=U,this.refs=B,this.updater=Y||Z}var F=$.prototype=new V;F.constructor=$,G(F,Q.prototype),F.isPureReactComponent=!0;var le=Array.isArray;function be(){}var X={H:null,A:null,T:null,S:null},Ne=Object.prototype.hasOwnProperty;function Ke(g,U,Y){var K=Y.ref;return{$$typeof:i,type:g,key:U,ref:K!==void 0?K:null,props:Y}}function Ct(g,U){return Ke(g.type,U,g.props)}function vt(g){return typeof g=="object"&&g!==null&&g.$$typeof===i}function Je(g){var U={"=":"=0",":":"=2"};return"$"+g.replace(/[=:]/g,function(Y){return U[Y]})}var Ut=/\/+/g;function yt(g,U){return typeof g=="object"&&g!==null&&g.key!=null?Je(""+g.key):U.toString(36)}function Ce(g){switch(g.status){case"fulfilled":return g.value;case"rejected":throw g.reason;default:switch(typeof g.status=="string"?g.then(be,be):(g.status="pending",g.then(function(U){g.status==="pending"&&(g.status="fulfilled",g.value=U)},function(U){g.status==="pending"&&(g.status="rejected",g.reason=U)})),g.status){case"fulfilled":return g.value;case"rejected":throw g.reason}}throw g}function D(g,U,Y,K,ee){var ne=typeof g;(ne==="undefined"||ne==="boolean")&&(g=null);var me=!1;if(g===null)me=!0;else switch(ne){case"bigint":case"string":case"number":me=!0;break;case"object":switch(g.$$typeof){case i:case s:me=!0;break;case j:return me=g._init,D(me(g._payload),U,Y,K,ee)}}if(me)return ee=ee(g),me=K===""?"."+yt(g,0):K,le(ee)?(Y="",me!=null&&(Y=me.replace(Ut,"$&/")+"/"),D(ee,U,Y,"",function(La){return La})):ee!=null&&(vt(ee)&&(ee=Ct(ee,Y+(ee.key==null||g&&g.key===ee.key?"":(""+ee.key).replace(Ut,"$&/")+"/")+me)),U.push(ee)),1;me=0;var We=K===""?".":K+":";if(le(g))for(var je=0;je>>1,_e=D[ye];if(0>>1;yed(Y,I))K<_e&&0>d(ee,Y)?(D[ye]=ee,D[K]=I,ye=K):(D[ye]=Y,D[U]=I,ye=U);else if(K<_e&&0>d(ee,I))D[ye]=ee,D[K]=I,ye=K;else break e}}return L}function d(D,L){var I=D.sortIndex-L.sortIndex;return I!==0?I:D.id-L.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var h=performance;i.unstable_now=function(){return h.now()}}else{var b=Date,N=b.now();i.unstable_now=function(){return b.now()-N}}var S=[],y=[],j=1,A=null,q=3,H=!1,Z=!1,G=!1,B=!1,Q=typeof setTimeout=="function"?setTimeout:null,V=typeof clearTimeout=="function"?clearTimeout:null,$=typeof setImmediate<"u"?setImmediate:null;function F(D){for(var L=o(y);L!==null;){if(L.callback===null)f(y);else if(L.startTime<=D)f(y),L.sortIndex=L.expirationTime,s(S,L);else break;L=o(y)}}function le(D){if(G=!1,F(D),!Z)if(o(S)!==null)Z=!0,be||(be=!0,Je());else{var L=o(y);L!==null&&Ce(le,L.startTime-D)}}var be=!1,X=-1,Ne=5,Ke=-1;function Ct(){return B?!0:!(i.unstable_now()-KeD&&Ct());){var ye=A.callback;if(typeof ye=="function"){A.callback=null,q=A.priorityLevel;var _e=ye(A.expirationTime<=D);if(D=i.unstable_now(),typeof _e=="function"){A.callback=_e,F(D),L=!0;break t}A===o(S)&&f(S),F(D)}else f(S);A=o(S)}if(A!==null)L=!0;else{var g=o(y);g!==null&&Ce(le,g.startTime-D),L=!1}}break e}finally{A=null,q=I,H=!1}L=void 0}}finally{L?Je():be=!1}}}var Je;if(typeof $=="function")Je=function(){$(vt)};else if(typeof MessageChannel<"u"){var Ut=new MessageChannel,yt=Ut.port2;Ut.port1.onmessage=vt,Je=function(){yt.postMessage(null)}}else Je=function(){Q(vt,0)};function Ce(D,L){X=Q(function(){D(i.unstable_now())},L)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(D){D.callback=null},i.unstable_forceFrameRate=function(D){0>D||125ye?(D.sortIndex=I,s(y,D),o(S)===null&&D===o(y)&&(G?(V(X),X=-1):G=!0,Ce(le,I-ye))):(D.sortIndex=_e,s(S,D),Z||H||(Z=!0,be||(be=!0,Je()))),D},i.unstable_shouldYield=Ct,i.unstable_wrapCallback=function(D){var L=q;return function(){var I=q;q=L;try{return D.apply(this,arguments)}finally{q=I}}}})(qf)),qf}var om;function by(){return om||(om=1,Bf.exports=Sy()),Bf.exports}var Lf={exports:{}},$e={};var dm;function _y(){if(dm)return $e;dm=1;var i=$f();function s(S){var y="https://react.dev/errors/"+S;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(s){console.error(s)}}return i(),Lf.exports=_y(),Lf.exports}var hm;function Ty(){if(hm)return Cn;hm=1;var i=by(),s=$f(),o=Ey();function f(e){var t="https://react.dev/errors/"+e;if(1_e||(e.current=ye[_e],ye[_e]=null,_e--)}function Y(e,t){_e++,ye[_e]=e.current,e.current=t}var K=g(null),ee=g(null),ne=g(null),me=g(null);function We(e,t){switch(Y(ne,t),Y(ee,e),Y(K,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Od(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Od(t),e=jd(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}U(K),Y(K,e)}function je(){U(K),U(ee),U(ne)}function La(e){e.memoizedState!==null&&Y(me,e);var t=K.current,l=jd(t,e.type);t!==l&&(Y(ee,e),Y(K,l))}function Yn(e){ee.current===e&&(U(K),U(ee)),me.current===e&&(U(me),Rn._currentValue=I)}var hi,ns;function jl(e){if(hi===void 0)try{throw Error()}catch(l){var t=l.stack.trim().match(/\n( *(at )?)/);hi=t&&t[1]||"",ns=-1)":-1n||v[a]!==x[n]){var O=` +`+v[a].replace(" at new "," at ");return e.displayName&&O.includes("")&&(O=O.replace("",e.displayName)),O}while(1<=a&&0<=n);break}}}finally{vi=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?jl(l):""}function Jm(e,t){switch(e.tag){case 26:case 27:case 5:return jl(e.type);case 16:return jl("Lazy");case 13:return e.child!==t&&t!==null?jl("Suspense Fallback"):jl("Suspense");case 19:return jl("SuspenseList");case 0:case 15:return yi(e.type,!1);case 11:return yi(e.type.render,!1);case 1:return yi(e.type,!0);case 31:return jl("Activity");default:return""}}function us(e){try{var t="",l=null;do t+=Jm(e,l),l=e,e=e.return;while(e);return t}catch(a){return` +Error generating stack: `+a.message+` +`+a.stack}}var pi=Object.prototype.hasOwnProperty,gi=i.unstable_scheduleCallback,Si=i.unstable_cancelCallback,$m=i.unstable_shouldYield,Wm=i.unstable_requestPaint,nt=i.unstable_now,km=i.unstable_getCurrentPriorityLevel,is=i.unstable_ImmediatePriority,cs=i.unstable_UserBlockingPriority,Gn=i.unstable_NormalPriority,Fm=i.unstable_LowPriority,fs=i.unstable_IdlePriority,Im=i.log,Pm=i.unstable_setDisableYieldValue,Ya=null,ut=null;function ul(e){if(typeof Im=="function"&&Pm(e),ut&&typeof ut.setStrictMode=="function")try{ut.setStrictMode(Ya,e)}catch{}}var it=Math.clz32?Math.clz32:lh,eh=Math.log,th=Math.LN2;function lh(e){return e>>>=0,e===0?32:31-(eh(e)/th|0)|0}var Xn=256,Qn=262144,Zn=4194304;function Dl(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Vn(e,t,l){var a=e.pendingLanes;if(a===0)return 0;var n=0,u=e.suspendedLanes,c=e.pingedLanes;e=e.warmLanes;var r=a&134217727;return r!==0?(a=r&~u,a!==0?n=Dl(a):(c&=r,c!==0?n=Dl(c):l||(l=r&~e,l!==0&&(n=Dl(l))))):(r=a&~u,r!==0?n=Dl(r):c!==0?n=Dl(c):l||(l=a&~e,l!==0&&(n=Dl(l)))),n===0?0:t!==0&&t!==n&&(t&u)===0&&(u=n&-n,l=t&-t,u>=l||u===32&&(l&4194048)!==0)?t:n}function Ga(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function ah(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function ss(){var e=Zn;return Zn<<=1,(Zn&62914560)===0&&(Zn=4194304),e}function bi(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function Xa(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function nh(e,t,l,a,n,u){var c=e.pendingLanes;e.pendingLanes=l,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=l,e.entangledLanes&=l,e.errorRecoveryDisabledLanes&=l,e.shellSuspendCounter=0;var r=e.entanglements,v=e.expirationTimes,x=e.hiddenUpdates;for(l=c&~l;0"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var rh=/[\n"\\]/g;function gt(e){return e.replace(rh,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function zi(e,t,l,a,n,u,c,r){e.name="",c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"?e.type=c:e.removeAttribute("type"),t!=null?c==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+pt(t)):e.value!==""+pt(t)&&(e.value=""+pt(t)):c!=="submit"&&c!=="reset"||e.removeAttribute("value"),t!=null?Ni(e,c,pt(t)):l!=null?Ni(e,c,pt(l)):a!=null&&e.removeAttribute("value"),n==null&&u!=null&&(e.defaultChecked=!!u),n!=null&&(e.checked=n&&typeof n!="function"&&typeof n!="symbol"),r!=null&&typeof r!="function"&&typeof r!="symbol"&&typeof r!="boolean"?e.name=""+pt(r):e.removeAttribute("name")}function Es(e,t,l,a,n,u,c,r){if(u!=null&&typeof u!="function"&&typeof u!="symbol"&&typeof u!="boolean"&&(e.type=u),t!=null||l!=null){if(!(u!=="submit"&&u!=="reset"||t!=null)){Ai(e);return}l=l!=null?""+pt(l):"",t=t!=null?""+pt(t):l,r||t===e.value||(e.value=t),e.defaultValue=t}a=a??n,a=typeof a!="function"&&typeof a!="symbol"&&!!a,e.checked=r?e.checked:!!a,e.defaultChecked=!!a,c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"&&(e.name=c),Ai(e)}function Ni(e,t,l){t==="number"&&Jn(e.ownerDocument)===e||e.defaultValue===""+l||(e.defaultValue=""+l)}function la(e,t,l,a){if(e=e.options,t){t={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Mi=!1;if(Qt)try{var wa={};Object.defineProperty(wa,"passive",{get:function(){Mi=!0}}),window.addEventListener("test",wa,wa),window.removeEventListener("test",wa,wa)}catch{Mi=!1}var cl=null,Ci=null,Wn=null;function Os(){if(Wn)return Wn;var e,t=Ci,l=t.length,a,n="value"in cl?cl.value:cl.textContent,u=n.length;for(e=0;e=$a),Hs=" ",Bs=!1;function qs(e,t){switch(e){case"keyup":return Lh.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Ls(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ia=!1;function Gh(e,t){switch(e){case"compositionend":return Ls(t);case"keypress":return t.which!==32?null:(Bs=!0,Hs);case"textInput":return e=t.data,e===Hs&&Bs?null:e;default:return null}}function Xh(e,t){if(ia)return e==="compositionend"||!Li&&qs(e,t)?(e=Os(),Wn=Ci=cl=null,ia=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:l,offset:t-e};e=a}e:{for(;l;){if(l.nextSibling){l=l.nextSibling;break e}l=l.parentNode}l=void 0}l=Ks(l)}}function $s(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?$s(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ws(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Jn(e.document);t instanceof e.HTMLIFrameElement;){try{var l=typeof t.contentWindow.location.href=="string"}catch{l=!1}if(l)e=t.contentWindow;else break;t=Jn(e.document)}return t}function Xi(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var Wh=Qt&&"documentMode"in document&&11>=document.documentMode,ca=null,Qi=null,Ia=null,Zi=!1;function ks(e,t,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;Zi||ca==null||ca!==Jn(a)||(a=ca,"selectionStart"in a&&Xi(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Ia&&Fa(Ia,a)||(Ia=a,a=Qu(Qi,"onSelect"),0>=c,n-=c,Ht=1<<32-it(t)+n|l<ae?(fe=J,J=null):fe=J.sibling;var oe=z(_,J,T[ae],M);if(oe===null){J===null&&(J=fe);break}e&&J&&oe.alternate===null&&t(_,J),p=u(oe,p,ae),re===null?W=oe:re.sibling=oe,re=oe,J=fe}if(ae===T.length)return l(_,J),se&&Vt(_,ae),W;if(J===null){for(;aeae?(fe=J,J=null):fe=J.sibling;var Ol=z(_,J,oe.value,M);if(Ol===null){J===null&&(J=fe);break}e&&J&&Ol.alternate===null&&t(_,J),p=u(Ol,p,ae),re===null?W=Ol:re.sibling=Ol,re=Ol,J=fe}if(oe.done)return l(_,J),se&&Vt(_,ae),W;if(J===null){for(;!oe.done;ae++,oe=T.next())oe=C(_,oe.value,M),oe!==null&&(p=u(oe,p,ae),re===null?W=oe:re.sibling=oe,re=oe);return se&&Vt(_,ae),W}for(J=a(J);!oe.done;ae++,oe=T.next())oe=R(J,_,ae,oe.value,M),oe!==null&&(e&&oe.alternate!==null&&J.delete(oe.key===null?ae:oe.key),p=u(oe,p,ae),re===null?W=oe:re.sibling=oe,re=oe);return e&&J.forEach(function(vy){return t(_,vy)}),se&&Vt(_,ae),W}function Se(_,p,T,M){if(typeof T=="object"&&T!==null&&T.type===G&&T.key===null&&(T=T.props.children),typeof T=="object"&&T!==null){switch(T.$$typeof){case H:e:{for(var W=T.key;p!==null;){if(p.key===W){if(W=T.type,W===G){if(p.tag===7){l(_,p.sibling),M=n(p,T.props.children),M.return=_,_=M;break e}}else if(p.elementType===W||typeof W=="object"&&W!==null&&W.$$typeof===Ne&&Ql(W)===p.type){l(_,p.sibling),M=n(p,T.props),nn(M,T),M.return=_,_=M;break e}l(_,p);break}else t(_,p);p=p.sibling}T.type===G?(M=ql(T.props.children,_.mode,M,T.key),M.return=_,_=M):(M=uu(T.type,T.key,T.props,null,_.mode,M),nn(M,T),M.return=_,_=M)}return c(_);case Z:e:{for(W=T.key;p!==null;){if(p.key===W)if(p.tag===4&&p.stateNode.containerInfo===T.containerInfo&&p.stateNode.implementation===T.implementation){l(_,p.sibling),M=n(p,T.children||[]),M.return=_,_=M;break e}else{l(_,p);break}else t(_,p);p=p.sibling}M=ki(T,_.mode,M),M.return=_,_=M}return c(_);case Ne:return T=Ql(T),Se(_,p,T,M)}if(Ce(T))return w(_,p,T,M);if(Je(T)){if(W=Je(T),typeof W!="function")throw Error(f(150));return T=W.call(T),k(_,p,T,M)}if(typeof T.then=="function")return Se(_,p,du(T),M);if(T.$$typeof===$)return Se(_,p,fu(_,T),M);mu(_,T)}return typeof T=="string"&&T!==""||typeof T=="number"||typeof T=="bigint"?(T=""+T,p!==null&&p.tag===6?(l(_,p.sibling),M=n(p,T),M.return=_,_=M):(l(_,p),M=Wi(T,_.mode,M),M.return=_,_=M),c(_)):l(_,p)}return function(_,p,T,M){try{an=0;var W=Se(_,p,T,M);return ga=null,W}catch(J){if(J===pa||J===ru)throw J;var re=ft(29,J,null,_.mode);return re.lanes=M,re.return=_,re}}}var Vl=Sr(!0),br=Sr(!1),dl=!1;function fc(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function sc(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function ml(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function hl(e,t,l){var a=e.updateQueue;if(a===null)return null;if(a=a.shared,(de&2)!==0){var n=a.pending;return n===null?t.next=t:(t.next=n.next,n.next=t),a.pending=t,t=nu(e),ar(e,null,l),t}return au(e,a,t,l),nu(e)}function un(e,t,l){if(t=t.updateQueue,t!==null&&(t=t.shared,(l&4194048)!==0)){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,os(e,l)}}function rc(e,t){var l=e.updateQueue,a=e.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var n=null,u=null;if(l=l.firstBaseUpdate,l!==null){do{var c={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};u===null?n=u=c:u=u.next=c,l=l.next}while(l!==null);u===null?n=u=t:u=u.next=t}else n=u=t;l={baseState:a.baseState,firstBaseUpdate:n,lastBaseUpdate:u,shared:a.shared,callbacks:a.callbacks},e.updateQueue=l;return}e=l.lastBaseUpdate,e===null?l.firstBaseUpdate=t:e.next=t,l.lastBaseUpdate=t}var oc=!1;function cn(){if(oc){var e=ya;if(e!==null)throw e}}function fn(e,t,l,a){oc=!1;var n=e.updateQueue;dl=!1;var u=n.firstBaseUpdate,c=n.lastBaseUpdate,r=n.shared.pending;if(r!==null){n.shared.pending=null;var v=r,x=v.next;v.next=null,c===null?u=x:c.next=x,c=v;var O=e.alternate;O!==null&&(O=O.updateQueue,r=O.lastBaseUpdate,r!==c&&(r===null?O.firstBaseUpdate=x:r.next=x,O.lastBaseUpdate=v))}if(u!==null){var C=n.baseState;c=0,O=x=v=null,r=u;do{var z=r.lane&-536870913,R=z!==r.lane;if(R?(ce&z)===z:(a&z)===z){z!==0&&z===va&&(oc=!0),O!==null&&(O=O.next={lane:0,tag:r.tag,payload:r.payload,callback:null,next:null});e:{var w=e,k=r;z=t;var Se=l;switch(k.tag){case 1:if(w=k.payload,typeof w=="function"){C=w.call(Se,C,z);break e}C=w;break e;case 3:w.flags=w.flags&-65537|128;case 0:if(w=k.payload,z=typeof w=="function"?w.call(Se,C,z):w,z==null)break e;C=A({},C,z);break e;case 2:dl=!0}}z=r.callback,z!==null&&(e.flags|=64,R&&(e.flags|=8192),R=n.callbacks,R===null?n.callbacks=[z]:R.push(z))}else R={lane:z,tag:r.tag,payload:r.payload,callback:r.callback,next:null},O===null?(x=O=R,v=C):O=O.next=R,c|=z;if(r=r.next,r===null){if(r=n.shared.pending,r===null)break;R=r,r=R.next,R.next=null,n.lastBaseUpdate=R,n.shared.pending=null}}while(!0);O===null&&(v=C),n.baseState=v,n.firstBaseUpdate=x,n.lastBaseUpdate=O,u===null&&(n.shared.lanes=0),Sl|=c,e.lanes=c,e.memoizedState=C}}function _r(e,t){if(typeof e!="function")throw Error(f(191,e));e.call(t)}function Er(e,t){var l=e.callbacks;if(l!==null)for(e.callbacks=null,e=0;eu?u:8;var c=D.T,r={};D.T=r,jc(e,!1,t,l);try{var v=n(),x=D.S;if(x!==null&&x(r,v),v!==null&&typeof v=="object"&&typeof v.then=="function"){var O=nv(v,a);on(e,t,O,mt(e))}else on(e,t,a,mt(e))}catch(C){on(e,t,{then:function(){},status:"rejected",reason:C},mt())}finally{L.p=u,c!==null&&r.types!==null&&(c.types=r.types),D.T=c}}function rv(){}function Rc(e,t,l,a){if(e.tag!==5)throw Error(f(476));var n=eo(e).queue;Pr(e,n,t,I,l===null?rv:function(){return to(e),l(a)})}function eo(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:I,baseState:I,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:$t,lastRenderedState:I},next:null};var l={};return t.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:$t,lastRenderedState:l},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function to(e){var t=eo(e);t.next===null&&(t=e.alternate.memoizedState),on(e,t.next.queue,{},mt())}function Oc(){return Ze(Rn)}function lo(){return Me().memoizedState}function ao(){return Me().memoizedState}function ov(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var l=mt();e=ml(l);var a=hl(t,e,l);a!==null&&(at(a,t,l),un(a,t,l)),t={cache:nc()},e.payload=t;return}t=t.return}}function dv(e,t,l){var a=mt();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Tu(e)?uo(t,l):(l=Ji(e,t,l,a),l!==null&&(at(l,e,a),io(l,t,a)))}function no(e,t,l){var a=mt();on(e,t,l,a)}function on(e,t,l,a){var n={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(Tu(e))uo(t,n);else{var u=e.alternate;if(e.lanes===0&&(u===null||u.lanes===0)&&(u=t.lastRenderedReducer,u!==null))try{var c=t.lastRenderedState,r=u(c,l);if(n.hasEagerState=!0,n.eagerState=r,ct(r,c))return au(e,t,n,0),Ee===null&&lu(),!1}catch{}if(l=Ji(e,t,n,a),l!==null)return at(l,e,a),io(l,t,a),!0}return!1}function jc(e,t,l,a){if(a={lane:2,revertLane:sf(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Tu(e)){if(t)throw Error(f(479))}else t=Ji(e,l,a,2),t!==null&&at(t,e,2)}function Tu(e){var t=e.alternate;return e===te||t!==null&&t===te}function uo(e,t){ba=yu=!0;var l=e.pending;l===null?t.next=t:(t.next=l.next,l.next=t),e.pending=t}function io(e,t,l){if((l&4194048)!==0){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,os(e,l)}}var dn={readContext:Ze,use:Su,useCallback:Re,useContext:Re,useEffect:Re,useImperativeHandle:Re,useLayoutEffect:Re,useInsertionEffect:Re,useMemo:Re,useReducer:Re,useRef:Re,useState:Re,useDebugValue:Re,useDeferredValue:Re,useTransition:Re,useSyncExternalStore:Re,useId:Re,useHostTransitionStatus:Re,useFormState:Re,useActionState:Re,useOptimistic:Re,useMemoCache:Re,useCacheRefresh:Re};dn.useEffectEvent=Re;var co={readContext:Ze,use:Su,useCallback:function(e,t){return ke().memoizedState=[e,t===void 0?null:t],e},useContext:Ze,useEffect:Vr,useImperativeHandle:function(e,t,l){l=l!=null?l.concat([e]):null,_u(4194308,4,$r.bind(null,t,e),l)},useLayoutEffect:function(e,t){return _u(4194308,4,e,t)},useInsertionEffect:function(e,t){_u(4,2,e,t)},useMemo:function(e,t){var l=ke();t=t===void 0?null:t;var a=e();if(wl){ul(!0);try{e()}finally{ul(!1)}}return l.memoizedState=[a,t],a},useReducer:function(e,t,l){var a=ke();if(l!==void 0){var n=l(t);if(wl){ul(!0);try{l(t)}finally{ul(!1)}}}else n=t;return a.memoizedState=a.baseState=n,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},a.queue=e,e=e.dispatch=dv.bind(null,te,e),[a.memoizedState,e]},useRef:function(e){var t=ke();return e={current:e},t.memoizedState=e},useState:function(e){e=Tc(e);var t=e.queue,l=no.bind(null,te,t);return t.dispatch=l,[e.memoizedState,l]},useDebugValue:zc,useDeferredValue:function(e,t){var l=ke();return Nc(l,e,t)},useTransition:function(){var e=Tc(!1);return e=Pr.bind(null,te,e.queue,!0,!1),ke().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,l){var a=te,n=ke();if(se){if(l===void 0)throw Error(f(407));l=l()}else{if(l=t(),Ee===null)throw Error(f(349));(ce&127)!==0||Rr(a,t,l)}n.memoizedState=l;var u={value:l,getSnapshot:t};return n.queue=u,Vr(jr.bind(null,a,u,e),[e]),a.flags|=2048,Ea(9,{destroy:void 0},Or.bind(null,a,u,l,t),null),l},useId:function(){var e=ke(),t=Ee.identifierPrefix;if(se){var l=Bt,a=Ht;l=(a&~(1<<32-it(a)-1)).toString(32)+l,t="_"+t+"R_"+l,l=pu++,0<\/script>",u=u.removeChild(u.firstChild);break;case"select":u=typeof a.is=="string"?c.createElement("select",{is:a.is}):c.createElement("select"),a.multiple?u.multiple=!0:a.size&&(u.size=a.size);break;default:u=typeof a.is=="string"?c.createElement(n,{is:a.is}):c.createElement(n)}}u[Xe]=t,u[Fe]=a;e:for(c=t.child;c!==null;){if(c.tag===5||c.tag===6)u.appendChild(c.stateNode);else if(c.tag!==4&&c.tag!==27&&c.child!==null){c.child.return=c,c=c.child;continue}if(c===t)break e;for(;c.sibling===null;){if(c.return===null||c.return===t)break e;c=c.return}c.sibling.return=c.return,c=c.sibling}t.stateNode=u;e:switch(we(u,n,a),n){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break e;case"img":a=!0;break e;default:a=!1}a&&kt(t)}}return xe(t),Vc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,l),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==a&&kt(t);else{if(typeof a!="string"&&t.stateNode===null)throw Error(f(166));if(e=ne.current,ma(t)){if(e=t.stateNode,l=t.memoizedProps,a=null,n=Qe,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}e[Xe]=t,e=!!(e.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||Nd(e.nodeValue,l)),e||rl(t,!0)}else e=Zu(e).createTextNode(a),e[Xe]=t,t.stateNode=e}return xe(t),null;case 31:if(l=t.memoizedState,e===null||e.memoizedState!==null){if(a=ma(t),l!==null){if(e===null){if(!a)throw Error(f(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(f(557));e[Xe]=t}else Ll(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;xe(t),e=!1}else l=ec(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=l),e=!0;if(!e)return t.flags&256?(rt(t),t):(rt(t),null);if((t.flags&128)!==0)throw Error(f(558))}return xe(t),null;case 13:if(a=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(n=ma(t),a!==null&&a.dehydrated!==null){if(e===null){if(!n)throw Error(f(318));if(n=t.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(f(317));n[Xe]=t}else Ll(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;xe(t),n=!1}else n=ec(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),n=!0;if(!n)return t.flags&256?(rt(t),t):(rt(t),null)}return rt(t),(t.flags&128)!==0?(t.lanes=l,t):(l=a!==null,e=e!==null&&e.memoizedState!==null,l&&(a=t.child,n=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(n=a.alternate.memoizedState.cachePool.pool),u=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(u=a.memoizedState.cachePool.pool),u!==n&&(a.flags|=2048)),l!==e&&l&&(t.child.flags|=8192),Ru(t,t.updateQueue),xe(t),null);case 4:return je(),e===null&&mf(t.stateNode.containerInfo),xe(t),null;case 10:return Kt(t.type),xe(t),null;case 19:if(U(De),a=t.memoizedState,a===null)return xe(t),null;if(n=(t.flags&128)!==0,u=a.rendering,u===null)if(n)hn(a,!1);else{if(Oe!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(u=vu(e),u!==null){for(t.flags|=128,hn(a,!1),e=u.updateQueue,t.updateQueue=e,Ru(t,e),t.subtreeFlags=0,e=l,l=t.child;l!==null;)nr(l,e),l=l.sibling;return Y(De,De.current&1|2),se&&Vt(t,a.treeForkCount),t.child}e=e.sibling}a.tail!==null&&nt()>Cu&&(t.flags|=128,n=!0,hn(a,!1),t.lanes=4194304)}else{if(!n)if(e=vu(u),e!==null){if(t.flags|=128,n=!0,e=e.updateQueue,t.updateQueue=e,Ru(t,e),hn(a,!0),a.tail===null&&a.tailMode==="hidden"&&!u.alternate&&!se)return xe(t),null}else 2*nt()-a.renderingStartTime>Cu&&l!==536870912&&(t.flags|=128,n=!0,hn(a,!1),t.lanes=4194304);a.isBackwards?(u.sibling=t.child,t.child=u):(e=a.last,e!==null?e.sibling=u:t.child=u,a.last=u)}return a.tail!==null?(e=a.tail,a.rendering=e,a.tail=e.sibling,a.renderingStartTime=nt(),e.sibling=null,l=De.current,Y(De,n?l&1|2:l&1),se&&Vt(t,a.treeForkCount),e):(xe(t),null);case 22:case 23:return rt(t),mc(),a=t.memoizedState!==null,e!==null?e.memoizedState!==null!==a&&(t.flags|=8192):a&&(t.flags|=8192),a?(l&536870912)!==0&&(t.flags&128)===0&&(xe(t),t.subtreeFlags&6&&(t.flags|=8192)):xe(t),l=t.updateQueue,l!==null&&Ru(t,l.retryQueue),l=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(l=e.memoizedState.cachePool.pool),a=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(a=t.memoizedState.cachePool.pool),a!==l&&(t.flags|=2048),e!==null&&U(Xl),null;case 24:return l=null,e!==null&&(l=e.memoizedState.cache),t.memoizedState.cache!==l&&(t.flags|=2048),Kt(Ue),xe(t),null;case 25:return null;case 30:return null}throw Error(f(156,t.tag))}function pv(e,t){switch(Ii(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Kt(Ue),je(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return Yn(t),null;case 31:if(t.memoizedState!==null){if(rt(t),t.alternate===null)throw Error(f(340));Ll()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(rt(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(f(340));Ll()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return U(De),null;case 4:return je(),null;case 10:return Kt(t.type),null;case 22:case 23:return rt(t),mc(),e!==null&&U(Xl),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Kt(Ue),null;case 25:return null;default:return null}}function Mo(e,t){switch(Ii(t),t.tag){case 3:Kt(Ue),je();break;case 26:case 27:case 5:Yn(t);break;case 4:je();break;case 31:t.memoizedState!==null&&rt(t);break;case 13:rt(t);break;case 19:U(De);break;case 10:Kt(t.type);break;case 22:case 23:rt(t),mc(),e!==null&&U(Xl);break;case 24:Kt(Ue)}}function vn(e,t){try{var l=t.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var n=a.next;l=n;do{if((l.tag&e)===e){a=void 0;var u=l.create,c=l.inst;a=u(),c.destroy=a}l=l.next}while(l!==n)}}catch(r){ve(t,t.return,r)}}function pl(e,t,l){try{var a=t.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var u=n.next;a=u;do{if((a.tag&e)===e){var c=a.inst,r=c.destroy;if(r!==void 0){c.destroy=void 0,n=t;var v=l,x=r;try{x()}catch(O){ve(n,v,O)}}}a=a.next}while(a!==u)}}catch(O){ve(t,t.return,O)}}function Co(e){var t=e.updateQueue;if(t!==null){var l=e.stateNode;try{Er(t,l)}catch(a){ve(e,e.return,a)}}}function Uo(e,t,l){l.props=Kl(e.type,e.memoizedProps),l.state=e.memoizedState;try{l.componentWillUnmount()}catch(a){ve(e,t,a)}}function yn(e,t){try{var l=e.ref;if(l!==null){switch(e.tag){case 26:case 27:case 5:var a=e.stateNode;break;case 30:a=e.stateNode;break;default:a=e.stateNode}typeof l=="function"?e.refCleanup=l(a):l.current=a}}catch(n){ve(e,t,n)}}function qt(e,t){var l=e.ref,a=e.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(n){ve(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(n){ve(e,t,n)}else l.current=null}function Ho(e){var t=e.type,l=e.memoizedProps,a=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":l.autoFocus&&a.focus();break e;case"img":l.src?a.src=l.src:l.srcSet&&(a.srcset=l.srcSet)}}catch(n){ve(e,e.return,n)}}function wc(e,t,l){try{var a=e.stateNode;Yv(a,e.type,l,t),a[Fe]=t}catch(n){ve(e,e.return,n)}}function Bo(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&xl(e.type)||e.tag===4}function Kc(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Bo(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&xl(e.type)||e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Jc(e,t,l){var a=e.tag;if(a===5||a===6)e=e.stateNode,t?(l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l).insertBefore(e,t):(t=l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l,t.appendChild(e),l=l._reactRootContainer,l!=null||t.onclick!==null||(t.onclick=Xt));else if(a!==4&&(a===27&&xl(e.type)&&(l=e.stateNode,t=null),e=e.child,e!==null))for(Jc(e,t,l),e=e.sibling;e!==null;)Jc(e,t,l),e=e.sibling}function Ou(e,t,l){var a=e.tag;if(a===5||a===6)e=e.stateNode,t?l.insertBefore(e,t):l.appendChild(e);else if(a!==4&&(a===27&&xl(e.type)&&(l=e.stateNode),e=e.child,e!==null))for(Ou(e,t,l),e=e.sibling;e!==null;)Ou(e,t,l),e=e.sibling}function qo(e){var t=e.stateNode,l=e.memoizedProps;try{for(var a=e.type,n=t.attributes;n.length;)t.removeAttributeNode(n[0]);we(t,a,l),t[Xe]=e,t[Fe]=l}catch(u){ve(e,e.return,u)}}var Ft=!1,qe=!1,$c=!1,Lo=typeof WeakSet=="function"?WeakSet:Set,Ge=null;function gv(e,t){if(e=e.containerInfo,yf=ku,e=Ws(e),Xi(e)){if("selectionStart"in e)var l={start:e.selectionStart,end:e.selectionEnd};else e:{l=(l=e.ownerDocument)&&l.defaultView||window;var a=l.getSelection&&l.getSelection();if(a&&a.rangeCount!==0){l=a.anchorNode;var n=a.anchorOffset,u=a.focusNode;a=a.focusOffset;try{l.nodeType,u.nodeType}catch{l=null;break e}var c=0,r=-1,v=-1,x=0,O=0,C=e,z=null;t:for(;;){for(var R;C!==l||n!==0&&C.nodeType!==3||(r=c+n),C!==u||a!==0&&C.nodeType!==3||(v=c+a),C.nodeType===3&&(c+=C.nodeValue.length),(R=C.firstChild)!==null;)z=C,C=R;for(;;){if(C===e)break t;if(z===l&&++x===n&&(r=c),z===u&&++O===a&&(v=c),(R=C.nextSibling)!==null)break;C=z,z=C.parentNode}C=R}l=r===-1||v===-1?null:{start:r,end:v}}else l=null}l=l||{start:0,end:0}}else l=null;for(pf={focusedElem:e,selectionRange:l},ku=!1,Ge=t;Ge!==null;)if(t=Ge,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,Ge=e;else for(;Ge!==null;){switch(t=Ge,u=t.alternate,e=t.flags,t.tag){case 0:if((e&4)!==0&&(e=t.updateQueue,e=e!==null?e.events:null,e!==null))for(l=0;l title"))),we(u,a,l),u[Xe]=e,Ye(u),a=u;break e;case"link":var c=Vd("link","href",n).get(a+(l.href||""));if(c){for(var r=0;rSe&&(c=Se,Se=k,k=c);var _=Js(r,k),p=Js(r,Se);if(_&&p&&(R.rangeCount!==1||R.anchorNode!==_.node||R.anchorOffset!==_.offset||R.focusNode!==p.node||R.focusOffset!==p.offset)){var T=C.createRange();T.setStart(_.node,_.offset),R.removeAllRanges(),k>Se?(R.addRange(T),R.extend(p.node,p.offset)):(T.setEnd(p.node,p.offset),R.addRange(T))}}}}for(C=[],R=r;R=R.parentNode;)R.nodeType===1&&C.push({element:R,left:R.scrollLeft,top:R.scrollTop});for(typeof r.focus=="function"&&r.focus(),r=0;rl?32:l,D.T=null,l=tf,tf=null;var u=_l,c=ll;if(Le=0,Na=_l=null,ll=0,(de&6)!==0)throw Error(f(331));var r=de;if(de|=4,Wo(u.current),Ko(u,u.current,c,l),de=r,En(0,!1),ut&&typeof ut.onPostCommitFiberRoot=="function")try{ut.onPostCommitFiberRoot(Ya,u)}catch{}return!0}finally{L.p=n,D.T=a,md(e,t)}}function vd(e,t,l){t=bt(l,t),t=Uc(e.stateNode,t,2),e=hl(e,t,2),e!==null&&(Xa(e,2),Lt(e))}function ve(e,t,l){if(e.tag===3)vd(e,e,l);else for(;t!==null;){if(t.tag===3){vd(t,e,l);break}else if(t.tag===1){var a=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(bl===null||!bl.has(a))){e=bt(l,e),l=yo(2),a=hl(t,l,2),a!==null&&(po(l,a,t,e),Xa(a,2),Lt(a));break}}t=t.return}}function uf(e,t,l){var a=e.pingCache;if(a===null){a=e.pingCache=new _v;var n=new Set;a.set(t,n)}else n=a.get(t),n===void 0&&(n=new Set,a.set(t,n));n.has(l)||(Fc=!0,n.add(l),e=zv.bind(null,e,t,l),t.then(e,e))}function zv(e,t,l){var a=e.pingCache;a!==null&&a.delete(t),e.pingedLanes|=e.suspendedLanes&l,e.warmLanes&=~l,Ee===e&&(ce&l)===l&&(Oe===4||Oe===3&&(ce&62914560)===ce&&300>nt()-Mu?(de&2)===0&&Ra(e,0):Ic|=l,za===ce&&(za=0)),Lt(e)}function yd(e,t){t===0&&(t=ss()),e=Bl(e,t),e!==null&&(Xa(e,t),Lt(e))}function Nv(e){var t=e.memoizedState,l=0;t!==null&&(l=t.retryLane),yd(e,l)}function Rv(e,t){var l=0;switch(e.tag){case 31:case 13:var a=e.stateNode,n=e.memoizedState;n!==null&&(l=n.retryLane);break;case 19:a=e.stateNode;break;case 22:a=e.stateNode._retryCache;break;default:throw Error(f(314))}a!==null&&a.delete(t),yd(e,l)}function Ov(e,t){return gi(e,t)}var Yu=null,ja=null,cf=!1,Gu=!1,ff=!1,Tl=0;function Lt(e){e!==ja&&e.next===null&&(ja===null?Yu=ja=e:ja=ja.next=e),Gu=!0,cf||(cf=!0,Dv())}function En(e,t){if(!ff&&Gu){ff=!0;do for(var l=!1,a=Yu;a!==null;){if(e!==0){var n=a.pendingLanes;if(n===0)var u=0;else{var c=a.suspendedLanes,r=a.pingedLanes;u=(1<<31-it(42|e)+1)-1,u&=n&~(c&~r),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(l=!0,bd(a,u))}else u=ce,u=Vn(a,a===Ee?u:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(u&3)===0||Ga(a,u)||(l=!0,bd(a,u));a=a.next}while(l);ff=!1}}function jv(){pd()}function pd(){Gu=cf=!1;var e=0;Tl!==0&&Xv()&&(e=Tl);for(var t=nt(),l=null,a=Yu;a!==null;){var n=a.next,u=gd(a,t);u===0?(a.next=null,l===null?Yu=n:l.next=n,n===null&&(ja=l)):(l=a,(e!==0||(u&3)!==0)&&(Gu=!0)),a=n}Le!==0&&Le!==5||En(e),Tl!==0&&(Tl=0)}function gd(e,t){for(var l=e.suspendedLanes,a=e.pingedLanes,n=e.expirationTimes,u=e.pendingLanes&-62914561;0r)break;var O=v.transferSize,C=v.initiatorType;O&&Rd(C)&&(v=v.responseEnd,c+=O*(v"u"?null:document;function Gd(e,t,l){var a=Da;if(a&&typeof t=="string"&&t){var n=gt(t);n='link[rel="'+e+'"][href="'+n+'"]',typeof l=="string"&&(n+='[crossorigin="'+l+'"]'),Yd.has(n)||(Yd.add(n),e={rel:e,crossOrigin:l,href:t},a.querySelector(n)===null&&(t=a.createElement("link"),we(t,"link",e),Ye(t),a.head.appendChild(t)))}}function kv(e){al.D(e),Gd("dns-prefetch",e,null)}function Fv(e,t){al.C(e,t),Gd("preconnect",e,t)}function Iv(e,t,l){al.L(e,t,l);var a=Da;if(a&&e&&t){var n='link[rel="preload"][as="'+gt(t)+'"]';t==="image"&&l&&l.imageSrcSet?(n+='[imagesrcset="'+gt(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(n+='[imagesizes="'+gt(l.imageSizes)+'"]')):n+='[href="'+gt(e)+'"]';var u=n;switch(t){case"style":u=Ma(e);break;case"script":u=Ca(e)}zt.has(u)||(e=A({rel:"preload",href:t==="image"&&l&&l.imageSrcSet?void 0:e,as:t},l),zt.set(u,e),a.querySelector(n)!==null||t==="style"&&a.querySelector(zn(u))||t==="script"&&a.querySelector(Nn(u))||(t=a.createElement("link"),we(t,"link",e),Ye(t),a.head.appendChild(t)))}}function Pv(e,t){al.m(e,t);var l=Da;if(l&&e){var a=t&&typeof t.as=="string"?t.as:"script",n='link[rel="modulepreload"][as="'+gt(a)+'"][href="'+gt(e)+'"]',u=n;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":u=Ca(e)}if(!zt.has(u)&&(e=A({rel:"modulepreload",href:e},t),zt.set(u,e),l.querySelector(n)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(Nn(u)))return}a=l.createElement("link"),we(a,"link",e),Ye(a),l.head.appendChild(a)}}}function ey(e,t,l){al.S(e,t,l);var a=Da;if(a&&e){var n=ea(a).hoistableStyles,u=Ma(e);t=t||"default";var c=n.get(u);if(!c){var r={loading:0,preload:null};if(c=a.querySelector(zn(u)))r.loading=5;else{e=A({rel:"stylesheet",href:e,"data-precedence":t},l),(l=zt.get(u))&&xf(e,l);var v=c=a.createElement("link");Ye(v),we(v,"link",e),v._p=new Promise(function(x,O){v.onload=x,v.onerror=O}),v.addEventListener("load",function(){r.loading|=1}),v.addEventListener("error",function(){r.loading|=2}),r.loading|=4,wu(c,t,a)}c={type:"stylesheet",instance:c,count:1,state:r},n.set(u,c)}}}function ty(e,t){al.X(e,t);var l=Da;if(l&&e){var a=ea(l).hoistableScripts,n=Ca(e),u=a.get(n);u||(u=l.querySelector(Nn(n)),u||(e=A({src:e,async:!0},t),(t=zt.get(n))&&Af(e,t),u=l.createElement("script"),Ye(u),we(u,"link",e),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function ly(e,t){al.M(e,t);var l=Da;if(l&&e){var a=ea(l).hoistableScripts,n=Ca(e),u=a.get(n);u||(u=l.querySelector(Nn(n)),u||(e=A({src:e,async:!0,type:"module"},t),(t=zt.get(n))&&Af(e,t),u=l.createElement("script"),Ye(u),we(u,"link",e),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function Xd(e,t,l,a){var n=(n=ne.current)?Vu(n):null;if(!n)throw Error(f(446));switch(e){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(t=Ma(l.href),l=ea(n).hoistableStyles,a=l.get(t),a||(a={type:"style",instance:null,count:0,state:null},l.set(t,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){e=Ma(l.href);var u=ea(n).hoistableStyles,c=u.get(e);if(c||(n=n.ownerDocument||n,c={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(e,c),(u=n.querySelector(zn(e)))&&!u._p&&(c.instance=u,c.state.loading=5),zt.has(e)||(l={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},zt.set(e,l),u||ay(n,e,l,c.state))),t&&a===null)throw Error(f(528,""));return c}if(t&&a!==null)throw Error(f(529,""));return null;case"script":return t=l.async,l=l.src,typeof l=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=Ca(l),l=ea(n).hoistableScripts,a=l.get(t),a||(a={type:"script",instance:null,count:0,state:null},l.set(t,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(f(444,e))}}function Ma(e){return'href="'+gt(e)+'"'}function zn(e){return'link[rel="stylesheet"]['+e+"]"}function Qd(e){return A({},e,{"data-precedence":e.precedence,precedence:null})}function ay(e,t,l,a){e.querySelector('link[rel="preload"][as="style"]['+t+"]")?a.loading=1:(t=e.createElement("link"),a.preload=t,t.addEventListener("load",function(){return a.loading|=1}),t.addEventListener("error",function(){return a.loading|=2}),we(t,"link",l),Ye(t),e.head.appendChild(t))}function Ca(e){return'[src="'+gt(e)+'"]'}function Nn(e){return"script[async]"+e}function Zd(e,t,l){if(t.count++,t.instance===null)switch(t.type){case"style":var a=e.querySelector('style[data-href~="'+gt(l.href)+'"]');if(a)return t.instance=a,Ye(a),a;var n=A({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(e.ownerDocument||e).createElement("style"),Ye(a),we(a,"style",n),wu(a,l.precedence,e),t.instance=a;case"stylesheet":n=Ma(l.href);var u=e.querySelector(zn(n));if(u)return t.state.loading|=4,t.instance=u,Ye(u),u;a=Qd(l),(n=zt.get(n))&&xf(a,n),u=(e.ownerDocument||e).createElement("link"),Ye(u);var c=u;return c._p=new Promise(function(r,v){c.onload=r,c.onerror=v}),we(u,"link",a),t.state.loading|=4,wu(u,l.precedence,e),t.instance=u;case"script":return u=Ca(l.src),(n=e.querySelector(Nn(u)))?(t.instance=n,Ye(n),n):(a=l,(n=zt.get(u))&&(a=A({},l),Af(a,n)),e=e.ownerDocument||e,n=e.createElement("script"),Ye(n),we(n,"link",a),e.head.appendChild(n),t.instance=n);case"void":return null;default:throw Error(f(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(a=t.instance,t.state.loading|=4,wu(a,l.precedence,e));return t.instance}function wu(e,t,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=a.length?a[a.length-1]:null,u=n,c=0;c title"):null)}function ny(e,t,l){if(l===1||t.itemProp!=null)return!1;switch(e){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;return t.rel==="stylesheet"?(e=t.disabled,typeof t.precedence=="string"&&e==null):!0;case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function Kd(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function uy(e,t,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var n=Ma(a.href),u=t.querySelector(zn(n));if(u){t=u._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=Ju.bind(e),t.then(e,e)),l.state.loading|=4,l.instance=u,Ye(u);return}u=t.ownerDocument||t,a=Qd(a),(n=zt.get(n))&&xf(a,n),u=u.createElement("link"),Ye(u);var c=u;c._p=new Promise(function(r,v){c.onload=r,c.onerror=v}),we(u,"link",a),l.instance=u}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(l,t),(t=l.state.preload)&&(l.state.loading&3)===0&&(e.count++,l=Ju.bind(e),t.addEventListener("load",l),t.addEventListener("error",l))}}var zf=0;function iy(e,t){return e.stylesheets&&e.count===0&&Wu(e,e.stylesheets),0zf?50:800)+t);return e.unsuspend=l,function(){e.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function Ju(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Wu(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var $u=null;function Wu(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,$u=new Map,t.forEach(cy,e),$u=null,Ju.call(e))}function cy(e,t){if(!(t.state.loading&4)){var l=$u.get(e);if(l)var a=l.get(null);else{l=new Map,$u.set(e,l);for(var n=e.querySelectorAll("link[data-precedence],style[data-precedence]"),u=0;u"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(s){console.error(s)}}return i(),Hf.exports=Ty(),Hf.exports}var Ay=xy();var ym="popstate";function pm(i){return typeof i=="object"&&i!=null&&"pathname"in i&&"search"in i&&"hash"in i&&"state"in i&&"key"in i}function zy(i={}){function s(d,h){let{pathname:b="/",search:N="",hash:S=""}=kl(d.location.hash.substring(1));return!b.startsWith("/")&&!b.startsWith(".")&&(b="/"+b),Kf("",{pathname:b,search:N,hash:S},h.state&&h.state.usr||null,h.state&&h.state.key||"default")}function o(d,h){let b=d.document.querySelector("base"),N="";if(b&&b.getAttribute("href")){let S=d.location.href,y=S.indexOf("#");N=y===-1?S:S.slice(0,y)}return N+"#"+(typeof h=="string"?h:Hn(h))}function f(d,h){Nt(d.pathname.charAt(0)==="/",`relative pathnames are not supported in hash history.push(${JSON.stringify(h)})`)}return Ry(s,o,f,i)}function ze(i,s){if(i===!1||i===null||typeof i>"u")throw new Error(s)}function Nt(i,s){if(!i){typeof console<"u"&&console.warn(s);try{throw new Error(s)}catch{}}}function Ny(){return Math.random().toString(36).substring(2,10)}function gm(i,s){return{usr:i.state,key:i.key,idx:s,masked:i.unstable_mask?{pathname:i.pathname,search:i.search,hash:i.hash}:void 0}}function Kf(i,s,o=null,f,d){return{pathname:typeof i=="string"?i:i.pathname,search:"",hash:"",...typeof s=="string"?kl(s):s,state:o,key:s&&s.key||f||Ny(),unstable_mask:d}}function Hn({pathname:i="/",search:s="",hash:o=""}){return s&&s!=="?"&&(i+=s.charAt(0)==="?"?s:"?"+s),o&&o!=="#"&&(i+=o.charAt(0)==="#"?o:"#"+o),i}function kl(i){let s={};if(i){let o=i.indexOf("#");o>=0&&(s.hash=i.substring(o),i=i.substring(0,o));let f=i.indexOf("?");f>=0&&(s.search=i.substring(f),i=i.substring(0,f)),i&&(s.pathname=i)}return s}function Ry(i,s,o,f={}){let{window:d=document.defaultView,v5Compat:h=!1}=f,b=d.history,N="POP",S=null,y=j();y==null&&(y=0,b.replaceState({...b.state,idx:y},""));function j(){return(b.state||{idx:null}).idx}function A(){N="POP";let B=j(),Q=B==null?null:B-y;y=B,S&&S({action:N,location:G.location,delta:Q})}function q(B,Q){N="PUSH";let V=pm(B)?B:Kf(G.location,B,Q);o&&o(V,B),y=j()+1;let $=gm(V,y),F=G.createHref(V.unstable_mask||V);try{b.pushState($,"",F)}catch(le){if(le instanceof DOMException&&le.name==="DataCloneError")throw le;d.location.assign(F)}h&&S&&S({action:N,location:G.location,delta:1})}function H(B,Q){N="REPLACE";let V=pm(B)?B:Kf(G.location,B,Q);o&&o(V,B),y=j();let $=gm(V,y),F=G.createHref(V.unstable_mask||V);b.replaceState($,"",F),h&&S&&S({action:N,location:G.location,delta:0})}function Z(B){return Oy(B)}let G={get action(){return N},get location(){return i(d,b)},listen(B){if(S)throw new Error("A history only accepts one active listener");return d.addEventListener(ym,A),S=B,()=>{d.removeEventListener(ym,A),S=null}},createHref(B){return s(d,B)},createURL:Z,encodeLocation(B){let Q=Z(B);return{pathname:Q.pathname,search:Q.search,hash:Q.hash}},push:q,replace:H,go(B){return b.go(B)}};return G}function Oy(i,s=!1){let o="http://localhost";typeof window<"u"&&(o=window.location.origin!=="null"?window.location.origin:window.location.href),ze(o,"No window.location.(origin|href) available to create URL");let f=typeof i=="string"?i:Hn(i);return f=f.replace(/ $/,"%20"),!s&&f.startsWith("//")&&(f=o+f),new URL(f,o)}function xm(i,s,o="/"){return jy(i,s,o,!1)}function jy(i,s,o,f){let d=typeof s=="string"?kl(s):s,h=nl(d.pathname||"/",o);if(h==null)return null;let b=Am(i);Dy(b);let N=null;for(let S=0;N==null&&S{let j={relativePath:y===void 0?b.path||"":y,caseSensitive:b.caseSensitive===!0,childrenIndex:N,route:b};if(j.relativePath.startsWith("/")){if(!j.relativePath.startsWith(f)&&S)return;ze(j.relativePath.startsWith(f),`Absolute route path "${j.relativePath}" nested under path "${f}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),j.relativePath=j.relativePath.slice(f.length)}let A=Dt([f,j.relativePath]),q=o.concat(j);b.children&&b.children.length>0&&(ze(b.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${A}".`),Am(b.children,s,q,A,S)),!(b.path==null&&!b.index)&&s.push({path:A,score:Ly(A,b.index),routesMeta:q})};return i.forEach((b,N)=>{if(b.path===""||!b.path?.includes("?"))h(b,N);else for(let S of zm(b.path))h(b,N,!0,S)}),s}function zm(i){let s=i.split("/");if(s.length===0)return[];let[o,...f]=s,d=o.endsWith("?"),h=o.replace(/\?$/,"");if(f.length===0)return d?[h,""]:[h];let b=zm(f.join("/")),N=[];return N.push(...b.map(S=>S===""?h:[h,S].join("/"))),d&&N.push(...b),N.map(S=>i.startsWith("/")&&S===""?"/":S)}function Dy(i){i.sort((s,o)=>s.score!==o.score?o.score-s.score:Yy(s.routesMeta.map(f=>f.childrenIndex),o.routesMeta.map(f=>f.childrenIndex)))}var My=/^:[\w-]+$/,Cy=3,Uy=2,Hy=1,By=10,qy=-2,Sm=i=>i==="*";function Ly(i,s){let o=i.split("/"),f=o.length;return o.some(Sm)&&(f+=qy),s&&(f+=Uy),o.filter(d=>!Sm(d)).reduce((d,h)=>d+(My.test(h)?Cy:h===""?Hy:By),f)}function Yy(i,s){return i.length===s.length&&i.slice(0,-1).every((f,d)=>f===s[d])?i[i.length-1]-s[s.length-1]:0}function Gy(i,s,o=!1){let{routesMeta:f}=i,d={},h="/",b=[];for(let N=0;N{if(j==="*"){let Z=N[q]||"";b=h.slice(0,h.length-Z.length).replace(/(.)\/+$/,"$1")}const H=N[q];return A&&!H?y[j]=void 0:y[j]=(H||"").replace(/%2F/g,"/"),y},{}),pathname:h,pathnameBase:b,pattern:i}}function Xy(i,s=!1,o=!0){Nt(i==="*"||!i.endsWith("*")||i.endsWith("/*"),`Route path "${i}" will be treated as if it were "${i.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${i.replace(/\*$/,"/*")}".`);let f=[],d="^"+i.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(b,N,S,y,j)=>{if(f.push({paramName:N,isOptional:S!=null}),S){let A=j.charAt(y+b.length);return A&&A!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return i.endsWith("*")?(f.push({paramName:"*"}),d+=i==="*"||i==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):o?d+="\\/*$":i!==""&&i!=="/"&&(d+="(?:(?=\\/|$))"),[new RegExp(d,s?void 0:"i"),f]}function Qy(i){try{return i.split("/").map(s=>decodeURIComponent(s).replace(/\//g,"%2F")).join("/")}catch(s){return Nt(!1,`The URL path "${i}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${s}).`),i}}function nl(i,s){if(s==="/")return i;if(!i.toLowerCase().startsWith(s.toLowerCase()))return null;let o=s.endsWith("/")?s.length-1:s.length,f=i.charAt(o);return f&&f!=="/"?null:i.slice(o)||"/"}var Zy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function Vy(i,s="/"){let{pathname:o,search:f="",hash:d=""}=typeof i=="string"?kl(i):i,h;return o?(o=Nm(o),o.startsWith("/")?h=bm(o.substring(1),"/"):h=bm(o,s)):h=s,{pathname:h,search:Jy(f),hash:$y(d)}}function bm(i,s){let o=si(s).split("/");return i.split("/").forEach(d=>{d===".."?o.length>1&&o.pop():d!=="."&&o.push(d)}),o.length>1?o.join("/"):"/"}function Yf(i,s,o,f){return`Cannot include a '${i}' character in a manually specified \`to.${s}\` field [${JSON.stringify(f)}]. Please separate it out to the \`to.${o}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function wy(i){return i.filter((s,o)=>o===0||s.route.path&&s.route.path.length>0)}function Wf(i){let s=wy(i);return s.map((o,f)=>f===s.length-1?o.pathname:o.pathnameBase)}function oi(i,s,o,f=!1){let d;typeof i=="string"?d=kl(i):(d={...i},ze(!d.pathname||!d.pathname.includes("?"),Yf("?","pathname","search",d)),ze(!d.pathname||!d.pathname.includes("#"),Yf("#","pathname","hash",d)),ze(!d.search||!d.search.includes("#"),Yf("#","search","hash",d)));let h=i===""||d.pathname==="",b=h?"/":d.pathname,N;if(b==null)N=o;else{let A=s.length-1;if(!f&&b.startsWith("..")){let q=b.split("/");for(;q[0]==="..";)q.shift(),A-=1;d.pathname=q.join("/")}N=A>=0?s[A]:"/"}let S=Vy(d,N),y=b&&b!=="/"&&b.endsWith("/"),j=(h||b===".")&&o.endsWith("/");return!S.pathname.endsWith("/")&&(y||j)&&(S.pathname+="/"),S}var Nm=i=>i.replace(/\/\/+/g,"/"),Dt=i=>Nm(i.join("/")),si=i=>i.replace(/\/+$/,""),Ky=i=>si(i).replace(/^\/*/,"/"),Jy=i=>!i||i==="?"?"":i.startsWith("?")?i:"?"+i,$y=i=>!i||i==="#"?"":i.startsWith("#")?i:"#"+i,Wy=class{constructor(i,s,o,f=!1){this.status=i,this.statusText=s||"",this.internal=f,o instanceof Error?(this.data=o.toString(),this.error=o):this.data=o}};function ky(i){return i!=null&&typeof i.status=="number"&&typeof i.statusText=="string"&&typeof i.internal=="boolean"&&"data"in i}function Fy(i){let s=i.map(o=>o.route.path).filter(Boolean);return Dt(s)||"/"}var Rm=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Om(i,s){let o=i;if(typeof o!="string"||!Zy.test(o))return{absoluteURL:void 0,isExternal:!1,to:o};let f=o,d=!1;if(Rm)try{let h=new URL(window.location.href),b=o.startsWith("//")?new URL(h.protocol+o):new URL(o),N=nl(b.pathname,s);b.origin===h.origin&&N!=null?o=N+b.search+b.hash:d=!0}catch{Nt(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:f,isExternal:d,to:o}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var jm=["POST","PUT","PATCH","DELETE"];new Set(jm);var Iy=["GET",...jm];new Set(Iy);var Ba=E.createContext(null);Ba.displayName="DataRouter";var di=E.createContext(null);di.displayName="DataRouterState";var Dm=E.createContext(!1);function Py(){return E.useContext(Dm)}var Mm=E.createContext({isTransitioning:!1});Mm.displayName="ViewTransition";var e0=E.createContext(new Map);e0.displayName="Fetchers";var t0=E.createContext(null);t0.displayName="Await";var ht=E.createContext(null);ht.displayName="Navigation";var qn=E.createContext(null);qn.displayName="Location";var Mt=E.createContext({outlet:null,matches:[],isDataRoute:!1});Mt.displayName="Route";var kf=E.createContext(null);kf.displayName="RouteError";var Cm="REACT_ROUTER_ERROR",l0="REDIRECT",a0="ROUTE_ERROR_RESPONSE";function n0(i){if(i.startsWith(`${Cm}:${l0}:{`))try{let s=JSON.parse(i.slice(28));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string"&&typeof s.location=="string"&&typeof s.reloadDocument=="boolean"&&typeof s.replace=="boolean")return s}catch{}}function u0(i){if(i.startsWith(`${Cm}:${a0}:{`))try{let s=JSON.parse(i.slice(40));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string")return new Wy(s.status,s.statusText,s.data)}catch{}}function i0(i,{relative:s}={}){ze(qa(),"useHref() may be used only in the context of a component.");let{basename:o,navigator:f}=E.useContext(ht),{hash:d,pathname:h,search:b}=Ln(i,{relative:s}),N=h;return o!=="/"&&(N=h==="/"?o:Dt([o,h])),f.createHref({pathname:N,search:b,hash:d})}function qa(){return E.useContext(qn)!=null}function Yt(){return ze(qa(),"useLocation() may be used only in the context of a component."),E.useContext(qn).location}var Um="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Hm(i){E.useContext(ht).static||E.useLayoutEffect(i)}function Bm(){let{isDataRoute:i}=E.useContext(Mt);return i?_0():c0()}function c0(){ze(qa(),"useNavigate() may be used only in the context of a component.");let i=E.useContext(Ba),{basename:s,navigator:o}=E.useContext(ht),{matches:f}=E.useContext(Mt),{pathname:d}=Yt(),h=JSON.stringify(Wf(f)),b=E.useRef(!1);return Hm(()=>{b.current=!0}),E.useCallback((S,y={})=>{if(Nt(b.current,Um),!b.current)return;if(typeof S=="number"){o.go(S);return}let j=oi(S,JSON.parse(h),d,y.relative==="path");i==null&&s!=="/"&&(j.pathname=j.pathname==="/"?s:Dt([s,j.pathname])),(y.replace?o.replace:o.push)(j,y.state,y)},[s,o,h,d,i])}var f0=E.createContext(null);function s0(i){let s=E.useContext(Mt).outlet;return E.useMemo(()=>s&&E.createElement(f0.Provider,{value:i},s),[s,i])}function Ln(i,{relative:s}={}){let{matches:o}=E.useContext(Mt),{pathname:f}=Yt(),d=JSON.stringify(Wf(o));return E.useMemo(()=>oi(i,JSON.parse(d),f,s==="path"),[i,d,f,s])}function r0(i,s){return qm(i,s)}function qm(i,s,o){ze(qa(),"useRoutes() may be used only in the context of a component.");let{navigator:f}=E.useContext(ht),{matches:d}=E.useContext(Mt),h=d[d.length-1],b=h?h.params:{},N=h?h.pathname:"/",S=h?h.pathnameBase:"/",y=h&&h.route;{let B=y&&y.path||"";Ym(N,!y||B.endsWith("*")||B.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${N}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + +Please change the parent to .`)}let j=Yt(),A;if(s){let B=typeof s=="string"?kl(s):s;ze(S==="/"||B.pathname?.startsWith(S),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${S}" but pathname "${B.pathname}" was given in the \`location\` prop.`),A=B}else A=j;let q=A.pathname||"/",H=q;if(S!=="/"){let B=S.replace(/^\//,"").split("/");H="/"+q.replace(/^\//,"").split("/").slice(B.length).join("/")}let Z=xm(i,{pathname:H});Nt(y||Z!=null,`No routes matched location "${A.pathname}${A.search}${A.hash}" `),Nt(Z==null||Z[Z.length-1].route.element!==void 0||Z[Z.length-1].route.Component!==void 0||Z[Z.length-1].route.lazy!==void 0,`Matched leaf route at location "${A.pathname}${A.search}${A.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let G=v0(Z&&Z.map(B=>Object.assign({},B,{params:Object.assign({},b,B.params),pathname:Dt([S,f.encodeLocation?f.encodeLocation(B.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:B.pathname]),pathnameBase:B.pathnameBase==="/"?S:Dt([S,f.encodeLocation?f.encodeLocation(B.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:B.pathnameBase])})),d,o);return s&&G?E.createElement(qn.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",unstable_mask:void 0,...A},navigationType:"POP"}},G):G}function o0(){let i=b0(),s=ky(i)?`${i.status} ${i.statusText}`:i instanceof Error?i.message:JSON.stringify(i),o=i instanceof Error?i.stack:null,f="rgba(200,200,200, 0.5)",d={padding:"0.5rem",backgroundColor:f},h={padding:"2px 4px",backgroundColor:f},b=null;return console.error("Error handled by React Router default ErrorBoundary:",i),b=E.createElement(E.Fragment,null,E.createElement("p",null,"💿 Hey developer 👋"),E.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",E.createElement("code",{style:h},"ErrorBoundary")," or"," ",E.createElement("code",{style:h},"errorElement")," prop on your route.")),E.createElement(E.Fragment,null,E.createElement("h2",null,"Unexpected Application Error!"),E.createElement("h3",{style:{fontStyle:"italic"}},s),o?E.createElement("pre",{style:d},o):null,b)}var d0=E.createElement(o0,null),Lm=class extends E.Component{constructor(i){super(i),this.state={location:i.location,revalidation:i.revalidation,error:i.error}}static getDerivedStateFromError(i){return{error:i}}static getDerivedStateFromProps(i,s){return s.location!==i.location||s.revalidation!=="idle"&&i.revalidation==="idle"?{error:i.error,location:i.location,revalidation:i.revalidation}:{error:i.error!==void 0?i.error:s.error,location:s.location,revalidation:i.revalidation||s.revalidation}}componentDidCatch(i,s){this.props.onError?this.props.onError(i,s):console.error("React Router caught the following error during render",i)}render(){let i=this.state.error;if(this.context&&typeof i=="object"&&i&&"digest"in i&&typeof i.digest=="string"){const o=u0(i.digest);o&&(i=o)}let s=i!==void 0?E.createElement(Mt.Provider,{value:this.props.routeContext},E.createElement(kf.Provider,{value:i,children:this.props.component})):this.props.children;return this.context?E.createElement(m0,{error:i},s):s}};Lm.contextType=Dm;var Gf=new WeakMap;function m0({children:i,error:s}){let{basename:o}=E.useContext(ht);if(typeof s=="object"&&s&&"digest"in s&&typeof s.digest=="string"){let f=n0(s.digest);if(f){let d=Gf.get(s);if(d)throw d;let h=Om(f.location,o);if(Rm&&!Gf.get(s))if(h.isExternal||f.reloadDocument)window.location.href=h.absoluteURL||h.to;else{const b=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(h.to,{replace:f.replace}));throw Gf.set(s,b),b}return E.createElement("meta",{httpEquiv:"refresh",content:`0;url=${h.absoluteURL||h.to}`})}}return i}function h0({routeContext:i,match:s,children:o}){let f=E.useContext(Ba);return f&&f.static&&f.staticContext&&(s.route.errorElement||s.route.ErrorBoundary)&&(f.staticContext._deepestRenderedBoundaryId=s.route.id),E.createElement(Mt.Provider,{value:i},o)}function v0(i,s=[],o){let f=o?.state;if(i==null){if(!f)return null;if(f.errors)i=f.matches;else if(s.length===0&&!f.initialized&&f.matches.length>0)i=f.matches;else return null}let d=i,h=f?.errors;if(h!=null){let j=d.findIndex(A=>A.route.id&&h?.[A.route.id]!==void 0);ze(j>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(h).join(",")}`),d=d.slice(0,Math.min(d.length,j+1))}let b=!1,N=-1;if(o&&f){b=f.renderFallback;for(let j=0;j=0?d=d.slice(0,N+1):d=[d[0]];break}}}}let S=o?.onError,y=f&&S?(j,A)=>{S(j,{location:f.location,params:f.matches?.[0]?.params??{},unstable_pattern:Fy(f.matches),errorInfo:A})}:void 0;return d.reduceRight((j,A,q)=>{let H,Z=!1,G=null,B=null;f&&(H=h&&A.route.id?h[A.route.id]:void 0,G=A.route.errorElement||d0,b&&(N<0&&q===0?(Ym("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),Z=!0,B=null):N===q&&(Z=!0,B=A.route.hydrateFallbackElement||null)));let Q=s.concat(d.slice(0,q+1)),V=()=>{let $;return H?$=G:Z?$=B:A.route.Component?$=E.createElement(A.route.Component,null):A.route.element?$=A.route.element:$=j,E.createElement(h0,{match:A,routeContext:{outlet:j,matches:Q,isDataRoute:f!=null},children:$})};return f&&(A.route.ErrorBoundary||A.route.errorElement||q===0)?E.createElement(Lm,{location:f.location,revalidation:f.revalidation,component:G,error:H,children:V(),routeContext:{outlet:null,matches:Q,isDataRoute:!0},onError:y}):V()},null)}function Ff(i){return`${i} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function y0(i){let s=E.useContext(Ba);return ze(s,Ff(i)),s}function p0(i){let s=E.useContext(di);return ze(s,Ff(i)),s}function g0(i){let s=E.useContext(Mt);return ze(s,Ff(i)),s}function If(i){let s=g0(i),o=s.matches[s.matches.length-1];return ze(o.route.id,`${i} can only be used on routes that contain a unique "id"`),o.route.id}function S0(){return If("useRouteId")}function b0(){let i=E.useContext(kf),s=p0("useRouteError"),o=If("useRouteError");return i!==void 0?i:s.errors?.[o]}function _0(){let{router:i}=y0("useNavigate"),s=If("useNavigate"),o=E.useRef(!1);return Hm(()=>{o.current=!0}),E.useCallback(async(d,h={})=>{Nt(o.current,Um),o.current&&(typeof d=="number"?await i.navigate(d):await i.navigate(d,{fromRouteId:s,...h}))},[i,s])}var _m={};function Ym(i,s,o){!s&&!_m[i]&&(_m[i]=!0,Nt(!1,o))}E.memo(E0);function E0({routes:i,future:s,state:o,isStatic:f,onError:d}){return qm(i,void 0,{state:o,isStatic:f,onError:d})}function T0({to:i,replace:s,state:o,relative:f}){ze(qa()," may be used only in the context of a component.");let{static:d}=E.useContext(ht);Nt(!d," must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.");let{matches:h}=E.useContext(Mt),{pathname:b}=Yt(),N=Bm(),S=oi(i,Wf(h),b,f==="path"),y=JSON.stringify(S);return E.useEffect(()=>{N(JSON.parse(y),{replace:s,state:o,relative:f})},[N,y,f,s,o]),null}function x0(i){return s0(i.context)}function Ha(i){ze(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function A0({basename:i="/",children:s=null,location:o,navigationType:f="POP",navigator:d,static:h=!1,unstable_useTransitions:b}){ze(!qa(),"You cannot render a inside another . You should never have more than one in your app.");let N=i.replace(/^\/*/,"/"),S=E.useMemo(()=>({basename:N,navigator:d,static:h,unstable_useTransitions:b,future:{}}),[N,d,h,b]);typeof o=="string"&&(o=kl(o));let{pathname:y="/",search:j="",hash:A="",state:q=null,key:H="default",unstable_mask:Z}=o,G=E.useMemo(()=>{let B=nl(y,N);return B==null?null:{location:{pathname:B,search:j,hash:A,state:q,key:H,unstable_mask:Z},navigationType:f}},[N,y,j,A,q,H,f,Z]);return Nt(G!=null,` is not able to match the URL "${y}${j}${A}" because it does not start with the basename, so the won't render anything.`),G==null?null:E.createElement(ht.Provider,{value:S},E.createElement(qn.Provider,{children:s,value:G}))}function z0({children:i,location:s}){return r0(Jf(i),s)}function Jf(i,s=[]){let o=[];return E.Children.forEach(i,(f,d)=>{if(!E.isValidElement(f))return;let h=[...s,d];if(f.type===E.Fragment){o.push.apply(o,Jf(f.props.children,h));return}ze(f.type===Ha,`[${typeof f.type=="string"?f.type:f.type.name}] is not a component. All component children of must be a or `),ze(!f.props.index||!f.props.children,"An index route cannot have child routes.");let b={id:f.props.id||h.join("-"),caseSensitive:f.props.caseSensitive,element:f.props.element,Component:f.props.Component,index:f.props.index,path:f.props.path,middleware:f.props.middleware,loader:f.props.loader,action:f.props.action,hydrateFallbackElement:f.props.hydrateFallbackElement,HydrateFallback:f.props.HydrateFallback,errorElement:f.props.errorElement,ErrorBoundary:f.props.ErrorBoundary,hasErrorBoundary:f.props.hasErrorBoundary===!0||f.props.ErrorBoundary!=null||f.props.errorElement!=null,shouldRevalidate:f.props.shouldRevalidate,handle:f.props.handle,lazy:f.props.lazy};f.props.children&&(b.children=Jf(f.props.children,h)),o.push(b)}),o}var ui="get",ii="application/x-www-form-urlencoded";function mi(i){return typeof HTMLElement<"u"&&i instanceof HTMLElement}function N0(i){return mi(i)&&i.tagName.toLowerCase()==="button"}function R0(i){return mi(i)&&i.tagName.toLowerCase()==="form"}function O0(i){return mi(i)&&i.tagName.toLowerCase()==="input"}function j0(i){return!!(i.metaKey||i.altKey||i.ctrlKey||i.shiftKey)}function D0(i,s){return i.button===0&&(!s||s==="_self")&&!j0(i)}var ai=null;function M0(){if(ai===null)try{new FormData(document.createElement("form"),0),ai=!1}catch{ai=!0}return ai}var C0=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Xf(i){return i!=null&&!C0.has(i)?(Nt(!1,`"${i}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${ii}"`),null):i}function U0(i,s){let o,f,d,h,b;if(R0(i)){let N=i.getAttribute("action");f=N?nl(N,s):null,o=i.getAttribute("method")||ui,d=Xf(i.getAttribute("enctype"))||ii,h=new FormData(i)}else if(N0(i)||O0(i)&&(i.type==="submit"||i.type==="image")){let N=i.form;if(N==null)throw new Error('Cannot submit a