diff --git a/CHANGELOG.md b/CHANGELOG.md index 09e80e1..c9e3ea9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0** ## Unreleased +### Changed + +- **Web Runs:** forensics UX — empty / offset / truncation messaging, export copy aligned to server limits, trace band rows, **View** drawer with structured fields and full event JSON, extra table columns (trace, status). +- **Web Diff:** scannable sections (policy, evidence window, pricing/catalog/hints, rollups), pre-query hint, `evaluated_at` when present; **examples** index and **integration** README link **`/#/diff`** and **`POST /v1/diff`** to the end-to-end loop. +- **Web Actions:** workspace loading skeleton; numbered steps when approval is on; pending table **Use for confirm** and **Refresh list**; clearer browser confirm copy and approval-reason placeholder. +- **Web shell / Overview:** skeleton loading instead of plain “Loading…”; **Refresh** disabled while loading; ledger metrics line with links to **Diff** and **Runs**; Diff query card **`aria-busy`** while computing. + ### Added - **`GET /v1/runs/export`** — NDJSON stream of the same filtered slice as **`GET /v1/runs`** (optional response headers when truncated). diff --git a/ROADMAP.md b/ROADMAP.md index 2f98be7..69b3961 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,174 +1,158 @@ # Roadmap -FlightDeck helps teams **ship AI agents safely** with release diffs, runtime evidence, and policy gates: immutable releases, runtime evidence, trusted diffs, and policy-gated promotion. +FlightDeck helps teams **ship AI agents safely** with release diffs, runtime evidence, and policy gates: immutable releases, trusted diffs, and policy-gated promotion. -This roadmap is meant to be clear from **what is already shipped** to **near-term commitments** and **long-horizon possibilities**. It also calls out what still makes the product feel standalone in production settings. +This document is **strategy and ordering**, not a second changelog. It goes from **what is already shipped** to **what we are building next**, **why production can still feel standalone**, and **what stays off the table**. Per-version shipping notes live elsewhere (see below). -**Reality check:** today FlightDeck is intentionally **local-first** (CLI + SQLite + optional `flightdeck serve`). That keeps trust boundaries explicit, but it also means teams still need integration glue to run it broadly in production. +**Reality check:** FlightDeck is intentionally **local-first** (CLI + SQLite + optional `flightdeck serve`). That keeps trust boundaries explicit; teams still supply integration glue to run it broadly in production. + +**Version detail:** The current shipping line is **v1.1.2**. For SemVer-by-SemVer behavior and migrations, use **[RELEASE_NOTES.md](RELEASE_NOTES.md)** and **[CHANGELOG.md](CHANGELOG.md)**. --- -## What is shipped today +## What is shipped (capability snapshot) - **Release registry and verification:** versioned `release.yaml` artifacts with checksums, `flightdeck release verify`. -- **Economic + operational governance:** immutable pricing imports, trusted `release diff`, policy-gated `promote` and `rollback`. +- **Economic + operational governance:** immutable pricing imports, trusted `release diff`, policy-gated `promote` and `rollback` (including optional approval request/confirm when configured). - **Audit trail:** promotion/rollback history with stable sequencing (`audit_seq`) and integrity checks via `doctor`. -- **Evidence ingestion:** `runs ingest` from JSONL/JSON arrays plus stable `POST /v1/events` contracts (`schemas/v1/`); **`GET /v1/runs`**, **`runs list`**, optional **`trace_id`** filter, and **`runs export`** (JSONL) for operator forensics. -- **Local API + UI:** `flightdeck serve` routes and web UI (Overview with ledger metrics, Diff, Runs, Promote) in `src/flightdeck/server/static/`. +- **Evidence ingestion:** `runs ingest` from JSONL/JSON arrays plus stable `POST /v1/events` (`schemas/v1/`); **`GET /v1/runs`**, **`runs list`**, optional **`trace_id`** filter, and **`runs export`** (JSONL) for operator forensics. +- **Local API + UI:** `flightdeck serve` routes and shipped web bundle under `src/flightdeck/server/static/`; surfaces summarized in **Web UI and operator experience** below. - **SDK and tooling:** Python sync/async clients with retries/batching and `flightdeck-quickstart-verify`. +- **Operator references:** CI examples, deploy/Compose guidance, Helm and fleet examples under `examples/`. --- -## Next release +## Web UI and operator experience + +Strategic UX intent for the bundled React app (routing and components: **[docs/web-ui.md](docs/web-ui.md)**). This is not a visual design spec; it keeps UI work aligned with evidence, diff trust, and promotion safety—not dashboard sprawl (see **[AGENTS.md](AGENTS.md)**). + +**Principles** + +- **Operator-first:** fewer steps to answer “Can I promote?” and “What broke?”; clarity over decoration. +- **Trust and safety:** mutations are obvious; token/read-only posture stays visible. +- **Evidence over chrome:** structured fields and light timelines where APIs are stable; raw JSON as an escape hatch, not the default reading path. +- **Density, not platforms:** guided flows and scannable summaries—no APM-style UI, no charting product. + +**Shipped surfaces** -Further work after **v1.1.2** — **OTLP-oriented** telemetry, **replay-style web** forensics, deeper **catalog lifecycle** governance, and **cross-workspace / fleet** product surfaces stay on **Phase 2 / mid-term** (see gaps table below). Track **[CHANGELOG.md](CHANGELOG.md)**. +| Surface | Role | +|--------|------| +| **Overview** | Ledger / promotion snapshot, ledger metrics | +| **Diff** | Release comparison, pricing / catalog / hints, policy outcome | +| **Runs** | Forensics filters, listing, export | +| **Actions / Promote** | Direct promote vs approval request/confirm, rollback | +| **Shell** | Primary nav, security/status strip, optional read-only build | -**v1.1.2** (patch, shipped): **`trace_id`** filter on **`GET /v1/runs`**, **`flightdeck runs list --trace-id`**, and SDK **`list_runs`**, plus **`flightdeck runs export`** (JSONL, stderr warning when truncated); **[examples/README.md](examples/README.md)** adds a **readiness checklist**; **productization tranche status** (this document) records closure of the scoped local-first slice. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. +**UX and UI backlog (grouped)** -**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)**. +These map to **What is next** items **1**, **2**, and **5**; ship notes stay in **RELEASE_NOTES** / **CHANGELOG**. -**v1.1.0** (minor, shipped): **`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)**. +1. **Runs and forensics (web)** — Run or trace **detail** (drawer or page), clearer **empty and error** states, optional **timeline** grouping by `trace_id` / session, export affordances consistent with server limits. +2. **Diff comprehension** — Stronger **scannability** for policy blocks and pricing/catalog lines; surface **version skew** and hint copy when the API exposes it. +3. **Promotion and approval** — **Progressive disclosure** for approval vs direct promote, clearer confirmation copy, **pending requests** table polish. +4. **Overview and trust** — Metrics **context** (what a counter means), light cross-links to Diff/Runs—not a metrics dashboard product. +5. **Shell and quality bar** — **Loading** states, consistent spacing and type rhythm, keyboard **focus** and labels, layouts that tolerate narrow viewports where cheap. +6. **Security ergonomics (UI)** — Token/env/mutation visibility, read-only build behavior, cautious affordances for destructive actions. -**v1.0.6** (patch, shipped): Phase 0 closure — **`flightdeck release diff --output json`** (same shape as **`POST /v1/diff`**); **`pricing.warnings`** when a release model has no row in its pricing table (CLI **`WARNING:`** lines + web Diff); **Overview** ledger metrics card (**`GET /v1/metrics`**); **`curl`** + **Node** samples under **[examples/integration/](examples/integration/README.md)**; **`flightdeck doctor --backup PATH`** (SQLite online backup); **[examples/deploy/](examples/deploy/README.md)** documents Compose **`/health`** healthcheck and backup scheduling. **Phase 0** is declared **closed**; **catalog-level** multi-provider normalization moves to **mid-term productization** (the **v1.1.x** track). See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. No breaking changes to stable CLI, HTTP, or **`api_version` `v1`** contracts. +**Explicit UI deferrals** + +Out of scope for the near-term web app: theme marketplaces; embedded arbitrary log viewers; full observability or fleet consoles in the browser; multi-workspace UI (follows conditional **Fleet / cross-workspace** in **What is next**). --- ## Production readiness gaps (why it can feel standalone) -These are current gaps between "works locally" and "easy to use across production services." +Gaps between “works locally” and “easy to use across production services.” | Gap | What production-ready usually requires | FlightDeck intent | |-----|----------------------------------------|-------------------| | **Event pipeline** | Reliable `RunEvent` emission from app/agent runtimes. | Near term: reference integration examples; operator owns final runtime wiring. | -| **CI/GitOps flow** | Register -> ingest -> diff -> gate -> promote in pipelines. | Near term: maintained CI examples/templates. | +| **CI/GitOps flow** | Register → ingest → diff → gate → promote in pipelines. | Near term: maintained CI examples/templates. | | **Deployment unit** | Repeatable `serve` packaging, health checks, process supervision. | Near term: container/compose guidance; still local-first by default. | -| **Identity and access** | Strong auth beyond loopback + optional bearer token. | Mid term: documented hardened patterns; first-class enterprise auth is longer arc. | +| **Identity and access** | Strong auth beyond loopback + optional bearer token. | Mid term: documented hardened patterns; first-class enterprise auth is a longer arc. | | **Storage/availability** | Backup/restore, scaling, HA story. | Operator-owned today; improve docs and patterns. | | **Observability integration** | Correlated telemetry export and operational visibility. | Mid term: OTLP-oriented integration paths (not an APM/dashboard product). | | **Multi-workspace/fleet** | Cross-workspace views and policy coordination. | Long term and conditional; one workspace = one ledger today. | --- -## Phase 0: Foundation to PMF (near term, next releases) +## What is next (ordered) -Goal: prove the wedge with real teams using FlightDeck as release governance source-of-truth. +Each item ties to the core promise: **release integrity**, **runtime evidence**, **policy-gated promotion**, and **auditability** (see **[AGENTS.md](AGENTS.md)**). -### Must ship in this phase +1. **Evidence and forensics (web)** — Replay/trace-oriented views and richer export semantics on top of `runs list`, `trace_id`, and JSONL export, so operators can reason over evidence without leaving the product surface. *UI details: **[Web UI and operator experience](#web-ui-and-operator-experience)**.* +2. **Catalog lifecycle and diff diagnostics** — Stronger mismatch signals beyond pricing-table row presence (for example version skew hints), strengthening economic governance on diffs. *UI details: **[Web UI and operator experience](#web-ui-and-operator-experience)**.* +3. **Integration glue** — Maintain app runtime emitters, CI/GitOps examples, and `serve` deployment recipes so the path from code to gated promotion is copy-pasteable. +4. **Serve and deployment hardening** — Clear operator narrative for health checks, supervision, and backup/restore alongside existing Compose/Helm references. +5. **Security ergonomics** — Continue explicit token/env status, mutation guardrails, and optional read-only UI patterns for local and bounded remote use. *UI details: **[Web UI and operator experience](#web-ui-and-operator-experience)**.* +6. **OTLP-oriented integration (mid term)** — Documented or thin adapter-style paths for correlated telemetry; not a commitment to an in-product APM. +7. **Fleet / cross-workspace (conditional)** — Broader governance surfaces only after the signals in **Horizons and conditions** below; default remains one workspace, one ledger. -- Harden CLI/schema contracts and edge-case policy coverage (sample windows, sparse traffic, error paths). -- Add concrete integration references: app runtime event emitters, CI pipeline examples, and deployment recipes for `flightdeck serve`. -- **Catalog-level cross-vendor pricing normalization** — first operator-driven slice in **v1.1.0** (`pricing_catalog_path`, **`pricing.catalog`** on diffs); **v1.0.4–v1.0.6** shipped per-side **`pricing.prices`** and **`pricing.warnings`** only. -- Strengthen local security ergonomics: explicit token/env status in UI, mutation guardrails, optional read-only UX. -- Continue UI productization for current scope (structured views over raw JSON where stable). +Optional milestone framing (headline only): a **v1.2** line might emphasize **forensics + catalog diagnostics**; ship notes still land in **RELEASE_NOTES** / **CHANGELOG**. -### Phase 0 progress (v1.0.3–v1.0.6) +--- -Shipped on **`main`**: +## Horizons and conditions -- **Policy / diff tests (v1.0.3):** `diff_releases` coverage for MEDIUM confidence vs `require_high_diff_confidence`, LOW sample floor boundaries, `max_latency_ms` (including skip when latency is absent), `max_error_rate`, and stacked policy failure reasons; CLI integration for MEDIUM blocking a second promotion after a baseline is established. -- **Ingest tests (v1.0.3):** empty JSONL (zero inserts), malformed line (non-zero exit), JSON array file accepted. -- **Multi-provider pricing (v1.0.3):** integration tests that diff baseline vs candidate releases with different **`pricing_reference`** providers (and same-provider different models), including parity checks on **`POST /v1/diff`** `pricing.pricing_or_model_changed`. -- **Web UI (v1.0.3):** structured outcome card after promote/rollback (policy, pointer, IDs) with raw JSON in a collapsible panel; Diff summary shows pricing/model change when the server marks it. -- **Pricing diagnostics (v1.0.4):** **`pricing.prices`** on **`POST /v1/diff`** and matching CLI / web lines for per-1k input/output unit prices when pricing or model differs. -- **Operating narrative (v1.0.4):** **[examples/README.md](examples/README.md)** index tying emit → ingest → verify → diff/gate → promote → serve. -- **Observability foundation (v1.0.4):** **`GET /v1/metrics`** JSON counters over the local ledger (not Prometheus/OTel; longer arc stays mid term). -- **Diff ergonomics (v1.0.5):** **`flightdeck release diff --output json`**; **`pricing.warnings`** on **`POST /v1/diff`** / CLI / web when the release model is missing from the imported pricing table; **Overview** shows key **`GET /v1/metrics`** counters. -- **Operator + pipeline breadth (v1.0.6):** **`curl`** and **Node** **`emit_sample_events.node.mjs`** under **[examples/integration/](examples/integration/README.md)**; **`flightdeck doctor --backup`**; deploy README covers healthcheck + backup scheduling. +### Near-term committed direction -### Phase 0 status +The ordered list above is the default backlog shape: deepen evidence and diff trust, reduce integration friction, and harden how `serve` is run—not a pivot to a hosted control plane. -**Phase 0 is closed** as of **v1.0.6** for the local-first wedge (immutable releases, evidence ingest, diff + policy gate, promote/rollback, audit, CI/deploy/integration references, metrics, diagnostics, and operator backup ergonomics). +### Conditional directions (not committed by default) -**Carried forward** (see gaps table): **catalog-level** multi-provider pricing normalization (single comparable unit across vendors), deeper **fleet** ergonomics, and **OTLP-oriented** telemetry — not blocking further patch releases on the Phase 0 spine. +- Optional hosted or federated control plane for cross-workspace policy and read models. +- Fleet-level analytics via export/read-model patterns (without turning the core into a general data warehouse). +- Deeper cost attribution and vendor/tool pricing coverage as the evidence model supports it. +- Provenance or supply-chain-style attestations only where they directly strengthen release trust boundaries. -### Phase-0 success signals +### When to expand scope (e.g. fleet / platform options) -- Teams use release versioning + checksum verification as the source of truth for promotion decisions. -- Cost/latency/error diff output drives at least one real rollout decision (not demo-only usage). -- Policy gates actively block at least one unsafe promotion in normal team workflows. -- CI templates are adopted externally without local patching. - ---- +- Repeated external demand for cross-workspace governance. +- Clear operator pain that cannot be solved with local-first patterns plus documented integrations. +- Confidence that expansion does not break core trust boundaries and contract stability. -## Mid term: Productization (v1.1.x tranche, roughly quarters) +### Vision (directional only, not backlog) -Goal: move from solid local tooling to repeatable production usage patterns. +- FlightDeck as a common release attestation reference for AI systems. +- Federated policy models across teams/workspaces with auditable inheritance. +- Ecosystem adapters that keep FlightDeck as a governance layer, not an agent framework. -**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. **v1.1.2** adds **CLI/HTTP forensics** filters and **JSONL export**; see **tranche status** below. +--- -### Productization progress (post–v1.1.0 / v1.1.1) +## Success and readiness signals -- **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. -- **Forensics (v1.1.2):** **`trace_id`** filter and **`runs export`** (JSONL). **Still open** for replay-style **web** views and richer export semantics if needed; **OTLP** and **catalog lifecycle** depth remain mid-term (gaps table). +Use **[examples/README.md](examples/README.md)** as a discoverability pass against these signals (not a product guarantee). -### Build targets +**Product (PMF wedge):** -- Human-in-the-loop approval workflow on top of policy gates (without requiring a hosted control plane). -- **Catalog-level multi-provider pricing normalization** — single comparable tariff unit across vendors; additive to today's per-provider **`pricing import`** tables and **`pricing.prices`** / **`pricing.warnings`** diagnostics. -- Stronger mismatch diagnostics beyond table row presence (for example version skew hints) as needed for the catalog work. -- Incident forensics improvements (replay/trace-style analysis over ingested evidence) as governance support tooling. -- Deployment hardening artifacts (for example Helm or equivalent) if a blessed server topology is chosen. -- Multi-workspace operator ergonomics (naming, templates, reproducible setup patterns). +- Teams treat release versioning + checksum verification as the source of truth for promotion decisions. +- Cost/latency/error diff output drives at least one real rollout decision (not demo-only usage). +- Policy gates actively block at least one unsafe promotion in normal team workflows. +- CI templates are adopted externally without local patching. -### Readiness signals +**Productization:** - Approval-gated promotion is used in at least one end-to-end production pipeline. - At least two provider pricing sources compare cleanly in one diff workflow. - Teams can stand up and operate `flightdeck serve` with documented deployment guidance. -### Productization tranche status +**Operator experience (web):** -**The scoped v1.1.0–v1.1.2 tranche is closed** as of **v1.1.2**: catalog-aware diffs + hints, approval-gated promote (HTTP + CLI + web), read-only runs forensics (**`GET /v1/runs`**, **`runs list`**, **`trace_id`**, **`runs export`**), reference **Helm** / **Compose** / **fleet** examples, SQLite migration **v4**, **`GET /v1/workspace`**, and a discoverability **checklist** for the **readiness signals** in **[examples/README.md](examples/README.md)**. - -**Carried forward to Phase 2 / mid-term** (explicitly not claimed by this closure): **OTLP-oriented** correlated telemetry; **cross-workspace / fleet** governance products; **replay-style** forensics UI beyond CLI/HTTP listing; **enterprise-grade** identity beyond documented bearer + loopback patterns; deeper **catalog lifecycle** governance (for example version skew hints). - ---- - -## Phase 2: Scale and platform options (long term, conditional) - -Goal: expand from single-workspace governance to broader fleet patterns when demand is proven. - -### Candidate directions (conditional, not committed by default) - -- Optional hosted/federated control plane for cross-workspace policy and read models. -- Fleet-level analytics via export/read-model patterns (without turning core into a general data warehouse). -- Deeper cost attribution and vendor/tool pricing coverage once evidence model supports it. -- Provenance/supply-chain style attestations if they directly strengthen release trust boundaries. - -### Conditions to enter Phase 2 - -- Repeated external demand for cross-workspace governance. -- Clear operator pain that cannot be solved with local-first patterns + documented integrations. -- Confidence that expansion does not break core trust boundaries and contract stability. +- An operator can reach a **promote vs blocked-by-policy** conclusion from **Diff** and **Actions** without opening raw JSON first. +- A forensics task (for example trace-scoped triage) is completed from **Runs** without falling back to the CLI for the same filters and slice. --- -## Phase 3: Super long term (vision only, highly conditional) - -- FlightDeck as a common release attestation standard for AI systems. -- Federated policy models across teams/workspaces with auditable inheritance. -- Broader ecosystem adapters that preserve FlightDeck's role as governance layer, not agent framework. - -These are directional, not committed backlog items. - ---- - -## Explicit non-goals (near term, aligned with AGENTS.md) - -- No prompt IDE, no agent framework, no gateway/proxy-by-default. -- No compliance-scanner product as a near-term deliverable. -- No fine-tuning operations roadmap in core. -- No broad plugin/marketplace direction near term. -- No dashboard-heavy product before CLI/local HTTP reliability is deeply proven. +## Non-goals -Hosted control plane and in-path traffic routing remain opt-in long-term considerations, not default product posture. +Near-term exclusions match **[AGENTS.md](AGENTS.md)** (no prompt IDE, no agent framework, no gateway-by-default, no compliance-scanner product, no fine-tuning ops roadmap in core, no broad plugin system, no dashboard-heavy product before CLI/local HTTP is deeply proven). Hosted control plane and in-path traffic routing stay opt-in long-term considerations, not default posture. --- ## References -- **Contracts and trust:** `RELEASE_NOTES.md`, `CHANGELOG.md`, `SECURITY.md` -- **Versioning:** `VERSIONING.md` -- **Contributors/org workflow:** `CONTRIBUTING.md` -- **Engineering rules and doctrine:** `AGENTS.md` +- **Contracts and trust:** [RELEASE_NOTES.md](RELEASE_NOTES.md), [CHANGELOG.md](CHANGELOG.md), [SECURITY.md](SECURITY.md) +- **Versioning:** [VERSIONING.md](VERSIONING.md) +- **Contributors/org workflow:** [CONTRIBUTING.md](CONTRIBUTING.md) +- **Engineering rules and doctrine:** [AGENTS.md](AGENTS.md) +- **Web UI routing and components:** [docs/web-ui.md](docs/web-ui.md) diff --git a/docs/web-ui.md b/docs/web-ui.md index 3de3f2a..35d055e 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -1,5 +1,7 @@ # FlightDeck web UI reference +Strategic UI priorities and UX intent live in **[ROADMAP.md](../ROADMAP.md#web-ui-and-operator-experience)**; this document is the **technical** reference (routes, components, build flags). + `flightdeck serve` serves the React app at `/`. It is built from **`web/`** and the committed production bundle lives under **`src/flightdeck/server/static/`**. @@ -16,10 +18,10 @@ 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` | Renders `pricing.warnings`, optional **`pricing.catalog`** / **`pricing.hints`**, per-1k prices when present | -| `#/runs` | `RunsPage` | `GET /v1/releases` (for datalist), `GET /v1/runs`, `GET /v1/runs/export` | Forensics: filters, table, NDJSON download | -| `#/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 | +| `#/` | `OverviewPage` | `GET /v1/releases`, `GET /v1/promoted`, `GET /v1/actions`, `GET /v1/metrics` (parallel where applicable) | Ledger metrics (read-only); skeleton while loading; links to Diff/Runs | +| `#/diff` | `DiffPage` | `POST /v1/diff` | Sections: policy gate (incl. `evaluated_at`), evidence window, pricing/catalog/hints, per-1k prices when present, cost/quality rollups; raw JSON panel | +| `#/runs` | `RunsPage` | `GET /v1/releases` (for datalist), `GET /v1/runs`, `GET /v1/runs/export` | Forensics: filters, table (trace/status, trace band rows), **View** drawer, empty/offset/truncation hints, NDJSON download | +| `#/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 skeleton then strip; approval path: numbered steps, pending **Refresh list** / **Use for confirm**; see **ActionsPage** below | | `#/*` (any other) | — | Redirects to `#/` | | `App.tsx` declares the route tree. `AppShell` is the layout wrapper rendered for all routes. diff --git a/examples/README.md b/examples/README.md index 30c89dc..ceb90d9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,14 +7,14 @@ 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. Optional **`pricing_catalog_path`** in `flightdeck.yaml` adds **`pricing.catalog`** / **`pricing.hints`** on diffs (see [docs/pricing-catalog.md](../docs/pricing-catalog.md)). +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)). **Same contract in the browser or HTTP:** with `flightdeck serve`, open **`/#/diff`** for structured policy / pricing / rollup sections, or call **`POST /v1/diff`** (matches **`flightdeck release diff --output json`**). Details: [docs/web-ui.md](../docs/web-ui.md), [docs/http-api.md](../docs/http-api.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) and [ci/README.md](ci/README.md) (GitHub Actions patterns). 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`** / **`runs export`** 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). ## Readiness checklist (quick pass) -Use this as a **discoverability** pass for the **[ROADMAP.md](../ROADMAP.md)** readiness signals (not a product guarantee): +Use this as a **discoverability** pass for the **[ROADMAP.md](../ROADMAP.md)** success and readiness signals (not a product guarantee): | Signal | Where to start | |--------|----------------| diff --git a/examples/integration/README.md b/examples/integration/README.md index fef3160..22bef73 100644 --- a/examples/integration/README.md +++ b/examples/integration/README.md @@ -31,6 +31,10 @@ python examples/integration/emit_sample_events.py --release-id rel_abc123 --agen The HTTP body is `{"events": [, ...]}` with **`api_version`: `"v1"`**. Field reference: **[docs/http-api.md](../../docs/http-api.md)** and **`schemas/v1/run_event.schema.json`**. +### After ingest + +Use **`flightdeck release diff`**, **`POST /v1/diff`** (same JSON as **`release diff --output json`**), or the bundled UI at **`/#/diff`** to compare baseline vs candidate over a window once both releases are registered and run evidence exists. CI pattern: [examples/ci/README.md](../ci/README.md). + ### `curl` (no SDK) Replace **`REL_ID`**, **`AGENT`**, and optionally **`BASE`** (default `http://127.0.0.1:8765`): diff --git a/src/flightdeck/server/static/assets/index-7m5ayZhE.js b/src/flightdeck/server/static/assets/index-7m5ayZhE.js deleted file mode 100644 index 7e0b7eb..0000000 --- a/src/flightdeck/server/static/assets/index-7m5ayZhE.js +++ /dev/null @@ -1,11 +0,0 @@ -(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 qf={exports:{}},Un={};var cm;function gy(){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 z in m)z!=="key"&&(h[z]=m[z])}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 Sy(){return fm||(fm=1,qf.exports=gy()),qf.exports}var r=Sy(),Bf={exports:{}},ee={};var sm;function by(){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"),z=Symbol.for("react.forward_ref"),S=Symbol.for("react.suspense"),y=Symbol.for("react.memo"),C=Symbol.for("react.lazy"),T=Symbol.for("react.activity"),Y=Symbol.iterator;function H(p){return p===null||typeof p!="object"?null:(p=Y&&p[Y]||p["@@iterator"],typeof p=="function"?p:null)}var Q={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Z=Object.assign,q={};function V(p,A,B){this.props=p,this.context=A,this.refs=q,this.updater=B||Q}V.prototype.isReactComponent={},V.prototype.setState=function(p,A){if(typeof p!="object"&&typeof p!="function"&&p!=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,p,A,"setState")},V.prototype.forceUpdate=function(p){this.updater.enqueueForceUpdate(this,p,"forceUpdate")};function W(){}W.prototype=V.prototype;function X(p,A,B){this.props=p,this.context=A,this.refs=q,this.updater=B||Q}var w=X.prototype=new W;w.constructor=X,Z(w,V.prototype),w.isPureReactComponent=!0;var ae=Array.isArray;function te(){}var G={H:null,A:null,T:null,S:null},ie=Object.prototype.hasOwnProperty;function de(p,A,B){var K=B.ref;return{$$typeof:i,type:p,key:A,ref:K!==void 0?K:null,props:B}}function qe(p,A){return de(p.type,A,p.props)}function Xe(p){return typeof p=="object"&&p!==null&&p.$$typeof===i}function Oe(p){var A={"=":"=0",":":"=2"};return"$"+p.replace(/[=:]/g,function(B){return A[B]})}var We=/\/+/g;function Fe(p,A){return typeof p=="object"&&p!==null&&p.key!=null?Oe(""+p.key):A.toString(36)}function je(p){switch(p.status){case"fulfilled":return p.value;case"rejected":throw p.reason;default:switch(typeof p.status=="string"?p.then(te,te):(p.status="pending",p.then(function(A){p.status==="pending"&&(p.status="fulfilled",p.value=A)},function(A){p.status==="pending"&&(p.status="rejected",p.reason=A)})),p.status){case"fulfilled":return p.value;case"rejected":throw p.reason}}throw p}function x(p,A,B,K,P){var ce=typeof p;(ce==="undefined"||ce==="boolean")&&(p=null);var ge=!1;if(p===null)ge=!0;else switch(ce){case"bigint":case"string":case"number":ge=!0;break;case"object":switch(p.$$typeof){case i:case s:ge=!0;break;case C:return ge=p._init,x(ge(p._payload),A,B,K,P)}}if(ge)return P=P(p),ge=K===""?"."+Fe(p,0):K,ae(P)?(B="",ge!=null&&(B=ge.replace(We,"$&/")+"/"),x(P,A,B,"",function(Ga){return Ga})):P!=null&&(Xe(P)&&(P=qe(P,B+(P.key==null||p&&p.key===P.key?"":(""+P.key).replace(We,"$&/")+"/")+ge)),A.push(P)),1;ge=0;var Pe=K===""?".":K+":";if(ae(p))for(var Me=0;Me>>1,he=x[ue];if(0>>1;uem(B,$))Km(P,B)?(x[ue]=P,x[K]=$,ue=K):(x[ue]=B,x[A]=$,ue=A);else if(Km(P,$))x[ue]=P,x[K]=$,ue=K;else break e}}return L}function m(x,L){var $=x.sortIndex-L.sortIndex;return $!==0?$:x.id-L.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var h=performance;i.unstable_now=function(){return h.now()}}else{var b=Date,z=b.now();i.unstable_now=function(){return b.now()-z}}var S=[],y=[],C=1,T=null,Y=3,H=!1,Q=!1,Z=!1,q=!1,V=typeof setTimeout=="function"?setTimeout:null,W=typeof clearTimeout=="function"?clearTimeout:null,X=typeof setImmediate<"u"?setImmediate:null;function w(x){for(var L=d(y);L!==null;){if(L.callback===null)f(y);else if(L.startTime<=x)f(y),L.sortIndex=L.expirationTime,s(S,L);else break;L=d(y)}}function ae(x){if(Z=!1,w(x),!Q)if(d(S)!==null)Q=!0,te||(te=!0,Oe());else{var L=d(y);L!==null&&je(ae,L.startTime-x)}}var te=!1,G=-1,ie=5,de=-1;function qe(){return q?!0:!(i.unstable_now()-dex&&qe());){var ue=T.callback;if(typeof ue=="function"){T.callback=null,Y=T.priorityLevel;var he=ue(T.expirationTime<=x);if(x=i.unstable_now(),typeof he=="function"){T.callback=he,w(x),L=!0;break t}T===d(S)&&f(S),w(x)}else f(S);T=d(S)}if(T!==null)L=!0;else{var p=d(y);p!==null&&je(ae,p.startTime-x),L=!1}}break e}finally{T=null,Y=$,H=!1}L=void 0}}finally{L?Oe():te=!1}}}var Oe;if(typeof X=="function")Oe=function(){X(Xe)};else if(typeof MessageChannel<"u"){var We=new MessageChannel,Fe=We.port2;We.port1.onmessage=Xe,Oe=function(){Fe.postMessage(null)}}else Oe=function(){V(Xe,0)};function je(x,L){G=V(function(){x(i.unstable_now())},L)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(x){x.callback=null},i.unstable_forceFrameRate=function(x){0>x||125ue?(x.sortIndex=$,s(y,x),d(S)===null&&x===d(y)&&(Z?(W(G),G=-1):Z=!0,je(ae,$-ue))):(x.sortIndex=he,s(S,x),Q||H||(Q=!0,te||(te=!0,Oe()))),x},i.unstable_shouldYield=qe,i.unstable_wrapCallback=function(x){var L=Y;return function(){var $=Y;Y=L;try{return x.apply(this,arguments)}finally{Y=$}}}})(Gf)),Gf}var dm;function Ey(){return dm||(dm=1,Yf.exports=_y()),Yf.exports}var wf={exports:{}},Ie={};var mm;function xy(){if(mm)return Ie;mm=1;var i=Wf();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(),wf.exports=xy(),wf.exports}var vm;function jy(){if(vm)return Hn;vm=1;var i=Ey(),s=Wf(),d=Ny();function f(e){var t="https://react.dev/errors/"+e;if(1he||(e.current=ue[he],ue[he]=null,he--)}function B(e,t){he++,ue[he]=e.current,e.current=t}var K=p(null),P=p(null),ce=p(null),ge=p(null);function Pe(e,t){switch(B(ce,t),B(P,e),B(K,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Od(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Od(t),e=Cd(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}A(K),B(K,e)}function Me(){A(K),A(P),A(ce)}function Ga(e){e.memoizedState!==null&&B(ge,e);var t=K.current,l=Cd(t,e.type);t!==l&&(B(P,e),B(K,l))}function Qn(e){P.current===e&&(A(K),A(P)),ge.current===e&&(A(ge),On._currentValue=$)}var pi,us;function Cl(e){if(pi===void 0)try{throw Error()}catch(l){var t=l.stack.trim().match(/\n( *(at )?)/);pi=t&&t[1]||"",us=-1)":-1n||v[a]!==j[n]){var D=` -`+v[a].replace(" at new "," at ");return e.displayName&&D.includes("")&&(D=D.replace("",e.displayName)),D}while(1<=a&&0<=n);break}}}finally{gi=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?Cl(l):""}function km(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 Si(e.type,!1);case 11:return Si(e.type.render,!1);case 1:return Si(e.type,!0);case 31:return Cl("Activity");default:return""}}function is(e){try{var t="",l=null;do t+=km(e,l),l=e,e=e.return;while(e);return t}catch(a){return` -Error generating stack: `+a.message+` -`+a.stack}}var bi=Object.prototype.hasOwnProperty,_i=i.unstable_scheduleCallback,Ei=i.unstable_cancelCallback,Wm=i.unstable_shouldYield,Fm=i.unstable_requestPaint,ft=i.unstable_now,Im=i.unstable_getCurrentPriorityLevel,cs=i.unstable_ImmediatePriority,fs=i.unstable_UserBlockingPriority,Zn=i.unstable_NormalPriority,Pm=i.unstable_LowPriority,ss=i.unstable_IdlePriority,eh=i.log,th=i.unstable_setDisableYieldValue,wa=null,st=null;function il(e){if(typeof eh=="function"&&th(e),st&&typeof st.setStrictMode=="function")try{st.setStrictMode(wa,e)}catch{}}var rt=Math.clz32?Math.clz32:nh,lh=Math.log,ah=Math.LN2;function nh(e){return e>>>=0,e===0?32:31-(lh(e)/ah|0)|0}var Vn=256,Kn=262144,Jn=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 $n(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 o=a&134217727;return o!==0?(a=o&~u,a!==0?n=Dl(a):(c&=o,c!==0?n=Dl(c):l||(l=o&~e,l!==0&&(n=Dl(l))))):(o=a&~u,o!==0?n=Dl(o):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 Xa(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function uh(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=Jn;return Jn<<=1,(Jn&62914560)===0&&(Jn=4194304),e}function xi(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function Qa(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function ih(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 o=e.entanglements,v=e.expirationTimes,j=e.hiddenUpdates;for(l=c&~l;0"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var dh=/[\n"\\]/g;function bt(e){return e.replace(dh,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function zi(e,t,l,a,n,u,c,o){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?Oi(e,c,St(t)):l!=null?Oi(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"),o!=null&&typeof o!="function"&&typeof o!="symbol"&&typeof o!="boolean"?e.name=""+St(o):e.removeAttribute("name")}function xs(e,t,l,a,n,u,c,o){if(u!=null&&typeof u!="function"&&typeof u!="symbol"&&typeof u!="boolean"&&(e.type=u),t!=null||l!=null){if(!(u!=="submit"&&u!=="reset"||t!=null)){Ai(e);return}l=l!=null?""+St(l):"",t=t!=null?""+St(t):l,o||t===e.value||(e.value=t),e.defaultValue=t}a=a??n,a=typeof a!="function"&&typeof a!="symbol"&&!!a,e.checked=o?e.checked:!!a,e.defaultChecked=!!a,c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"&&(e.name=c),Ai(e)}function Oi(e,t,l){t==="number"&&Fn(e.ownerDocument)===e||e.defaultValue===""+l||(e.defaultValue=""+l)}function aa(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"),Hi=!1;if(Qt)try{var Ja={};Object.defineProperty(Ja,"passive",{get:function(){Hi=!0}}),window.addEventListener("test",Ja,Ja),window.removeEventListener("test",Ja,Ja)}catch{Hi=!1}var fl=null,qi=null,Pn=null;function Os(){if(Pn)return Pn;var e,t=qi,l=t.length,a,n="value"in fl?fl.value:fl.textContent,u=n.length;for(e=0;e=Wa),qs=" ",Bs=!1;function Ls(e,t){switch(e){case"keyup":return Gh.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 ca=!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(ca)return e==="compositionend"||!wi&&Ls(e,t)?(e=Os(),Pn=qi=fl=null,ca=!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 ks(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?ks(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ws(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Fn(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=Fn(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 Fh=Qt&&"documentMode"in document&&11>=document.documentMode,fa=null,Vi=null,en=null,Ki=!1;function Fs(e,t,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;Ki||fa==null||fa!==Fn(a)||(a=fa,"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=Ku(Vi,"onSelect"),0>=c,n-=c,qt=1<<32-rt(t)+n|l<ne?(oe=k,k=null):oe=k.sibling;var ye=R(E,k,N[ne],M);if(ye===null){k===null&&(k=oe);break}e&&k&&ye.alternate===null&&t(E,k),g=u(ye,g,ne),ve===null?F=ye:ve.sibling=ye,ve=ye,k=oe}if(ne===N.length)return l(E,k),me&&Vt(E,ne),F;if(k===null){for(;nene?(oe=k,k=null):oe=k.sibling;var Ol=R(E,k,ye.value,M);if(Ol===null){k===null&&(k=oe);break}e&&k&&Ol.alternate===null&&t(E,k),g=u(Ol,g,ne),ve===null?F=Ol:ve.sibling=Ol,ve=Ol,k=oe}if(ye.done)return l(E,k),me&&Vt(E,ne),F;if(k===null){for(;!ye.done;ne++,ye=N.next())ye=U(E,ye.value,M),ye!==null&&(g=u(ye,g,ne),ve===null?F=ye:ve.sibling=ye,ve=ye);return me&&Vt(E,ne),F}for(k=a(k);!ye.done;ne++,ye=N.next())ye=O(k,E,ne,ye.value,M),ye!==null&&(e&&ye.alternate!==null&&k.delete(ye.key===null?ne:ye.key),g=u(ye,g,ne),ve===null?F=ye:ve.sibling=ye,ve=ye);return e&&k.forEach(function(py){return t(E,py)}),me&&Vt(E,ne),F}function xe(E,g,N,M){if(typeof N=="object"&&N!==null&&N.type===Z&&N.key===null&&(N=N.props.children),typeof N=="object"&&N!==null){switch(N.$$typeof){case H:e:{for(var F=N.key;g!==null;){if(g.key===F){if(F=N.type,F===Z){if(g.tag===7){l(E,g.sibling),M=n(g,N.props.children),M.return=E,E=M;break e}}else if(g.elementType===F||typeof F=="object"&&F!==null&&F.$$typeof===ie&&Ql(F)===g.type){l(E,g.sibling),M=n(g,N.props),cn(M,N),M.return=E,E=M;break e}l(E,g);break}else t(E,g);g=g.sibling}N.type===Z?(M=Ll(N.props.children,E.mode,M,N.key),M.return=E,E=M):(M=su(N.type,N.key,N.props,null,E.mode,M),cn(M,N),M.return=E,E=M)}return c(E);case Q:e:{for(F=N.key;g!==null;){if(g.key===F)if(g.tag===4&&g.stateNode.containerInfo===N.containerInfo&&g.stateNode.implementation===N.implementation){l(E,g.sibling),M=n(g,N.children||[]),M.return=E,E=M;break e}else{l(E,g);break}else t(E,g);g=g.sibling}M=Pi(N,E.mode,M),M.return=E,E=M}return c(E);case ie:return N=Ql(N),xe(E,g,N,M)}if(je(N))return J(E,g,N,M);if(Oe(N)){if(F=Oe(N),typeof F!="function")throw Error(f(150));return N=F.call(N),I(E,g,N,M)}if(typeof N.then=="function")return xe(E,g,yu(N),M);if(N.$$typeof===X)return xe(E,g,du(E,N),M);pu(E,N)}return typeof N=="string"&&N!==""||typeof N=="number"||typeof N=="bigint"?(N=""+N,g!==null&&g.tag===6?(l(E,g.sibling),M=n(g,N),M.return=E,E=M):(l(E,g),M=Ii(N,E.mode,M),M.return=E,E=M),c(E)):l(E,g)}return function(E,g,N,M){try{un=0;var F=xe(E,g,N,M);return Sa=null,F}catch(k){if(k===ga||k===hu)throw k;var ve=dt(29,k,null,E.mode);return ve.lanes=M,ve.return=E,ve}}}var Vl=br(!0),_r=br(!1),ml=!1;function oc(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function dc(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,(pe&2)!==0){var n=a.pending;return n===null?t.next=t:(t.next=n.next,n.next=t),a.pending=t,t=fu(e),nr(e,null,l),t}return cu(e,a,t,l),fu(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 mc(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 hc=!1;function sn(){if(hc){var e=pa;if(e!==null)throw e}}function rn(e,t,l,a){hc=!1;var n=e.updateQueue;ml=!1;var u=n.firstBaseUpdate,c=n.lastBaseUpdate,o=n.shared.pending;if(o!==null){n.shared.pending=null;var v=o,j=v.next;v.next=null,c===null?u=j:c.next=j,c=v;var D=e.alternate;D!==null&&(D=D.updateQueue,o=D.lastBaseUpdate,o!==c&&(o===null?D.firstBaseUpdate=j:o.next=j,D.lastBaseUpdate=v))}if(u!==null){var U=n.baseState;c=0,D=j=v=null,o=u;do{var R=o.lane&-536870913,O=R!==o.lane;if(O?(re&R)===R:(a&R)===R){R!==0&&R===ya&&(hc=!0),D!==null&&(D=D.next={lane:0,tag:o.tag,payload:o.payload,callback:null,next:null});e:{var J=e,I=o;R=t;var xe=l;switch(I.tag){case 1:if(J=I.payload,typeof J=="function"){U=J.call(xe,U,R);break e}U=J;break e;case 3:J.flags=J.flags&-65537|128;case 0:if(J=I.payload,R=typeof J=="function"?J.call(xe,U,R):J,R==null)break e;U=T({},U,R);break e;case 2:ml=!0}}R=o.callback,R!==null&&(e.flags|=64,O&&(e.flags|=8192),O=n.callbacks,O===null?n.callbacks=[R]:O.push(R))}else O={lane:R,tag:o.tag,payload:o.payload,callback:o.callback,next:null},D===null?(j=D=O,v=U):D=D.next=O,c|=R;if(o=o.next,o===null){if(o=n.shared.pending,o===null)break;O=o,o=O.next,O.next=null,n.lastBaseUpdate=O,n.shared.pending=null}}while(!0);D===null&&(v=U),n.baseState=v,n.firstBaseUpdate=j,n.lastBaseUpdate=D,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=x.T,o={};x.T=o,Mc(e,!1,t,l);try{var v=n(),j=x.S;if(j!==null&&j(o,v),v!==null&&typeof v=="object"&&typeof v.then=="function"){var D=iv(v,a);mn(e,t,D,pt(e))}else mn(e,t,a,pt(e))}catch(U){mn(e,t,{then:function(){},status:"rejected",reason:U},pt())}finally{L.p=u,c!==null&&o.types!==null&&(c.types=o.types),x.T=c}}function dv(){}function Cc(e,t,l,a){if(e.tag!==5)throw Error(f(476));var n=to(e).queue;eo(e,n,t,$,l===null?dv:function(){return lo(e),l(a)})}function to(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:$,baseState:$,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:kt,lastRenderedState:$},next:null};var l={};return t.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:kt,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 Dc(){return Je(On)}function ao(){return He().memoizedState}function no(){return He().memoizedState}function mv(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&&(ct(a,t,l),fn(a,t,l)),t={cache:cc()},e.payload=t;return}t=t.return}}function hv(e,t,l){var a=pt();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Ru(e)?io(t,l):(l=Wi(e,t,l,a),l!==null&&(ct(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(Ru(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,o=u(c,l);if(n.hasEagerState=!0,n.eagerState=o,ot(o,c))return cu(e,t,n,0),Ne===null&&iu(),!1}catch{}if(l=Wi(e,t,n,a),l!==null)return ct(l,e,a),co(l,t,a),!0}return!1}function Mc(e,t,l,a){if(a={lane:2,revertLane:df(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Ru(e)){if(t)throw Error(f(479))}else t=Wi(e,l,a,2),t!==null&&ct(t,e,2)}function Ru(e){var t=e.alternate;return e===le||t!==null&&t===le}function io(e,t){_a=bu=!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:Je,use:xu,useCallback:Ce,useContext:Ce,useEffect:Ce,useImperativeHandle:Ce,useLayoutEffect:Ce,useInsertionEffect:Ce,useMemo:Ce,useReducer:Ce,useRef:Ce,useState:Ce,useDebugValue:Ce,useDeferredValue:Ce,useTransition:Ce,useSyncExternalStore:Ce,useId:Ce,useHostTransitionStatus:Ce,useFormState:Ce,useActionState:Ce,useOptimistic:Ce,useMemoCache:Ce,useCacheRefresh:Ce};hn.useEffectEvent=Ce;var fo={readContext:Je,use:xu,useCallback:function(e,t){return et().memoizedState=[e,t===void 0?null:t],e},useContext:Je,useEffect:Vr,useImperativeHandle:function(e,t,l){l=l!=null?l.concat([e]):null,ju(4194308,4,kr.bind(null,t,e),l)},useLayoutEffect:function(e,t){return ju(4194308,4,e,t)},useInsertionEffect:function(e,t){ju(4,2,e,t)},useMemo:function(e,t){var l=et();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=et();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=hv.bind(null,le,e),[a.memoizedState,e]},useRef:function(e){var t=et();return e={current:e},t.memoizedState=e},useState:function(e){e=Tc(e);var t=e.queue,l=uo.bind(null,le,t);return t.dispatch=l,[e.memoizedState,l]},useDebugValue:zc,useDeferredValue:function(e,t){var l=et();return Oc(l,e,t)},useTransition:function(){var e=Tc(!1);return e=eo.bind(null,le,e.queue,!0,!1),et().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,l){var a=le,n=et();if(me){if(l===void 0)throw Error(f(407));l=l()}else{if(l=t(),Ne===null)throw Error(f(349));(re&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,xa(9,{destroy:void 0},Or.bind(null,a,u,l,t),null),l},useId:function(){var e=et(),t=Ne.identifierPrefix;if(me){var l=Bt,a=qt;l=(a&~(1<<32-rt(a)-1)).toString(32)+l,t="_"+t+"R_"+l,l=_u++,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[Ve]=t,u[tt]=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(ke(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 Re(t),Jc(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=ce.current,ha(t)){if(e=t.stateNode,l=t.memoizedProps,a=null,n=Ke,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}e[Ve]=t,e=!!(e.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||Ad(e.nodeValue,l)),e||ol(t,!0)}else e=Ju(e).createTextNode(a),e[Ve]=t,t.stateNode=e}return Re(t),null;case 31:if(l=t.memoizedState,e===null||e.memoizedState!==null){if(a=ha(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[Ve]=t}else Yl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Re(t),e=!1}else l=ac(),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 Re(t),null;case 13:if(a=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(n=ha(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[Ve]=t}else Yl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Re(t),n=!1}else n=ac(),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),Du(t,t.updateQueue),Re(t),null);case 4:return Me(),e===null&&yf(t.stateNode.containerInfo),Re(t),null;case 10:return Jt(t.type),Re(t),null;case 19:if(A(Ue),a=t.memoizedState,a===null)return Re(t),null;if(n=(t.flags&128)!==0,u=a.rendering,u===null)if(n)yn(a,!1);else{if(De!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(u=Su(e),u!==null){for(t.flags|=128,yn(a,!1),e=u.updateQueue,t.updateQueue=e,Du(t,e),t.subtreeFlags=0,e=l,l=t.child;l!==null;)ur(l,e),l=l.sibling;return B(Ue,Ue.current&1|2),me&&Vt(t,a.treeForkCount),t.child}e=e.sibling}a.tail!==null&&ft()>Bu&&(t.flags|=128,n=!0,yn(a,!1),t.lanes=4194304)}else{if(!n)if(e=Su(u),e!==null){if(t.flags|=128,n=!0,e=e.updateQueue,t.updateQueue=e,Du(t,e),yn(a,!0),a.tail===null&&a.tailMode==="hidden"&&!u.alternate&&!me)return Re(t),null}else 2*ft()-a.renderingStartTime>Bu&&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=Ue.current,B(Ue,n?l&1|2:l&1),me&&Vt(t,a.treeForkCount),e):(Re(t),null);case 22:case 23:return ht(t),yc(),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&&(Re(t),t.subtreeFlags&6&&(t.flags|=8192)):Re(t),l=t.updateQueue,l!==null&&Du(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&&A(Xl),null;case 24:return l=null,e!==null&&(l=e.memoizedState.cache),t.memoizedState.cache!==l&&(t.flags|=2048),Jt(Be),Re(t),null;case 25:return null;case 30:return null}throw Error(f(156,t.tag))}function Sv(e,t){switch(tc(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Jt(Be),Me(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return Qn(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 A(Ue),null;case 4:return Me(),null;case 10:return Jt(t.type),null;case 22:case 23:return ht(t),yc(),e!==null&&A(Xl),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Jt(Be),null;case 25:return null;default:return null}}function Mo(e,t){switch(tc(t),t.tag){case 3:Jt(Be),Me();break;case 26:case 27:case 5:Qn(t);break;case 4:Me();break;case 31:t.memoizedState!==null&&ht(t);break;case 13:ht(t);break;case 19:A(Ue);break;case 10:Jt(t.type);break;case 22:case 23:ht(t),yc(),e!==null&&A(Xl);break;case 24:Jt(Be)}}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(o){be(t,t.return,o)}}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,o=c.destroy;if(o!==void 0){c.destroy=void 0,n=t;var v=l,j=o;try{j()}catch(D){be(n,v,D)}}}a=a.next}while(a!==u)}}catch(D){be(t,t.return,D)}}function Uo(e){var t=e.updateQueue;if(t!==null){var l=e.stateNode;try{xr(t,l)}catch(a){be(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){be(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){be(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){be(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){be(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){be(e,e.return,n)}}function $c(e,t,l){try{var a=e.stateNode;wv(a,e.type,l,t),a[tt]=t}catch(n){be(e,e.return,n)}}function Bo(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&jl(e.type)||e.tag===4}function kc(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Bo(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&jl(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=Xt));else if(a!==4&&(a===27&&jl(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 Mu(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&&jl(e.type)&&(l=e.stateNode),e=e.child,e!==null))for(Mu(e,t,l),e=e.sibling;e!==null;)Mu(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]);ke(t,a,l),t[Ve]=e,t[tt]=l}catch(u){be(e,e.return,u)}}var It=!1,Ge=!1,Fc=!1,Yo=typeof WeakSet=="function"?WeakSet:Set,Ze=null;function bv(e,t){if(e=e.containerInfo,Sf=ei,e=Ws(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,o=-1,v=-1,j=0,D=0,U=e,R=null;t:for(;;){for(var O;U!==l||n!==0&&U.nodeType!==3||(o=c+n),U!==u||a!==0&&U.nodeType!==3||(v=c+a),U.nodeType===3&&(c+=U.nodeValue.length),(O=U.firstChild)!==null;)R=U,U=O;for(;;){if(U===e)break t;if(R===l&&++j===n&&(o=c),R===u&&++D===a&&(v=c),(O=U.nextSibling)!==null)break;U=R,R=U.parentNode}U=O}l=o===-1||v===-1?null:{start:o,end:v}}else l=null}l=l||{start:0,end:0}}else l=null;for(bf={focusedElem:e,selectionRange:l},ei=!1,Ze=t;Ze!==null;)if(t=Ze,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,Ze=e;else for(;Ze!==null;){switch(t=Ze,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"))),ke(u,a,l),u[Ve]=e,Qe(u),a=u;break e;case"link":var c=Vd("link","href",n).get(a+(l.href||""));if(c){for(var o=0;oxe&&(c=xe,xe=I,I=c);var E=$s(o,I),g=$s(o,xe);if(E&&g&&(O.rangeCount!==1||O.anchorNode!==E.node||O.anchorOffset!==E.offset||O.focusNode!==g.node||O.focusOffset!==g.offset)){var N=U.createRange();N.setStart(E.node,E.offset),O.removeAllRanges(),I>xe?(O.addRange(N),O.extend(g.node,g.offset)):(N.setEnd(g.node,g.offset),O.addRange(N))}}}}for(U=[],O=o;O=O.parentNode;)O.nodeType===1&&U.push({element:O,left:O.scrollLeft,top:O.scrollTop});for(typeof o.focus=="function"&&o.focus(),o=0;ol?32:l,x.T=null,l=nf,nf=null;var u=El,c=al;if(we=0,Aa=El=null,al=0,(pe&6)!==0)throw Error(f(331));var o=pe;if(pe|=4,Wo(u.current),Jo(u,u.current,c,l),pe=o,Nn(0,!1),st&&typeof st.onPostCommitFiberRoot=="function")try{st.onPostCommitFiberRoot(wa,u)}catch{}return!0}finally{L.p=n,x.T=a,hd(e,t)}}function yd(e,t,l){t=Et(l,t),t=Bc(e.stateNode,t,2),e=vl(e,t,2),e!==null&&(Qa(e,2),Yt(e))}function be(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),Qa(a,2),Yt(a));break}}t=t.return}}function sf(e,t,l){var a=e.pingCache;if(a===null){a=e.pingCache=new xv;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)||(ef=!0,n.add(l),e=Av.bind(null,e,t,l),t.then(e,e))}function Av(e,t,l){var a=e.pingCache;a!==null&&a.delete(t),e.pingedLanes|=e.suspendedLanes&l,e.warmLanes&=~l,Ne===e&&(re&l)===l&&(De===4||De===3&&(re&62914560)===re&&300>ft()-qu?(pe&2)===0&&za(e,0):tf|=l,Ra===re&&(Ra=0)),Yt(e)}function pd(e,t){t===0&&(t=rs()),e=Bl(e,t),e!==null&&(Qa(e,t),Yt(e))}function zv(e){var t=e.memoizedState,l=0;t!==null&&(l=t.retryLane),pd(e,l)}function Ov(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 Cv(e,t){return _i(e,t)}var Qu=null,Ca=null,rf=!1,Zu=!1,of=!1,Nl=0;function Yt(e){e!==Ca&&e.next===null&&(Ca===null?Qu=Ca=e:Ca=Ca.next=e),Zu=!0,rf||(rf=!0,Mv())}function Nn(e,t){if(!of&&Zu){of=!0;do for(var l=!1,a=Qu;a!==null;){if(e!==0){var n=a.pendingLanes;if(n===0)var u=0;else{var c=a.suspendedLanes,o=a.pingedLanes;u=(1<<31-rt(42|e)+1)-1,u&=n&~(c&~o),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(l=!0,_d(a,u))}else u=re,u=$n(a,a===Ne?u:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(u&3)===0||Xa(a,u)||(l=!0,_d(a,u));a=a.next}while(l);of=!1}}function Dv(){gd()}function gd(){Zu=rf=!1;var e=0;Nl!==0&&Qv()&&(e=Nl);for(var t=ft(),l=null,a=Qu;a!==null;){var n=a.next,u=Sd(a,t);u===0?(a.next=null,l===null?Qu=n:l.next=n,n===null&&(Ca=l)):(l=a,(e!==0||(u&3)!==0)&&(Zu=!0)),a=n}we!==0&&we!==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;0o)break;var D=v.transferSize,U=v.initiatorType;D&&zd(U)&&(v=v.responseEnd,c+=D*(v"u"?null:document;function wd(e,t,l){var a=Da;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"),ke(t,"link",e),Qe(t),a.head.appendChild(t)))}}function Iv(e){nl.D(e),wd("dns-prefetch",e,null)}function Pv(e,t){nl.C(e,t),wd("preconnect",e,t)}function ey(e,t,l){nl.L(e,t,l);var a=Da;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=Ma(e);break;case"script":u=Ua(e)}At.has(u)||(e=T({rel:"preload",href:t==="image"&&l&&l.imageSrcSet?void 0:e,as:t},l),At.set(u,e),a.querySelector(n)!==null||t==="style"&&a.querySelector(An(u))||t==="script"&&a.querySelector(zn(u))||(t=a.createElement("link"),ke(t,"link",e),Qe(t),a.head.appendChild(t)))}}function ty(e,t){nl.m(e,t);var l=Da;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=Ua(e)}if(!At.has(u)&&(e=T({rel:"modulepreload",href:e},t),At.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"),ke(a,"link",e),Qe(a),l.head.appendChild(a)}}}function ly(e,t,l){nl.S(e,t,l);var a=Da;if(a&&e){var n=ta(a).hoistableStyles,u=Ma(e);t=t||"default";var c=n.get(u);if(!c){var o={loading:0,preload:null};if(c=a.querySelector(An(u)))o.loading=5;else{e=T({rel:"stylesheet",href:e,"data-precedence":t},l),(l=At.get(u))&&Rf(e,l);var v=c=a.createElement("link");Qe(v),ke(v,"link",e),v._p=new Promise(function(j,D){v.onload=j,v.onerror=D}),v.addEventListener("load",function(){o.loading|=1}),v.addEventListener("error",function(){o.loading|=2}),o.loading|=4,ku(c,t,a)}c={type:"stylesheet",instance:c,count:1,state:o},n.set(u,c)}}}function ay(e,t){nl.X(e,t);var l=Da;if(l&&e){var a=ta(l).hoistableScripts,n=Ua(e),u=a.get(n);u||(u=l.querySelector(zn(n)),u||(e=T({src:e,async:!0},t),(t=At.get(n))&&Af(e,t),u=l.createElement("script"),Qe(u),ke(u,"link",e),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function ny(e,t){nl.M(e,t);var l=Da;if(l&&e){var a=ta(l).hoistableScripts,n=Ua(e),u=a.get(n);u||(u=l.querySelector(zn(n)),u||(e=T({src:e,async:!0,type:"module"},t),(t=At.get(n))&&Af(e,t),u=l.createElement("script"),Qe(u),ke(u,"link",e),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function Xd(e,t,l,a){var n=(n=ce.current)?$u(n):null;if(!n)throw Error(f(446));switch(e){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(t=Ma(l.href),l=ta(n).hoistableStyles,a=l.get(t),a||(a={type:"style",instance:null,count:0,state:null},l.set(t,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){e=Ma(l.href);var u=ta(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(An(e)))&&!u._p&&(c.instance=u,c.state.loading=5),At.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},At.set(e,l),u||uy(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=Ua(l),l=ta(n).hoistableScripts,a=l.get(t),a||(a={type:"script",instance:null,count:0,state:null},l.set(t,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(f(444,e))}}function Ma(e){return'href="'+bt(e)+'"'}function An(e){return'link[rel="stylesheet"]['+e+"]"}function Qd(e){return T({},e,{"data-precedence":e.precedence,precedence:null})}function uy(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}),ke(t,"link",l),Qe(t),e.head.appendChild(t))}function Ua(e){return'[src="'+bt(e)+'"]'}function zn(e){return"script[async]"+e}function Zd(e,t,l){if(t.count++,t.instance===null)switch(t.type){case"style":var a=e.querySelector('style[data-href~="'+bt(l.href)+'"]');if(a)return t.instance=a,Qe(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"),Qe(a),ke(a,"style",n),ku(a,l.precedence,e),t.instance=a;case"stylesheet":n=Ma(l.href);var u=e.querySelector(An(n));if(u)return t.state.loading|=4,t.instance=u,Qe(u),u;a=Qd(l),(n=At.get(n))&&Rf(a,n),u=(e.ownerDocument||e).createElement("link"),Qe(u);var c=u;return c._p=new Promise(function(o,v){c.onload=o,c.onerror=v}),ke(u,"link",a),t.state.loading|=4,ku(u,l.precedence,e),t.instance=u;case"script":return u=Ua(l.src),(n=e.querySelector(zn(u)))?(t.instance=n,Qe(n),n):(a=l,(n=At.get(u))&&(a=T({},l),Af(a,n)),e=e.ownerDocument||e,n=e.createElement("script"),Qe(n),ke(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,ku(a,l.precedence,e));return t.instance}function ku(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 iy(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 cy(e,t,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var n=Ma(a.href),u=t.querySelector(An(n));if(u){t=u._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=Fu.bind(e),t.then(e,e)),l.state.loading|=4,l.instance=u,Qe(u);return}u=t.ownerDocument||t,a=Qd(a),(n=At.get(n))&&Rf(a,n),u=u.createElement("link"),Qe(u);var c=u;c._p=new Promise(function(o,v){c.onload=o,c.onerror=v}),ke(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=Fu.bind(e),t.addEventListener("load",l),t.addEventListener("error",l))}}var zf=0;function fy(e,t){return e.stylesheets&&e.count===0&&Pu(e,e.stylesheets),0zf?50:800)+t);return e.unsuspend=l,function(){e.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function Fu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Pu(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Iu=null;function Pu(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Iu=new Map,t.forEach(sy,e),Iu=null,Fu.call(e))}function sy(e,t){if(!(t.state.loading&4)){var l=Iu.get(e);if(l)var a=l.get(null);else{l=new Map,Iu.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(),Lf.exports=jy(),Lf.exports}var Ry=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 Ay(i={}){function s(m,h){let{pathname:b="/",search:z="",hash:S=""}=Fl(m.location.hash.substring(1));return!b.startsWith("/")&&!b.startsWith(".")&&(b="/"+b),Jf("",{pathname:b,search:z,hash:S},h.state&&h.state.usr||null,h.state&&h.state.key||"default")}function d(m,h){let b=m.document.querySelector("base"),z="";if(b&&b.getAttribute("href")){let S=m.location.href,y=S.indexOf("#");z=y===-1?S:S.slice(0,y)}return z+"#"+(typeof h=="string"?h:Yn(h))}function f(m,h){Ot(m.pathname.charAt(0)==="/",`relative pathnames are not supported in hash history.push(${JSON.stringify(h)})`)}return Oy(s,d,f,i)}function ze(i,s){if(i===!1||i===null||typeof i>"u")throw new Error(s)}function Ot(i,s){if(!i){typeof console<"u"&&console.warn(s);try{throw new Error(s)}catch{}}}function zy(){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"?Fl(s):s,state:d,key:s&&s.key||f||zy(),unstable_mask:m}}function Yn({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 Fl(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 Oy(i,s,d,f={}){let{window:m=document.defaultView,v5Compat:h=!1}=f,b=m.history,z="POP",S=null,y=C();y==null&&(y=0,b.replaceState({...b.state,idx:y},""));function C(){return(b.state||{idx:null}).idx}function T(){z="POP";let q=C(),V=q==null?null:q-y;y=q,S&&S({action:z,location:Z.location,delta:V})}function Y(q,V){z="PUSH";let W=gm(q)?q:Jf(Z.location,q,V);d&&d(W,q),y=C()+1;let X=Sm(W,y),w=Z.createHref(W.unstable_mask||W);try{b.pushState(X,"",w)}catch(ae){if(ae instanceof DOMException&&ae.name==="DataCloneError")throw ae;m.location.assign(w)}h&&S&&S({action:z,location:Z.location,delta:1})}function H(q,V){z="REPLACE";let W=gm(q)?q:Jf(Z.location,q,V);d&&d(W,q),y=C();let X=Sm(W,y),w=Z.createHref(W.unstable_mask||W);b.replaceState(X,"",w),h&&S&&S({action:z,location:Z.location,delta:0})}function Q(q){return Cy(q)}let Z={get action(){return z},get location(){return i(m,b)},listen(q){if(S)throw new Error("A history only accepts one active listener");return m.addEventListener(pm,T),S=q,()=>{m.removeEventListener(pm,T),S=null}},createHref(q){return s(m,q)},createURL:Q,encodeLocation(q){let V=Q(q);return{pathname:V.pathname,search:V.search,hash:V.hash}},push:Y,replace:H,go(q){return b.go(q)}};return Z}function Cy(i,s=!1){let d="http://localhost";typeof window<"u"&&(d=window.location.origin!=="null"?window.location.origin:window.location.href),ze(d,"No window.location.(origin|href) available to create URL");let f=typeof i=="string"?i:Yn(i);return f=f.replace(/ $/,"%20"),!s&&f.startsWith("//")&&(f=d+f),new URL(f,d)}function jm(i,s,d="/"){return Dy(i,s,d,!1)}function Dy(i,s,d,f){let m=typeof s=="string"?Fl(s):s,h=ul(m.pathname||"/",d);if(h==null)return null;let b=Tm(i);My(b);let z=null;for(let S=0;z==null&&S{let C={relativePath:y===void 0?b.path||"":y,caseSensitive:b.caseSensitive===!0,childrenIndex:z,route:b};if(C.relativePath.startsWith("/")){if(!C.relativePath.startsWith(f)&&S)return;ze(C.relativePath.startsWith(f),`Absolute route path "${C.relativePath}" nested under path "${f}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),C.relativePath=C.relativePath.slice(f.length)}let T=Ut([f,C.relativePath]),Y=d.concat(C);b.children&&b.children.length>0&&(ze(b.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${T}".`),Tm(b.children,s,Y,T,S)),!(b.path==null&&!b.index)&&s.push({path:T,score:Gy(T,b.index),routesMeta:Y})};return i.forEach((b,z)=>{if(b.path===""||!b.path?.includes("?"))h(b,z);else for(let S of Rm(b.path))h(b,z,!0,S)}),s}function Rm(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=Rm(f.join("/")),z=[];return z.push(...b.map(S=>S===""?h:[h,S].join("/"))),m&&z.push(...b),z.map(S=>i.startsWith("/")&&S===""?"/":S)}function My(i){i.sort((s,d)=>s.score!==d.score?d.score-s.score:wy(s.routesMeta.map(f=>f.childrenIndex),d.routesMeta.map(f=>f.childrenIndex)))}var Uy=/^:[\w-]+$/,Hy=3,qy=2,By=1,Ly=10,Yy=-2,bm=i=>i==="*";function Gy(i,s){let d=i.split("/"),f=d.length;return d.some(bm)&&(f+=Yy),s&&(f+=qy),d.filter(m=>!bm(m)).reduce((m,h)=>m+(Uy.test(h)?Hy:h===""?By:Ly),f)}function wy(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 z=0;z{if(C==="*"){let Q=z[Y]||"";b=h.slice(0,h.length-Q.length).replace(/(.)\/+$/,"$1")}const H=z[Y];return T&&!H?y[C]=void 0:y[C]=(H||"").replace(/%2F/g,"/"),y},{}),pathname:h,pathnameBase:b,pattern:i}}function Qy(i,s=!1,d=!0){Ot(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,z,S,y,C)=>{if(f.push({paramName:z,isOptional:S!=null}),S){let T=C.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 Ot(!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 Vy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function Ky(i,s="/"){let{pathname:d,search:f="",hash:m=""}=typeof i=="string"?Fl(i):i,h;return d?(d=Am(d),d.startsWith("/")?h=_m(d.substring(1),"/"):h=_m(d,s)):h=s,{pathname:h,search:ky(f),hash:Wy(m)}}function _m(i,s){let d=mi(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 Jy(i){return i.filter((s,d)=>d===0||s.route.path&&s.route.path.length>0)}function Ff(i){let s=Jy(i);return s.map((d,f)=>f===s.length-1?d.pathname:d.pathnameBase)}function hi(i,s,d,f=!1){let m;typeof i=="string"?m=Fl(i):(m={...i},ze(!m.pathname||!m.pathname.includes("?"),Xf("?","pathname","search",m)),ze(!m.pathname||!m.pathname.includes("#"),Xf("#","pathname","hash",m)),ze(!m.search||!m.search.includes("#"),Xf("#","search","hash",m)));let h=i===""||m.pathname==="",b=h?"/":m.pathname,z;if(b==null)z=d;else{let T=s.length-1;if(!f&&b.startsWith("..")){let Y=b.split("/");for(;Y[0]==="..";)Y.shift(),T-=1;m.pathname=Y.join("/")}z=T>=0?s[T]:"/"}let S=Ky(m,z),y=b&&b!=="/"&&b.endsWith("/"),C=(h||b===".")&&d.endsWith("/");return!S.pathname.endsWith("/")&&(y||C)&&(S.pathname+="/"),S}var Am=i=>i.replace(/\/\/+/g,"/"),Ut=i=>Am(i.join("/")),mi=i=>i.replace(/\/+$/,""),$y=i=>mi(i).replace(/^\/*/,"/"),ky=i=>!i||i==="?"?"":i.startsWith("?")?i:"?"+i,Wy=i=>!i||i==="#"?"":i.startsWith("#")?i:"#"+i,Fy=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 Iy(i){return i!=null&&typeof i.status=="number"&&typeof i.statusText=="string"&&typeof i.internal=="boolean"&&"data"in i}function Py(i){let s=i.map(d=>d.route.path).filter(Boolean);return Ut(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"||!Vy.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),z=ul(b.pathname,s);b.origin===h.origin&&z!=null?d=z+b.search+b.hash:m=!0}catch{Ot(!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 e0=["GET",...Cm];new Set(e0);var La=_.createContext(null);La.displayName="DataRouter";var vi=_.createContext(null);vi.displayName="DataRouterState";var Dm=_.createContext(!1);function t0(){return _.useContext(Dm)}var Mm=_.createContext({isTransitioning:!1});Mm.displayName="ViewTransition";var l0=_.createContext(new Map);l0.displayName="Fetchers";var a0=_.createContext(null);a0.displayName="Await";var gt=_.createContext(null);gt.displayName="Navigation";var wn=_.createContext(null);wn.displayName="Location";var Ht=_.createContext({outlet:null,matches:[],isDataRoute:!1});Ht.displayName="Route";var If=_.createContext(null);If.displayName="RouteError";var Um="REACT_ROUTER_ERROR",n0="REDIRECT",u0="ROUTE_ERROR_RESPONSE";function i0(i){if(i.startsWith(`${Um}:${n0}:{`))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 c0(i){if(i.startsWith(`${Um}:${u0}:{`))try{let s=JSON.parse(i.slice(40));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string")return new Fy(s.status,s.statusText,s.data)}catch{}}function f0(i,{relative:s}={}){ze(Ya(),"useHref() may be used only in the context of a component.");let{basename:d,navigator:f}=_.useContext(gt),{hash:m,pathname:h,search:b}=Xn(i,{relative:s}),z=h;return d!=="/"&&(z=h==="/"?d:Ut([d,h])),f.createHref({pathname:z,search:b,hash:m})}function Ya(){return _.useContext(wn)!=null}function Gt(){return ze(Ya(),"useLocation() may be used only in the context of a component."),_.useContext(wn).location}var Hm="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function qm(i){_.useContext(gt).static||_.useLayoutEffect(i)}function Bm(){let{isDataRoute:i}=_.useContext(Ht);return i?x0():s0()}function s0(){ze(Ya(),"useNavigate() may be used only in the context of a component.");let i=_.useContext(La),{basename:s,navigator:d}=_.useContext(gt),{matches:f}=_.useContext(Ht),{pathname:m}=Gt(),h=JSON.stringify(Ff(f)),b=_.useRef(!1);return qm(()=>{b.current=!0}),_.useCallback((S,y={})=>{if(Ot(b.current,Hm),!b.current)return;if(typeof S=="number"){d.go(S);return}let C=hi(S,JSON.parse(h),m,y.relative==="path");i==null&&s!=="/"&&(C.pathname=C.pathname==="/"?s:Ut([s,C.pathname])),(y.replace?d.replace:d.push)(C,y.state,y)},[s,d,h,m,i])}var r0=_.createContext(null);function o0(i){let s=_.useContext(Ht).outlet;return _.useMemo(()=>s&&_.createElement(r0.Provider,{value:i},s),[s,i])}function Xn(i,{relative:s}={}){let{matches:d}=_.useContext(Ht),{pathname:f}=Gt(),m=JSON.stringify(Ff(d));return _.useMemo(()=>hi(i,JSON.parse(m),f,s==="path"),[i,m,f,s])}function d0(i,s){return Lm(i,s)}function Lm(i,s,d){ze(Ya(),"useRoutes() may be used only in the context of a component.");let{navigator:f}=_.useContext(gt),{matches:m}=_.useContext(Ht),h=m[m.length-1],b=h?h.params:{},z=h?h.pathname:"/",S=h?h.pathnameBase:"/",y=h&&h.route;{let q=y&&y.path||"";Gm(z,!y||q.endsWith("*")||q.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${z}" (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 C=Gt(),T;if(s){let q=typeof s=="string"?Fl(s):s;ze(S==="/"||q.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 "${q.pathname}" was given in the \`location\` prop.`),T=q}else T=C;let Y=T.pathname||"/",H=Y;if(S!=="/"){let q=S.replace(/^\//,"").split("/");H="/"+Y.replace(/^\//,"").split("/").slice(q.length).join("/")}let Q=jm(i,{pathname:H});Ot(y||Q!=null,`No routes matched location "${T.pathname}${T.search}${T.hash}" `),Ot(Q==null||Q[Q.length-1].route.element!==void 0||Q[Q.length-1].route.Component!==void 0||Q[Q.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 Z=p0(Q&&Q.map(q=>Object.assign({},q,{params:Object.assign({},b,q.params),pathname:Ut([S,f.encodeLocation?f.encodeLocation(q.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:q.pathname]),pathnameBase:q.pathnameBase==="/"?S:Ut([S,f.encodeLocation?f.encodeLocation(q.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:q.pathnameBase])})),m,d);return s&&Z?_.createElement(wn.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",unstable_mask:void 0,...T},navigationType:"POP"}},Z):Z}function m0(){let i=E0(),s=Iy(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=_.createElement(_.Fragment,null,_.createElement("p",null,"💿 Hey developer 👋"),_.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",_.createElement("code",{style:h},"ErrorBoundary")," or"," ",_.createElement("code",{style:h},"errorElement")," prop on your route.")),_.createElement(_.Fragment,null,_.createElement("h2",null,"Unexpected Application Error!"),_.createElement("h3",{style:{fontStyle:"italic"}},s),d?_.createElement("pre",{style:m},d):null,b)}var h0=_.createElement(m0,null),Ym=class extends _.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=c0(i.digest);d&&(i=d)}let s=i!==void 0?_.createElement(Ht.Provider,{value:this.props.routeContext},_.createElement(If.Provider,{value:i,children:this.props.component})):this.props.children;return this.context?_.createElement(v0,{error:i},s):s}};Ym.contextType=Dm;var Qf=new WeakMap;function v0({children:i,error:s}){let{basename:d}=_.useContext(gt);if(typeof s=="object"&&s&&"digest"in s&&typeof s.digest=="string"){let f=i0(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 _.createElement("meta",{httpEquiv:"refresh",content:`0;url=${h.absoluteURL||h.to}`})}}return i}function y0({routeContext:i,match:s,children:d}){let f=_.useContext(La);return f&&f.static&&f.staticContext&&(s.route.errorElement||s.route.ErrorBoundary)&&(f.staticContext._deepestRenderedBoundaryId=s.route.id),_.createElement(Ht.Provider,{value:i},d)}function p0(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 C=m.findIndex(T=>T.route.id&&h?.[T.route.id]!==void 0);ze(C>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(h).join(",")}`),m=m.slice(0,Math.min(m.length,C+1))}let b=!1,z=-1;if(d&&f){b=f.renderFallback;for(let C=0;C=0?m=m.slice(0,z+1):m=[m[0]];break}}}}let S=d?.onError,y=f&&S?(C,T)=>{S(C,{location:f.location,params:f.matches?.[0]?.params??{},unstable_pattern:Py(f.matches),errorInfo:T})}:void 0;return m.reduceRight((C,T,Y)=>{let H,Q=!1,Z=null,q=null;f&&(H=h&&T.route.id?h[T.route.id]:void 0,Z=T.route.errorElement||h0,b&&(z<0&&Y===0?(Gm("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),Q=!0,q=null):z===Y&&(Q=!0,q=T.route.hydrateFallbackElement||null)));let V=s.concat(m.slice(0,Y+1)),W=()=>{let X;return H?X=Z:Q?X=q:T.route.Component?X=_.createElement(T.route.Component,null):T.route.element?X=T.route.element:X=C,_.createElement(y0,{match:T,routeContext:{outlet:C,matches:V,isDataRoute:f!=null},children:X})};return f&&(T.route.ErrorBoundary||T.route.errorElement||Y===0)?_.createElement(Ym,{location:f.location,revalidation:f.revalidation,component:Z,error:H,children:W(),routeContext:{outlet:null,matches:V,isDataRoute:!0},onError:y}):W()},null)}function Pf(i){return`${i} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function g0(i){let s=_.useContext(La);return ze(s,Pf(i)),s}function S0(i){let s=_.useContext(vi);return ze(s,Pf(i)),s}function b0(i){let s=_.useContext(Ht);return ze(s,Pf(i)),s}function es(i){let s=b0(i),d=s.matches[s.matches.length-1];return ze(d.route.id,`${i} can only be used on routes that contain a unique "id"`),d.route.id}function _0(){return es("useRouteId")}function E0(){let i=_.useContext(If),s=S0("useRouteError"),d=es("useRouteError");return i!==void 0?i:s.errors?.[d]}function x0(){let{router:i}=g0("useNavigate"),s=es("useNavigate"),d=_.useRef(!1);return qm(()=>{d.current=!0}),_.useCallback(async(m,h={})=>{Ot(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,Ot(!1,d))}_.memo(N0);function N0({routes:i,future:s,state:d,isStatic:f,onError:m}){return Lm(i,void 0,{state:d,isStatic:f,onError:m})}function j0({to:i,replace:s,state:d,relative:f}){ze(Ya()," may be used only in the context of a component.");let{static:m}=_.useContext(gt);Ot(!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}=_.useContext(Ht),{pathname:b}=Gt(),z=Bm(),S=hi(i,Ff(h),b,f==="path"),y=JSON.stringify(S);return _.useEffect(()=>{z(JSON.parse(y),{replace:s,state:d,relative:f})},[z,y,f,s,d]),null}function T0(i){return o0(i.context)}function Wl(i){ze(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function R0({basename:i="/",children:s=null,location:d,navigationType:f="POP",navigator:m,static:h=!1,unstable_useTransitions:b}){ze(!Ya(),"You cannot render a inside another . You should never have more than one in your app.");let z=i.replace(/^\/*/,"/"),S=_.useMemo(()=>({basename:z,navigator:m,static:h,unstable_useTransitions:b,future:{}}),[z,m,h,b]);typeof d=="string"&&(d=Fl(d));let{pathname:y="/",search:C="",hash:T="",state:Y=null,key:H="default",unstable_mask:Q}=d,Z=_.useMemo(()=>{let q=ul(y,z);return q==null?null:{location:{pathname:q,search:C,hash:T,state:Y,key:H,unstable_mask:Q},navigationType:f}},[z,y,C,T,Y,H,f,Q]);return Ot(Z!=null,` is not able to match the URL "${y}${C}${T}" because it does not start with the basename, so the won't render anything.`),Z==null?null:_.createElement(gt.Provider,{value:S},_.createElement(wn.Provider,{children:s,value:Z}))}function A0({children:i,location:s}){return d0($f(i),s)}function $f(i,s=[]){let d=[];return _.Children.forEach(i,(f,m)=>{if(!_.isValidElement(f))return;let h=[...s,m];if(f.type===_.Fragment){d.push.apply(d,$f(f.props.children,h));return}ze(f.type===Wl,`[${typeof f.type=="string"?f.type:f.type.name}] is not a component. All component children of must be a or `),ze(!f.props.index||!f.props.children,"An index route cannot have child routes.");let b={id:f.props.id||h.join("-"),caseSensitive:f.props.caseSensitive,element:f.props.element,Component:f.props.Component,index:f.props.index,path:f.props.path,middleware:f.props.middleware,loader:f.props.loader,action:f.props.action,hydrateFallbackElement:f.props.hydrateFallbackElement,HydrateFallback:f.props.HydrateFallback,errorElement:f.props.errorElement,ErrorBoundary:f.props.ErrorBoundary,hasErrorBoundary:f.props.hasErrorBoundary===!0||f.props.ErrorBoundary!=null||f.props.errorElement!=null,shouldRevalidate:f.props.shouldRevalidate,handle:f.props.handle,lazy:f.props.lazy};f.props.children&&(b.children=$f(f.props.children,h)),d.push(b)}),d}var ri="get",oi="application/x-www-form-urlencoded";function yi(i){return typeof HTMLElement<"u"&&i instanceof HTMLElement}function z0(i){return yi(i)&&i.tagName.toLowerCase()==="button"}function O0(i){return yi(i)&&i.tagName.toLowerCase()==="form"}function C0(i){return yi(i)&&i.tagName.toLowerCase()==="input"}function D0(i){return!!(i.metaKey||i.altKey||i.ctrlKey||i.shiftKey)}function M0(i,s){return i.button===0&&(!s||s==="_self")&&!D0(i)}var ci=null;function U0(){if(ci===null)try{new FormData(document.createElement("form"),0),ci=!1}catch{ci=!0}return ci}var H0=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Zf(i){return i!=null&&!H0.has(i)?(Ot(!1,`"${i}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${oi}"`),null):i}function q0(i,s){let d,f,m,h,b;if(O0(i)){let z=i.getAttribute("action");f=z?ul(z,s):null,d=i.getAttribute("method")||ri,m=Zf(i.getAttribute("enctype"))||oi,h=new FormData(i)}else if(z0(i)||C0(i)&&(i.type==="submit"||i.type==="image")){let z=i.form;if(z==null)throw new Error('Cannot submit a {pendingErr ?

{pendingErr}

: null} {pendingList.length === 0 ? ( -

No pending requests.

+

No pending requests. After you request a promotion, it appears here.

) : (
- - - - + + + + + @@ -355,6 +441,17 @@ export function ActionsPage() { + ))} @@ -365,7 +462,11 @@ export function ActionsPage() {
-

Confirm promotion

+

3. Confirm a request

+

+ Paste the full request ID (or pick from the table), add an approval reason, then confirm. This runs the same + policy gate as direct promote. +

diff --git a/web/src/pages/DiffPage.tsx b/web/src/pages/DiffPage.tsx index 0fb3046..4627efd 100644 --- a/web/src/pages/DiffPage.tsx +++ b/web/src/pages/DiffPage.tsx @@ -9,14 +9,20 @@ function isRecord(v: unknown): v is Record { return typeof v === "object" && v !== null; } -function pickPolicy(data: DiffJson): { passed: boolean; reasons: string[] } | null { +function pickPolicy(data: DiffJson): { + passed: boolean; + reasons: string[]; + evaluatedAt: string | null; +} | null { const p = data.policy; if (!isRecord(p)) return null; const passed = p.passed; const reasons = p.reasons; + const ev = p.evaluated_at; return { passed: passed === true, reasons: Array.isArray(reasons) ? reasons.filter((x): x is string => typeof x === "string") : [], + evaluatedAt: typeof ev === "string" ? ev : null, }; } @@ -205,7 +211,7 @@ export function DiffPage() { -
+
{diffErr ?

{diffErr}

: null} + {!diffOut && !diffErr ? ( +
+

+ Enter baseline and candidate release IDs, then Compute diff. + Same contract as POST /v1/diff and{" "} + flightdeck release diff — structured sections below summarize policy, + samples, pricing/catalog hints, and rollups; open Raw diff JSON for the full payload. +

+
+ ) : null} + {diffOut ? ( <> -
+
-

Summary

- {policy ? ( -
- Policy:{" "} - {policy.passed ? "PASS" : "FAIL"} -
- ) : null} +

Diff result

- {policy && policy.reasons.length > 0 ? ( -
    - {policy.reasons.map((r) => ( -
  • {r}
  • - ))} -
- ) : null} - {samples ? ( -

- Samples: baseline={num(samples.baseline_runs)} · candidate={num(samples.candidate_runs)} · - confidence: {String(samples.confidence ?? "—")} - {typeof samples.confidence_reason === "string" ? ` — ${samples.confidence_reason}` : null} -

- ) : null} - {pricing && pricing.warnings.length > 0 ? ( -
    - {pricing.warnings.map((w) => ( -
  • Pricing warning: {w}
  • - ))} -
- ) : null} - {pricing && pricing.hints.length > 0 ? ( -
    - {pricing.hints.map((h) => ( -
  • Hint: {h}
  • - ))} -
- ) : null} - {pricing && pricing.catalog && (pricing.catalog.enabled || pricing.catalog.warnings.length > 0) ? ( -
- Catalog{" "} - {pricing.catalog.enabled ? ( - <> - v{pricing.catalog.version ?? "—"} · slots{" "} - {pricing.catalog.baselineSlot ?? "—"} →{" "} - {pricing.catalog.candidateSlot ?? "—"} - {pricing.catalog.baselineCost !== null && - pricing.catalog.candidateCost !== null && - pricing.catalog.deltaCost !== null ? ( +
+
+

+ Policy gate +

+ {policy ? ( +
+ {policy.passed ? "PASS" : "FAIL"} + {policy.evaluatedAt ? ( + + evaluated_at {policy.evaluatedAt} + + ) : null} + {policy.reasons.length > 0 ? ( +
    + {policy.reasons.map((r) => ( +
  • {r}
  • + ))} +
+ ) : ( +

+ No policy constraint messages (pass with empty reasons, or policy omitted). +

+ )} +
+ ) : ( +

No policy block in this response.

+ )} +
+ +
+

+ Evidence window +

+ {samples ? ( +

+ Baseline runs: {num(samples.baseline_runs)} · Candidate runs:{" "} + {num(samples.candidate_runs)} · Confidence:{" "} + {String(samples.confidence ?? "—")} + {typeof samples.confidence_reason === "string" ? ` — ${samples.confidence_reason}` : null} +

+ ) : ( +

No sample counts in this response.

+ )} +
+ +
+

+ Pricing, model, and catalog +

+ {pricing ? ( +
+

+ Resolved models:{" "} + + {pricing.baselineProvider}/{pricing.baselineVersion} {pricing.baselineModel} + {" "} + →{" "} + + {pricing.candidateProvider}/{pricing.candidateVersion} {pricing.candidateModel} + + {pricing.changed ? ( + + pricing/model changed + + ) : ( + + unchanged + + )} +

+ {pricing.warnings.length > 0 ? ( <> -
- Comparable cost/run: {pricing.catalog.baselineCost.toFixed(6)} →{" "} - {pricing.catalog.candidateCost.toFixed(6)} (Δ {pricing.catalog.deltaCost >= 0 ? "+" : ""} - {pricing.catalog.deltaCost.toFixed(6)}) +

+ Pricing warnings +

+
    + {pricing.warnings.map((w) => ( +
  • {w}
  • + ))} +
) : null} - + {pricing.hints.length > 0 ? ( + <> +

+ Hints +

+
    + {pricing.hints.map((h) => ( +
  • {h}
  • + ))} +
+ + ) : null} + {pricing.catalog && (pricing.catalog.enabled || pricing.catalog.warnings.length > 0) ? ( + <> +

+ Pricing catalog +

+
+ {pricing.catalog.enabled ? ( +

+ Catalog v{pricing.catalog.version ?? "—"} · slots{" "} + {pricing.catalog.baselineSlot ?? "—"} →{" "} + {pricing.catalog.candidateSlot ?? "—"} + {pricing.catalog.baselineCost !== null && + pricing.catalog.candidateCost !== null && + pricing.catalog.deltaCost !== null ? ( + <> +
+ Comparable cost/run: {pricing.catalog.baselineCost.toFixed(6)} →{" "} + {pricing.catalog.candidateCost.toFixed(6)} (Δ{" "} + {pricing.catalog.deltaCost >= 0 ? "+" : ""} + {pricing.catalog.deltaCost.toFixed(6)}) + + ) : null} +

+ ) : ( +

+ Catalog disabled or incomplete for this diff. +

+ )} + {pricing.catalog.warnings.length > 0 ? ( +
    + {pricing.catalog.warnings.map((w) => ( +
  • {w}
  • + ))} +
+ ) : null} +
+ + ) : null} + {pricing.changed && + pricing.prices && + pricing.prices.baselineInput !== null && + pricing.prices.candidateInput !== null && + pricing.prices.baselineOutput !== null && + pricing.prices.candidateOutput !== null ? ( + <> +

+ Per-1k token prices (USD) +

+
+
+
Input / 1k
+
+ {pricing.prices.baselineInput.toFixed(6)} → {pricing.prices.candidateInput.toFixed(6)} +
+
+
+
Output / 1k
+
+ {pricing.prices.baselineOutput.toFixed(6)} → {pricing.prices.candidateOutput.toFixed(6)} +
+
+
+

+ Cost rollups reflect pricing table and model identity; compare with catalog lines above when + configured. +

+ + ) : null} +
) : ( - disabled or incomplete +

No pricing block in this response.

)} - {pricing.catalog.warnings.length > 0 ? ( -
    - {pricing.catalog.warnings.map((w) => ( -
  • {w}
  • - ))} -
- ) : null}
- ) : null} - {pricing && pricing.changed ? ( -

- Pricing/model changed:{" "} - - {pricing.baselineProvider}/{pricing.baselineVersion} {pricing.baselineModel} - {" "} - →{" "} - - {pricing.candidateProvider}/{pricing.candidateVersion} {pricing.candidateModel} - - . Cost delta includes pricing and model assumption changes. - {pricing.prices && - pricing.prices.baselineInput !== null && - pricing.prices.candidateInput !== null && - pricing.prices.baselineOutput !== null && - pricing.prices.candidateOutput !== null ? ( - <> -
- Per-1k token prices: input{" "} - - {pricing.prices.baselineInput.toFixed(6)} → {pricing.prices.candidateInput.toFixed(6)} - - , output{" "} - - {pricing.prices.baselineOutput.toFixed(6)} → {pricing.prices.candidateOutput.toFixed(6)} - - - ) : null} -

- ) : null} - {metrics ? ( -
- = 0 ? "+" : ""}${(metrics.delta_cost_per_run_pct * 100).toFixed(2)}% vs baseline)` - : "" - }` - : undefined - } - /> - - + +
+

+ Cost and quality rollups +

+ {metrics ? ( +
+ = 0 ? "+" : ""}${(metrics.delta_cost_per_run_pct * 100).toFixed(2)}% vs baseline)` + : "" + }` + : undefined + } + /> + + +
+ ) : ( +

No metrics block in this response.

+ )}
- ) : null} +
diff --git a/web/src/pages/OverviewPage.tsx b/web/src/pages/OverviewPage.tsx index d4fbbf0..1668360 100644 --- a/web/src/pages/OverviewPage.tsx +++ b/web/src/pages/OverviewPage.tsx @@ -1,4 +1,5 @@ import { useCallback, useEffect, useId, useState, type ReactNode } from "react"; +import { Link } from "react-router-dom"; import type { ActionRow, MetricsPayload, PromotedRow, ReleaseRow, TimelinePayload } from "../api"; import { fetchMetrics, loadTimeline } from "../api"; import { useTimelineRefresh } from "../context/TimelineRefreshContext"; @@ -84,13 +85,20 @@ export function OverviewPage() {

Overview

Registered releases, promotion pointers, and recent ledger actions.

- {error && !loading ?

{error}

: null} - {loading ?

Loading…

: null} + {loading ? ( +
+ Loading overview + + + +
+ ) : null} {metrics ? (
@@ -143,6 +151,10 @@ export function OverviewPage() { ) : null} +

+ Next: open Diff to compare releases, or Runs for evidence + forensics. +

) : metricsError && !loading ? (

Ledger metrics unavailable: {metricsError}

diff --git a/web/src/pages/RunsPage.tsx b/web/src/pages/RunsPage.tsx index 659ec4b..a2fd1e5 100644 --- a/web/src/pages/RunsPage.tsx +++ b/web/src/pages/RunsPage.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useId, useRef, useState, type ReactNode } from "react"; import type { ReleaseRow, RunsListPayload } from "../api"; import { fetchRuns, fetchRunsExportBlob, loadTimeline } from "../api"; import { JsonPanel } from "../components/JsonPanel"; @@ -8,7 +8,41 @@ function shortId(id: string, keepStart = 12, keepEnd = 6) { return `${id.slice(0, keepStart)}…${id.slice(-keepEnd)}`; } +function asRecord(v: unknown): Record | null { + if (v && typeof v === "object" && !Array.isArray(v)) return v as Record; + return null; +} + +function getRequest(ev: Record): Record | null { + return asRecord(ev.request); +} + +function getMetrics(ev: Record): Record | null { + return asRecord(ev.metrics); +} + +function getTraceId(ev: Record): string { + const req = getRequest(ev); + const t = req?.trace_id; + return typeof t === "string" ? t : ""; +} + +function getLatencyMs(ev: Record): number | null { + const m = getMetrics(ev); + const n = m?.latency_ms; + return typeof n === "number" && Number.isFinite(n) ? n : null; +} + +function getSuccess(ev: Record): boolean { + const m = getMetrics(ev); + const s = m?.success; + return s !== false; +} + export function RunsPage() { + const drawerTitleId = useId(); + const closeBtnRef = useRef(null); + const [releases, setReleases] = useState([]); const [releaseId, setReleaseId] = useState(""); const [windowVal, setWindowVal] = useState("7d"); @@ -25,6 +59,7 @@ export function RunsPage() { const [rawErr, setRawErr] = useState(null); const [busy, setBusy] = useState(false); const [exportBusy, setExportBusy] = useState(false); + const [detailEvent, setDetailEvent] = useState | null>(null); useEffect(() => { void loadTimeline() @@ -34,6 +69,21 @@ export function RunsPage() { }); }, []); + useEffect(() => { + if (!detailEvent) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") setDetailEvent(null); + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [detailEvent]); + + useEffect(() => { + if (!detailEvent) return; + const t = window.setTimeout(() => closeBtnRef.current?.focus(), 0); + return () => window.clearTimeout(t); + }, [detailEvent]); + const runQuery = useCallback(async () => { setRawErr(null); setResult(null); @@ -136,6 +186,8 @@ export function RunsPage() { windowVal, ]); + const closeDrawer = useCallback(() => setDetailEvent(null), []); + return ( <>
@@ -143,7 +195,8 @@ export function RunsPage() {

Run events

Read-only slice of ingested runs (GET /v1/runs). Newest - first; offset pages through the match set. + first; offset pages through the match set. Use a row's View action for structured + detail (same payload as export lines).

@@ -206,6 +259,10 @@ export function RunsPage() { setLimit(e.target.value)} /> +

+ Export uses the same filters and limit as this form (server cap 500 rows + per download). Truncation warnings apply to the returned page, not necessarily the whole ledger. +

{result ? ( -
+

Results

@@ -231,41 +288,202 @@ export function RunsPage() { {String(result.truncated)} offset={result.offset}

-
-
Request IDReleaseEnvCreatedRequest IDReleaseEnvCreatedConfirm
{row.environment} {row.created_at} + +
- - - - - - - - - {result.events.length === 0 ? ( + + {result.matched_total === 0 ? ( +
+

No run events matched

+

+ Nothing in this release matched the time window and filters. Try a wider Window, + clear optional filters, or confirm events were ingested for this Release ID. +

+
+ ) : null} + + {result.matched_total > 0 && result.returned === 0 ? ( +

+ This page is empty: offset is past the end of the match set. Lower offset or increase + limit. +

+ ) : null} + + {result.truncated ? ( +

+ More events match this query than fit in this page. Increase offset to page forward, or + narrow filters (for example Trace ID) to shrink the match set. +

+ ) : null} + + {result.matched_total > 0 ? ( +
+
Run IDTimestampAgent
+ - + + + + + + - ) : ( - result.events.map((ev, idx) => { - const runId = typeof ev.run_id === "string" ? ev.run_id : ""; - const ts = typeof ev.timestamp === "string" ? ev.timestamp : ""; - const agent = typeof ev.agent_id === "string" ? ev.agent_id : ""; - return ( - - - - - - ); - }) - )} - -
- No events in this page. - Run IDTimestampAgentTraceStatus + Actions +
{shortId(runId)}{ts}{agent}
-
- + + + {result.events.length === 0 ? ( + + + No events in this page. + + + ) : ( + (() => { + const rows: ReactNode[] = []; + let prevTrace: string | null = null; + result.events.forEach((ev, idx) => { + const rec = ev as Record; + const tid = getTraceId(rec); + if (tid && tid !== prevTrace) { + rows.push( + + + Trace{" "} + {shortId(tid, 18, 10)} + + , + ); + prevTrace = tid; + } + const runId = typeof rec.run_id === "string" ? rec.run_id : ""; + const ts = typeof rec.timestamp === "string" ? rec.timestamp : ""; + const agent = typeof rec.agent_id === "string" ? rec.agent_id : ""; + const lat = getLatencyMs(rec); + const ok = getSuccess(rec); + rows.push( + + {shortId(runId)} + {ts} + {agent} + {tid ? shortId(tid, 8, 4) : "—"} + + + {ok ? "ok" : "err"} + + {lat != null ? ( + + {lat}ms + + ) : null} + + + + + , + ); + }); + return rows; + })() + )} + + + + ) : null} + + + + ) : ( +
+

+ Choose a Release ID (datalist is filled from registered releases when the server is + reachable), then Load runs to query GET /v1/runs + . +

+ )} + + {detailEvent ? ( +
+ +
+
+ {(() => { + const runId = typeof detailEvent.run_id === "string" ? detailEvent.run_id : ""; + const ts = typeof detailEvent.timestamp === "string" ? detailEvent.timestamp : ""; + const agent = typeof detailEvent.agent_id === "string" ? detailEvent.agent_id : ""; + const tid = getTraceId(detailEvent); + const sess = getRequest(detailEvent)?.session_id; + const span = getRequest(detailEvent)?.span_id; + const lat = getLatencyMs(detailEvent); + const ok = getSuccess(detailEvent); + const err = getMetrics(detailEvent)?.error_type; + return ( +
+
+
run_id
+
{runId || "—"}
+
+
+
timestamp
+
{ts || "—"}
+
+
+
agent_id
+
{agent || "—"}
+
+
+
trace_id
+
{tid || "—"}
+
+
+
session_id
+
{typeof sess === "string" ? sess : "—"}
+
+
+
span_id
+
{typeof span === "string" ? span : "—"}
+
+
+
metrics
+
+ + {ok ? "success" : "failed"} + + {lat != null ? · {lat}ms : null} + {typeof err === "string" && err ? ( + · error_type: {err} + ) : null} +
+
+
+ ); + })()} + +
+ + ) : null} );