diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e24a738..d704393 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,14 @@ jobs: npx playwright install chromium npm run test:e2e + - name: Playwright E2E (approval workspace) + env: + FD_E2E_FORCE_APPROVAL: "1" + run: | + cd web + npx playwright install chromium + npx playwright test e2e/actions-approval.spec.ts + - name: Lint run: uv run python -m ruff check src tests @@ -114,6 +122,15 @@ jobs: npx playwright install chromium npm run test:e2e + - name: Playwright E2E (approval workspace) + shell: bash + env: + FD_E2E_FORCE_APPROVAL: "1" + run: | + cd web + npx playwright install chromium + npx playwright test e2e/actions-approval.spec.ts + - name: Lint run: uv run python -m ruff check src tests diff --git a/.github/workflows/release-pypi.yml b/.github/workflows/release-pypi.yml index c50e886..ab975ca 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.1.0). +# Publish sdist + wheel to PyPI when a SemVer tag is pushed (e.g. v1.1.1). # 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 9b688a8..0eb5718 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,18 @@ This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0** ## Unreleased +## 1.1.1 - 2026-05-02 + +### Added + +- **`GET /v1/workspace`:** read-only JSON for operators and the web UI — **`promotion_requires_approval`**, **`pricing_catalog_configured`**, **`server_version`** (normative schema + Python SDK helper). +- **Web Actions:** Promote flow uses workspace flags — direct **`POST /v1/promote`** when approval is off; **request → list pending → confirm** when **`promotion_requires_approval`** is on, with clearer errors. +- **Docs:** README / **release-artifact** / **examples** / **web-ui** / **http-api** / **sdk** updates for Phase 1 remainder; optional **`docs/pricing-catalog.md`**; **`examples/ci/promote_with_approval.sh`** and CI README **GitHub Actions** pattern for approval-gated promote. + +### Changed + +- **Examples / CI snippets:** **`flightdeck-ai>=1.1.1`** where version pins apply. + ## 1.1.0 - 2026-05-03 ### Added diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 51c7281..7524de8 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -133,7 +133,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.1.0`**) then **`git push origin vX.Y.Z`**. +4. **`git tag vX.Y.Z`** (must match **`pyproject.toml`** exactly, e.g. **`v1.1.1`**) 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/README.md b/README.md index d550e06..9b79dfc 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,10 @@ Current local spine: FlightDeck is **local-first** and ships as a Python CLI backed by SQLite. -**v1.0.0** establishes **SemVer-stable public contracts** for the documented CLI -(**[README.md](https://github.com/flightdeckdev/flightdeck/blob/main/README.md)** on `main`), -committed **`schemas/v1/`**, and **`POST /v1/events`** with **`api_version` `v1`**. See -**[RELEASE_NOTES.md](RELEASE_NOTES.md)** (same narrative on -**[`main`](https://github.com/flightdeckdev/flightdeck/blob/main/RELEASE_NOTES.md)**). +**v1.0.0** froze **SemVer-stable public contracts** for the documented CLI, committed **`schemas/v1/`**, +and **`POST /v1/events`** with **`api_version` `v1`**. **v1.1.x** adds Phase 1 slices (optional pricing catalog on diffs, +promotion request/confirm, read-only runs listing, **`GET /v1/workspace`** for UI and automation, Helm/fleet examples) +without breaking those v1.0 shapes. See **[RELEASE_NOTES.md](RELEASE_NOTES.md)** and **[CHANGELOG.md](CHANGELOG.md)**. The product scope is still intentionally narrow (release governance, not a hosted agent platform). Not implemented yet: @@ -46,10 +45,12 @@ Shipped locally: - `flightdeck serve` + JSON routes under `/v1/*` (read + diff/promote/rollback + event ingest); see **Local HTTP API** below - minimal Python SDK (`flightdeck.sdk.client`) - `flightdeck release rollback` (policy-gated, audited) +- optional **`promotion_requires_approval`** in `flightdeck.yaml` with **`POST /v1/promote/request`** and **`POST /v1/promote/confirm`** ### Local HTTP API -With **`flightdeck serve`** (default bind **127.0.0.1**), the app exposes **`GET /health`**, **`GET /v1/releases`**, **`GET /v1/promoted`**, **`GET /v1/actions`**, **`POST /v1/events`**, **`POST /v1/diff`**, **`POST /v1/promote`**, and **`POST /v1/rollback`**. **`POST /v1/promote`** and **`POST /v1/rollback`** accept requests only from loopback clients unless **`FLIGHTDECK_LOCAL_API_TOKEN`** is set, in which case callers must send **`Authorization: Bearer `** (same behavior as the **`web/`** dev UI via **`VITE_FLIGHTDECK_LOCAL_API_TOKEN`**). See **[SECURITY.md](SECURITY.md)**. +With **`flightdeck serve`** (default bind **127.0.0.1**), the app exposes **`GET /health`**, **`GET /v1/workspace`** +(read-only workspace flags for scripts and the bundled UI), **`GET /v1/metrics`**, **`GET /v1/releases`**, **`GET /v1/promoted`**, **`GET /v1/actions`**, **`GET /v1/promotion-requests`**, **`GET /v1/runs`**, **`POST /v1/events`**, **`POST /v1/diff`**, **`POST /v1/promote`**, **`POST /v1/promote/request`**, **`POST /v1/promote/confirm`**, and **`POST /v1/rollback`**. **`POST /v1/promote`**, **`POST /v1/promote/request`**, **`POST /v1/promote/confirm`**, and **`POST /v1/rollback`** accept requests only from loopback clients unless **`FLIGHTDECK_LOCAL_API_TOKEN`** is set, in which case callers must send **`Authorization: Bearer `** (same behavior as the **`web/`** dev UI via **`VITE_FLIGHTDECK_LOCAL_API_TOKEN`**). See **[docs/http-api.md](docs/http-api.md)** and **[SECURITY.md](SECURITY.md)**. ## Quickstart @@ -116,6 +117,7 @@ Substitute them before ingestion, or run **`uv run flightdeck-quickstart-verify` - [Python SDK](docs/sdk.md) — `FlightdeckClient` / `AsyncFlightdeckClient` usage guide - [Operations and policy](docs/operations-and-policy.md) — diff, promote, rollback internals; policy model and confidence tiers - [Release artifacts and pricing](docs/release-artifact.md) — `release.yaml` format, bundle layout, checksum algorithm, workspace config, pricing tables +- [Pricing catalog](docs/pricing-catalog.md) — optional `pricing_catalog_path`, catalog vs imported tables, troubleshooting - [JSON Schemas](schemas/v1/) - [Release notes (maintainer)](RELEASE_NOTES.md) - [Roadmap](ROADMAP.md) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index aae6251..bf5ce59 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,10 @@ 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.1.1 — Workspace discovery, web approval UX, docs + CI cookbook + +Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): **`GET /v1/workspace`** exposes non-secret workspace flags (**`promotion_requires_approval`**, **`pricing_catalog_configured`**, **`server_version`**) for scripting and the **Actions** page; web **Promote** follows the two-step request/confirm path when approval is required; expanded operator docs and **`examples/ci/promote_with_approval.sh`** + README workflow snippet. **Stable contracts:** additive HTTP and JSON Schema only. + ## v1.1.0 — Phase 1 first slice (catalog, approval, runs, Helm, fleet) Minor release (see **[CHANGELOG.md](CHANGELOG.md)**): optional **`pricing_catalog_path`** + **`PricingCatalog`** YAML for cross-vendor comparable **`pricing.catalog`** lines on diffs; **`pricing.hints`** for multi-version and model-name diagnostics; **`promotion_requires_approval`** with **`POST /v1/promote/request`** / **`POST /v1/promote/confirm`** / **`GET /v1/promotion-requests`** and matching CLI; **`GET /v1/runs`** and **`flightdeck runs list`**; SQLite migration **v4** (`promotion_requests`); reference **Helm** chart and **fleet** docs under **`examples/`**. **Stable contracts:** additive HTTP and CLI surfaces; existing **`v1`** event and release payloads unchanged. diff --git a/ROADMAP.md b/ROADMAP.md index 61ab595..b5ca539 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -21,7 +21,9 @@ This roadmap is meant to be clear from **what is already shipped** to **near-ter ## Next release -Further **Phase 1** work after **v1.1.0** (deeper forensics / replay UX, richer approval UI if needed, OTLP-oriented telemetry per gaps table). Track **[CHANGELOG.md](CHANGELOG.md)**. +Further **Phase 1** work after **v1.1.1** (deeper forensics / replay UX, OTLP-oriented telemetry per gaps table). Track **[CHANGELOG.md](CHANGELOG.md)**. + +**v1.1.1** (patch, shipped): **`GET /v1/workspace`** (read-only flags + **`server_version`**); web **Actions** page uses it for **direct promote** vs **request / pending list / confirm**; operator docs refresh (**README**, **release-artifact**, **examples**, **web-ui**, **http-api**, **sdk**, **`docs/pricing-catalog.md`**, **SECURITY**); **`examples/ci/promote_with_approval.sh`** and **`github-actions/promote-approval-twostep.yml`**; CI runs a second Playwright pass with **`FD_E2E_FORCE_APPROVAL`**. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. **v1.1.0** (minor, shipped): Phase 1 first slice — **`pricing_catalog_path`** + **`pricing.catalog`** / **`pricing.hints`** on diffs; **`promotion_requires_approval`** + promote **request/confirm** (HTTP + CLI) + **`GET /v1/promotion-requests`**; **`GET /v1/runs`** / **`runs list`**; **Helm** reference chart; **`examples/fleet/`**; SQLite migration **v4**. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. @@ -90,7 +92,13 @@ Shipped on **`main`**: Goal: move from solid local tooling to repeatable production usage patterns. -**v1.1.0** ships the first tranche: catalog + hints on diffs, approval-gated promote (HTTP + CLI), read-only runs listing, Helm + fleet reference docs, and migration **v4**. Remaining bullets below are still in scope for later minors/patches. +**v1.1.0** ships the first tranche: catalog + hints on diffs, approval-gated promote (HTTP + CLI), read-only runs listing, Helm + fleet reference docs, and migration **v4**. **v1.1.1** closes the **web + discovery** gap for approval (**`GET /v1/workspace`**, Actions UX) and refreshes team docs / CI examples. Remaining bullets below are still in scope for later minors/patches. + +### Phase 1 progress (post–v1.1.0 / v1.1.1) + +- **Approval workflow:** **v1.1.0** added HTTP + CLI + **`GET /v1/promotion-requests`**. **v1.1.1** adds **`GET /v1/workspace`** and a first-class **web** path (request → pending table → confirm) keyed off `promotion_requires_approval`, plus **`examples/ci/promote_with_approval.sh`** and a **`workflow_dispatch`** sample workflow. +- **Operator narrative:** README / **examples** index / **web-ui** / **release-artifact** / **http-api** / **sdk** / optional **`docs/pricing-catalog.md`** describe catalog fields, runs listing, and the two promote modes. +- **Still open:** **Forensics** beyond read-only runs list (replay/trace-style views, exports), richer catalog lifecycle governance, OTLP-oriented telemetry — see gaps table above. ### Build in this phase diff --git a/SECURITY.md b/SECURITY.md index 50405de..2304267 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -36,6 +36,8 @@ See **[CONTRIBUTING.md](CONTRIBUTING.md)** for a pre-push checklist aligned with ## Local HTTP API (`flightdeck serve`) -The bundled server is intended for **local development and demos**. **`POST /v1/promote`** and **`POST /v1/rollback`** are gated in server code so that, with no token configured, only **loopback** clients (`127.0.0.1`, `::1`, `localhost`) can invoke them. If you set **`FLIGHTDECK_LOCAL_API_TOKEN`**, every mutation request must include **`Authorization: Bearer `**; use a strong random value and treat it like a local secret. +The bundled server is intended for **local development and demos**. **`POST /v1/promote`**, **`POST /v1/promote/request`**, **`POST /v1/promote/confirm`**, and **`POST /v1/rollback`** are gated in server code so that, with no token configured, only **loopback** clients (`127.0.0.1`, `::1`, `localhost`) can invoke them. If you set **`FLIGHTDECK_LOCAL_API_TOKEN`**, every mutation request must include **`Authorization: Bearer `**; use a strong random value and treat it like a local secret. + +**Human approval** (`promotion_requires_approval: true` in `flightdeck.yaml`) adds a **second actor step** before a promote is applied: **`POST /v1/promote/request`** creates a pending row; **`POST /v1/promote/confirm`** completes it. **Policy still runs on confirm** — approval is not a bypass; a request that fails policy remains blocked with the same HTTP **409** outcome as a direct promote. **`GET /v1/workspace`**, **`GET /v1/promotion-requests`**, and other read-only **`GET /v1/*`** routes stay on the read tier (no Bearer required unless you add external controls). **`POST /v1/events`** and **`POST /v1/diff`** have **no server-side host or token check** in `server/routes/ingest.py` and `server/routes/actions.py`. They are open to any caller that can reach the server. When `flightdeck serve` binds to `127.0.0.1` (the default), this is safe by network topology. If you use `--host 0.0.0.0` or bind to a non-loopback address, event ingest and diff become reachable from any client. Protect them at the network layer (firewall / reverse proxy) if the server is exposed on a shared or public network. diff --git a/docs/http-api.md b/docs/http-api.md index 4b913b7..b802c2f 100644 --- a/docs/http-api.md +++ b/docs/http-api.md @@ -22,7 +22,7 @@ Two access tiers: | Route | No token configured | `FLIGHTDECK_LOCAL_API_TOKEN` set | |-------|--------------------|---------------------------------| | `GET /health` | open | open | -| `GET /v1/*` (reads, including `GET /v1/metrics`, `GET /v1/runs`, `GET /v1/promotion-requests`) | open | open | +| `GET /v1/*` (reads, including `GET /v1/workspace`, `GET /v1/metrics`, `GET /v1/runs`, `GET /v1/promotion-requests`) | open | open | | `POST /v1/events` | open† | open (no Bearer required) | | `POST /v1/diff` | open | open | | `POST /v1/promote` | loopback only | `Authorization: Bearer ` required | @@ -94,6 +94,24 @@ Read-only JSON snapshot of aggregate counts in the local SQLite ledger (releases --- +## `GET /v1/workspace` + +Read-only flags derived from `flightdeck.yaml` plus the running package version. Used by the web UI and automation to choose **direct promote** vs **request/confirm** without embedding workspace YAML in the client. No secrets and no catalog file contents — only whether a **non-empty** `pricing_catalog_path` is set (`pricing_catalog_configured`). + +**Response** (`WorkspacePublic` — see `schemas/v1/workspace_public.schema.json`) + +```json +{ + "api_version": "v1", + "kind": "WorkspacePublic", + "promotion_requires_approval": false, + "pricing_catalog_configured": false, + "server_version": "1.1.1" +} +``` + +--- + ## `GET /v1/releases` List all registered releases. diff --git a/docs/pricing-catalog.md b/docs/pricing-catalog.md new file mode 100644 index 0000000..7f2e235 --- /dev/null +++ b/docs/pricing-catalog.md @@ -0,0 +1,39 @@ +# Pricing catalog (operator YAML) + +FlightDeck can load an optional **operator-defined** [`PricingCatalog`](../schemas/v1/pricing_catalog.schema.json) YAML +referenced by **`pricing_catalog_path`** in [`flightdeck.yaml`](release-artifact.md#workspace-config-flightdeckyaml). + +## Purpose + +Imported **pricing tables** (`flightdeck pricing import …`) drive per-model token rates for runs that match the table’s +**`provider`** + **`pricing_version`**. A **catalog** adds **cross-vendor comparable** rows on diffs (`pricing.catalog` on +`POST /v1/diff` / `release diff`) and diagnostics in **`pricing.hints`** when multiple pricing table versions or naming +patterns appear in the evidence window. + +## Relationship to `pricing.prices` + +On a diff, **`pricing.prices`** (when present) reflects **per-side imported tables** for the resolved baseline/candidate +models. **`pricing.catalog`** is **additive**: slot/tariff lines from the catalog file, gated by whether the catalog is +enabled and resolvable. You can use **tables only**, **catalog only** for comparable lines, or **both** — they answer +different questions (strict table match vs operator normalization). + +## Configuration + +Set a non-empty path in `flightdeck.yaml`: + +```yaml +pricing_catalog_path: pricing/catalog.yaml +``` + +Paths are relative to the workspace working directory or absolute. **`GET /v1/workspace`** reports +**`pricing_catalog_configured: true`** when this field is set to a non-empty string (the file is not opened for that probe). + +## Failure modes + +- **Missing file** — catalog features stay off; diff may note disabled catalog in the payload. +- **Malformed YAML** — syntax errors are treated as **non-fatal** for the diff: the request still returns **HTTP 200** with + catalog disabled and diagnostics where implemented; see [CHANGELOG.md](../CHANGELOG.md) for behavior on your version. + +## Sample + +See **[examples/pricing/catalog.sample.yaml](../examples/pricing/catalog.sample.yaml)**. diff --git a/docs/release-artifact.md b/docs/release-artifact.md index 66a7dc9..5f330fe 100644 --- a/docs/release-artifact.md +++ b/docs/release-artifact.md @@ -148,11 +148,22 @@ diff: min_candidate_runs: 500 # HIGH confidence threshold (candidate side) min_baseline_runs: 500 # HIGH confidence threshold (baseline side) min_low_runs: 50 # LOW confidence floor +# Optional: YAML PricingCatalog for cross-vendor comparable lines on diffs (see schemas/v1/pricing_catalog.schema.json) +# pricing_catalog_path: pricing/catalog.yaml +# Optional: when true, direct promote is rejected until a pending request is confirmed (HTTP/CLI request + confirm) +# promotion_requires_approval: false ``` All fields have defaults; an empty `flightdeck.yaml` is valid. `db_path` accepts any relative or absolute path — the parent directory is created automatically on first use. +**`pricing_catalog_path`** — optional path to a [`PricingCatalog`](../schemas/v1/pricing_catalog.schema.json) YAML +(relative to the workspace cwd or absolute). When set, diffs include additive `pricing.catalog` / `pricing.hints`. +**`promotion_requires_approval`** — when `true`, `POST /v1/promote` and `flightdeck release promote` reject until a row is +completed via `POST /v1/promote/request` then `POST /v1/promote/confirm` (or CLI `release promote-request` / `promote-confirm`). +**`GET /v1/workspace`** exposes non-secret booleans for automation and the web UI (`promotion_requires_approval`, +`pricing_catalog_configured`, `server_version`). + `diff.*` thresholds are the **workspace defaults** used when the active policy does not override them. The policy's `min_*` fields take precedence when set (including `0` for "no minimum"). See [operations-and-policy.md § Confidence tiers](operations-and-policy.md). diff --git a/docs/sdk.md b/docs/sdk.md index de26716..2d3034a 100644 --- a/docs/sdk.md +++ b/docs/sdk.md @@ -91,6 +91,10 @@ See [SECURITY.md](../SECURITY.md) for the full access model. `GET /health` — returns `{"status": "ok", "mutation_auth": "loopback"|"bearer"}` when the server is up (`mutation_auth` describes promote/rollback auth; see **HTTP API**). +### `get_workspace() -> dict` + +`GET /v1/workspace` — returns `WorkspacePublic` JSON: `promotion_requires_approval`, `pricing_catalog_configured` (whether `pricing_catalog_path` is set to a non-empty string), and `server_version` (installed `flightdeck-ai` SemVer). Same method exists on `AsyncFlightdeckClient`. See [http-api.md § GET /v1/workspace](http-api.md#get-v1workspace). + ### `GET /v1/metrics` (no SDK wrapper) The metrics endpoint has no dedicated SDK method. Call it directly via `httpx` or `requests`: diff --git a/docs/web-ui.md b/docs/web-ui.md index b1a3bc3..a5431f3 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -17,8 +17,8 @@ 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`, `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` | +| `#/diff` | `DiffPage` | `POST /v1/diff` | Renders `pricing.warnings`, optional **`pricing.catalog`** / **`pricing.hints`**, per-1k prices when present | +| `#/actions` | `ActionsPage` | `GET /v1/workspace`, `GET /v1/promotion-requests` (when `promotion_requires_approval`), `POST /v1/promote` **or** `POST /v1/promote/request` + `POST /v1/promote/confirm`, `POST /v1/rollback` | Workspace strip shows server version + mode; see **ActionsPage** below | | `#/*` (any other) | — | Redirects to `#/` | | `App.tsx` declares the route tree. `AppShell` is the layout wrapper rendered for all routes. @@ -75,7 +75,7 @@ so it is stable across renders. Throws if called outside `TimelineRefreshProvider`. -**Data flow for a successful promote:** +**Data flow for a successful direct promote** (when `promotion_requires_approval` is **false** in `flightdeck.yaml`): 1. User fills the `ActionsPage` form and clicks **Promote**. 2. `ActionsPage` calls `fetchJson` → `POST /v1/promote`. @@ -83,6 +83,9 @@ Throws if called outside `TimelineRefreshProvider`. 4. `OverviewPage` (mounted in the same shell) sees `generation` change via context. 5. `useEffect` fires `loadTimeline()` and re-renders tables with fresh data. +When **`promotion_requires_approval`** is **true**, step 2 uses **`POST /v1/promote/request`** instead; confirm uses +**`POST /v1/promote/confirm`** from the same page. `GET /v1/workspace` on mount drives which buttons are shown. + --- ## `uiConfig.ts` (`web/src/uiConfig.ts`) @@ -163,6 +166,8 @@ On submit, the raw diff response is parsed and rendered as: 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). +- **Catalog / hints:** when `pricing.catalog` or `pricing.hints` is present, the UI surfaces + catalog enabled state, lines, and hint strings (see [pricing-catalog.md](pricing-catalog.md)). - **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 @@ -185,48 +190,40 @@ token. See [http-api.md](http-api.md) for the full response schema. ## `ActionsPage` (`web/src/pages/ActionsPage.tsx`) -Mutation form for promote and rollback. Fields: +On mount, loads **`GET /v1/workspace`** (`fetchWorkspace`) and renders a short status strip: +**`server_version`**, whether a **`pricing_catalog_path`** is configured (**`pricing_catalog_configured`**), and +whether **`promotion_requires_approval`** is on. + +Mutation form fields: | Field | Default | Maps to | |-------|---------|---------| | Release ID | (empty) | `release_id` | | Environment | `local` | `environment` | | Window | `7d` | `window` | -| Reason (required) | (empty) | `reason` | +| Reason (required) | (empty) | `reason` (promote, rollback, and promotion **request**) | The `actor` field is hardcoded to `"react-ui"` for audit log attribution. -Both **Promote** and **Rollback** buttons are disabled while any request is in flight. A -`window.confirm` dialog appears before each mutation. An empty reason field aborts before -any network call with an inline error. - -After a successful mutation: - -1. The response is parsed by `pickOutcome()` — a defensive coercion function that returns - an `ActionOutcomePayload` when the response matches the documented contract, or `null` - when it does not (allowing graceful fallback to the raw JSON panel). -2. If `pickOutcome()` returns a structured value, a **outcome card** is rendered: - - **Policy badge**: `PASS` / `FAIL` tone from `actOutcome.policy.passed`. - - **Pointer badge**: `Updated` (pass tone) or `Unchanged` (neutral tone) from - `promoted_pointer_changed`. Shown alongside `agent_id` and `environment`. - - **Metric grid**: Action ID, Release ID, and Previous baseline (truncated via - `shortId()`; full value on `title` hover). When `baseline_release_id` is `null` - (first promotion), "none (first promotion)" is shown. - - **Policy reasons list** (`fd-reasons`): rendered only when `policy.reasons` is - non-empty (e.g. on a FAIL or when advisory reasons are present). -3. The raw response JSON is always kept in a `JsonPanel` titled "Raw response JSON". - It opens by default **only** when `pickOutcome()` returned `null` (i.e. the outcome - card could not be rendered). -4. `notifyTimelineMutated()` is called, refreshing `OverviewPage` automatically. - -**Auth:** When `VITE_FLIGHTDECK_LOCAL_API_TOKEN` is set in the build environment (or -`.env.local` during dev), `fetchJson` adds `Authorization: Bearer ` to every request. -This satisfies the `FLIGHTDECK_LOCAL_API_TOKEN` gate on `POST /v1/promote` and -`POST /v1/rollback`. See [http-api.md § Authentication](http-api.md#authentication-and-access-control). - -**HTTP 409 handling:** when the server returns 409 (policy blocked), `fetchJson` throws with -the `detail.message` extracted from the response body. The error is shown in the alert -element; the audit ledger entry is still recorded server-side. +**Direct promotion** (default): **Promote** calls `POST /v1/promote`. **Rollback** always calls `POST /v1/rollback`. + +**Approval mode:** **Promote** is replaced by **Request promotion** (`POST /v1/promote/request`). A **Pending promotion requests** +table refreshes from `GET /v1/promotion-requests?status=pending`. **Confirm promotion** posts `POST /v1/promote/confirm` with +`request_id` (prefilled after a successful request) and `approval_reason`. A collapsible **Promotion request (raw JSON)** +panel shows the request response. + +All primary actions use `window.confirm`. Buttons are disabled while a request is in flight (`busy` state). Empty +reason / empty confirm fields abort with inline errors. + +After a successful **promote** or **rollback** (or **confirm**): + +1. The response is parsed by `pickOutcome()` when it matches the promote/rollback outcome contract; otherwise the raw JSON panel opens. +2. Outcome card shows policy badge, pointer badge, metric grid, and reasons list when applicable. +3. `notifyTimelineMutated()` runs so `OverviewPage` refetches. + +**Auth:** `VITE_FLIGHTDECK_LOCAL_API_TOKEN` is sent on every `fetchJson` call, including **`POST /v1/promote/request`** and **`POST /v1/promote/confirm`**. See [http-api.md § Authentication](http-api.md#authentication-and-access-control). + +**HTTP errors:** `fetchJson` formats FastAPI **`detail`** strings, validation arrays, and `{ message: … }` objects into a single `Error` message for the alert line. --- @@ -293,7 +290,7 @@ Thin wrapper around `fetch`: `Authorization: Bearer …` header if the env var is non-empty and no `Authorization` header is already set. 2. Calls `fetch(path, { ...init, headers })`. -3. On non-2xx, extracts `response.json().detail` (string or array) and throws `Error(detail)`. +3. On non-2xx, formats `response.json().detail` (string, validation array, or object with `message`) into a readable message and throws `Error(…)`. 4. On JSON parse failure, falls back to `{}` before checking `res.ok`. ### `fetchHealth(): Promise` @@ -311,6 +308,15 @@ Fires three `GET` requests in parallel via `Promise.all`: Returns a merged `TimelinePayload`. Used by `OverviewPage` on mount and on every `generation` increment. +### `fetchWorkspace(): Promise` + +Calls `GET /v1/workspace`. Used by `ActionsPage` on mount. + +### `fetchPromotionRequests({ status?, limit? })` + +Calls `GET /v1/promotion-requests` with optional query parameters. Used by `ActionsPage` when +`promotion_requires_approval` is true to populate the pending requests table. + --- ## Shared components diff --git a/examples/README.md b/examples/README.md index 5d1517a..2a142e5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,10 +7,10 @@ This folder holds **copy-pasteable** references for wiring FlightDeck into a rea 1. **Emit run events** from your app or a test harness — see [integration/](integration/README.md) (`emit_sample_events.py` and `POST /v1/events` shape). 2. **Ingest** evidence: `flightdeck runs ingest ` (or JSON array file), or HTTP `POST /v1/events` while `flightdeck serve` is running. 3. **Register** a release bundle: `flightdeck release register ` then **`flightdeck release verify`** against the same tree before you trust the checksum. -4. **Diff and gate** in CI: `flightdeck release diff …` with **`--fail-on-policy`** when you want a non-zero exit without mutating promotion — see [ci/](ci/README.md) and `ledger_gate.py` / GitHub Actions templates. -5. **Promote or rollback** via CLI (`flightdeck release promote` / `rollback`) or HTTP `POST /v1/promote` and `POST /v1/rollback` (token + loopback rules apply). -6. **Run the server** in a container or compose stack — see [deploy/](deploy/README.md). -7. **Observe** aggregate ledger size with **`GET /v1/metrics`** (JSON counters; read-only, same access tier as other `GET /v1/*` routes). +4. **Diff and gate** in CI: `flightdeck release diff …` with **`--fail-on-policy`** when you want a non-zero exit without mutating promotion — see [ci/](ci/README.md) and `ledger_gate.py` / GitHub Actions templates. Optional **`pricing_catalog_path`** in `flightdeck.yaml` adds **`pricing.catalog`** / **`pricing.hints`** on diffs (see [docs/pricing-catalog.md](../docs/pricing-catalog.md)). +5. **Promote or rollback** via CLI (`flightdeck release promote` / `rollback`) or HTTP `POST /v1/promote` and `POST /v1/rollback` (token + loopback rules apply). When **`promotion_requires_approval: true`**, use **`release promote-request`** / **`promote-confirm`** or **`POST /v1/promote/request`** then **`POST /v1/promote/confirm`** — see [ci/promote_with_approval.sh](ci/promote_with_approval.sh). +6. **Run the server** in a container or compose stack — see [deploy/](deploy/README.md). The bundled UI calls **`GET /v1/workspace`** to choose direct promote vs request/confirm. +7. **Triage runs** with **`flightdeck runs list`** or **`GET /v1/runs`**, and **observe** aggregate ledger size with **`GET /v1/metrics`** (JSON counters; read-only, same access tier as other `GET /v1/*` routes). ## Subfolders diff --git a/examples/ci/README.md b/examples/ci/README.md index b5f9ea6..733a865 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.1.0" +pip install "flightdeck-ai>=1.1.1" export WORKSPACE="$(mktemp -d)" export QUICKSTART_ROOT=/path/to/flightdeck/examples/quickstart python /path/to/flightdeck/examples/ci/ledger_gate.py @@ -51,11 +51,23 @@ Copy a workflow from `github-actions/` into `.github/workflows/` in your reposit |------|----------| | [`policy-gate-monorepo.yml`](github-actions/policy-gate-monorepo.yml) | This repository (or a fork): `uv sync` + `uv run python examples/ci/ledger_gate.py`. | | [`policy-gate-pypi.yml`](github-actions/policy-gate-pypi.yml) | Another repo: install **`flightdeck-ai`** from PyPI and sparse-checkout upstream `examples/quickstart` for fixtures (pin the checkout ref to match your installed version when possible). | +| [`promote-approval-twostep.yml`](github-actions/promote-approval-twostep.yml) | Example **`workflow_dispatch`** job that runs **`promote-request`** and logs **`request_id`** for a follow-up confirm. | ### Promoting from CI `flightdeck release promote` is intentionally **not** in the gate script: many teams run diff/verify on every PR and only promote from a protected branch or manual workflow with secrets and review. If you automate promote, reuse the same workspace (or a trusted replica), set policy explicitly, and pass a non-empty `--reason` (for example the Git run URL). +### Human approval (`promotion_requires_approval`) + +When **`promotion_requires_approval: true`** in `flightdeck.yaml`, direct **`release promote`** / **`POST /v1/promote`** are rejected until a pending row is confirmed: + +1. **`flightdeck release promote-request …`** (or **`POST /v1/promote/request`**) — prints **`request_id=…`** on success; policy must pass for the request to be created. +2. **`flightdeck release promote-confirm --approval-reason "…"`** (or **`POST /v1/promote/confirm`**) — applies the promote; policy is evaluated again on confirm. + +Shell helper (bash): **[promote_with_approval.sh](promote_with_approval.sh)** — `request` and `confirm` subcommands wrap the CLI. + +GitHub Actions sketch: **[github-actions/promote-approval-twostep.yml](github-actions/promote-approval-twostep.yml)** — `workflow_dispatch` runs **request**; run **confirm** from a separate gated job or locally after review, passing the captured `request_id`. + ## Related - [Quickstart fixtures](../quickstart/README.md) diff --git a/examples/ci/github-actions/policy-gate-pypi.yml b/examples/ci/github-actions/policy-gate-pypi.yml index e294f88..d8aa794 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.1.0" + FLIGHTDECK_AI_SPEC: ">=1.1.1" jobs: ledger-gate: diff --git a/examples/ci/github-actions/promote-approval-twostep.yml b/examples/ci/github-actions/promote-approval-twostep.yml new file mode 100644 index 0000000..b736b6c --- /dev/null +++ b/examples/ci/github-actions/promote-approval-twostep.yml @@ -0,0 +1,46 @@ +# Example: workflow_dispatch runs promote-request and logs request_id= for a human follow-up. +# Copy into your repo; point `working-directory` at a clone that contains flightdeck.yaml with +# promotion_requires_approval: true and a populated ledger. +# +# Confirm step: run locally or in a second gated workflow: +# flightdeck release promote-confirm "" --approval-reason "…" + +name: flightdeck-promote-approval-example + +on: + workflow_dispatch: + inputs: + release_id: + description: Registered release ID to promote + required: true + type: string + environment: + description: Target environment + required: true + default: local + type: string + window: + description: Evidence window (e.g. 7d) + required: true + default: 7d + type: string + reason: + description: Rationale for the promotion request + required: true + type: string + +jobs: + request: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v5 + - name: Install FlightDeck + run: uv sync --frozen --extra dev + - name: Request promotion (capture request_id from log) + working-directory: ./path/to/your-flightdeck-workspace + run: | + uv run flightdeck release promote-request "${{ inputs.release_id }}" \ + --env "${{ inputs.environment }}" \ + --window "${{ inputs.window }}" \ + --reason "${{ inputs.reason }}" diff --git a/examples/ci/promote_with_approval.sh b/examples/ci/promote_with_approval.sh new file mode 100644 index 0000000..f787dce --- /dev/null +++ b/examples/ci/promote_with_approval.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Two-step human-in-the-loop promote when flightdeck.yaml sets promotion_requires_approval: true. +# Usage: +# promote_with_approval.sh request "" +# promote_with_approval.sh confirm "" +# +# The "request" step prints request_id=… for capture in CI logs or a follow-up job. +set -euo pipefail + +cmd="${1:?command: request | confirm}" +shift + +case "$cmd" in + request) + rel="${1:?release_id}" + env="${2:?environment}" + win="${3:?window}" + reason="${4:?reason}" + flightdeck release promote-request "$rel" --env "$env" --window "$win" --reason "$reason" + ;; + confirm) + rid="${1:?request_id}" + areason="${2:?approval_reason}" + flightdeck release promote-confirm "$rid" --approval-reason "$areason" + ;; + *) + echo "usage: $0 request \"\"" >&2 + echo " $0 confirm \"\"" >&2 + exit 2 + ;; +esac diff --git a/examples/deploy/Dockerfile b/examples/deploy/Dockerfile index ff6d071..a2788eb 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.1.0" + && pip install --no-cache-dir "flightdeck-ai>=1.1.1" WORKDIR /workspace diff --git a/examples/deploy/chart/flightdeck/Chart.yaml b/examples/deploy/chart/flightdeck/Chart.yaml index ddc2714..dcffa04 100644 --- a/examples/deploy/chart/flightdeck/Chart.yaml +++ b/examples/deploy/chart/flightdeck/Chart.yaml @@ -3,4 +3,4 @@ name: flightdeck description: Optional Helm chart for flightdeck serve (single-replica reference) type: application version: 0.1.0 -appVersion: "1.1.0" +appVersion: "1.1.1" diff --git a/pyproject.toml b/pyproject.toml index eb006b5..3951ed6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "flightdeck-ai" -version = "1.1.0" +version = "1.1.1" description = "AI Release Governance for production agents." readme = "README.md" license = "Apache-2.0" diff --git a/schemas/v1/workspace_public.schema.json b/schemas/v1/workspace_public.schema.json new file mode 100644 index 0000000..225f20d --- /dev/null +++ b/schemas/v1/workspace_public.schema.json @@ -0,0 +1,36 @@ +{ + "description": "Read-only workspace flags for ``GET /v1/workspace`` (no secrets, no full YAML).", + "properties": { + "api_version": { + "const": "v1", + "default": "v1", + "title": "Api Version", + "type": "string" + }, + "kind": { + "const": "WorkspacePublic", + "default": "WorkspacePublic", + "title": "Kind", + "type": "string" + }, + "pricing_catalog_configured": { + "title": "Pricing Catalog Configured", + "type": "boolean" + }, + "promotion_requires_approval": { + "title": "Promotion Requires Approval", + "type": "boolean" + }, + "server_version": { + "title": "Server Version", + "type": "string" + } + }, + "required": [ + "promotion_requires_approval", + "pricing_catalog_configured", + "server_version" + ], + "title": "WorkspacePublic", + "type": "object" +} diff --git a/scripts/generate_schemas.py b/scripts/generate_schemas.py index aec96bb..e31f8f4 100644 --- a/scripts/generate_schemas.py +++ b/scripts/generate_schemas.py @@ -4,7 +4,7 @@ from pathlib import Path from flightdeck.catalog import PricingCatalog -from flightdeck.models import Policy, PricingTable, ReleaseArtifact, RunEvent +from flightdeck.models import Policy, PricingTable, ReleaseArtifact, RunEvent, WorkspacePublic def write_schema(path: Path, schema: dict) -> None: @@ -19,6 +19,7 @@ def main() -> None: write_schema(root / "pricing_table.schema.json", PricingTable.model_json_schema()) write_schema(root / "policy.schema.json", Policy.model_json_schema()) write_schema(root / "pricing_catalog.schema.json", PricingCatalog.model_json_schema()) + write_schema(root / "workspace_public.schema.json", WorkspacePublic.model_json_schema()) if __name__ == "__main__": diff --git a/src/flightdeck/__init__.py b/src/flightdeck/__init__.py index 5539e47..34e7a5d 100644 --- a/src/flightdeck/__init__.py +++ b/src/flightdeck/__init__.py @@ -1,3 +1,3 @@ """FlightDeck - AI Release Governance for production agents.""" -__version__ = "1.1.0" +__version__ = "1.1.1" diff --git a/src/flightdeck/models.py b/src/flightdeck/models.py index 1c13dc4..385420c 100644 --- a/src/flightdeck/models.py +++ b/src/flightdeck/models.py @@ -30,6 +30,25 @@ class WorkspaceConfig(BaseModel): promotion_requires_approval: bool = False +class WorkspacePublic(BaseModel): + """Read-only workspace flags for ``GET /v1/workspace`` (no secrets, no full YAML).""" + + api_version: Literal["v1"] = "v1" + kind: Literal["WorkspacePublic"] = "WorkspacePublic" + promotion_requires_approval: bool + pricing_catalog_configured: bool + server_version: str + + @classmethod + def from_workspace_config(cls, cfg: WorkspaceConfig, *, server_version: str) -> WorkspacePublic: + path = (cfg.pricing_catalog_path or "").strip() + return cls( + promotion_requires_approval=cfg.promotion_requires_approval, + pricing_catalog_configured=bool(path), + server_version=server_version, + ) + + class PricingEntry(BaseModel): model: str input_usd_per_1k_tokens: float = Field(ge=0) diff --git a/src/flightdeck/sdk/client.py b/src/flightdeck/sdk/client.py index 0f13968..35655d1 100644 --- a/src/flightdeck/sdk/client.py +++ b/src/flightdeck/sdk/client.py @@ -44,6 +44,11 @@ def health(self) -> dict[str, Any]: resp.raise_for_status() return resp.json() + def get_workspace(self) -> dict[str, Any]: + resp = self._request_with_retry("GET", "/v1/workspace", headers=self._auth_headers() or None) + resp.raise_for_status() + return resp.json() + def list_releases(self) -> dict[str, Any]: resp = self._request_with_retry("GET", "/v1/releases", headers=self._auth_headers() or None) resp.raise_for_status() @@ -293,6 +298,11 @@ async def health(self) -> dict[str, Any]: resp.raise_for_status() return resp.json() + async def get_workspace(self) -> dict[str, Any]: + resp = await self._request_with_retry("GET", "/v1/workspace", headers=self._auth_headers() or None) + resp.raise_for_status() + return resp.json() + async def list_releases(self) -> dict[str, Any]: resp = await self._request_with_retry("GET", "/v1/releases", headers=self._auth_headers() or None) resp.raise_for_status() diff --git a/src/flightdeck/server/routes/read.py b/src/flightdeck/server/routes/read.py index 7717083..8c9f8fc 100644 --- a/src/flightdeck/server/routes/read.py +++ b/src/flightdeck/server/routes/read.py @@ -2,13 +2,21 @@ from fastapi import APIRouter, HTTPException, Query, Request -from flightdeck.models import PromotionRequestRecord +from flightdeck import __version__ as flightdeck_version +from flightdeck.models import PromotionRequestRecord, WorkspacePublic from flightdeck.operations import OperationError, list_timeline, query_run_events_page from flightdeck.server.routes.common import ensure_app_state router = APIRouter() +@router.get("/v1/workspace") +def get_workspace(request: Request) -> WorkspacePublic: + """Non-secret workspace flags for operators and the web UI.""" + cfg, _ = ensure_app_state(request) + return WorkspacePublic.from_workspace_config(cfg, server_version=flightdeck_version) + + @router.get("/v1/releases") def get_releases(request: Request) -> dict[str, list[dict[str, object]]]: _, storage = ensure_app_state(request) diff --git a/src/flightdeck/server/static/assets/index-B6DTQYWv.js b/src/flightdeck/server/static/assets/index-B6DTQYWv.js new file mode 100644 index 0000000..19bf142 --- /dev/null +++ b/src/flightdeck/server/static/assets/index-B6DTQYWv.js @@ -0,0 +1,11 @@ +(function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const m of document.querySelectorAll('link[rel="modulepreload"]'))f(m);new MutationObserver(m=>{for(const h of m)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 d(m){const h={};return m.integrity&&(h.integrity=m.integrity),m.referrerPolicy&&(h.referrerPolicy=m.referrerPolicy),m.crossOrigin==="use-credentials"?h.credentials="include":m.crossOrigin==="anonymous"?h.credentials="omit":h.credentials="same-origin",h}function f(m){if(m.ep)return;m.ep=!0;const h=d(m);fetch(m.href,h)}})();var Hf={exports:{}},Un={};var cm;function py(){if(cm)return Un;cm=1;var i=Symbol.for("react.transitional.element"),s=Symbol.for("react.fragment");function d(f,m,h){var b=null;if(h!==void 0&&(b=""+h),m.key!==void 0&&(b=""+m.key),"key"in m){h={};for(var R in m)R!=="key"&&(h[R]=m[R])}else h=m;return m=h.ref,{$$typeof:i,type:f,key:b,ref:m!==void 0?m:null,props:h}}return Un.Fragment=s,Un.jsx=d,Un.jsxs=d,Un}var fm;function gy(){return fm||(fm=1,Hf.exports=py()),Hf.exports}var o=gy(),qf={exports:{}},ee={};var sm;function Sy(){if(sm)return ee;sm=1;var i=Symbol.for("react.transitional.element"),s=Symbol.for("react.portal"),d=Symbol.for("react.fragment"),f=Symbol.for("react.strict_mode"),m=Symbol.for("react.profiler"),h=Symbol.for("react.consumer"),b=Symbol.for("react.context"),R=Symbol.for("react.forward_ref"),S=Symbol.for("react.suspense"),y=Symbol.for("react.memo"),D=Symbol.for("react.lazy"),T=Symbol.for("react.activity"),L=Symbol.iterator;function H(g){return g===null||typeof g!="object"?null:(g=L&&g[L]||g["@@iterator"],typeof g=="function"?g:null)}var K={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},w=Object.assign,B={};function V(g,j,q){this.props=g,this.context=j,this.refs=B,this.updater=q||K}V.prototype.isReactComponent={},V.prototype.setState=function(g,j){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,j,"setState")},V.prototype.forceUpdate=function(g){this.updater.enqueueForceUpdate(this,g,"forceUpdate")};function k(){}k.prototype=V.prototype;function X(g,j,q){this.props=g,this.context=j,this.refs=B,this.updater=q||K}var Q=X.prototype=new k;Q.constructor=X,w(Q,V.prototype),Q.isPureReactComponent=!0;var ae=Array.isArray;function oe(){}var Y={H:null,A:null,T:null,S:null},re=Object.prototype.hasOwnProperty;function Ne(g,j,q){var Z=q.ref;return{$$typeof:i,type:g,key:j,ref:Z!==void 0?Z:null,props:q}}function Ze(g,j){return Ne(g.type,j,g.props)}function ke(g){return typeof g=="object"&&g!==null&&g.$$typeof===i}function Ye(g){var j={"=":"=0",":":"=2"};return"$"+g.replace(/[=:]/g,function(q){return j[q]})}var ct=/\/+/g;function Pe(g,j){return typeof g=="object"&&g!==null&&g.key!=null?Ye(""+g.key):j.toString(36)}function ze(g){switch(g.status){case"fulfilled":return g.value;case"rejected":throw g.reason;default:switch(typeof g.status=="string"?g.then(oe,oe):(g.status="pending",g.then(function(j){g.status==="pending"&&(g.status="fulfilled",g.value=j)},function(j){g.status==="pending"&&(g.status="rejected",g.reason=j)})),g.status){case"fulfilled":return g.value;case"rejected":throw g.reason}}throw g}function O(g,j,q,Z,P){var ne=typeof g;(ne==="undefined"||ne==="boolean")&&(g=null);var ye=!1;if(g===null)ye=!0;else switch(ne){case"bigint":case"string":case"number":ye=!0;break;case"object":switch(g.$$typeof){case i:case s:ye=!0;break;case D:return ye=g._init,O(ye(g._payload),j,q,Z,P)}}if(ye)return P=P(g),ye=Z===""?"."+Pe(g,0):Z,ae(P)?(q="",ye!=null&&(q=ye.replace(ct,"$&/")+"/"),O(P,j,q,"",function(Ga){return Ga})):P!=null&&(ke(P)&&(P=Ze(P,q+(P.key==null||g&&g.key===P.key?"":(""+P.key).replace(ct,"$&/")+"/")+ye)),j.push(P)),1;ye=0;var Fe=Z===""?".":Z+":";if(ae(g))for(var De=0;De>>1,pe=O[ve];if(0>>1;vem(q,F))Zm(P,q)?(O[ve]=P,O[Z]=F,ve=Z):(O[ve]=q,O[j]=F,ve=j);else if(Zm(P,F))O[ve]=P,O[Z]=F,ve=Z;else break e}}return G}function m(O,G){var F=O.sortIndex-G.sortIndex;return F!==0?F:O.id-G.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,R=b.now();i.unstable_now=function(){return b.now()-R}}var S=[],y=[],D=1,T=null,L=3,H=!1,K=!1,w=!1,B=!1,V=typeof setTimeout=="function"?setTimeout:null,k=typeof clearTimeout=="function"?clearTimeout:null,X=typeof setImmediate<"u"?setImmediate:null;function Q(O){for(var G=d(y);G!==null;){if(G.callback===null)f(y);else if(G.startTime<=O)f(y),G.sortIndex=G.expirationTime,s(S,G);else break;G=d(y)}}function ae(O){if(w=!1,Q(O),!K)if(d(S)!==null)K=!0,oe||(oe=!0,Ye());else{var G=d(y);G!==null&&ze(ae,G.startTime-O)}}var oe=!1,Y=-1,re=5,Ne=-1;function Ze(){return B?!0:!(i.unstable_now()-NeO&&Ze());){var ve=T.callback;if(typeof ve=="function"){T.callback=null,L=T.priorityLevel;var pe=ve(T.expirationTime<=O);if(O=i.unstable_now(),typeof pe=="function"){T.callback=pe,Q(O),G=!0;break t}T===d(S)&&f(S),Q(O)}else f(S);T=d(S)}if(T!==null)G=!0;else{var g=d(y);g!==null&&ze(ae,g.startTime-O),G=!1}}break e}finally{T=null,L=F,H=!1}G=void 0}}finally{G?Ye():oe=!1}}}var Ye;if(typeof X=="function")Ye=function(){X(ke)};else if(typeof MessageChannel<"u"){var ct=new MessageChannel,Pe=ct.port2;ct.port1.onmessage=ke,Ye=function(){Pe.postMessage(null)}}else Ye=function(){V(ke,0)};function ze(O,G){Y=V(function(){O(i.unstable_now())},G)}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(O){O.callback=null},i.unstable_forceFrameRate=function(O){0>O||125ve?(O.sortIndex=F,s(y,O),d(S)===null&&O===d(y)&&(w?(k(Y),Y=-1):w=!0,ze(ae,F-ve))):(O.sortIndex=pe,s(S,O),K||H||(K=!0,oe||(oe=!0,Ye()))),O},i.unstable_shouldYield=Ze,i.unstable_wrapCallback=function(O){var G=L;return function(){var F=L;L=G;try{return O.apply(this,arguments)}finally{L=F}}}})(Yf)),Yf}var dm;function _y(){return dm||(dm=1,Lf.exports=by()),Lf.exports}var Gf={exports:{}},We={};var mm;function Ey(){if(mm)return We;mm=1;var i=kf();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(),Gf.exports=Ey(),Gf.exports}var vm;function Ny(){if(vm)return Hn;vm=1;var i=_y(),s=kf(),d=xy();function f(e){var t="https://react.dev/errors/"+e;if(1pe||(e.current=ve[pe],ve[pe]=null,pe--)}function q(e,t){pe++,ve[pe]=e.current,e.current=t}var Z=g(null),P=g(null),ne=g(null),ye=g(null);function Fe(e,t){switch(q(ne,t),q(P,e),q(Z,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=Cd(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}j(Z),q(Z,e)}function De(){j(Z),j(P),j(ne)}function Ga(e){e.memoizedState!==null&&q(ye,e);var t=Z.current,l=Cd(t,e.type);t!==l&&(q(P,e),q(Z,l))}function Xn(e){P.current===e&&(j(Z),j(P)),ye.current===e&&(j(ye),On._currentValue=F)}var yi,us;function Cl(e){if(yi===void 0)try{throw Error()}catch(l){var t=l.stack.trim().match(/\n( *(at )?)/);yi=t&&t[1]||"",us=-1)":-1n||v[a]!==N[n]){var C=` +`+v[a].replace(" at new "," at ");return e.displayName&&C.includes("")&&(C=C.replace("",e.displayName)),C}while(1<=a&&0<=n);break}}}finally{pi=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?Cl(l):""}function $m(e,t){switch(e.tag){case 26:case 27:case 5:return Cl(e.type);case 16:return Cl("Lazy");case 13:return e.child!==t&&t!==null?Cl("Suspense Fallback"):Cl("Suspense");case 19:return Cl("SuspenseList");case 0:case 15:return gi(e.type,!1);case 11:return gi(e.type.render,!1);case 1:return gi(e.type,!0);case 31:return Cl("Activity");default:return""}}function is(e){try{var t="",l=null;do t+=$m(e,l),l=e,e=e.return;while(e);return t}catch(a){return` +Error generating stack: `+a.message+` +`+a.stack}}var Si=Object.prototype.hasOwnProperty,bi=i.unstable_scheduleCallback,_i=i.unstable_cancelCallback,Wm=i.unstable_shouldYield,km=i.unstable_requestPaint,ft=i.unstable_now,Fm=i.unstable_getCurrentPriorityLevel,cs=i.unstable_ImmediatePriority,fs=i.unstable_UserBlockingPriority,Qn=i.unstable_NormalPriority,Im=i.unstable_LowPriority,ss=i.unstable_IdlePriority,Pm=i.log,eh=i.unstable_setDisableYieldValue,Xa=null,st=null;function il(e){if(typeof Pm=="function"&&eh(e),st&&typeof st.setStrictMode=="function")try{st.setStrictMode(Xa,e)}catch{}}var rt=Math.clz32?Math.clz32:ah,th=Math.log,lh=Math.LN2;function ah(e){return e>>>=0,e===0?32:31-(th(e)/lh|0)|0}var Zn=256,wn=262144,Vn=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 Kn(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 Qa(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function nh(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 rs(){var e=Vn;return Vn<<=1,(Vn&62914560)===0&&(Vn=4194304),e}function Ei(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function Za(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function uh(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,N=e.hiddenUpdates;for(l=c&~l;0"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var oh=/[\n"\\]/g;function bt(e){return e.replace(oh,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Ri(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=""+St(t)):e.value!==""+St(t)&&(e.value=""+St(t)):c!=="submit"&&c!=="reset"||e.removeAttribute("value"),t!=null?zi(e,c,St(t)):l!=null?zi(e,c,St(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=""+St(r):e.removeAttribute("name")}function xs(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)){ji(e);return}l=l!=null?""+St(l):"",t=t!=null?""+St(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),ji(e)}function zi(e,t,l){t==="number"&&Wn(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"),Ui=!1;if(Zt)try{var Ja={};Object.defineProperty(Ja,"passive",{get:function(){Ui=!0}}),window.addEventListener("test",Ja,Ja),window.removeEventListener("test",Ja,Ja)}catch{Ui=!1}var fl=null,Hi=null,Fn=null;function Os(){if(Fn)return Fn;var e,t=Hi,l=t.length,a,n="value"in fl?fl.value:fl.textContent,u=n.length;for(e=0;e=ka),qs=" ",Bs=!1;function Ls(e,t){switch(e){case"keyup":return Yh.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Ys(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ia=!1;function Xh(e,t){switch(e){case"compositionend":return Ys(t);case"keypress":return t.which!==32?null:(Bs=!0,qs);case"textInput":return e=t.data,e===qs&&Bs?null:e;default:return null}}function Qh(e,t){if(ia)return e==="compositionend"||!Gi&&Ls(e,t)?(e=Os(),Fn=Hi=fl=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=Js(l)}}function Ws(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ws(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function ks(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Wn(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=Wn(e.document)}return t}function Zi(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 kh=Zt&&"documentMode"in document&&11>=document.documentMode,ca=null,wi=null,en=null,Vi=!1;function Fs(e,t,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;Vi||ca==null||ca!==Wn(a)||(a=ca,"selectionStart"in a&&Zi(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}),en&&Pa(en,a)||(en=a,a=wu(wi,"onSelect"),0>=c,n-=c,qt=1<<32-rt(t)+n|l<le?(fe=$,$=null):fe=$.sibling;var me=A(_,$,x[le],M);if(me===null){$===null&&($=fe);break}e&&$&&me.alternate===null&&t(_,$),p=u(me,p,le),de===null?W=me:de.sibling=me,de=me,$=fe}if(le===x.length)return l(_,$),se&&Vt(_,le),W;if($===null){for(;lele?(fe=$,$=null):fe=$.sibling;var Ol=A(_,$,me.value,M);if(Ol===null){$===null&&($=fe);break}e&&$&&Ol.alternate===null&&t(_,$),p=u(Ol,p,le),de===null?W=Ol:de.sibling=Ol,de=Ol,$=fe}if(me.done)return l(_,$),se&&Vt(_,le),W;if($===null){for(;!me.done;le++,me=x.next())me=U(_,me.value,M),me!==null&&(p=u(me,p,le),de===null?W=me:de.sibling=me,de=me);return se&&Vt(_,le),W}for($=a($);!me.done;le++,me=x.next())me=z($,_,le,me.value,M),me!==null&&(e&&me.alternate!==null&&$.delete(me.key===null?le:me.key),p=u(me,p,le),de===null?W=me:de.sibling=me,de=me);return e&&$.forEach(function(yy){return t(_,yy)}),se&&Vt(_,le),W}function Ee(_,p,x,M){if(typeof x=="object"&&x!==null&&x.type===w&&x.key===null&&(x=x.props.children),typeof x=="object"&&x!==null){switch(x.$$typeof){case H:e:{for(var W=x.key;p!==null;){if(p.key===W){if(W=x.type,W===w){if(p.tag===7){l(_,p.sibling),M=n(p,x.props.children),M.return=_,_=M;break e}}else if(p.elementType===W||typeof W=="object"&&W!==null&&W.$$typeof===re&&Zl(W)===p.type){l(_,p.sibling),M=n(p,x.props),cn(M,x),M.return=_,_=M;break e}l(_,p);break}else t(_,p);p=p.sibling}x.type===w?(M=Ll(x.props.children,_.mode,M,x.key),M.return=_,_=M):(M=cu(x.type,x.key,x.props,null,_.mode,M),cn(M,x),M.return=_,_=M)}return c(_);case K:e:{for(W=x.key;p!==null;){if(p.key===W)if(p.tag===4&&p.stateNode.containerInfo===x.containerInfo&&p.stateNode.implementation===x.implementation){l(_,p.sibling),M=n(p,x.children||[]),M.return=_,_=M;break e}else{l(_,p);break}else t(_,p);p=p.sibling}M=Ii(x,_.mode,M),M.return=_,_=M}return c(_);case re:return x=Zl(x),Ee(_,p,x,M)}if(ze(x))return J(_,p,x,M);if(Ye(x)){if(W=Ye(x),typeof W!="function")throw Error(f(150));return x=W.call(x),I(_,p,x,M)}if(typeof x.then=="function")return Ee(_,p,hu(x),M);if(x.$$typeof===X)return Ee(_,p,ru(_,x),M);vu(_,x)}return typeof x=="string"&&x!==""||typeof x=="number"||typeof x=="bigint"?(x=""+x,p!==null&&p.tag===6?(l(_,p.sibling),M=n(p,x),M.return=_,_=M):(l(_,p),M=Fi(x,_.mode,M),M.return=_,_=M),c(_)):l(_,p)}return function(_,p,x,M){try{un=0;var W=Ee(_,p,x,M);return ga=null,W}catch($){if($===pa||$===du)throw $;var de=dt(29,$,null,_.mode);return de.lanes=M,de.return=_,de}}}var Vl=br(!0),_r=br(!1),ml=!1;function rc(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function oc(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 hl(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function vl(e,t,l){var a=e.updateQueue;if(a===null)return null;if(a=a.shared,(he&2)!==0){var n=a.pending;return n===null?t.next=t:(t.next=n.next,n.next=t),a.pending=t,t=iu(e),nr(e,null,l),t}return uu(e,a,t,l),iu(e)}function fn(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,ds(e,l)}}function dc(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 mc=!1;function sn(){if(mc){var e=ya;if(e!==null)throw e}}function rn(e,t,l,a){mc=!1;var n=e.updateQueue;ml=!1;var u=n.firstBaseUpdate,c=n.lastBaseUpdate,r=n.shared.pending;if(r!==null){n.shared.pending=null;var v=r,N=v.next;v.next=null,c===null?u=N:c.next=N,c=v;var C=e.alternate;C!==null&&(C=C.updateQueue,r=C.lastBaseUpdate,r!==c&&(r===null?C.firstBaseUpdate=N:r.next=N,C.lastBaseUpdate=v))}if(u!==null){var U=n.baseState;c=0,C=N=v=null,r=u;do{var A=r.lane&-536870913,z=A!==r.lane;if(z?(ce&A)===A:(a&A)===A){A!==0&&A===va&&(mc=!0),C!==null&&(C=C.next={lane:0,tag:r.tag,payload:r.payload,callback:null,next:null});e:{var J=e,I=r;A=t;var Ee=l;switch(I.tag){case 1:if(J=I.payload,typeof J=="function"){U=J.call(Ee,U,A);break e}U=J;break e;case 3:J.flags=J.flags&-65537|128;case 0:if(J=I.payload,A=typeof J=="function"?J.call(Ee,U,A):J,A==null)break e;U=T({},U,A);break e;case 2:ml=!0}}A=r.callback,A!==null&&(e.flags|=64,z&&(e.flags|=8192),z=n.callbacks,z===null?n.callbacks=[A]:z.push(A))}else z={lane:A,tag:r.tag,payload:r.payload,callback:r.callback,next:null},C===null?(N=C=z,v=U):C=C.next=z,c|=A;if(r=r.next,r===null){if(r=n.shared.pending,r===null)break;z=r,r=z.next,z.next=null,n.lastBaseUpdate=z,n.shared.pending=null}}while(!0);C===null&&(v=U),n.baseState=v,n.firstBaseUpdate=N,n.lastBaseUpdate=C,u===null&&(n.shared.lanes=0),bl|=c,e.lanes=c,e.memoizedState=U}}function Er(e,t){if(typeof e!="function")throw Error(f(191,e));e.call(t)}function xr(e,t){var l=e.callbacks;if(l!==null)for(e.callbacks=null,e=0;eu?u:8;var c=O.T,r={};O.T=r,Dc(e,!1,t,l);try{var v=n(),N=O.S;if(N!==null&&N(r,v),v!==null&&typeof v=="object"&&typeof v.then=="function"){var C=uv(v,a);mn(e,t,C,pt(e))}else mn(e,t,a,pt(e))}catch(U){mn(e,t,{then:function(){},status:"rejected",reason:U},pt())}finally{G.p=u,c!==null&&r.types!==null&&(c.types=r.types),O.T=c}}function ov(){}function Oc(e,t,l,a){if(e.tag!==5)throw Error(f(476));var n=to(e).queue;eo(e,n,t,F,l===null?ov:function(){return lo(e),l(a)})}function to(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:F,baseState:F,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Wt,lastRenderedState:F},next:null};var l={};return t.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Wt,lastRenderedState:l},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function lo(e){var t=to(e);t.next===null&&(t=e.alternate.memoizedState),mn(e,t.next.queue,{},pt())}function Cc(){return Ke(On)}function ao(){return Ue().memoizedState}function no(){return Ue().memoizedState}function dv(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var l=pt();e=hl(l);var a=vl(t,e,l);a!==null&&(it(a,t,l),fn(a,t,l)),t={cache:ic()},e.payload=t;return}t=t.return}}function mv(e,t,l){var a=pt();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Tu(e)?io(t,l):(l=Wi(e,t,l,a),l!==null&&(it(l,e,a),co(l,t,a)))}function uo(e,t,l){var a=pt();mn(e,t,l,a)}function mn(e,t,l,a){var n={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(Tu(e))io(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,ot(r,c))return uu(e,t,n,0),xe===null&&nu(),!1}catch{}if(l=Wi(e,t,n,a),l!==null)return it(l,e,a),co(l,t,a),!0}return!1}function Dc(e,t,l,a){if(a={lane:2,revertLane:of(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Tu(e)){if(t)throw Error(f(479))}else t=Wi(e,l,a,2),t!==null&&it(t,e,2)}function Tu(e){var t=e.alternate;return e===te||t!==null&&t===te}function io(e,t){ba=gu=!0;var l=e.pending;l===null?t.next=t:(t.next=l.next,l.next=t),e.pending=t}function co(e,t,l){if((l&4194048)!==0){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,ds(e,l)}}var hn={readContext:Ke,use:_u,useCallback:Oe,useContext:Oe,useEffect:Oe,useImperativeHandle:Oe,useLayoutEffect:Oe,useInsertionEffect:Oe,useMemo:Oe,useReducer:Oe,useRef:Oe,useState:Oe,useDebugValue:Oe,useDeferredValue:Oe,useTransition:Oe,useSyncExternalStore:Oe,useId:Oe,useHostTransitionStatus:Oe,useFormState:Oe,useActionState:Oe,useOptimistic:Oe,useMemoCache:Oe,useCacheRefresh:Oe};hn.useEffectEvent=Oe;var fo={readContext:Ke,use:_u,useCallback:function(e,t){return Ie().memoizedState=[e,t===void 0?null:t],e},useContext:Ke,useEffect:Vr,useImperativeHandle:function(e,t,l){l=l!=null?l.concat([e]):null,xu(4194308,4,Wr.bind(null,t,e),l)},useLayoutEffect:function(e,t){return xu(4194308,4,e,t)},useInsertionEffect:function(e,t){xu(4,2,e,t)},useMemo:function(e,t){var l=Ie();t=t===void 0?null:t;var a=e();if(Kl){il(!0);try{e()}finally{il(!1)}}return l.memoizedState=[a,t],a},useReducer:function(e,t,l){var a=Ie();if(l!==void 0){var n=l(t);if(Kl){il(!0);try{l(t)}finally{il(!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=mv.bind(null,te,e),[a.memoizedState,e]},useRef:function(e){var t=Ie();return e={current:e},t.memoizedState=e},useState:function(e){e=Tc(e);var t=e.queue,l=uo.bind(null,te,t);return t.dispatch=l,[e.memoizedState,l]},useDebugValue:Rc,useDeferredValue:function(e,t){var l=Ie();return zc(l,e,t)},useTransition:function(){var e=Tc(!1);return e=eo.bind(null,te,e.queue,!0,!1),Ie().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,l){var a=te,n=Ie();if(se){if(l===void 0)throw Error(f(407));l=l()}else{if(l=t(),xe===null)throw Error(f(349));(ce&127)!==0||zr(a,t,l)}n.memoizedState=l;var u={value:l,getSnapshot:t};return n.queue=u,Vr(Cr.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=Ie(),t=xe.identifierPrefix;if(se){var l=Bt,a=qt;l=(a&~(1<<32-rt(a)-1)).toString(32)+l,t="_"+t+"R_"+l,l=Su++,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[we]=t,u[et]=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($e(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&&Ft(t)}}return Ae(t),Kc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,l),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==a&&Ft(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=Ve,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}e[we]=t,e=!!(e.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||Rd(e.nodeValue,l)),e||ol(t,!0)}else e=Vu(e).createTextNode(a),e[we]=t,t.stateNode=e}return Ae(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[we]=t}else Yl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ae(t),e=!1}else l=lc(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=l),e=!0;if(!e)return t.flags&256?(ht(t),t):(ht(t),null);if((t.flags&128)!==0)throw Error(f(558))}return Ae(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[we]=t}else Yl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Ae(t),n=!1}else n=lc(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),n=!0;if(!n)return t.flags&256?(ht(t),t):(ht(t),null)}return ht(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),Ou(t,t.updateQueue),Ae(t),null);case 4:return De(),e===null&&vf(t.stateNode.containerInfo),Ae(t),null;case 10:return Jt(t.type),Ae(t),null;case 19:if(j(Me),a=t.memoizedState,a===null)return Ae(t),null;if(n=(t.flags&128)!==0,u=a.rendering,u===null)if(n)yn(a,!1);else{if(Ce!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(u=pu(e),u!==null){for(t.flags|=128,yn(a,!1),e=u.updateQueue,t.updateQueue=e,Ou(t,e),t.subtreeFlags=0,e=l,l=t.child;l!==null;)ur(l,e),l=l.sibling;return q(Me,Me.current&1|2),se&&Vt(t,a.treeForkCount),t.child}e=e.sibling}a.tail!==null&&ft()>Hu&&(t.flags|=128,n=!0,yn(a,!1),t.lanes=4194304)}else{if(!n)if(e=pu(u),e!==null){if(t.flags|=128,n=!0,e=e.updateQueue,t.updateQueue=e,Ou(t,e),yn(a,!0),a.tail===null&&a.tailMode==="hidden"&&!u.alternate&&!se)return Ae(t),null}else 2*ft()-a.renderingStartTime>Hu&&l!==536870912&&(t.flags|=128,n=!0,yn(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=ft(),e.sibling=null,l=Me.current,q(Me,n?l&1|2:l&1),se&&Vt(t,a.treeForkCount),e):(Ae(t),null);case 22:case 23:return ht(t),vc(),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&&(Ae(t),t.subtreeFlags&6&&(t.flags|=8192)):Ae(t),l=t.updateQueue,l!==null&&Ou(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&&j(Ql),null;case 24:return l=null,e!==null&&(l=e.memoizedState.cache),t.memoizedState.cache!==l&&(t.flags|=2048),Jt(He),Ae(t),null;case 25:return null;case 30:return null}throw Error(f(156,t.tag))}function gv(e,t){switch(ec(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Jt(He),De(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return Xn(t),null;case 31:if(t.memoizedState!==null){if(ht(t),t.alternate===null)throw Error(f(340));Yl()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(ht(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(f(340));Yl()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return j(Me),null;case 4:return De(),null;case 10:return Jt(t.type),null;case 22:case 23:return ht(t),vc(),e!==null&&j(Ql),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Jt(He),null;case 25:return null;default:return null}}function Mo(e,t){switch(ec(t),t.tag){case 3:Jt(He),De();break;case 26:case 27:case 5:Xn(t);break;case 4:De();break;case 31:t.memoizedState!==null&&ht(t);break;case 13:ht(t);break;case 19:j(Me);break;case 10:Jt(t.type);break;case 22:case 23:ht(t),vc(),e!==null&&j(Ql);break;case 24:Jt(He)}}function pn(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){Se(t,t.return,r)}}function gl(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,N=r;try{N()}catch(C){Se(n,v,C)}}}a=a.next}while(a!==u)}}catch(C){Se(t,t.return,C)}}function Uo(e){var t=e.updateQueue;if(t!==null){var l=e.stateNode;try{xr(t,l)}catch(a){Se(e,e.return,a)}}}function Ho(e,t,l){l.props=Jl(e.type,e.memoizedProps),l.state=e.memoizedState;try{l.componentWillUnmount()}catch(a){Se(e,t,a)}}function gn(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){Se(e,t,n)}}function Lt(e,t){var l=e.ref,a=e.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(n){Se(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){Se(e,t,n)}else l.current=null}function qo(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){Se(e,e.return,n)}}function Jc(e,t,l){try{var a=e.stateNode;Gv(a,e.type,l,t),a[et]=t}catch(n){Se(e,e.return,n)}}function Bo(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&Tl(e.type)||e.tag===4}function $c(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&&Tl(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 Wc(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=Qt));else if(a!==4&&(a===27&&Tl(e.type)&&(l=e.stateNode,t=null),e=e.child,e!==null))for(Wc(e,t,l),e=e.sibling;e!==null;)Wc(e,t,l),e=e.sibling}function Cu(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&&Tl(e.type)&&(l=e.stateNode),e=e.child,e!==null))for(Cu(e,t,l),e=e.sibling;e!==null;)Cu(e,t,l),e=e.sibling}function Lo(e){var t=e.stateNode,l=e.memoizedProps;try{for(var a=e.type,n=t.attributes;n.length;)t.removeAttributeNode(n[0]);$e(t,a,l),t[we]=e,t[et]=l}catch(u){Se(e,e.return,u)}}var It=!1,Le=!1,kc=!1,Yo=typeof WeakSet=="function"?WeakSet:Set,Qe=null;function Sv(e,t){if(e=e.containerInfo,gf=Iu,e=ks(e),Zi(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,N=0,C=0,U=e,A=null;t:for(;;){for(var z;U!==l||n!==0&&U.nodeType!==3||(r=c+n),U!==u||a!==0&&U.nodeType!==3||(v=c+a),U.nodeType===3&&(c+=U.nodeValue.length),(z=U.firstChild)!==null;)A=U,U=z;for(;;){if(U===e)break t;if(A===l&&++N===n&&(r=c),A===u&&++C===a&&(v=c),(z=U.nextSibling)!==null)break;U=A,A=U.parentNode}U=z}l=r===-1||v===-1?null:{start:r,end:v}}else l=null}l=l||{start:0,end:0}}else l=null;for(Sf={focusedElem:e,selectionRange:l},Iu=!1,Qe=t;Qe!==null;)if(t=Qe,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,Qe=e;else for(;Qe!==null;){switch(t=Qe,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"))),$e(u,a,l),u[we]=e,Xe(u),a=u;break e;case"link":var c=Vd("link","href",n).get(a+(l.href||""));if(c){for(var r=0;rEe&&(c=Ee,Ee=I,I=c);var _=$s(r,I),p=$s(r,Ee);if(_&&p&&(z.rangeCount!==1||z.anchorNode!==_.node||z.anchorOffset!==_.offset||z.focusNode!==p.node||z.focusOffset!==p.offset)){var x=U.createRange();x.setStart(_.node,_.offset),z.removeAllRanges(),I>Ee?(z.addRange(x),z.extend(p.node,p.offset)):(x.setEnd(p.node,p.offset),z.addRange(x))}}}}for(U=[],z=r;z=z.parentNode;)z.nodeType===1&&U.push({element:z,left:z.scrollLeft,top:z.scrollTop});for(typeof r.focus=="function"&&r.focus(),r=0;rl?32:l,O.T=null,l=af,af=null;var u=El,c=al;if(Ge=0,ja=El=null,al=0,(he&6)!==0)throw Error(f(331));var r=he;if(he|=4,ko(u.current),Jo(u,u.current,c,l),he=r,Nn(0,!1),st&&typeof st.onPostCommitFiberRoot=="function")try{st.onPostCommitFiberRoot(Xa,u)}catch{}return!0}finally{G.p=n,O.T=a,hd(e,t)}}function yd(e,t,l){t=Et(l,t),t=qc(e.stateNode,t,2),e=vl(e,t,2),e!==null&&(Za(e,2),Yt(e))}function Se(e,t,l){if(e.tag===3)yd(e,e,l);else for(;t!==null;){if(t.tag===3){yd(t,e,l);break}else if(t.tag===1){var a=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(_l===null||!_l.has(a))){e=Et(l,e),l=po(2),a=vl(t,l,2),a!==null&&(go(l,a,t,e),Za(a,2),Yt(a));break}}t=t.return}}function ff(e,t,l){var a=e.pingCache;if(a===null){a=e.pingCache=new Ev;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)||(Pc=!0,n.add(l),e=jv.bind(null,e,t,l),t.then(e,e))}function jv(e,t,l){var a=e.pingCache;a!==null&&a.delete(t),e.pingedLanes|=e.suspendedLanes&l,e.warmLanes&=~l,xe===e&&(ce&l)===l&&(Ce===4||Ce===3&&(ce&62914560)===ce&&300>ft()-Uu?(he&2)===0&&Ra(e,0):ef|=l,Aa===ce&&(Aa=0)),Yt(e)}function pd(e,t){t===0&&(t=rs()),e=Bl(e,t),e!==null&&(Za(e,t),Yt(e))}function Rv(e){var t=e.memoizedState,l=0;t!==null&&(l=t.retryLane),pd(e,l)}function zv(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),pd(e,l)}function Ov(e,t){return bi(e,t)}var Xu=null,Oa=null,sf=!1,Qu=!1,rf=!1,Nl=0;function Yt(e){e!==Oa&&e.next===null&&(Oa===null?Xu=Oa=e:Oa=Oa.next=e),Qu=!0,sf||(sf=!0,Dv())}function Nn(e,t){if(!rf&&Qu){rf=!0;do for(var l=!1,a=Xu;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-rt(42|e)+1)-1,u&=n&~(c&~r),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(l=!0,_d(a,u))}else u=ce,u=Kn(a,a===xe?u:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(u&3)===0||Qa(a,u)||(l=!0,_d(a,u));a=a.next}while(l);rf=!1}}function Cv(){gd()}function gd(){Qu=sf=!1;var e=0;Nl!==0&&Qv()&&(e=Nl);for(var t=ft(),l=null,a=Xu;a!==null;){var n=a.next,u=Sd(a,t);u===0?(a.next=null,l===null?Xu=n:l.next=n,n===null&&(Oa=l)):(l=a,(e!==0||(u&3)!==0)&&(Qu=!0)),a=n}Ge!==0&&Ge!==5||Nn(e),Nl!==0&&(Nl=0)}function Sd(e,t){for(var l=e.suspendedLanes,a=e.pingedLanes,n=e.expirationTimes,u=e.pendingLanes&-62914561;0r)break;var C=v.transferSize,U=v.initiatorType;C&&zd(U)&&(v=v.responseEnd,c+=C*(v"u"?null:document;function Xd(e,t,l){var a=Ca;if(a&&typeof t=="string"&&t){var n=bt(t);n='link[rel="'+e+'"][href="'+n+'"]',typeof l=="string"&&(n+='[crossorigin="'+l+'"]'),Gd.has(n)||(Gd.add(n),e={rel:e,crossOrigin:l,href:t},a.querySelector(n)===null&&(t=a.createElement("link"),$e(t,"link",e),Xe(t),a.head.appendChild(t)))}}function Fv(e){nl.D(e),Xd("dns-prefetch",e,null)}function Iv(e,t){nl.C(e,t),Xd("preconnect",e,t)}function Pv(e,t,l){nl.L(e,t,l);var a=Ca;if(a&&e&&t){var n='link[rel="preload"][as="'+bt(t)+'"]';t==="image"&&l&&l.imageSrcSet?(n+='[imagesrcset="'+bt(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(n+='[imagesizes="'+bt(l.imageSizes)+'"]')):n+='[href="'+bt(e)+'"]';var u=n;switch(t){case"style":u=Da(e);break;case"script":u=Ma(e)}Rt.has(u)||(e=T({rel:"preload",href:t==="image"&&l&&l.imageSrcSet?void 0:e,as:t},l),Rt.set(u,e),a.querySelector(n)!==null||t==="style"&&a.querySelector(Rn(u))||t==="script"&&a.querySelector(zn(u))||(t=a.createElement("link"),$e(t,"link",e),Xe(t),a.head.appendChild(t)))}}function ey(e,t){nl.m(e,t);var l=Ca;if(l&&e){var a=t&&typeof t.as=="string"?t.as:"script",n='link[rel="modulepreload"][as="'+bt(a)+'"][href="'+bt(e)+'"]',u=n;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":u=Ma(e)}if(!Rt.has(u)&&(e=T({rel:"modulepreload",href:e},t),Rt.set(u,e),l.querySelector(n)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(zn(u)))return}a=l.createElement("link"),$e(a,"link",e),Xe(a),l.head.appendChild(a)}}}function ty(e,t,l){nl.S(e,t,l);var a=Ca;if(a&&e){var n=ea(a).hoistableStyles,u=Da(e);t=t||"default";var c=n.get(u);if(!c){var r={loading:0,preload:null};if(c=a.querySelector(Rn(u)))r.loading=5;else{e=T({rel:"stylesheet",href:e,"data-precedence":t},l),(l=Rt.get(u))&&Af(e,l);var v=c=a.createElement("link");Xe(v),$e(v,"link",e),v._p=new Promise(function(N,C){v.onload=N,v.onerror=C}),v.addEventListener("load",function(){r.loading|=1}),v.addEventListener("error",function(){r.loading|=2}),r.loading|=4,Ju(c,t,a)}c={type:"stylesheet",instance:c,count:1,state:r},n.set(u,c)}}}function ly(e,t){nl.X(e,t);var l=Ca;if(l&&e){var a=ea(l).hoistableScripts,n=Ma(e),u=a.get(n);u||(u=l.querySelector(zn(n)),u||(e=T({src:e,async:!0},t),(t=Rt.get(n))&&jf(e,t),u=l.createElement("script"),Xe(u),$e(u,"link",e),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function ay(e,t){nl.M(e,t);var l=Ca;if(l&&e){var a=ea(l).hoistableScripts,n=Ma(e),u=a.get(n);u||(u=l.querySelector(zn(n)),u||(e=T({src:e,async:!0,type:"module"},t),(t=Rt.get(n))&&jf(e,t),u=l.createElement("script"),Xe(u),$e(u,"link",e),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function Qd(e,t,l,a){var n=(n=ne.current)?Ku(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=Da(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=Da(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(Rn(e)))&&!u._p&&(c.instance=u,c.state.loading=5),Rt.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},Rt.set(e,l),u||ny(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=Ma(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 Da(e){return'href="'+bt(e)+'"'}function Rn(e){return'link[rel="stylesheet"]['+e+"]"}function Zd(e){return T({},e,{"data-precedence":e.precedence,precedence:null})}function ny(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}),$e(t,"link",l),Xe(t),e.head.appendChild(t))}function Ma(e){return'[src="'+bt(e)+'"]'}function zn(e){return"script[async]"+e}function wd(e,t,l){if(t.count++,t.instance===null)switch(t.type){case"style":var a=e.querySelector('style[data-href~="'+bt(l.href)+'"]');if(a)return t.instance=a,Xe(a),a;var n=T({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(e.ownerDocument||e).createElement("style"),Xe(a),$e(a,"style",n),Ju(a,l.precedence,e),t.instance=a;case"stylesheet":n=Da(l.href);var u=e.querySelector(Rn(n));if(u)return t.state.loading|=4,t.instance=u,Xe(u),u;a=Zd(l),(n=Rt.get(n))&&Af(a,n),u=(e.ownerDocument||e).createElement("link"),Xe(u);var c=u;return c._p=new Promise(function(r,v){c.onload=r,c.onerror=v}),$e(u,"link",a),t.state.loading|=4,Ju(u,l.precedence,e),t.instance=u;case"script":return u=Ma(l.src),(n=e.querySelector(zn(u)))?(t.instance=n,Xe(n),n):(a=l,(n=Rt.get(u))&&(a=T({},l),jf(a,n)),e=e.ownerDocument||e,n=e.createElement("script"),Xe(n),$e(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,Ju(a,l.precedence,e));return t.instance}function Ju(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 uy(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 Jd(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function iy(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=Da(a.href),u=t.querySelector(Rn(n));if(u){t=u._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=Wu.bind(e),t.then(e,e)),l.state.loading|=4,l.instance=u,Xe(u);return}u=t.ownerDocument||t,a=Zd(a),(n=Rt.get(n))&&Af(a,n),u=u.createElement("link"),Xe(u);var c=u;c._p=new Promise(function(r,v){c.onload=r,c.onerror=v}),$e(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=Wu.bind(e),t.addEventListener("load",l),t.addEventListener("error",l))}}var Rf=0;function cy(e,t){return e.stylesheets&&e.count===0&&Fu(e,e.stylesheets),0Rf?50:800)+t);return e.unsuspend=l,function(){e.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function Wu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Fu(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var ku=null;function Fu(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,ku=new Map,t.forEach(fy,e),ku=null,Wu.call(e))}function fy(e,t){if(!(t.state.loading&4)){var l=ku.get(e);if(l)var a=l.get(null);else{l=new Map,ku.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(),Bf.exports=Ny(),Bf.exports}var Ay=Ty();var pm="popstate";function gm(i){return typeof i=="object"&&i!=null&&"pathname"in i&&"search"in i&&"hash"in i&&"state"in i&&"key"in i}function jy(i={}){function s(m,h){let{pathname:b="/",search:R="",hash:S=""}=kl(m.location.hash.substring(1));return!b.startsWith("/")&&!b.startsWith(".")&&(b="/"+b),Jf("",{pathname:b,search:R,hash:S},h.state&&h.state.usr||null,h.state&&h.state.key||"default")}function d(m,h){let b=m.document.querySelector("base"),R="";if(b&&b.getAttribute("href")){let S=m.location.href,y=S.indexOf("#");R=y===-1?S:S.slice(0,y)}return R+"#"+(typeof h=="string"?h:Ln(h))}function f(m,h){zt(m.pathname.charAt(0)==="/",`relative pathnames are not supported in hash history.push(${JSON.stringify(h)})`)}return zy(s,d,f,i)}function Re(i,s){if(i===!1||i===null||typeof i>"u")throw new Error(s)}function zt(i,s){if(!i){typeof console<"u"&&console.warn(s);try{throw new Error(s)}catch{}}}function Ry(){return Math.random().toString(36).substring(2,10)}function Sm(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 Jf(i,s,d=null,f,m){return{pathname:typeof i=="string"?i:i.pathname,search:"",hash:"",...typeof s=="string"?kl(s):s,state:d,key:s&&s.key||f||Ry(),unstable_mask:m}}function Ln({pathname:i="/",search:s="",hash:d=""}){return s&&s!=="?"&&(i+=s.charAt(0)==="?"?s:"?"+s),d&&d!=="#"&&(i+=d.charAt(0)==="#"?d:"#"+d),i}function kl(i){let s={};if(i){let d=i.indexOf("#");d>=0&&(s.hash=i.substring(d),i=i.substring(0,d));let f=i.indexOf("?");f>=0&&(s.search=i.substring(f),i=i.substring(0,f)),i&&(s.pathname=i)}return s}function zy(i,s,d,f={}){let{window:m=document.defaultView,v5Compat:h=!1}=f,b=m.history,R="POP",S=null,y=D();y==null&&(y=0,b.replaceState({...b.state,idx:y},""));function D(){return(b.state||{idx:null}).idx}function T(){R="POP";let B=D(),V=B==null?null:B-y;y=B,S&&S({action:R,location:w.location,delta:V})}function L(B,V){R="PUSH";let k=gm(B)?B:Jf(w.location,B,V);d&&d(k,B),y=D()+1;let X=Sm(k,y),Q=w.createHref(k.unstable_mask||k);try{b.pushState(X,"",Q)}catch(ae){if(ae instanceof DOMException&&ae.name==="DataCloneError")throw ae;m.location.assign(Q)}h&&S&&S({action:R,location:w.location,delta:1})}function H(B,V){R="REPLACE";let k=gm(B)?B:Jf(w.location,B,V);d&&d(k,B),y=D();let X=Sm(k,y),Q=w.createHref(k.unstable_mask||k);b.replaceState(X,"",Q),h&&S&&S({action:R,location:w.location,delta:0})}function K(B){return Oy(B)}let w={get action(){return R},get location(){return i(m,b)},listen(B){if(S)throw new Error("A history only accepts one active listener");return m.addEventListener(pm,T),S=B,()=>{m.removeEventListener(pm,T),S=null}},createHref(B){return s(m,B)},createURL:K,encodeLocation(B){let V=K(B);return{pathname:V.pathname,search:V.search,hash:V.hash}},push:L,replace:H,go(B){return b.go(B)}};return w}function Oy(i,s=!1){let d="http://localhost";typeof window<"u"&&(d=window.location.origin!=="null"?window.location.origin:window.location.href),Re(d,"No window.location.(origin|href) available to create URL");let f=typeof i=="string"?i:Ln(i);return f=f.replace(/ $/,"%20"),!s&&f.startsWith("//")&&(f=d+f),new URL(f,d)}function Tm(i,s,d="/"){return Cy(i,s,d,!1)}function Cy(i,s,d,f){let m=typeof s=="string"?kl(s):s,h=ul(m.pathname||"/",d);if(h==null)return null;let b=Am(i);Dy(b);let R=null;for(let S=0;R==null&&S{let D={relativePath:y===void 0?b.path||"":y,caseSensitive:b.caseSensitive===!0,childrenIndex:R,route:b};if(D.relativePath.startsWith("/")){if(!D.relativePath.startsWith(f)&&S)return;Re(D.relativePath.startsWith(f),`Absolute route path "${D.relativePath}" nested under path "${f}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),D.relativePath=D.relativePath.slice(f.length)}let T=Mt([f,D.relativePath]),L=d.concat(D);b.children&&b.children.length>0&&(Re(b.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${T}".`),Am(b.children,s,L,T,S)),!(b.path==null&&!b.index)&&s.push({path:T,score:Yy(T,b.index),routesMeta:L})};return i.forEach((b,R)=>{if(b.path===""||!b.path?.includes("?"))h(b,R);else for(let S of jm(b.path))h(b,R,!0,S)}),s}function jm(i){let s=i.split("/");if(s.length===0)return[];let[d,...f]=s,m=d.endsWith("?"),h=d.replace(/\?$/,"");if(f.length===0)return m?[h,""]:[h];let b=jm(f.join("/")),R=[];return R.push(...b.map(S=>S===""?h:[h,S].join("/"))),m&&R.push(...b),R.map(S=>i.startsWith("/")&&S===""?"/":S)}function Dy(i){i.sort((s,d)=>s.score!==d.score?d.score-s.score:Gy(s.routesMeta.map(f=>f.childrenIndex),d.routesMeta.map(f=>f.childrenIndex)))}var My=/^:[\w-]+$/,Uy=3,Hy=2,qy=1,By=10,Ly=-2,bm=i=>i==="*";function Yy(i,s){let d=i.split("/"),f=d.length;return d.some(bm)&&(f+=Ly),s&&(f+=Hy),d.filter(m=>!bm(m)).reduce((m,h)=>m+(My.test(h)?Uy:h===""?qy:By),f)}function Gy(i,s){return i.length===s.length&&i.slice(0,-1).every((f,m)=>f===s[m])?i[i.length-1]-s[s.length-1]:0}function Xy(i,s,d=!1){let{routesMeta:f}=i,m={},h="/",b=[];for(let R=0;R{if(D==="*"){let K=R[L]||"";b=h.slice(0,h.length-K.length).replace(/(.)\/+$/,"$1")}const H=R[L];return T&&!H?y[D]=void 0:y[D]=(H||"").replace(/%2F/g,"/"),y},{}),pathname:h,pathnameBase:b,pattern:i}}function Qy(i,s=!1,d=!0){zt(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=[],m="^"+i.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(b,R,S,y,D)=>{if(f.push({paramName:R,isOptional:S!=null}),S){let T=D.charAt(y+b.length);return T&&T!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return i.endsWith("*")?(f.push({paramName:"*"}),m+=i==="*"||i==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):d?m+="\\/*$":i!==""&&i!=="/"&&(m+="(?:(?=\\/|$))"),[new RegExp(m,s?void 0:"i"),f]}function Zy(i){try{return i.split("/").map(s=>decodeURIComponent(s).replace(/\//g,"%2F")).join("/")}catch(s){return zt(!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 ul(i,s){if(s==="/")return i;if(!i.toLowerCase().startsWith(s.toLowerCase()))return null;let d=s.endsWith("/")?s.length-1:s.length,f=i.charAt(d);return f&&f!=="/"?null:i.slice(d)||"/"}var wy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function Vy(i,s="/"){let{pathname:d,search:f="",hash:m=""}=typeof i=="string"?kl(i):i,h;return d?(d=Rm(d),d.startsWith("/")?h=_m(d.substring(1),"/"):h=_m(d,s)):h=s,{pathname:h,search:$y(f),hash:Wy(m)}}function _m(i,s){let d=oi(s).split("/");return i.split("/").forEach(m=>{m===".."?d.length>1&&d.pop():m!=="."&&d.push(m)}),d.length>1?d.join("/"):"/"}function Xf(i,s,d,f){return`Cannot include a '${i}' character in a manually specified \`to.${s}\` field [${JSON.stringify(f)}]. Please separate it out to the \`to.${d}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function Ky(i){return i.filter((s,d)=>d===0||s.route.path&&s.route.path.length>0)}function Ff(i){let s=Ky(i);return s.map((d,f)=>f===s.length-1?d.pathname:d.pathnameBase)}function mi(i,s,d,f=!1){let m;typeof i=="string"?m=kl(i):(m={...i},Re(!m.pathname||!m.pathname.includes("?"),Xf("?","pathname","search",m)),Re(!m.pathname||!m.pathname.includes("#"),Xf("#","pathname","hash",m)),Re(!m.search||!m.search.includes("#"),Xf("#","search","hash",m)));let h=i===""||m.pathname==="",b=h?"/":m.pathname,R;if(b==null)R=d;else{let T=s.length-1;if(!f&&b.startsWith("..")){let L=b.split("/");for(;L[0]==="..";)L.shift(),T-=1;m.pathname=L.join("/")}R=T>=0?s[T]:"/"}let S=Vy(m,R),y=b&&b!=="/"&&b.endsWith("/"),D=(h||b===".")&&d.endsWith("/");return!S.pathname.endsWith("/")&&(y||D)&&(S.pathname+="/"),S}var Rm=i=>i.replace(/\/\/+/g,"/"),Mt=i=>Rm(i.join("/")),oi=i=>i.replace(/\/+$/,""),Jy=i=>oi(i).replace(/^\/*/,"/"),$y=i=>!i||i==="?"?"":i.startsWith("?")?i:"?"+i,Wy=i=>!i||i==="#"?"":i.startsWith("#")?i:"#"+i,ky=class{constructor(i,s,d,f=!1){this.status=i,this.statusText=s||"",this.internal=f,d instanceof Error?(this.data=d.toString(),this.error=d):this.data=d}};function Fy(i){return i!=null&&typeof i.status=="number"&&typeof i.statusText=="string"&&typeof i.internal=="boolean"&&"data"in i}function Iy(i){let s=i.map(d=>d.route.path).filter(Boolean);return Mt(s)||"/"}var zm=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Om(i,s){let d=i;if(typeof d!="string"||!wy.test(d))return{absoluteURL:void 0,isExternal:!1,to:d};let f=d,m=!1;if(zm)try{let h=new URL(window.location.href),b=d.startsWith("//")?new URL(h.protocol+d):new URL(d),R=ul(b.pathname,s);b.origin===h.origin&&R!=null?d=R+b.search+b.hash:m=!0}catch{zt(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:f,isExternal:m,to:d}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var Cm=["POST","PUT","PATCH","DELETE"];new Set(Cm);var Py=["GET",...Cm];new Set(Py);var La=E.createContext(null);La.displayName="DataRouter";var hi=E.createContext(null);hi.displayName="DataRouterState";var Dm=E.createContext(!1);function e0(){return E.useContext(Dm)}var Mm=E.createContext({isTransitioning:!1});Mm.displayName="ViewTransition";var t0=E.createContext(new Map);t0.displayName="Fetchers";var l0=E.createContext(null);l0.displayName="Await";var gt=E.createContext(null);gt.displayName="Navigation";var Yn=E.createContext(null);Yn.displayName="Location";var Ht=E.createContext({outlet:null,matches:[],isDataRoute:!1});Ht.displayName="Route";var If=E.createContext(null);If.displayName="RouteError";var Um="REACT_ROUTER_ERROR",a0="REDIRECT",n0="ROUTE_ERROR_RESPONSE";function u0(i){if(i.startsWith(`${Um}:${a0}:{`))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 i0(i){if(i.startsWith(`${Um}:${n0}:{`))try{let s=JSON.parse(i.slice(40));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string")return new ky(s.status,s.statusText,s.data)}catch{}}function c0(i,{relative:s}={}){Re(Ya(),"useHref() may be used only in the context of a component.");let{basename:d,navigator:f}=E.useContext(gt),{hash:m,pathname:h,search:b}=Gn(i,{relative:s}),R=h;return d!=="/"&&(R=h==="/"?d:Mt([d,h])),f.createHref({pathname:R,search:b,hash:m})}function Ya(){return E.useContext(Yn)!=null}function Gt(){return Re(Ya(),"useLocation() may be used only in the context of a component."),E.useContext(Yn).location}var Hm="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function qm(i){E.useContext(gt).static||E.useLayoutEffect(i)}function Bm(){let{isDataRoute:i}=E.useContext(Ht);return i?E0():f0()}function f0(){Re(Ya(),"useNavigate() may be used only in the context of a component.");let i=E.useContext(La),{basename:s,navigator:d}=E.useContext(gt),{matches:f}=E.useContext(Ht),{pathname:m}=Gt(),h=JSON.stringify(Ff(f)),b=E.useRef(!1);return qm(()=>{b.current=!0}),E.useCallback((S,y={})=>{if(zt(b.current,Hm),!b.current)return;if(typeof S=="number"){d.go(S);return}let D=mi(S,JSON.parse(h),m,y.relative==="path");i==null&&s!=="/"&&(D.pathname=D.pathname==="/"?s:Mt([s,D.pathname])),(y.replace?d.replace:d.push)(D,y.state,y)},[s,d,h,m,i])}var s0=E.createContext(null);function r0(i){let s=E.useContext(Ht).outlet;return E.useMemo(()=>s&&E.createElement(s0.Provider,{value:i},s),[s,i])}function Gn(i,{relative:s}={}){let{matches:d}=E.useContext(Ht),{pathname:f}=Gt(),m=JSON.stringify(Ff(d));return E.useMemo(()=>mi(i,JSON.parse(m),f,s==="path"),[i,m,f,s])}function o0(i,s){return Lm(i,s)}function Lm(i,s,d){Re(Ya(),"useRoutes() may be used only in the context of a component.");let{navigator:f}=E.useContext(gt),{matches:m}=E.useContext(Ht),h=m[m.length-1],b=h?h.params:{},R=h?h.pathname:"/",S=h?h.pathnameBase:"/",y=h&&h.route;{let B=y&&y.path||"";Gm(R,!y||B.endsWith("*")||B.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${R}" (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 D=Gt(),T;if(s){let B=typeof s=="string"?kl(s):s;Re(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.`),T=B}else T=D;let L=T.pathname||"/",H=L;if(S!=="/"){let B=S.replace(/^\//,"").split("/");H="/"+L.replace(/^\//,"").split("/").slice(B.length).join("/")}let K=Tm(i,{pathname:H});zt(y||K!=null,`No routes matched location "${T.pathname}${T.search}${T.hash}" `),zt(K==null||K[K.length-1].route.element!==void 0||K[K.length-1].route.Component!==void 0||K[K.length-1].route.lazy!==void 0,`Matched leaf route at location "${T.pathname}${T.search}${T.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 w=y0(K&&K.map(B=>Object.assign({},B,{params:Object.assign({},b,B.params),pathname:Mt([S,f.encodeLocation?f.encodeLocation(B.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:B.pathname]),pathnameBase:B.pathnameBase==="/"?S:Mt([S,f.encodeLocation?f.encodeLocation(B.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:B.pathnameBase])})),m,d);return s&&w?E.createElement(Yn.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",unstable_mask:void 0,...T},navigationType:"POP"}},w):w}function d0(){let i=_0(),s=Fy(i)?`${i.status} ${i.statusText}`:i instanceof Error?i.message:JSON.stringify(i),d=i instanceof Error?i.stack:null,f="rgba(200,200,200, 0.5)",m={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),d?E.createElement("pre",{style:m},d):null,b)}var m0=E.createElement(d0,null),Ym=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 d=i0(i.digest);d&&(i=d)}let s=i!==void 0?E.createElement(Ht.Provider,{value:this.props.routeContext},E.createElement(If.Provider,{value:i,children:this.props.component})):this.props.children;return this.context?E.createElement(h0,{error:i},s):s}};Ym.contextType=Dm;var Qf=new WeakMap;function h0({children:i,error:s}){let{basename:d}=E.useContext(gt);if(typeof s=="object"&&s&&"digest"in s&&typeof s.digest=="string"){let f=u0(s.digest);if(f){let m=Qf.get(s);if(m)throw m;let h=Om(f.location,d);if(zm&&!Qf.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 Qf.set(s,b),b}return E.createElement("meta",{httpEquiv:"refresh",content:`0;url=${h.absoluteURL||h.to}`})}}return i}function v0({routeContext:i,match:s,children:d}){let f=E.useContext(La);return f&&f.static&&f.staticContext&&(s.route.errorElement||s.route.ErrorBoundary)&&(f.staticContext._deepestRenderedBoundaryId=s.route.id),E.createElement(Ht.Provider,{value:i},d)}function y0(i,s=[],d){let f=d?.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 m=i,h=f?.errors;if(h!=null){let D=m.findIndex(T=>T.route.id&&h?.[T.route.id]!==void 0);Re(D>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(h).join(",")}`),m=m.slice(0,Math.min(m.length,D+1))}let b=!1,R=-1;if(d&&f){b=f.renderFallback;for(let D=0;D=0?m=m.slice(0,R+1):m=[m[0]];break}}}}let S=d?.onError,y=f&&S?(D,T)=>{S(D,{location:f.location,params:f.matches?.[0]?.params??{},unstable_pattern:Iy(f.matches),errorInfo:T})}:void 0;return m.reduceRight((D,T,L)=>{let H,K=!1,w=null,B=null;f&&(H=h&&T.route.id?h[T.route.id]:void 0,w=T.route.errorElement||m0,b&&(R<0&&L===0?(Gm("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),K=!0,B=null):R===L&&(K=!0,B=T.route.hydrateFallbackElement||null)));let V=s.concat(m.slice(0,L+1)),k=()=>{let X;return H?X=w:K?X=B:T.route.Component?X=E.createElement(T.route.Component,null):T.route.element?X=T.route.element:X=D,E.createElement(v0,{match:T,routeContext:{outlet:D,matches:V,isDataRoute:f!=null},children:X})};return f&&(T.route.ErrorBoundary||T.route.errorElement||L===0)?E.createElement(Ym,{location:f.location,revalidation:f.revalidation,component:w,error:H,children:k(),routeContext:{outlet:null,matches:V,isDataRoute:!0},onError:y}):k()},null)}function Pf(i){return`${i} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function p0(i){let s=E.useContext(La);return Re(s,Pf(i)),s}function g0(i){let s=E.useContext(hi);return Re(s,Pf(i)),s}function S0(i){let s=E.useContext(Ht);return Re(s,Pf(i)),s}function es(i){let s=S0(i),d=s.matches[s.matches.length-1];return Re(d.route.id,`${i} can only be used on routes that contain a unique "id"`),d.route.id}function b0(){return es("useRouteId")}function _0(){let i=E.useContext(If),s=g0("useRouteError"),d=es("useRouteError");return i!==void 0?i:s.errors?.[d]}function E0(){let{router:i}=p0("useNavigate"),s=es("useNavigate"),d=E.useRef(!1);return qm(()=>{d.current=!0}),E.useCallback(async(m,h={})=>{zt(d.current,Hm),d.current&&(typeof m=="number"?await i.navigate(m):await i.navigate(m,{fromRouteId:s,...h}))},[i,s])}var Em={};function Gm(i,s,d){!s&&!Em[i]&&(Em[i]=!0,zt(!1,d))}E.memo(x0);function x0({routes:i,future:s,state:d,isStatic:f,onError:m}){return Lm(i,void 0,{state:d,isStatic:f,onError:m})}function N0({to:i,replace:s,state:d,relative:f}){Re(Ya()," may be used only in the context of a component.");let{static:m}=E.useContext(gt);zt(!m," 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(Ht),{pathname:b}=Gt(),R=Bm(),S=mi(i,Ff(h),b,f==="path"),y=JSON.stringify(S);return E.useEffect(()=>{R(JSON.parse(y),{replace:s,state:d,relative:f})},[R,y,f,s,d]),null}function T0(i){return r0(i.context)}function Ha(i){Re(!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:d,navigationType:f="POP",navigator:m,static:h=!1,unstable_useTransitions:b}){Re(!Ya(),"You cannot render a inside another . You should never have more than one in your app.");let R=i.replace(/^\/*/,"/"),S=E.useMemo(()=>({basename:R,navigator:m,static:h,unstable_useTransitions:b,future:{}}),[R,m,h,b]);typeof d=="string"&&(d=kl(d));let{pathname:y="/",search:D="",hash:T="",state:L=null,key:H="default",unstable_mask:K}=d,w=E.useMemo(()=>{let B=ul(y,R);return B==null?null:{location:{pathname:B,search:D,hash:T,state:L,key:H,unstable_mask:K},navigationType:f}},[R,y,D,T,L,H,f,K]);return zt(w!=null,` is not able to match the URL "${y}${D}${T}" because it does not start with the basename, so the won't render anything.`),w==null?null:E.createElement(gt.Provider,{value:S},E.createElement(Yn.Provider,{children:s,value:w}))}function j0({children:i,location:s}){return o0($f(i),s)}function $f(i,s=[]){let d=[];return E.Children.forEach(i,(f,m)=>{if(!E.isValidElement(f))return;let h=[...s,m];if(f.type===E.Fragment){d.push.apply(d,$f(f.props.children,h));return}Re(f.type===Ha,`[${typeof f.type=="string"?f.type:f.type.name}] is not a component. All component children of must be a or `),Re(!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=$f(f.props.children,h)),d.push(b)}),d}var ci="get",fi="application/x-www-form-urlencoded";function vi(i){return typeof HTMLElement<"u"&&i instanceof HTMLElement}function R0(i){return vi(i)&&i.tagName.toLowerCase()==="button"}function z0(i){return vi(i)&&i.tagName.toLowerCase()==="form"}function O0(i){return vi(i)&&i.tagName.toLowerCase()==="input"}function C0(i){return!!(i.metaKey||i.altKey||i.ctrlKey||i.shiftKey)}function D0(i,s){return i.button===0&&(!s||s==="_self")&&!C0(i)}var ui=null;function M0(){if(ui===null)try{new FormData(document.createElement("form"),0),ui=!1}catch{ui=!0}return ui}var U0=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Zf(i){return i!=null&&!U0.has(i)?(zt(!1,`"${i}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${fi}"`),null):i}function H0(i,s){let d,f,m,h,b;if(z0(i)){let R=i.getAttribute("action");f=R?ul(R,s):null,d=i.getAttribute("method")||ci,m=Zf(i.getAttribute("enctype"))||fi,h=new FormData(i)}else if(R0(i)||O0(i)&&(i.type==="submit"||i.type==="image")){let R=i.form;if(R==null)throw new Error('Cannot submit a + {approvalOn ? ( + + ) : ( + + )} + + + + ) : null} + {actErr ?

{actErr}

: null} {actOutcome ? ( @@ -195,10 +443,7 @@ export function ActionsPage() {
Previous baseline
{actOutcome.baseline_release_id ? ( - + {shortId(actOutcome.baseline_release_id)} ) : ( @@ -217,6 +462,8 @@ export function ActionsPage() { ) : null} + {requestRaw ? : null} + {actRaw ? ( ) : null}