diff --git a/.cursor/rules/flightdeck-ci-artifacts.mdc b/.cursor/rules/flightdeck-ci-artifacts.mdc new file mode 100644 index 0000000..6b76c85 --- /dev/null +++ b/.cursor/rules/flightdeck-ci-artifacts.mdc @@ -0,0 +1,27 @@ +--- +description: FlightDeck — commit web static bundle and regenerated schemas CI enforces +alwaysApply: true +--- + +# FlightDeck CI artifacts + +Normative detail: **`AGENTS.md`** (Verification), **`CLAUDE.md`**, **`CONTRIBUTING.md`**, **`DEVELOPMENT.md`**. Repo root **`.cursorrules`** defers here for Cursor users without rules UI. CI mirrors **`.github/workflows/ci.yml`**; PR checklist: **`.github/PULL_REQUEST_TEMPLATE.md`**. + +## Web UI (`web/` → `src/flightdeck/server/static/`) + +`flightdeck serve` serves the **checked-in** tree under **`src/flightdeck/server/static/`**, not a live Vite output. + +If the PR touches **`web/`** in a way that changes the production build (TS/TSX/CSS, **`vite.config.ts`**, **`web/package.json`** / lockfile, etc.): + +1. From **`web/`**: **`npm ci`** then **`npm run build`** (writes into **`src/flightdeck/server/static/`** and normalizes LF). +2. From repo root: **`git diff --exit-code src/flightdeck/server/static/`** must pass—if not, **commit all** changes under that path (hashed **`assets/*.js`**, **`index.html`** script tags, etc.). +3. If behavior changed, from **`web/`**: **`npm run test:e2e`**. + +## JSON Schemas (`schemas/`) + +If Pydantic models or generation scripts change wire contracts: + +1. **`uv run python scripts/generate_schemas.py`** +2. **`git diff --exit-code schemas/`** must pass—commit **`schemas/`** updates with the same PR. + +Do not hand-edit generated schema files without running the script. diff --git a/.cursorrules b/.cursorrules index 724e523..ad174a8 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1,7 +1,7 @@ # FlightDeck — Cursor -Read **`AGENTS.md`** for mission, non-goals, public contracts, engineering rules, verification, and docs policy. **`CLAUDE.md`** is a short index for Claude Code / Cursor. +Read **`AGENTS.md`** for mission, non-goals, public contracts, engineering rules, verification, and docs policy. **`CLAUDE.md`** is a short index for Claude Code / Cursor. In Cursor, **`.cursor/rules/flightdeck-ci-artifacts.mdc`** (`alwaysApply`) reminds you to commit **`src/flightdeck/server/static/`** and **`schemas/`** when CI gates apply. -Quick verify (uv): `uv sync --frozen --extra dev`, then `uv run python -m ruff check src tests`, `uv run python -m pytest`, `uv run flightdeck-quickstart-verify` (pip/venv equivalents in `DEVELOPMENT.md`; on Windows, `py -3` if needed). After **`web/src/`** edits: `cd web && npm ci && npm run build`, commit **`src/flightdeck/server/static/`** if it changes; optionally `npm run test:e2e` (see **`web/README.md`**). +Quick verify (uv): `uv sync --frozen --extra dev`, then `uv run python -m ruff check src tests`, `uv run python -m pytest`, `uv run flightdeck-quickstart-verify`, `uv run flightdeck --help` (pip/venv equivalents in **`DEVELOPMENT.md`**; on Windows, **`py -3`** if needed). After **Pydantic / wire model** edits: `uv run python scripts/generate_schemas.py` then `git diff --exit-code schemas/`. After **`web/`** edits that affect the production bundle: `cd web && npm ci && npm run build && cd .. && git diff --exit-code src/flightdeck/server/static/` (must be clean—commit all changes under **`static/`**); when UI behavior changes, `cd web && npx playwright install chromium && npm run test:e2e` (see **`web/README.md`**). Normative v1 direction: https://github.com/flightdeckdev/flightdeck/blob/main/RELEASE_NOTES.md · backlog: https://github.com/flightdeckdev/flightdeck/blob/main/ROADMAP.md · CLI: https://github.com/flightdeckdev/flightdeck/blob/main/README.md diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d141c28..fbd1f9c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,13 +6,13 @@ ## Validation -Run the same checks as **CI** (see **`.github/workflows/ci.yml`**) before opening / updating the PR: +Run the same checks as **CI** (see **`.github/workflows/ci.yml`**) before opening / updating the PR. Cursor loads **`.cursor/rules/flightdeck-ci-artifacts.mdc`** as a short reminder for **`static/`** + **`schemas/`** gates. - [ ] `uv sync --frozen --extra dev` - [ ] `uv run python -m ruff check src tests` - [ ] `uv run python -m pytest` - [ ] `uv run python scripts/generate_schemas.py` then `git diff --exit-code schemas/` (if models/schemas touched) -- [ ] `cd web && npm ci && npm run build && cd .. && git diff --exit-code src/flightdeck/server/static/` (if **`web/src/`** or deps changed) +- [ ] `cd web && npm ci && npm run build && cd .. && git diff --exit-code src/flightdeck/server/static/` (if **`web/`** sources, Vite config, or **`web/`** deps / lockfile change the production bundle) - [ ] `cd web && npx playwright install chromium && npm run test:e2e` (if **`web/`** changed) - [ ] `uv run flightdeck-quickstart-verify` - [ ] `uv run flightdeck --help` diff --git a/AGENTS.md b/AGENTS.md index 67f2858..bbbfd78 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ This tree is usually a **personal-account research repo** (`origin` → your Git When implementing features, prefer **small, PR-shaped slices** that could ship to an org repo without extra cleanup. Do not conflate “saved in research” with “ready for org push.” -Extended maintainer docs (research workflow, org checklist, canonical publish) live on **`main`** in that repository. This clone stays compact and keeps practical contributor guidance in **`CONTRIBUTING.md`**. Claude Code / short entrypoint: **`CLAUDE.md`**. +Extended maintainer docs (research workflow, org checklist, canonical publish) live on **`main`** in that repository. This clone stays compact and keeps practical contributor guidance in **`CONTRIBUTING.md`**. Claude Code / short entrypoint: **`CLAUDE.md`**. Cursor IDE: project rules under **`.cursor/rules/`** (for example **`flightdeck-ci-artifacts.mdc`** for web static + schema gates). ## Mission @@ -56,6 +56,7 @@ Treat these as **stable API** unless a change explicitly marks an experimental p Do a short review pass for: - **Contract drift** (CLI, JSON/YAML, SQLite columns consumers rely on). +- **Shipped web bundle** (**`web/`** → committed **`src/flightdeck/server/static/`**) and **schema drift** (**`schemas/`**, regenerate via Verification below) when your change touches UI or Pydantic wire shapes. - **Trust boundaries** (diff, pricing, policy, promotion, serve host binding). - **Cross-platform ergonomics** (Windows paths, line endings on fixtures, temp dirs). @@ -68,6 +69,7 @@ uv sync --frozen --extra dev uv run python -m ruff check src tests uv run python -m pytest uv run flightdeck-quickstart-verify +uv run flightdeck --help ``` After editing Pydantic models, regenerate schemas and ensure a clean diff: @@ -77,6 +79,18 @@ uv run python scripts/generate_schemas.py git diff --exit-code schemas/ ``` +After editing the **browser UI** under **`web/`** (for example **`web/src/`**), rebuild the committed static tree that **`flightdeck serve`** ships, then confirm CI’s static gate is clean (same check as **`.github/workflows/ci.yml`**): + +```bash +cd web +npm ci +npm run build +cd .. +git diff --exit-code src/flightdeck/server/static/ +``` + +Commit every change under **`src/flightdeck/server/static/`** (including new hashed **`assets/*.js`** and **`index.html`** script tags). **`npm run build`** already runs LF normalization via **`web/scripts/normalize-static-lf.mjs`**. For UI behavior, run **`npm run test:e2e`** from **`web/`** (Playwright runs in CI right after the static diff). + Fallback (activated **venv** or global tools): the same steps with **`python -m …`** / **`python scripts/…`** as in **`DEVELOPMENT.md`**. On **Windows**, use `py -3` in place of `python` if that is how your environment is set up. If pytest temp dirs fail with permissions, see **`DEVELOPMENT.md`** / **`tests/conftest.py`**. diff --git a/CHANGELOG.md b/CHANGELOG.md index a067c01..5a35083 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0** - **Docs:** README deduplicates **RELEASE_NOTES** links; **CHANGELOG** historic bullets use canonical **`https://github.com/flightdeckdev/flightdeck/...`** URLs for **`docs/*`** and **RESEARCH.md** (this slim tree does not ship those files). - **`tests/conftest.py`:** create repo **`.tmp/`** at import time so **`pytest --basetemp=.tmp/pytest`** works on fresh checkouts and **Linux** CI (parent dir is no longer Windows-only). - **`pyproject.toml` `[project] name`:** **`flightdeck-ai`** to match the **PyPI** trusted-publisher project; install with **`pip install flightdeck-ai`** / **`uv add flightdeck-ai`** (CLI remains **`flightdeck`**, imports **`flightdeck.*`**). -- **Contributor docs** (**`README.md`**, **`DEVELOPMENT.md`**, **`CONTRIBUTING.md`**, **`AGENTS.md`**, **`CLAUDE.md`**, **`.cursorrules`**): prefer **uv**; keep **pip** / **`python -m venv`** as fallback. +- **Contributor docs** (**`README.md`**, **`DEVELOPMENT.md`**, **`CONTRIBUTING.md`**, **`AGENTS.md`**, **`CLAUDE.md`**, **`.cursorrules`**, **`.github/PULL_REQUEST_TEMPLATE.md`**, **`web/README.md`**): prefer **uv**; document full CI parity (**`uv sync --frozen`**, **`flightdeck --help`**, **`schemas/`** + **`src/flightdeck/server/static/`** **`git diff --exit-code`** gates, Playwright when **`web/`** changes). Added **`.cursor/rules/flightdeck-ci-artifacts.mdc`** (Cursor **`alwaysApply`**). Keep **pip** / **`python -m venv`** as fallback. - **Python:** **`requires-python >=3.14,<3.15`**, **`.python-version`**, PyPI classifiers, **Ruff** `target-version`, **`uv.lock`**, and **CI** matrices now target **CPython 3.14** only (replacing broader **3.11–3.14** testing). ## 1.0.1 - 2026-05-01 diff --git a/CLAUDE.md b/CLAUDE.md index 9610fda..7016612 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,6 +9,7 @@ Canonical repository (full history and maintainer workflows): **[github.com/flig | Topic | Location | |--------|------| | Agent / contributor rules | `AGENTS.md` | +| Cursor IDE rules (CI artifacts, web static) | `.cursor/rules/flightdeck-ci-artifacts.mdc` | | Setup and local demo | `DEVELOPMENT.md` | | CLI flags and exit codes | [README.md](https://github.com/flightdeckdev/flightdeck/blob/main/README.md) (canonical repo) | | v1 direction | [RELEASE_NOTES.md](https://github.com/flightdeckdev/flightdeck/blob/main/RELEASE_NOTES.md) | @@ -27,12 +28,19 @@ uv sync --frozen --extra dev uv run python -m ruff check src tests uv run python -m pytest uv run flightdeck-quickstart-verify +uv run flightdeck --help ``` +If you changed **Pydantic / wire models** affecting **`schemas/`**: **`uv run python scripts/generate_schemas.py`**, then **`git diff --exit-code schemas/`** must be clean—commit **`schemas/`** updates with the PR. + +If you changed **`web/`** (React UI): from **`web/`**, run **`npm ci`** then **`npm run build`**, then from the repo root **`git diff --exit-code src/flightdeck/server/static/`** must be clean—commit all updates under that path (CI fails otherwise). When behavior changes, run **`npm run test:e2e`** from **`web/`**. + +Details: **`AGENTS.md`** (Verification), **`DEVELOPMENT.md`**, and **`.cursor/rules/flightdeck-ci-artifacts.mdc`**. + With **pip** + venv: use **`python -m …`** equivalents in **`DEVELOPMENT.md`**. **Windows:** if `python` is not on `PATH`, use `py -3` for the same commands (or install **uv** and use **`uv run`**). ## Repo shape -Python package under `src/flightdeck/`. Tests in `tests/`. Examples in `examples/quickstart/`. JSON Schemas under `schemas/` (regenerate with **`uv run python scripts/generate_schemas.py`** when models change). After **`pyproject.toml`** dependency edits, run **`uv lock`** and commit **`uv.lock`**. +Python package under `src/flightdeck/`. Tests in `tests/`. Examples in `examples/quickstart/`. JSON Schemas under `schemas/` (regenerate with **`uv run python scripts/generate_schemas.py`** when models change). Browser UI source in **`web/`**; production bundle committed under **`src/flightdeck/server/static/`** (rebuild with **`npm run build`** in **`web/`**). After **`pyproject.toml`** dependency edits, run **`uv lock`** and commit **`uv.lock`**. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3390807..24574e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ FlightDeck is **v1.0.0+** on a narrow local-first spine; changes should meet pro Contributions are accepted under the **Apache License, Version 2.0** (see **`LICENSE`**). The canonical tree is [github.com/flightdeckdev/flightdeck](https://github.com/flightdeckdev/flightdeck). -Human and AI contributors: follow **[AGENTS.md](AGENTS.md)** (full rules). For a short index, see **[CLAUDE.md](CLAUDE.md)**. +Human and AI contributors: follow **[AGENTS.md](AGENTS.md)** (full rules). For a short index, see **[CLAUDE.md](CLAUDE.md)**. In **Cursor**, the project rule **[`.cursor/rules/flightdeck-ci-artifacts.mdc`](.cursor/rules/flightdeck-ci-artifacts.mdc)** (`alwaysApply`) summarizes the **web `static/`** and **`schemas/`** drift gates CI enforces. ## Local Setup @@ -23,12 +23,14 @@ python -m pip install -e ".[dev]" ## Verify -With **uv** (matches CI): +With **uv** (core Python checks; see **`.github/workflows/ci.yml`** for the full job): ```bash +uv sync --frozen --extra dev uv run python -m ruff check src tests uv run python -m pytest uv run flightdeck-quickstart-verify +uv run flightdeck --help ``` With an activated **venv**: @@ -37,9 +39,17 @@ With an activated **venv**: python -m ruff check src tests python -m pytest flightdeck-quickstart-verify +flightdeck --help ``` -If you change **`pyproject.toml`** dependencies, run **`uv lock`** and commit **`uv.lock`**. Use the same checks as **CI** (see **`AGENTS.md`**) before opening a PR. +If you change **`pyproject.toml`** dependencies, run **`uv lock`** and commit **`uv.lock`**. + +**Also run before a PR** (when relevant—same gates as CI): + +- **Schemas:** `uv run python scripts/generate_schemas.py` then `git diff --exit-code schemas/` +- **Web UI:** `cd web && npm ci && npm run build && cd .. && git diff --exit-code src/flightdeck/server/static/`; if UI behavior changed, `cd web && npx playwright install chromium && npm run test:e2e` + +Details, Windows notes, and doctrine: **`AGENTS.md`** (Verification), **`DEVELOPMENT.md`**, **`web/README.md`**. ## Private files and pushing to GitHub diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 4b519d5..e2c432f 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -34,6 +34,7 @@ python -m pip install -e ".[dev]" With **uv**: ```bash +uv sync --frozen --extra dev uv run python -m ruff check src tests uv run python -m pytest uv run flightdeck --help @@ -51,8 +52,17 @@ flightdeck doctor flightdeck-quickstart-verify ``` +Match **CI**’s CLI smoke: **`flightdeck --help`** must run successfully after changes to the CLI surface. + Full command flags and exit codes: [README.md](https://github.com/flightdeckdev/flightdeck/blob/main/README.md). Cross-platform quickstart parity: **`flightdeck-quickstart-verify`** / **`python -m flightdeck.quickstart_smoke`** (also run in CI). HTTP API reference: **[docs/http-api.md](docs/http-api.md)**. Python SDK: **[docs/sdk.md](docs/sdk.md)**. +**JSON Schemas:** when **`src/flightdeck/`** models or **`scripts/generate_schemas.py`** change wire contracts, regenerate and match CI: + +```bash +uv run python scripts/generate_schemas.py +git diff --exit-code schemas/ +``` + **Lockfile:** when you change **`pyproject.toml`** dependencies or extras, run **`uv lock`** and commit **`uv.lock`** so CI stays **`--frozen`**-reproducible. ## Web UI (React + Vite) @@ -64,9 +74,11 @@ cd web npm ci npm run build cd .. -git status src/flightdeck/server/static/ +git diff --exit-code src/flightdeck/server/static/ ``` +If that **`git diff`** fails, **`git add`** / commit everything under **`src/flightdeck/server/static/`** (hashed **`assets/*.js`**, **`index.html`**, etc.)—CI uses the same check after **`npm run build`**. + **Playwright:** from **`web/`**, **`npx playwright install chromium`** once, then **`npm run test:e2e`** (matches CI after the **`static/`** diff gate; see **`web/README.md`**). **`npm run dev`:** proxies **`/v1`** to **`flightdeck serve`** on **`127.0.0.1:8765`** by default; copy **`web/.env.example`** to **`web/.env.local`** to set **`VITE_FLIGHTDECK_LOCAL_API_TOKEN`** when testing mutations against a token-protected server. diff --git a/README.md b/README.md index a1b5f9e..3c1cec9 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,11 @@ uv sync --frozen --extra dev uv run python -m ruff check src tests uv run python -m pytest uv run flightdeck-quickstart-verify +uv run flightdeck --help ``` +If you change **`web/`** or **Pydantic models**, also run the **`static/`** and **`schemas/`** drift checks from **[DEVELOPMENT.md](DEVELOPMENT.md)** (same gates as **`.github/workflows/ci.yml`**). **[AGENTS.md](AGENTS.md)** and **[`.cursor/rules/flightdeck-ci-artifacts.mdc`](.cursor/rules/flightdeck-ci-artifacts.mdc)** summarize them for humans and Cursor. + See [DEVELOPMENT.md](DEVELOPMENT.md) for **uv** and **pip** setup, verification, troubleshooting, and **PyPI releases** (tag-driven; not on merge to `main`). ## License diff --git a/ROADMAP.md b/ROADMAP.md index 32f6194..fbe0298 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,67 +1,126 @@ # Roadmap -## Now +FlightDeck is **AI release governance** for production agents: immutable releases, runtime evidence, trusted diffs, and policy-gated promotion. -- Local release registry -- Run event ingestion from JSONL/JSON arrays -- Trusted release diff -- Immutable pricing tables (with import audit log) -- Policy-gated promotion -- Promotion history -- Local HTTP ingestion (`flightdeck serve`) -- Rollback command (`flightdeck release rollback`) +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. -## Next Steps (Execution Plan) +**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. -### Target next release: v1.1.0 (UI-assisted local operations) +--- -- Deliver a minimal local UI that complements the CLI, not replaces it. -- Keep local-first trust boundaries: no hosted control plane dependency and no default remote services. -- Ship UI only for workflows already stable in CLI to avoid contract drift. +## What is shipped today -### Phase 1: Stabilize v1 operations (next 2-4 weeks) +- **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`. +- **Audit trail:** promotion/rollback history with stable sequencing (`audit_seq`) and integrity checks via `doctor`. +- **Evidence ingestion:** `runs ingest` from JSONL/JSON arrays plus stable `POST /v1/events` contracts (`schemas/v1/`). +- **Local API + UI:** `flightdeck serve` routes and web UI (Overview, Diff, Promote) in `src/flightdeck/server/static/`. +- **SDK and tooling:** Python sync/async clients with retries/batching and `flightdeck-quickstart-verify`. -- Finalize CLI contract coverage around `release diff`, `release promote`, `release rollback`, and `doctor`. -- Expand schema fixture coverage in `tests/fixtures/json/` for edge-case payloads and error paths. -- Tighten release audit tests for promotion and rollback history ordering (`audit_seq` continuity). -- Keep local-first reliability high on Windows by maintaining temp-dir and SQLite lock regression tests. +--- -### Phase 2: UI MVP + developer ergonomics (next 1-2 months) +## Production readiness gaps (why it can feel standalone) -UI MVP scope (ship fast): +These are current gaps between "works locally" and "easy to use across production services." -- Read-only release timeline view (registered releases, promoted pointer, recent actions). -- Diff runner form (`baseline`, `candidate`, `window`, filters) with confidence and policy output. -- Promotion history panel with reasons and PASS/FAIL status. -- Safe action guardrails in UI: require reason text for promote/rollback and show confirmation prompts. +| 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. | +| **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. | +| **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. | -Implementation constraints for UI MVP: +--- -- UI reads/writes through existing local HTTP/CLI contracts only. -- Reuse existing validation and policy paths; no separate business logic stack in UI. -- Include parity tests for one end-to-end path (CLI vs UI-triggered operation producing the same outcome). +## Phase 0: Foundation to PMF (near term, next releases) -- **Shipped (slim repo):** Python SDK retries, batching, **`AsyncFlightdeckClient`**, HTTP read/mutate helpers + optional **`api_token`**; **`flightdeck-quickstart-verify`**; Playwright smoke under **`web/e2e/`** (see **`web/README.md`**). -- Refine actionable CLI errors for pricing/model mismatches and unsupported policy states. +Goal: prove the wedge with real teams using FlightDeck as release governance source-of-truth. -### Phase 3: Hardening and scale signals (next 1-2 quarters) +### Must ship in this phase -- Formalize schema compatibility guidance for additive vs breaking payload evolution. -- Add larger-window ledger test scenarios to validate confidence labels under sparse and bursty traffic. -- Expand policy evaluation coverage for mixed rollout conditions (cost, latency, and error-rate interactions). -- Continue publishing-only hardening (tag/version checks, schema drift checks, reproducible builds). +- Harden CLI/schema contracts and edge-case policy coverage (sample windows, sparse traffic, error paths). +- Add concrete integration references: app runtime event emitters, CI pipeline examples, and deployment recipes for `flightdeck serve`. +- Improve pricing normalization for multiple provider inputs while keeping diff semantics stable. +- 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). -## References +### Phase-0 success signals + +- 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. + +--- + +## Phase 1: Productization (mid term, roughly quarters) + +Goal: move from solid local tooling to repeatable production usage patterns. + +### Build in this phase + +- Human-in-the-loop approval workflow on top of policy gates (without requiring a hosted control plane). +- Stronger multi-provider pricing normalization and clearer mismatch diagnostics. +- 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). + +### Phase-1 readiness signals + +- 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. + +--- + +## 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) -- Contract and release posture: **[RELEASE_NOTES.md](RELEASE_NOTES.md)** -- Versioning policy: **[VERSIONING.md](VERSIONING.md)** -- Contributor and org-push guidance: **[CONTRIBUTING.md](CONTRIBUTING.md)** -- Canonical repository: **[github.com/flightdeckdev/flightdeck](https://github.com/flightdeckdev/flightdeck)** +- 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. -## Later +### 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. + +--- + +## 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. + +Hosted control plane and in-path traffic routing remain opt-in long-term considerations, not default product posture. + +--- + +## References -- Hosted control plane -- Dashboard -- OpenTelemetry import/export mapping -- Tool-cost pricing -- Enterprise controls +- **Contracts and trust:** `RELEASE_NOTES.md`, `CHANGELOG.md`, `SECURITY.md` +- **Versioning:** `VERSIONING.md` +- **Contributors/org workflow:** `CONTRIBUTING.md` +- **Engineering rules and doctrine:** `AGENTS.md` +- **Canonical repo:** [github.com/flightdeckdev/flightdeck](https://github.com/flightdeckdev/flightdeck) diff --git a/src/flightdeck/server/static/assets/index-1te9rfzd.js b/src/flightdeck/server/static/assets/index-1te9rfzd.js new file mode 100644 index 0000000..457a3ad --- /dev/null +++ b/src/flightdeck/server/static/assets/index-1te9rfzd.js @@ -0,0 +1,11 @@ +(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const d of document.querySelectorAll('link[rel="modulepreload"]'))f(d);new MutationObserver(d=>{for(const y of d)if(y.type==="childList")for(const S of y.addedNodes)S.tagName==="LINK"&&S.rel==="modulepreload"&&f(S)}).observe(document,{childList:!0,subtree:!0});function o(d){const y={};return d.integrity&&(y.integrity=d.integrity),d.referrerPolicy&&(y.referrerPolicy=d.referrerPolicy),d.crossOrigin==="use-credentials"?y.credentials="include":d.crossOrigin==="anonymous"?y.credentials="omit":y.credentials="same-origin",y}function f(d){if(d.ep)return;d.ep=!0;const y=o(d);fetch(d.href,y)}})();var Mf={exports:{}},Mn={};var nh;function mv(){if(nh)return Mn;nh=1;var c=Symbol.for("react.transitional.element"),r=Symbol.for("react.fragment");function o(f,d,y){var S=null;if(y!==void 0&&(S=""+y),d.key!==void 0&&(S=""+d.key),"key"in d){y={};for(var O in d)O!=="key"&&(y[O]=d[O])}else y=d;return d=y.ref,{$$typeof:c,type:f,key:S,ref:d!==void 0?d:null,props:y}}return Mn.Fragment=r,Mn.jsx=o,Mn.jsxs=o,Mn}var uh;function yv(){return uh||(uh=1,Mf.exports=mv()),Mf.exports}var p=yv(),Cf={exports:{}},P={};var ih;function vv(){if(ih)return P;ih=1;var c=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),f=Symbol.for("react.strict_mode"),d=Symbol.for("react.profiler"),y=Symbol.for("react.consumer"),S=Symbol.for("react.context"),O=Symbol.for("react.forward_ref"),E=Symbol.for("react.suspense"),h=Symbol.for("react.memo"),M=Symbol.for("react.lazy"),R=Symbol.for("react.activity"),B=Symbol.iterator;function J(g){return g===null||typeof g!="object"?null:(g=B&&g[B]||g["@@iterator"],typeof g=="function"?g:null)}var V={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},L=Object.assign,H={};function G(g,U,Y){this.props=g,this.context=U,this.refs=H,this.updater=Y||V}G.prototype.isReactComponent={},G.prototype.setState=function(g,U){if(typeof g!="object"&&typeof g!="function"&&g!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,g,U,"setState")},G.prototype.forceUpdate=function(g){this.updater.enqueueForceUpdate(this,g,"forceUpdate")};function $(){}$.prototype=G.prototype;function K(g,U,Y){this.props=g,this.context=U,this.refs=H,this.updater=Y||V}var it=K.prototype=new $;it.constructor=K,L(it,G.prototype),it.isPureReactComponent=!0;var dt=Array.isArray;function F(){}var I={H:null,A:null,T:null,S:null},Nt=Object.prototype.hasOwnProperty;function Jt(g,U,Y){var Q=Y.ref;return{$$typeof:c,type:g,key:U,ref:Q!==void 0?Q:null,props:Y}}function je(g,U){return Jt(g.type,U,g.props)}function ye(g){return typeof g=="object"&&g!==null&&g.$$typeof===c}function wt(g){var U={"=":"=0",":":"=2"};return"$"+g.replace(/[=:]/g,function(Y){return U[Y]})}var Ue=/\/+/g;function ve(g,U){return typeof g=="object"&&g!==null&&g.key!=null?wt(""+g.key):U.toString(36)}function jt(g){switch(g.status){case"fulfilled":return g.value;case"rejected":throw g.reason;default:switch(typeof g.status=="string"?g.then(F,F):(g.status="pending",g.then(function(U){g.status==="pending"&&(g.status="fulfilled",g.value=U)},function(U){g.status==="pending"&&(g.status="rejected",g.reason=U)})),g.status){case"fulfilled":return g.value;case"rejected":throw g.reason}}throw g}function D(g,U,Y,Q,tt){var at=typeof g;(at==="undefined"||at==="boolean")&&(g=null);var mt=!1;if(g===null)mt=!0;else switch(at){case"bigint":case"string":case"number":mt=!0;break;case"object":switch(g.$$typeof){case c:case r:mt=!0;break;case M:return mt=g._init,D(mt(g._payload),U,Y,Q,tt)}}if(mt)return tt=tt(g),mt=Q===""?"."+ve(g,0):Q,dt(tt)?(Y="",mt!=null&&(Y=mt.replace(Ue,"$&/")+"/"),D(tt,U,Y,"",function(qa){return qa})):tt!=null&&(ye(tt)&&(tt=je(tt,Y+(tt.key==null||g&&g.key===tt.key?"":(""+tt.key).replace(Ue,"$&/")+"/")+mt)),U.push(tt)),1;mt=0;var Wt=Q===""?".":Q+":";if(dt(g))for(var Dt=0;Dt>>1,Et=D[gt];if(0>>1;gtd(Y,k))Qd(tt,Y)?(D[gt]=tt,D[Q]=k,gt=Q):(D[gt]=Y,D[U]=k,gt=U);else if(Qd(tt,k))D[gt]=tt,D[Q]=k,gt=Q;else break t}}return q}function d(D,q){var k=D.sortIndex-q.sortIndex;return k!==0?k:D.id-q.id}if(c.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var y=performance;c.unstable_now=function(){return y.now()}}else{var S=Date,O=S.now();c.unstable_now=function(){return S.now()-O}}var E=[],h=[],M=1,R=null,B=3,J=!1,V=!1,L=!1,H=!1,G=typeof setTimeout=="function"?setTimeout:null,$=typeof clearTimeout=="function"?clearTimeout:null,K=typeof setImmediate<"u"?setImmediate:null;function it(D){for(var q=o(h);q!==null;){if(q.callback===null)f(h);else if(q.startTime<=D)f(h),q.sortIndex=q.expirationTime,r(E,q);else break;q=o(h)}}function dt(D){if(L=!1,it(D),!V)if(o(E)!==null)V=!0,F||(F=!0,wt());else{var q=o(h);q!==null&&jt(dt,q.startTime-D)}}var F=!1,I=-1,Nt=5,Jt=-1;function je(){return H?!0:!(c.unstable_now()-JtD&&je());){var gt=R.callback;if(typeof gt=="function"){R.callback=null,B=R.priorityLevel;var Et=gt(R.expirationTime<=D);if(D=c.unstable_now(),typeof Et=="function"){R.callback=Et,it(D),q=!0;break e}R===o(E)&&f(E),it(D)}else f(E);R=o(E)}if(R!==null)q=!0;else{var g=o(h);g!==null&&jt(dt,g.startTime-D),q=!1}}break t}finally{R=null,B=k,J=!1}q=void 0}}finally{q?wt():F=!1}}}var wt;if(typeof K=="function")wt=function(){K(ye)};else if(typeof MessageChannel<"u"){var Ue=new MessageChannel,ve=Ue.port2;Ue.port1.onmessage=ye,wt=function(){ve.postMessage(null)}}else wt=function(){G(ye,0)};function jt(D,q){I=G(function(){D(c.unstable_now())},q)}c.unstable_IdlePriority=5,c.unstable_ImmediatePriority=1,c.unstable_LowPriority=4,c.unstable_NormalPriority=3,c.unstable_Profiling=null,c.unstable_UserBlockingPriority=2,c.unstable_cancelCallback=function(D){D.callback=null},c.unstable_forceFrameRate=function(D){0>D||125gt?(D.sortIndex=k,r(h,D),o(E)===null&&D===o(h)&&(L?($(I),I=-1):L=!0,jt(dt,k-gt))):(D.sortIndex=Et,r(E,D),V||J||(V=!0,F||(F=!0,wt()))),D},c.unstable_shouldYield=je,c.unstable_wrapCallback=function(D){var q=B;return function(){var k=B;B=q;try{return D.apply(this,arguments)}finally{B=k}}}})(Hf)),Hf}var sh;function pv(){return sh||(sh=1,Uf.exports=gv()),Uf.exports}var Bf={exports:{}},$t={};var rh;function Sv(){if(rh)return $t;rh=1;var c=Jf();function r(E){var h="https://react.dev/errors/"+E;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(c)}catch(r){console.error(r)}}return c(),Bf.exports=Sv(),Bf.exports}var dh;function Ev(){if(dh)return Cn;dh=1;var c=pv(),r=Jf(),o=bv();function f(t){var e="https://react.dev/errors/"+t;if(1Et||(t.current=gt[Et],gt[Et]=null,Et--)}function Y(t,e){Et++,gt[Et]=t.current,t.current=e}var Q=g(null),tt=g(null),at=g(null),mt=g(null);function Wt(t,e){switch(Y(at,e),Y(tt,t),Y(Q,null),e.nodeType){case 9:case 11:t=(t=e.documentElement)&&(t=t.namespaceURI)?xd(t):0;break;default:if(t=e.tagName,e=e.namespaceURI)e=xd(e),t=Od(e,t);else switch(t){case"svg":t=1;break;case"math":t=2;break;default:t=0}}U(Q),Y(Q,t)}function Dt(){U(Q),U(tt),U(at)}function qa(t){t.memoizedState!==null&&Y(mt,t);var e=Q.current,l=Od(e,t.type);e!==l&&(Y(tt,t),Y(Q,l))}function Yn(t){tt.current===t&&(U(Q),U(tt)),mt.current===t&&(U(mt),xn._currentValue=k)}var di,ls;function Dl(t){if(di===void 0)try{throw Error()}catch(l){var e=l.stack.trim().match(/\n( *(at )?)/);di=e&&e[1]||"",ls=-1)":-1n||m[a]!==T[n]){var N=` +`+m[a].replace(" at new "," at ");return t.displayName&&N.includes("")&&(N=N.replace("",t.displayName)),N}while(1<=a&&0<=n);break}}}finally{hi=!1,Error.prepareStackTrace=l}return(l=t?t.displayName||t.name:"")?Dl(l):""}function Kh(t,e){switch(t.tag){case 26:case 27:case 5:return Dl(t.type);case 16:return Dl("Lazy");case 13:return t.child!==e&&e!==null?Dl("Suspense Fallback"):Dl("Suspense");case 19:return Dl("SuspenseList");case 0:case 15:return mi(t.type,!1);case 11:return mi(t.type.render,!1);case 1:return mi(t.type,!0);case 31:return Dl("Activity");default:return""}}function as(t){try{var e="",l=null;do e+=Kh(t,l),l=t,t=t.return;while(t);return e}catch(a){return` +Error generating stack: `+a.message+` +`+a.stack}}var yi=Object.prototype.hasOwnProperty,vi=c.unstable_scheduleCallback,gi=c.unstable_cancelCallback,Jh=c.unstable_shouldYield,wh=c.unstable_requestPaint,ne=c.unstable_now,$h=c.unstable_getCurrentPriorityLevel,ns=c.unstable_ImmediatePriority,us=c.unstable_UserBlockingPriority,Ln=c.unstable_NormalPriority,Wh=c.unstable_LowPriority,is=c.unstable_IdlePriority,kh=c.log,Fh=c.unstable_setDisableYieldValue,Ya=null,ue=null;function ul(t){if(typeof kh=="function"&&Fh(t),ue&&typeof ue.setStrictMode=="function")try{ue.setStrictMode(Ya,t)}catch{}}var ie=Math.clz32?Math.clz32:tm,Ih=Math.log,Ph=Math.LN2;function tm(t){return t>>>=0,t===0?32:31-(Ih(t)/Ph|0)|0}var Gn=256,Xn=262144,Qn=4194304;function Ml(t){var e=t&42;if(e!==0)return e;switch(t&-t){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 t&261888;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function Zn(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var n=0,u=t.suspendedLanes,i=t.pingedLanes;t=t.warmLanes;var s=a&134217727;return s!==0?(a=s&~u,a!==0?n=Ml(a):(i&=s,i!==0?n=Ml(i):l||(l=s&~t,l!==0&&(n=Ml(l))))):(s=a&~u,s!==0?n=Ml(s):i!==0?n=Ml(i):l||(l=a&~t,l!==0&&(n=Ml(l)))),n===0?0:e!==0&&e!==n&&(e&u)===0&&(u=n&-n,l=e&-e,u>=l||u===32&&(l&4194048)!==0)?e:n}function La(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function em(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+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 e+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 cs(){var t=Qn;return Qn<<=1,(Qn&62914560)===0&&(Qn=4194304),t}function pi(t){for(var e=[],l=0;31>l;l++)e.push(t);return e}function Ga(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function lm(t,e,l,a,n,u){var i=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var s=t.entanglements,m=t.expirationTimes,T=t.hiddenUpdates;for(l=i&~l;0"u")return null;try{return t.activeElement||t.body}catch{return t.body}}var fm=/[\n"\\]/g;function pe(t){return t.replace(fm,function(e){return"\\"+e.charCodeAt(0).toString(16)+" "})}function zi(t,e,l,a,n,u,i,s){t.name="",i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"?t.type=i:t.removeAttribute("type"),e!=null?i==="number"?(e===0&&t.value===""||t.value!=e)&&(t.value=""+ge(e)):t.value!==""+ge(e)&&(t.value=""+ge(e)):i!=="submit"&&i!=="reset"||t.removeAttribute("value"),e!=null?Ai(t,i,ge(e)):l!=null?Ai(t,i,ge(l)):a!=null&&t.removeAttribute("value"),n==null&&u!=null&&(t.defaultChecked=!!u),n!=null&&(t.checked=n&&typeof n!="function"&&typeof n!="symbol"),s!=null&&typeof s!="function"&&typeof s!="symbol"&&typeof s!="boolean"?t.name=""+ge(s):t.removeAttribute("name")}function bs(t,e,l,a,n,u,i,s){if(u!=null&&typeof u!="function"&&typeof u!="symbol"&&typeof u!="boolean"&&(t.type=u),e!=null||l!=null){if(!(u!=="submit"&&u!=="reset"||e!=null)){Ti(t);return}l=l!=null?""+ge(l):"",e=e!=null?""+ge(e):l,s||e===t.value||(t.value=e),t.defaultValue=e}a=a??n,a=typeof a!="function"&&typeof a!="symbol"&&!!a,t.checked=s?t.checked:!!a,t.defaultChecked=!!a,i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(t.name=i),Ti(t)}function Ai(t,e,l){e==="number"&&Jn(t.ownerDocument)===t||t.defaultValue===""+l||(t.defaultValue=""+l)}function ea(t,e,l,a){if(t=t.options,e){e={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Di=!1;if(Qe)try{var Va={};Object.defineProperty(Va,"passive",{get:function(){Di=!0}}),window.addEventListener("test",Va,Va),window.removeEventListener("test",Va,Va)}catch{Di=!1}var cl=null,Mi=null,$n=null;function xs(){if($n)return $n;var t,e=Mi,l=e.length,a,n="value"in cl?cl.value:cl.textContent,u=n.length;for(t=0;t=wa),js=" ",Us=!1;function Hs(t,e){switch(t){case"keyup":return Bm.indexOf(e.keyCode)!==-1;case"keydown":return e.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Bs(t){return t=t.detail,typeof t=="object"&&"data"in t?t.data:null}var ua=!1;function Ym(t,e){switch(t){case"compositionend":return Bs(e);case"keypress":return e.which!==32?null:(Us=!0,js);case"textInput":return t=e.data,t===js&&Us?null:t;default:return null}}function Lm(t,e){if(ua)return t==="compositionend"||!Bi&&Hs(t,e)?(t=xs(),$n=Mi=cl=null,ua=!1,t):null;switch(t){case"paste":return null;case"keypress":if(!(e.ctrlKey||e.altKey||e.metaKey)||e.ctrlKey&&e.altKey){if(e.char&&1=e)return{node:l,offset:e-t};t=a}t:{for(;l;){if(l.nextSibling){l=l.nextSibling;break t}l=l.parentNode}l=void 0}l=Vs(l)}}function Js(t,e){return t&&e?t===e?!0:t&&t.nodeType===3?!1:e&&e.nodeType===3?Js(t,e.parentNode):"contains"in t?t.contains(e):t.compareDocumentPosition?!!(t.compareDocumentPosition(e)&16):!1:!1}function ws(t){t=t!=null&&t.ownerDocument!=null&&t.ownerDocument.defaultView!=null?t.ownerDocument.defaultView:window;for(var e=Jn(t.document);e instanceof t.HTMLIFrameElement;){try{var l=typeof e.contentWindow.location.href=="string"}catch{l=!1}if(l)t=e.contentWindow;else break;e=Jn(t.document)}return e}function Li(t){var e=t&&t.nodeName&&t.nodeName.toLowerCase();return e&&(e==="input"&&(t.type==="text"||t.type==="search"||t.type==="tel"||t.type==="url"||t.type==="password")||e==="textarea"||t.contentEditable==="true")}var wm=Qe&&"documentMode"in document&&11>=document.documentMode,ia=null,Gi=null,Fa=null,Xi=!1;function $s(t,e,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;Xi||ia==null||ia!==Jn(a)||(a=ia,"selectionStart"in a&&Li(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}),Fa&&ka(Fa,a)||(Fa=a,a=Xu(Gi,"onSelect"),0>=i,n-=i,He=1<<32-ie(e)+n|l<lt?(ft=Z,Z=null):ft=Z.sibling;var ot=A(b,Z,_[lt],C);if(ot===null){Z===null&&(Z=ft);break}t&&Z&&ot.alternate===null&&e(b,Z),v=u(ot,v,lt),rt===null?w=ot:rt.sibling=ot,rt=ot,Z=ft}if(lt===_.length)return l(b,Z),st&&Ve(b,lt),w;if(Z===null){for(;lt<_.length;lt++)Z=j(b,_[lt],C),Z!==null&&(v=u(Z,v,lt),rt===null?w=Z:rt.sibling=Z,rt=Z);return st&&Ve(b,lt),w}for(Z=a(Z);lt<_.length;lt++)ft=x(Z,b,lt,_[lt],C),ft!==null&&(t&&ft.alternate!==null&&Z.delete(ft.key===null?lt:ft.key),v=u(ft,v,lt),rt===null?w=ft:rt.sibling=ft,rt=ft);return t&&Z.forEach(function(Nl){return e(b,Nl)}),st&&Ve(b,lt),w}function W(b,v,_,C){if(_==null)throw Error(f(151));for(var w=null,rt=null,Z=v,lt=v=0,ft=null,ot=_.next();Z!==null&&!ot.done;lt++,ot=_.next()){Z.index>lt?(ft=Z,Z=null):ft=Z.sibling;var Nl=A(b,Z,ot.value,C);if(Nl===null){Z===null&&(Z=ft);break}t&&Z&&Nl.alternate===null&&e(b,Z),v=u(Nl,v,lt),rt===null?w=Nl:rt.sibling=Nl,rt=Nl,Z=ft}if(ot.done)return l(b,Z),st&&Ve(b,lt),w;if(Z===null){for(;!ot.done;lt++,ot=_.next())ot=j(b,ot.value,C),ot!==null&&(v=u(ot,v,lt),rt===null?w=ot:rt.sibling=ot,rt=ot);return st&&Ve(b,lt),w}for(Z=a(Z);!ot.done;lt++,ot=_.next())ot=x(Z,b,lt,ot.value,C),ot!==null&&(t&&ot.alternate!==null&&Z.delete(ot.key===null?lt:ot.key),v=u(ot,v,lt),rt===null?w=ot:rt.sibling=ot,rt=ot);return t&&Z.forEach(function(hv){return e(b,hv)}),st&&Ve(b,lt),w}function bt(b,v,_,C){if(typeof _=="object"&&_!==null&&_.type===L&&_.key===null&&(_=_.props.children),typeof _=="object"&&_!==null){switch(_.$$typeof){case J:t:{for(var w=_.key;v!==null;){if(v.key===w){if(w=_.type,w===L){if(v.tag===7){l(b,v.sibling),C=n(v,_.props.children),C.return=b,b=C;break t}}else if(v.elementType===w||typeof w=="object"&&w!==null&&w.$$typeof===Nt&&Ql(w)===v.type){l(b,v.sibling),C=n(v,_.props),an(C,_),C.return=b,b=C;break t}l(b,v);break}else e(b,v);v=v.sibling}_.type===L?(C=ql(_.props.children,b.mode,C,_.key),C.return=b,b=C):(C=nu(_.type,_.key,_.props,null,b.mode,C),an(C,_),C.return=b,b=C)}return i(b);case V:t:{for(w=_.key;v!==null;){if(v.key===w)if(v.tag===4&&v.stateNode.containerInfo===_.containerInfo&&v.stateNode.implementation===_.implementation){l(b,v.sibling),C=n(v,_.children||[]),C.return=b,b=C;break t}else{l(b,v);break}else e(b,v);v=v.sibling}C=$i(_,b.mode,C),C.return=b,b=C}return i(b);case Nt:return _=Ql(_),bt(b,v,_,C)}if(jt(_))return X(b,v,_,C);if(wt(_)){if(w=wt(_),typeof w!="function")throw Error(f(150));return _=w.call(_),W(b,v,_,C)}if(typeof _.then=="function")return bt(b,v,ou(_),C);if(_.$$typeof===K)return bt(b,v,cu(b,_),C);du(b,_)}return typeof _=="string"&&_!==""||typeof _=="number"||typeof _=="bigint"?(_=""+_,v!==null&&v.tag===6?(l(b,v.sibling),C=n(v,_),C.return=b,b=C):(l(b,v),C=wi(_,b.mode,C),C.return=b,b=C),i(b)):l(b,v)}return function(b,v,_,C){try{ln=0;var w=bt(b,v,_,C);return ga=null,w}catch(Z){if(Z===va||Z===su)throw Z;var rt=fe(29,Z,null,b.mode);return rt.lanes=C,rt.return=b,rt}}}var Vl=gr(!0),pr=gr(!1),dl=!1;function ic(t){t.updateQueue={baseState:t.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function cc(t,e){t=t.updateQueue,e.updateQueue===t&&(e.updateQueue={baseState:t.baseState,firstBaseUpdate:t.firstBaseUpdate,lastBaseUpdate:t.lastBaseUpdate,shared:t.shared,callbacks:null})}function hl(t){return{lane:t,tag:0,payload:null,callback:null,next:null}}function ml(t,e,l){var a=t.updateQueue;if(a===null)return null;if(a=a.shared,(ht&2)!==0){var n=a.pending;return n===null?e.next=e:(e.next=n.next,n.next=e),a.pending=e,e=au(t),er(t,null,l),e}return lu(t,a,e,l),au(t)}function nn(t,e,l){if(e=e.updateQueue,e!==null&&(e=e.shared,(l&4194048)!==0)){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,ss(t,l)}}function fc(t,e){var l=t.updateQueue,a=t.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var n=null,u=null;if(l=l.firstBaseUpdate,l!==null){do{var i={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};u===null?n=u=i:u=u.next=i,l=l.next}while(l!==null);u===null?n=u=e:u=u.next=e}else n=u=e;l={baseState:a.baseState,firstBaseUpdate:n,lastBaseUpdate:u,shared:a.shared,callbacks:a.callbacks},t.updateQueue=l;return}t=l.lastBaseUpdate,t===null?l.firstBaseUpdate=e:t.next=e,l.lastBaseUpdate=e}var sc=!1;function un(){if(sc){var t=ya;if(t!==null)throw t}}function cn(t,e,l,a){sc=!1;var n=t.updateQueue;dl=!1;var u=n.firstBaseUpdate,i=n.lastBaseUpdate,s=n.shared.pending;if(s!==null){n.shared.pending=null;var m=s,T=m.next;m.next=null,i===null?u=T:i.next=T,i=m;var N=t.alternate;N!==null&&(N=N.updateQueue,s=N.lastBaseUpdate,s!==i&&(s===null?N.firstBaseUpdate=T:s.next=T,N.lastBaseUpdate=m))}if(u!==null){var j=n.baseState;i=0,N=T=m=null,s=u;do{var A=s.lane&-536870913,x=A!==s.lane;if(x?(ct&A)===A:(a&A)===A){A!==0&&A===ma&&(sc=!0),N!==null&&(N=N.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});t:{var X=t,W=s;A=e;var bt=l;switch(W.tag){case 1:if(X=W.payload,typeof X=="function"){j=X.call(bt,j,A);break t}j=X;break t;case 3:X.flags=X.flags&-65537|128;case 0:if(X=W.payload,A=typeof X=="function"?X.call(bt,j,A):X,A==null)break t;j=R({},j,A);break t;case 2:dl=!0}}A=s.callback,A!==null&&(t.flags|=64,x&&(t.flags|=8192),x=n.callbacks,x===null?n.callbacks=[A]:x.push(A))}else x={lane:A,tag:s.tag,payload:s.payload,callback:s.callback,next:null},N===null?(T=N=x,m=j):N=N.next=x,i|=A;if(s=s.next,s===null){if(s=n.shared.pending,s===null)break;x=s,s=x.next,x.next=null,n.lastBaseUpdate=x,n.shared.pending=null}}while(!0);N===null&&(m=j),n.baseState=m,n.firstBaseUpdate=T,n.lastBaseUpdate=N,u===null&&(n.shared.lanes=0),Sl|=i,t.lanes=i,t.memoizedState=j}}function Sr(t,e){if(typeof t!="function")throw Error(f(191,t));t.call(e)}function br(t,e){var l=t.callbacks;if(l!==null)for(t.callbacks=null,t=0;tu?u:8;var i=D.T,s={};D.T=s,Oc(t,!1,e,l);try{var m=n(),T=D.S;if(T!==null&&T(s,m),m!==null&&typeof m=="object"&&typeof m.then=="function"){var N=ly(m,a);rn(t,e,N,he(t))}else rn(t,e,a,he(t))}catch(j){rn(t,e,{then:function(){},status:"rejected",reason:j},he())}finally{q.p=u,i!==null&&s.types!==null&&(i.types=s.types),D.T=i}}function fy(){}function Rc(t,e,l,a){if(t.tag!==5)throw Error(f(476));var n=Ir(t).queue;Fr(t,n,e,k,l===null?fy:function(){return Pr(t),l(a)})}function Ir(t){var e=t.memoizedState;if(e!==null)return e;e={memoizedState:k,baseState:k,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:$e,lastRenderedState:k},next:null};var l={};return e.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:$e,lastRenderedState:l},next:null},t.memoizedState=e,t=t.alternate,t!==null&&(t.memoizedState=e),e}function Pr(t){var e=Ir(t);e.next===null&&(e=t.alternate.memoizedState),rn(t,e.next.queue,{},he())}function xc(){return Zt(xn)}function to(){return Ct().memoizedState}function eo(){return Ct().memoizedState}function sy(t){for(var e=t.return;e!==null;){switch(e.tag){case 24:case 3:var l=he();t=hl(l);var a=ml(e,t,l);a!==null&&(ae(a,e,l),nn(a,e,l)),e={cache:lc()},t.payload=e;return}e=e.return}}function ry(t,e,l){var a=he();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},_u(t)?ao(e,l):(l=Ki(t,e,l,a),l!==null&&(ae(l,t,a),no(l,e,a)))}function lo(t,e,l){var a=he();rn(t,e,l,a)}function rn(t,e,l,a){var n={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(_u(t))ao(e,n);else{var u=t.alternate;if(t.lanes===0&&(u===null||u.lanes===0)&&(u=e.lastRenderedReducer,u!==null))try{var i=e.lastRenderedState,s=u(i,l);if(n.hasEagerState=!0,n.eagerState=s,ce(s,i))return lu(t,e,n,0),_t===null&&eu(),!1}catch{}if(l=Ki(t,e,n,a),l!==null)return ae(l,t,a),no(l,e,a),!0}return!1}function Oc(t,e,l,a){if(a={lane:2,revertLane:cf(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},_u(t)){if(e)throw Error(f(479))}else e=Ki(t,l,a,2),e!==null&&ae(e,t,2)}function _u(t){var e=t.alternate;return t===et||e!==null&&e===et}function ao(t,e){Sa=yu=!0;var l=t.pending;l===null?e.next=e:(e.next=l.next,l.next=e),t.pending=e}function no(t,e,l){if((l&4194048)!==0){var a=e.lanes;a&=t.pendingLanes,l|=a,e.lanes=l,ss(t,l)}}var on={readContext:Zt,use:pu,useCallback:xt,useContext:xt,useEffect:xt,useImperativeHandle:xt,useLayoutEffect:xt,useInsertionEffect:xt,useMemo:xt,useReducer:xt,useRef:xt,useState:xt,useDebugValue:xt,useDeferredValue:xt,useTransition:xt,useSyncExternalStore:xt,useId:xt,useHostTransitionStatus:xt,useFormState:xt,useActionState:xt,useOptimistic:xt,useMemoCache:xt,useCacheRefresh:xt};on.useEffectEvent=xt;var uo={readContext:Zt,use:pu,useCallback:function(t,e){return kt().memoizedState=[t,e===void 0?null:e],t},useContext:Zt,useEffect:Qr,useImperativeHandle:function(t,e,l){l=l!=null?l.concat([t]):null,bu(4194308,4,Jr.bind(null,e,t),l)},useLayoutEffect:function(t,e){return bu(4194308,4,t,e)},useInsertionEffect:function(t,e){bu(4,2,t,e)},useMemo:function(t,e){var l=kt();e=e===void 0?null:e;var a=t();if(Kl){ul(!0);try{t()}finally{ul(!1)}}return l.memoizedState=[a,e],a},useReducer:function(t,e,l){var a=kt();if(l!==void 0){var n=l(e);if(Kl){ul(!0);try{l(e)}finally{ul(!1)}}}else n=e;return a.memoizedState=a.baseState=n,t={pending:null,lanes:0,dispatch:null,lastRenderedReducer:t,lastRenderedState:n},a.queue=t,t=t.dispatch=ry.bind(null,et,t),[a.memoizedState,t]},useRef:function(t){var e=kt();return t={current:t},e.memoizedState=t},useState:function(t){t=Ec(t);var e=t.queue,l=lo.bind(null,et,e);return e.dispatch=l,[t.memoizedState,l]},useDebugValue:zc,useDeferredValue:function(t,e){var l=kt();return Ac(l,t,e)},useTransition:function(){var t=Ec(!1);return t=Fr.bind(null,et,t.queue,!0,!1),kt().memoizedState=t,[!1,t]},useSyncExternalStore:function(t,e,l){var a=et,n=kt();if(st){if(l===void 0)throw Error(f(407));l=l()}else{if(l=e(),_t===null)throw Error(f(349));(ct&127)!==0||Rr(a,e,l)}n.memoizedState=l;var u={value:l,getSnapshot:e};return n.queue=u,Qr(Or.bind(null,a,u,t),[t]),a.flags|=2048,Ea(9,{destroy:void 0},xr.bind(null,a,u,l,e),null),l},useId:function(){var t=kt(),e=_t.identifierPrefix;if(st){var l=Be,a=He;l=(a&~(1<<32-ie(a)-1)).toString(32)+l,e="_"+e+"R_"+l,l=vu++,0<\/script>",u=u.removeChild(u.firstChild);break;case"select":u=typeof a.is=="string"?i.createElement("select",{is:a.is}):i.createElement("select"),a.multiple?u.multiple=!0:a.size&&(u.size=a.size);break;default:u=typeof a.is=="string"?i.createElement(n,{is:a.is}):i.createElement(n)}}u[Xt]=e,u[Ft]=a;t:for(i=e.child;i!==null;){if(i.tag===5||i.tag===6)u.appendChild(i.stateNode);else if(i.tag!==4&&i.tag!==27&&i.child!==null){i.child.return=i,i=i.child;continue}if(i===e)break t;for(;i.sibling===null;){if(i.return===null||i.return===e)break t;i=i.return}i.sibling.return=i.return,i=i.sibling}e.stateNode=u;t:switch(Kt(u,n,a),n){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break t;case"img":a=!0;break t;default:a=!1}a&&ke(e)}}return zt(e),Qc(e,e.type,t===null?null:t.memoizedProps,e.pendingProps,l),null;case 6:if(t&&e.stateNode!=null)t.memoizedProps!==a&&ke(e);else{if(typeof a!="string"&&e.stateNode===null)throw Error(f(166));if(t=at.current,da(e)){if(t=e.stateNode,l=e.memoizedProps,a=null,n=Qt,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}t[Xt]=e,t=!!(t.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||Ad(t.nodeValue,l)),t||rl(e,!0)}else t=Qu(t).createTextNode(a),t[Xt]=e,e.stateNode=t}return zt(e),null;case 31:if(l=e.memoizedState,t===null||t.memoizedState!==null){if(a=da(e),l!==null){if(t===null){if(!a)throw Error(f(318));if(t=e.memoizedState,t=t!==null?t.dehydrated:null,!t)throw Error(f(557));t[Xt]=e}else Yl(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;zt(e),t=!1}else l=Ii(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=l),t=!0;if(!t)return e.flags&256?(re(e),e):(re(e),null);if((e.flags&128)!==0)throw Error(f(558))}return zt(e),null;case 13:if(a=e.memoizedState,t===null||t.memoizedState!==null&&t.memoizedState.dehydrated!==null){if(n=da(e),a!==null&&a.dehydrated!==null){if(t===null){if(!n)throw Error(f(318));if(n=e.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(f(317));n[Xt]=e}else Yl(),(e.flags&128)===0&&(e.memoizedState=null),e.flags|=4;zt(e),n=!1}else n=Ii(),t!==null&&t.memoizedState!==null&&(t.memoizedState.hydrationErrors=n),n=!0;if(!n)return e.flags&256?(re(e),e):(re(e),null)}return re(e),(e.flags&128)!==0?(e.lanes=l,e):(l=a!==null,t=t!==null&&t.memoizedState!==null,l&&(a=e.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!==t&&l&&(e.child.flags|=8192),xu(e,e.updateQueue),zt(e),null);case 4:return Dt(),t===null&&of(e.stateNode.containerInfo),zt(e),null;case 10:return Je(e.type),zt(e),null;case 19:if(U(Mt),a=e.memoizedState,a===null)return zt(e),null;if(n=(e.flags&128)!==0,u=a.rendering,u===null)if(n)hn(a,!1);else{if(Ot!==0||t!==null&&(t.flags&128)!==0)for(t=e.child;t!==null;){if(u=mu(t),u!==null){for(e.flags|=128,hn(a,!1),t=u.updateQueue,e.updateQueue=t,xu(e,t),e.subtreeFlags=0,t=l,l=e.child;l!==null;)lr(l,t),l=l.sibling;return Y(Mt,Mt.current&1|2),st&&Ve(e,a.treeForkCount),e.child}t=t.sibling}a.tail!==null&&ne()>Cu&&(e.flags|=128,n=!0,hn(a,!1),e.lanes=4194304)}else{if(!n)if(t=mu(u),t!==null){if(e.flags|=128,n=!0,t=t.updateQueue,e.updateQueue=t,xu(e,t),hn(a,!0),a.tail===null&&a.tailMode==="hidden"&&!u.alternate&&!st)return zt(e),null}else 2*ne()-a.renderingStartTime>Cu&&l!==536870912&&(e.flags|=128,n=!0,hn(a,!1),e.lanes=4194304);a.isBackwards?(u.sibling=e.child,e.child=u):(t=a.last,t!==null?t.sibling=u:e.child=u,a.last=u)}return a.tail!==null?(t=a.tail,a.rendering=t,a.tail=t.sibling,a.renderingStartTime=ne(),t.sibling=null,l=Mt.current,Y(Mt,n?l&1|2:l&1),st&&Ve(e,a.treeForkCount),t):(zt(e),null);case 22:case 23:return re(e),oc(),a=e.memoizedState!==null,t!==null?t.memoizedState!==null!==a&&(e.flags|=8192):a&&(e.flags|=8192),a?(l&536870912)!==0&&(e.flags&128)===0&&(zt(e),e.subtreeFlags&6&&(e.flags|=8192)):zt(e),l=e.updateQueue,l!==null&&xu(e,l.retryQueue),l=null,t!==null&&t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(l=t.memoizedState.cachePool.pool),a=null,e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(a=e.memoizedState.cachePool.pool),a!==l&&(e.flags|=2048),t!==null&&U(Xl),null;case 24:return l=null,t!==null&&(l=t.memoizedState.cache),e.memoizedState.cache!==l&&(e.flags|=2048),Je(Ut),zt(e),null;case 25:return null;case 30:return null}throw Error(f(156,e.tag))}function yy(t,e){switch(ki(e),e.tag){case 1:return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 3:return Je(Ut),Dt(),t=e.flags,(t&65536)!==0&&(t&128)===0?(e.flags=t&-65537|128,e):null;case 26:case 27:case 5:return Yn(e),null;case 31:if(e.memoizedState!==null){if(re(e),e.alternate===null)throw Error(f(340));Yl()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 13:if(re(e),t=e.memoizedState,t!==null&&t.dehydrated!==null){if(e.alternate===null)throw Error(f(340));Yl()}return t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 19:return U(Mt),null;case 4:return Dt(),null;case 10:return Je(e.type),null;case 22:case 23:return re(e),oc(),t!==null&&U(Xl),t=e.flags,t&65536?(e.flags=t&-65537|128,e):null;case 24:return Je(Ut),null;case 25:return null;default:return null}}function Do(t,e){switch(ki(e),e.tag){case 3:Je(Ut),Dt();break;case 26:case 27:case 5:Yn(e);break;case 4:Dt();break;case 31:e.memoizedState!==null&&re(e);break;case 13:re(e);break;case 19:U(Mt);break;case 10:Je(e.type);break;case 22:case 23:re(e),oc(),t!==null&&U(Xl);break;case 24:Je(Ut)}}function mn(t,e){try{var l=e.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var n=a.next;l=n;do{if((l.tag&t)===t){a=void 0;var u=l.create,i=l.inst;a=u(),i.destroy=a}l=l.next}while(l!==n)}}catch(s){vt(e,e.return,s)}}function gl(t,e,l){try{var a=e.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var u=n.next;a=u;do{if((a.tag&t)===t){var i=a.inst,s=i.destroy;if(s!==void 0){i.destroy=void 0,n=e;var m=l,T=s;try{T()}catch(N){vt(n,m,N)}}}a=a.next}while(a!==u)}}catch(N){vt(e,e.return,N)}}function Mo(t){var e=t.updateQueue;if(e!==null){var l=t.stateNode;try{br(e,l)}catch(a){vt(t,t.return,a)}}}function Co(t,e,l){l.props=Jl(t.type,t.memoizedProps),l.state=t.memoizedState;try{l.componentWillUnmount()}catch(a){vt(t,e,a)}}function yn(t,e){try{var l=t.ref;if(l!==null){switch(t.tag){case 26:case 27:case 5:var a=t.stateNode;break;case 30:a=t.stateNode;break;default:a=t.stateNode}typeof l=="function"?t.refCleanup=l(a):l.current=a}}catch(n){vt(t,e,n)}}function qe(t,e){var l=t.ref,a=t.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(n){vt(t,e,n)}finally{t.refCleanup=null,t=t.alternate,t!=null&&(t.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(n){vt(t,e,n)}else l.current=null}function jo(t){var e=t.type,l=t.memoizedProps,a=t.stateNode;try{t:switch(e){case"button":case"input":case"select":case"textarea":l.autoFocus&&a.focus();break t;case"img":l.src?a.src=l.src:l.srcSet&&(a.srcset=l.srcSet)}}catch(n){vt(t,t.return,n)}}function Zc(t,e,l){try{var a=t.stateNode;qy(a,t.type,l,e),a[Ft]=e}catch(n){vt(t,t.return,n)}}function Uo(t){return t.tag===5||t.tag===3||t.tag===26||t.tag===27&&zl(t.type)||t.tag===4}function Vc(t){t:for(;;){for(;t.sibling===null;){if(t.return===null||Uo(t.return))return null;t=t.return}for(t.sibling.return=t.return,t=t.sibling;t.tag!==5&&t.tag!==6&&t.tag!==18;){if(t.tag===27&&zl(t.type)||t.flags&2||t.child===null||t.tag===4)continue t;t.child.return=t,t=t.child}if(!(t.flags&2))return t.stateNode}}function Kc(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?(l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l).insertBefore(t,e):(e=l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l,e.appendChild(t),l=l._reactRootContainer,l!=null||e.onclick!==null||(e.onclick=Xe));else if(a!==4&&(a===27&&zl(t.type)&&(l=t.stateNode,e=null),t=t.child,t!==null))for(Kc(t,e,l),t=t.sibling;t!==null;)Kc(t,e,l),t=t.sibling}function Ou(t,e,l){var a=t.tag;if(a===5||a===6)t=t.stateNode,e?l.insertBefore(t,e):l.appendChild(t);else if(a!==4&&(a===27&&zl(t.type)&&(l=t.stateNode),t=t.child,t!==null))for(Ou(t,e,l),t=t.sibling;t!==null;)Ou(t,e,l),t=t.sibling}function Ho(t){var e=t.stateNode,l=t.memoizedProps;try{for(var a=t.type,n=e.attributes;n.length;)e.removeAttributeNode(n[0]);Kt(e,a,l),e[Xt]=t,e[Ft]=l}catch(u){vt(t,t.return,u)}}var Fe=!1,qt=!1,Jc=!1,Bo=typeof WeakSet=="function"?WeakSet:Set,Gt=null;function vy(t,e){if(t=t.containerInfo,mf=Wu,t=ws(t),Li(t)){if("selectionStart"in t)var l={start:t.selectionStart,end:t.selectionEnd};else t:{l=(l=t.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 t}var i=0,s=-1,m=-1,T=0,N=0,j=t,A=null;e:for(;;){for(var x;j!==l||n!==0&&j.nodeType!==3||(s=i+n),j!==u||a!==0&&j.nodeType!==3||(m=i+a),j.nodeType===3&&(i+=j.nodeValue.length),(x=j.firstChild)!==null;)A=j,j=x;for(;;){if(j===t)break e;if(A===l&&++T===n&&(s=i),A===u&&++N===a&&(m=i),(x=j.nextSibling)!==null)break;j=A,A=j.parentNode}j=x}l=s===-1||m===-1?null:{start:s,end:m}}else l=null}l=l||{start:0,end:0}}else l=null;for(yf={focusedElem:t,selectionRange:l},Wu=!1,Gt=e;Gt!==null;)if(e=Gt,t=e.child,(e.subtreeFlags&1028)!==0&&t!==null)t.return=e,Gt=t;else for(;Gt!==null;){switch(e=Gt,u=e.alternate,t=e.flags,e.tag){case 0:if((t&4)!==0&&(t=e.updateQueue,t=t!==null?t.events:null,t!==null))for(l=0;l title"))),Kt(u,a,l),u[Xt]=t,Lt(u),a=u;break t;case"link":var i=Qd("link","href",n).get(a+(l.href||""));if(i){for(var s=0;sbt&&(i=bt,bt=W,W=i);var b=Ks(s,W),v=Ks(s,bt);if(b&&v&&(x.rangeCount!==1||x.anchorNode!==b.node||x.anchorOffset!==b.offset||x.focusNode!==v.node||x.focusOffset!==v.offset)){var _=j.createRange();_.setStart(b.node,b.offset),x.removeAllRanges(),W>bt?(x.addRange(_),x.extend(v.node,v.offset)):(_.setEnd(v.node,v.offset),x.addRange(_))}}}}for(j=[],x=s;x=x.parentNode;)x.nodeType===1&&j.push({element:x,left:x.scrollLeft,top:x.scrollTop});for(typeof s.focus=="function"&&s.focus(),s=0;sl?32:l,D.T=null,l=Pc,Pc=null;var u=El,i=ll;if(Yt=0,Ra=El=null,ll=0,(ht&6)!==0)throw Error(f(331));var s=ht;if(ht|=4,wo(u.current),Vo(u,u.current,i,l),ht=s,En(0,!1),ue&&typeof ue.onPostCommitFiberRoot=="function")try{ue.onPostCommitFiberRoot(Ya,u)}catch{}return!0}finally{q.p=n,D.T=a,od(t,e)}}function hd(t,e,l){e=be(l,e),e=Cc(t.stateNode,e,2),t=ml(t,e,2),t!==null&&(Ga(t,2),Ye(t))}function vt(t,e,l){if(t.tag===3)hd(t,t,l);else for(;e!==null;){if(e.tag===3){hd(e,t,l);break}else if(e.tag===1){var a=e.stateNode;if(typeof e.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(bl===null||!bl.has(a))){t=be(l,t),l=mo(2),a=ml(e,l,2),a!==null&&(yo(l,a,e,t),Ga(a,2),Ye(a));break}}e=e.return}}function af(t,e,l){var a=t.pingCache;if(a===null){a=t.pingCache=new Sy;var n=new Set;a.set(e,n)}else n=a.get(e),n===void 0&&(n=new Set,a.set(e,n));n.has(l)||(Wc=!0,n.add(l),t=zy.bind(null,t,e,l),e.then(t,t))}function zy(t,e,l){var a=t.pingCache;a!==null&&a.delete(e),t.pingedLanes|=t.suspendedLanes&l,t.warmLanes&=~l,_t===t&&(ct&l)===l&&(Ot===4||Ot===3&&(ct&62914560)===ct&&300>ne()-Mu?(ht&2)===0&&xa(t,0):kc|=l,Aa===ct&&(Aa=0)),Ye(t)}function md(t,e){e===0&&(e=cs()),t=Bl(t,e),t!==null&&(Ga(t,e),Ye(t))}function Ay(t){var e=t.memoizedState,l=0;e!==null&&(l=e.retryLane),md(t,l)}function Ry(t,e){var l=0;switch(t.tag){case 31:case 13:var a=t.stateNode,n=t.memoizedState;n!==null&&(l=n.retryLane);break;case 19:a=t.stateNode;break;case 22:a=t.stateNode._retryCache;break;default:throw Error(f(314))}a!==null&&a.delete(e),md(t,l)}function xy(t,e){return vi(t,e)}var Yu=null,Na=null,nf=!1,Lu=!1,uf=!1,Tl=0;function Ye(t){t!==Na&&t.next===null&&(Na===null?Yu=Na=t:Na=Na.next=t),Lu=!0,nf||(nf=!0,Ny())}function En(t,e){if(!uf&&Lu){uf=!0;do for(var l=!1,a=Yu;a!==null;){if(t!==0){var n=a.pendingLanes;if(n===0)var u=0;else{var i=a.suspendedLanes,s=a.pingedLanes;u=(1<<31-ie(42|t)+1)-1,u&=n&~(i&~s),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(l=!0,pd(a,u))}else u=ct,u=Zn(a,a===_t?u:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(u&3)===0||La(a,u)||(l=!0,pd(a,u));a=a.next}while(l);uf=!1}}function Oy(){yd()}function yd(){Lu=nf=!1;var t=0;Tl!==0&&Ly()&&(t=Tl);for(var e=ne(),l=null,a=Yu;a!==null;){var n=a.next,u=vd(a,e);u===0?(a.next=null,l===null?Yu=n:l.next=n,n===null&&(Na=l)):(l=a,(t!==0||(u&3)!==0)&&(Lu=!0)),a=n}Yt!==0&&Yt!==5||En(t),Tl!==0&&(Tl=0)}function vd(t,e){for(var l=t.suspendedLanes,a=t.pingedLanes,n=t.expirationTimes,u=t.pendingLanes&-62914561;0s)break;var N=m.transferSize,j=m.initiatorType;N&&Rd(j)&&(m=m.responseEnd,i+=N*(m"u"?null:document;function Yd(t,e,l){var a=Da;if(a&&typeof e=="string"&&e){var n=pe(e);n='link[rel="'+t+'"][href="'+n+'"]',typeof l=="string"&&(n+='[crossorigin="'+l+'"]'),qd.has(n)||(qd.add(n),t={rel:t,crossOrigin:l,href:e},a.querySelector(n)===null&&(e=a.createElement("link"),Kt(e,"link",t),Lt(e),a.head.appendChild(e)))}}function $y(t){al.D(t),Yd("dns-prefetch",t,null)}function Wy(t,e){al.C(t,e),Yd("preconnect",t,e)}function ky(t,e,l){al.L(t,e,l);var a=Da;if(a&&t&&e){var n='link[rel="preload"][as="'+pe(e)+'"]';e==="image"&&l&&l.imageSrcSet?(n+='[imagesrcset="'+pe(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(n+='[imagesizes="'+pe(l.imageSizes)+'"]')):n+='[href="'+pe(t)+'"]';var u=n;switch(e){case"style":u=Ma(t);break;case"script":u=Ca(t)}Re.has(u)||(t=R({rel:"preload",href:e==="image"&&l&&l.imageSrcSet?void 0:t,as:e},l),Re.set(u,t),a.querySelector(n)!==null||e==="style"&&a.querySelector(An(u))||e==="script"&&a.querySelector(Rn(u))||(e=a.createElement("link"),Kt(e,"link",t),Lt(e),a.head.appendChild(e)))}}function Fy(t,e){al.m(t,e);var l=Da;if(l&&t){var a=e&&typeof e.as=="string"?e.as:"script",n='link[rel="modulepreload"][as="'+pe(a)+'"][href="'+pe(t)+'"]',u=n;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":u=Ca(t)}if(!Re.has(u)&&(t=R({rel:"modulepreload",href:t},e),Re.set(u,t),l.querySelector(n)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(Rn(u)))return}a=l.createElement("link"),Kt(a,"link",t),Lt(a),l.head.appendChild(a)}}}function Iy(t,e,l){al.S(t,e,l);var a=Da;if(a&&t){var n=Pl(a).hoistableStyles,u=Ma(t);e=e||"default";var i=n.get(u);if(!i){var s={loading:0,preload:null};if(i=a.querySelector(An(u)))s.loading=5;else{t=R({rel:"stylesheet",href:t,"data-precedence":e},l),(l=Re.get(u))&&_f(t,l);var m=i=a.createElement("link");Lt(m),Kt(m,"link",t),m._p=new Promise(function(T,N){m.onload=T,m.onerror=N}),m.addEventListener("load",function(){s.loading|=1}),m.addEventListener("error",function(){s.loading|=2}),s.loading|=4,Vu(i,e,a)}i={type:"stylesheet",instance:i,count:1,state:s},n.set(u,i)}}}function Py(t,e){al.X(t,e);var l=Da;if(l&&t){var a=Pl(l).hoistableScripts,n=Ca(t),u=a.get(n);u||(u=l.querySelector(Rn(n)),u||(t=R({src:t,async:!0},e),(e=Re.get(n))&&Tf(t,e),u=l.createElement("script"),Lt(u),Kt(u,"link",t),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function tv(t,e){al.M(t,e);var l=Da;if(l&&t){var a=Pl(l).hoistableScripts,n=Ca(t),u=a.get(n);u||(u=l.querySelector(Rn(n)),u||(t=R({src:t,async:!0,type:"module"},e),(e=Re.get(n))&&Tf(t,e),u=l.createElement("script"),Lt(u),Kt(u,"link",t),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function Ld(t,e,l,a){var n=(n=at.current)?Zu(n):null;if(!n)throw Error(f(446));switch(t){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(e=Ma(l.href),l=Pl(n).hoistableStyles,a=l.get(e),a||(a={type:"style",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){t=Ma(l.href);var u=Pl(n).hoistableStyles,i=u.get(t);if(i||(n=n.ownerDocument||n,i={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(t,i),(u=n.querySelector(An(t)))&&!u._p&&(i.instance=u,i.state.loading=5),Re.has(t)||(l={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},Re.set(t,l),u||ev(n,t,l,i.state))),e&&a===null)throw Error(f(528,""));return i}if(e&&a!==null)throw Error(f(529,""));return null;case"script":return e=l.async,l=l.src,typeof l=="string"&&e&&typeof e!="function"&&typeof e!="symbol"?(e=Ca(l),l=Pl(n).hoistableScripts,a=l.get(e),a||(a={type:"script",instance:null,count:0,state:null},l.set(e,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(f(444,t))}}function Ma(t){return'href="'+pe(t)+'"'}function An(t){return'link[rel="stylesheet"]['+t+"]"}function Gd(t){return R({},t,{"data-precedence":t.precedence,precedence:null})}function ev(t,e,l,a){t.querySelector('link[rel="preload"][as="style"]['+e+"]")?a.loading=1:(e=t.createElement("link"),a.preload=e,e.addEventListener("load",function(){return a.loading|=1}),e.addEventListener("error",function(){return a.loading|=2}),Kt(e,"link",l),Lt(e),t.head.appendChild(e))}function Ca(t){return'[src="'+pe(t)+'"]'}function Rn(t){return"script[async]"+t}function Xd(t,e,l){if(e.count++,e.instance===null)switch(e.type){case"style":var a=t.querySelector('style[data-href~="'+pe(l.href)+'"]');if(a)return e.instance=a,Lt(a),a;var n=R({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(t.ownerDocument||t).createElement("style"),Lt(a),Kt(a,"style",n),Vu(a,l.precedence,t),e.instance=a;case"stylesheet":n=Ma(l.href);var u=t.querySelector(An(n));if(u)return e.state.loading|=4,e.instance=u,Lt(u),u;a=Gd(l),(n=Re.get(n))&&_f(a,n),u=(t.ownerDocument||t).createElement("link"),Lt(u);var i=u;return i._p=new Promise(function(s,m){i.onload=s,i.onerror=m}),Kt(u,"link",a),e.state.loading|=4,Vu(u,l.precedence,t),e.instance=u;case"script":return u=Ca(l.src),(n=t.querySelector(Rn(u)))?(e.instance=n,Lt(n),n):(a=l,(n=Re.get(u))&&(a=R({},l),Tf(a,n)),t=t.ownerDocument||t,n=t.createElement("script"),Lt(n),Kt(n,"link",a),t.head.appendChild(n),e.instance=n);case"void":return null;default:throw Error(f(443,e.type))}else e.type==="stylesheet"&&(e.state.loading&4)===0&&(a=e.instance,e.state.loading|=4,Vu(a,l.precedence,t));return e.instance}function Vu(t,e,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=a.length?a[a.length-1]:null,u=n,i=0;i title"):null)}function lv(t,e,l){if(l===1||e.itemProp!=null)return!1;switch(t){case"meta":case"title":return!0;case"style":if(typeof e.precedence!="string"||typeof e.href!="string"||e.href==="")break;return!0;case"link":if(typeof e.rel!="string"||typeof e.href!="string"||e.href===""||e.onLoad||e.onError)break;return e.rel==="stylesheet"?(t=e.disabled,typeof e.precedence=="string"&&t==null):!0;case"script":if(e.async&&typeof e.async!="function"&&typeof e.async!="symbol"&&!e.onLoad&&!e.onError&&e.src&&typeof e.src=="string")return!0}return!1}function Vd(t){return!(t.type==="stylesheet"&&(t.state.loading&3)===0)}function av(t,e,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=e.querySelector(An(n));if(u){e=u._p,e!==null&&typeof e=="object"&&typeof e.then=="function"&&(t.count++,t=Ju.bind(t),e.then(t,t)),l.state.loading|=4,l.instance=u,Lt(u);return}u=e.ownerDocument||e,a=Gd(a),(n=Re.get(n))&&_f(a,n),u=u.createElement("link"),Lt(u);var i=u;i._p=new Promise(function(s,m){i.onload=s,i.onerror=m}),Kt(u,"link",a),l.instance=u}t.stylesheets===null&&(t.stylesheets=new Map),t.stylesheets.set(l,e),(e=l.state.preload)&&(l.state.loading&3)===0&&(t.count++,l=Ju.bind(t),e.addEventListener("load",l),e.addEventListener("error",l))}}var zf=0;function nv(t,e){return t.stylesheets&&t.count===0&&$u(t,t.stylesheets),0zf?50:800)+e);return t.unsuspend=l,function(){t.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function Ju(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)$u(this,this.stylesheets);else if(this.unsuspend){var t=this.unsuspend;this.unsuspend=null,t()}}}var wu=null;function $u(t,e){t.stylesheets=null,t.unsuspend!==null&&(t.count++,wu=new Map,e.forEach(uv,t),wu=null,Ju.call(t))}function uv(t,e){if(!(e.state.loading&4)){var l=wu.get(t);if(l)var a=l.get(null);else{l=new Map,wu.set(t,l);for(var n=t.querySelectorAll("link[data-precedence],style[data-precedence]"),u=0;u"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(c)}catch(r){console.error(r)}}return c(),jf.exports=Ev(),jf.exports}var Tv=_v();var mh="popstate";function yh(c){return typeof c=="object"&&c!=null&&"pathname"in c&&"search"in c&&"hash"in c&&"state"in c&&"key"in c}function zv(c={}){function r(d,y){let{pathname:S="/",search:O="",hash:E=""}=Wl(d.location.hash.substring(1));return!S.startsWith("/")&&!S.startsWith(".")&&(S="/"+S),Zf("",{pathname:S,search:O,hash:E},y.state&&y.state.usr||null,y.state&&y.state.key||"default")}function o(d,y){let S=d.document.querySelector("base"),O="";if(S&&S.getAttribute("href")){let E=d.location.href,h=E.indexOf("#");O=h===-1?E:E.slice(0,h)}return O+"#"+(typeof y=="string"?y:Hn(y))}function f(d,y){xe(d.pathname.charAt(0)==="/",`relative pathnames are not supported in hash history.push(${JSON.stringify(y)})`)}return Rv(r,o,f,c)}function Rt(c,r){if(c===!1||c===null||typeof c>"u")throw new Error(r)}function xe(c,r){if(!c){typeof console<"u"&&console.warn(r);try{throw new Error(r)}catch{}}}function Av(){return Math.random().toString(36).substring(2,10)}function vh(c,r){return{usr:c.state,key:c.key,idx:r,masked:c.unstable_mask?{pathname:c.pathname,search:c.search,hash:c.hash}:void 0}}function Zf(c,r,o=null,f,d){return{pathname:typeof c=="string"?c:c.pathname,search:"",hash:"",...typeof r=="string"?Wl(r):r,state:o,key:r&&r.key||f||Av(),unstable_mask:d}}function Hn({pathname:c="/",search:r="",hash:o=""}){return r&&r!=="?"&&(c+=r.charAt(0)==="?"?r:"?"+r),o&&o!=="#"&&(c+=o.charAt(0)==="#"?o:"#"+o),c}function Wl(c){let r={};if(c){let o=c.indexOf("#");o>=0&&(r.hash=c.substring(o),c=c.substring(0,o));let f=c.indexOf("?");f>=0&&(r.search=c.substring(f),c=c.substring(0,f)),c&&(r.pathname=c)}return r}function Rv(c,r,o,f={}){let{window:d=document.defaultView,v5Compat:y=!1}=f,S=d.history,O="POP",E=null,h=M();h==null&&(h=0,S.replaceState({...S.state,idx:h},""));function M(){return(S.state||{idx:null}).idx}function R(){O="POP";let H=M(),G=H==null?null:H-h;h=H,E&&E({action:O,location:L.location,delta:G})}function B(H,G){O="PUSH";let $=yh(H)?H:Zf(L.location,H,G);o&&o($,H),h=M()+1;let K=vh($,h),it=L.createHref($.unstable_mask||$);try{S.pushState(K,"",it)}catch(dt){if(dt instanceof DOMException&&dt.name==="DataCloneError")throw dt;d.location.assign(it)}y&&E&&E({action:O,location:L.location,delta:1})}function J(H,G){O="REPLACE";let $=yh(H)?H:Zf(L.location,H,G);o&&o($,H),h=M();let K=vh($,h),it=L.createHref($.unstable_mask||$);S.replaceState(K,"",it),y&&E&&E({action:O,location:L.location,delta:0})}function V(H){return xv(H)}let L={get action(){return O},get location(){return c(d,S)},listen(H){if(E)throw new Error("A history only accepts one active listener");return d.addEventListener(mh,R),E=H,()=>{d.removeEventListener(mh,R),E=null}},createHref(H){return r(d,H)},createURL:V,encodeLocation(H){let G=V(H);return{pathname:G.pathname,search:G.search,hash:G.hash}},push:B,replace:J,go(H){return S.go(H)}};return L}function xv(c,r=!1){let o="http://localhost";typeof window<"u"&&(o=window.location.origin!=="null"?window.location.origin:window.location.href),Rt(o,"No window.location.(origin|href) available to create URL");let f=typeof c=="string"?c:Hn(c);return f=f.replace(/ $/,"%20"),!r&&f.startsWith("//")&&(f=o+f),new URL(f,o)}function Eh(c,r,o="/"){return Ov(c,r,o,!1)}function Ov(c,r,o,f){let d=typeof r=="string"?Wl(r):r,y=nl(d.pathname||"/",o);if(y==null)return null;let S=_h(c);Nv(S);let O=null;for(let E=0;O==null&&E{let M={relativePath:h===void 0?S.path||"":h,caseSensitive:S.caseSensitive===!0,childrenIndex:O,route:S};if(M.relativePath.startsWith("/")){if(!M.relativePath.startsWith(f)&&E)return;Rt(M.relativePath.startsWith(f),`Absolute route path "${M.relativePath}" nested under path "${f}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),M.relativePath=M.relativePath.slice(f.length)}let R=Me([f,M.relativePath]),B=o.concat(M);S.children&&S.children.length>0&&(Rt(S.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${R}".`),_h(S.children,r,B,R,E)),!(S.path==null&&!S.index)&&r.push({path:R,score:Bv(R,S.index),routesMeta:B})};return c.forEach((S,O)=>{if(S.path===""||!S.path?.includes("?"))y(S,O);else for(let E of Th(S.path))y(S,O,!0,E)}),r}function Th(c){let r=c.split("/");if(r.length===0)return[];let[o,...f]=r,d=o.endsWith("?"),y=o.replace(/\?$/,"");if(f.length===0)return d?[y,""]:[y];let S=Th(f.join("/")),O=[];return O.push(...S.map(E=>E===""?y:[y,E].join("/"))),d&&O.push(...S),O.map(E=>c.startsWith("/")&&E===""?"/":E)}function Nv(c){c.sort((r,o)=>r.score!==o.score?o.score-r.score:qv(r.routesMeta.map(f=>f.childrenIndex),o.routesMeta.map(f=>f.childrenIndex)))}var Dv=/^:[\w-]+$/,Mv=3,Cv=2,jv=1,Uv=10,Hv=-2,gh=c=>c==="*";function Bv(c,r){let o=c.split("/"),f=o.length;return o.some(gh)&&(f+=Hv),r&&(f+=Cv),o.filter(d=>!gh(d)).reduce((d,y)=>d+(Dv.test(y)?Mv:y===""?jv:Uv),f)}function qv(c,r){return c.length===r.length&&c.slice(0,-1).every((f,d)=>f===r[d])?c[c.length-1]-r[r.length-1]:0}function Yv(c,r,o=!1){let{routesMeta:f}=c,d={},y="/",S=[];for(let O=0;O{if(M==="*"){let V=O[B]||"";S=y.slice(0,y.length-V.length).replace(/(.)\/+$/,"$1")}const J=O[B];return R&&!J?h[M]=void 0:h[M]=(J||"").replace(/%2F/g,"/"),h},{}),pathname:y,pathnameBase:S,pattern:c}}function Lv(c,r=!1,o=!0){xe(c==="*"||!c.endsWith("*")||c.endsWith("/*"),`Route path "${c}" will be treated as if it were "${c.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${c.replace(/\*$/,"/*")}".`);let f=[],d="^"+c.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(S,O,E,h,M)=>{if(f.push({paramName:O,isOptional:E!=null}),E){let R=M.charAt(h+S.length);return R&&R!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return c.endsWith("*")?(f.push({paramName:"*"}),d+=c==="*"||c==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):o?d+="\\/*$":c!==""&&c!=="/"&&(d+="(?:(?=\\/|$))"),[new RegExp(d,r?void 0:"i"),f]}function Gv(c){try{return c.split("/").map(r=>decodeURIComponent(r).replace(/\//g,"%2F")).join("/")}catch(r){return xe(!1,`The URL path "${c}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${r}).`),c}}function nl(c,r){if(r==="/")return c;if(!c.toLowerCase().startsWith(r.toLowerCase()))return null;let o=r.endsWith("/")?r.length-1:r.length,f=c.charAt(o);return f&&f!=="/"?null:c.slice(o)||"/"}var Xv=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function Qv(c,r="/"){let{pathname:o,search:f="",hash:d=""}=typeof c=="string"?Wl(c):c,y;return o?(o=zh(o),o.startsWith("/")?y=ph(o.substring(1),"/"):y=ph(o,r)):y=r,{pathname:y,search:Kv(f),hash:Jv(d)}}function ph(c,r){let o=fi(r).split("/");return c.split("/").forEach(d=>{d===".."?o.length>1&&o.pop():d!=="."&&o.push(d)}),o.length>1?o.join("/"):"/"}function qf(c,r,o,f){return`Cannot include a '${c}' character in a manually specified \`to.${r}\` field [${JSON.stringify(f)}]. Please separate it out to the \`to.${o}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function Zv(c){return c.filter((r,o)=>o===0||r.route.path&&r.route.path.length>0)}function wf(c){let r=Zv(c);return r.map((o,f)=>f===r.length-1?o.pathname:o.pathnameBase)}function si(c,r,o,f=!1){let d;typeof c=="string"?d=Wl(c):(d={...c},Rt(!d.pathname||!d.pathname.includes("?"),qf("?","pathname","search",d)),Rt(!d.pathname||!d.pathname.includes("#"),qf("#","pathname","hash",d)),Rt(!d.search||!d.search.includes("#"),qf("#","search","hash",d)));let y=c===""||d.pathname==="",S=y?"/":d.pathname,O;if(S==null)O=o;else{let R=r.length-1;if(!f&&S.startsWith("..")){let B=S.split("/");for(;B[0]==="..";)B.shift(),R-=1;d.pathname=B.join("/")}O=R>=0?r[R]:"/"}let E=Qv(d,O),h=S&&S!=="/"&&S.endsWith("/"),M=(y||S===".")&&o.endsWith("/");return!E.pathname.endsWith("/")&&(h||M)&&(E.pathname+="/"),E}var zh=c=>c.replace(/\/\/+/g,"/"),Me=c=>zh(c.join("/")),fi=c=>c.replace(/\/+$/,""),Vv=c=>fi(c).replace(/^\/*/,"/"),Kv=c=>!c||c==="?"?"":c.startsWith("?")?c:"?"+c,Jv=c=>!c||c==="#"?"":c.startsWith("#")?c:"#"+c,wv=class{constructor(c,r,o,f=!1){this.status=c,this.statusText=r||"",this.internal=f,o instanceof Error?(this.data=o.toString(),this.error=o):this.data=o}};function $v(c){return c!=null&&typeof c.status=="number"&&typeof c.statusText=="string"&&typeof c.internal=="boolean"&&"data"in c}function Wv(c){let r=c.map(o=>o.route.path).filter(Boolean);return Me(r)||"/"}var Ah=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Rh(c,r){let o=c;if(typeof o!="string"||!Xv.test(o))return{absoluteURL:void 0,isExternal:!1,to:o};let f=o,d=!1;if(Ah)try{let y=new URL(window.location.href),S=o.startsWith("//")?new URL(y.protocol+o):new URL(o),O=nl(S.pathname,r);S.origin===y.origin&&O!=null?o=O+S.search+S.hash:d=!0}catch{xe(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:f,isExternal:d,to:o}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var xh=["POST","PUT","PATCH","DELETE"];new Set(xh);var kv=["GET",...xh];new Set(kv);var Ha=z.createContext(null);Ha.displayName="DataRouter";var ri=z.createContext(null);ri.displayName="DataRouterState";var Oh=z.createContext(!1);function Fv(){return z.useContext(Oh)}var Nh=z.createContext({isTransitioning:!1});Nh.displayName="ViewTransition";var Iv=z.createContext(new Map);Iv.displayName="Fetchers";var Pv=z.createContext(null);Pv.displayName="Await";var me=z.createContext(null);me.displayName="Navigation";var Bn=z.createContext(null);Bn.displayName="Location";var Ce=z.createContext({outlet:null,matches:[],isDataRoute:!1});Ce.displayName="Route";var $f=z.createContext(null);$f.displayName="RouteError";var Dh="REACT_ROUTER_ERROR",t0="REDIRECT",e0="ROUTE_ERROR_RESPONSE";function l0(c){if(c.startsWith(`${Dh}:${t0}:{`))try{let r=JSON.parse(c.slice(28));if(typeof r=="object"&&r&&typeof r.status=="number"&&typeof r.statusText=="string"&&typeof r.location=="string"&&typeof r.reloadDocument=="boolean"&&typeof r.replace=="boolean")return r}catch{}}function a0(c){if(c.startsWith(`${Dh}:${e0}:{`))try{let r=JSON.parse(c.slice(40));if(typeof r=="object"&&r&&typeof r.status=="number"&&typeof r.statusText=="string")return new wv(r.status,r.statusText,r.data)}catch{}}function n0(c,{relative:r}={}){Rt(Ba(),"useHref() may be used only in the context of a component.");let{basename:o,navigator:f}=z.useContext(me),{hash:d,pathname:y,search:S}=qn(c,{relative:r}),O=y;return o!=="/"&&(O=y==="/"?o:Me([o,y])),f.createHref({pathname:O,search:S,hash:d})}function Ba(){return z.useContext(Bn)!=null}function Le(){return Rt(Ba(),"useLocation() may be used only in the context of a component."),z.useContext(Bn).location}var Mh="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function Ch(c){z.useContext(me).static||z.useLayoutEffect(c)}function jh(){let{isDataRoute:c}=z.useContext(Ce);return c?S0():u0()}function u0(){Rt(Ba(),"useNavigate() may be used only in the context of a component.");let c=z.useContext(Ha),{basename:r,navigator:o}=z.useContext(me),{matches:f}=z.useContext(Ce),{pathname:d}=Le(),y=JSON.stringify(wf(f)),S=z.useRef(!1);return Ch(()=>{S.current=!0}),z.useCallback((E,h={})=>{if(xe(S.current,Mh),!S.current)return;if(typeof E=="number"){o.go(E);return}let M=si(E,JSON.parse(y),d,h.relative==="path");c==null&&r!=="/"&&(M.pathname=M.pathname==="/"?r:Me([r,M.pathname])),(h.replace?o.replace:o.push)(M,h.state,h)},[r,o,y,d,c])}var i0=z.createContext(null);function c0(c){let r=z.useContext(Ce).outlet;return z.useMemo(()=>r&&z.createElement(i0.Provider,{value:c},r),[r,c])}function qn(c,{relative:r}={}){let{matches:o}=z.useContext(Ce),{pathname:f}=Le(),d=JSON.stringify(wf(o));return z.useMemo(()=>si(c,JSON.parse(d),f,r==="path"),[c,d,f,r])}function f0(c,r){return Uh(c,r)}function Uh(c,r,o){Rt(Ba(),"useRoutes() may be used only in the context of a component.");let{navigator:f}=z.useContext(me),{matches:d}=z.useContext(Ce),y=d[d.length-1],S=y?y.params:{},O=y?y.pathname:"/",E=y?y.pathnameBase:"/",h=y&&y.route;{let H=h&&h.path||"";Bh(O,!h||H.endsWith("*")||H.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${O}" (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 M=Le(),R;if(r){let H=typeof r=="string"?Wl(r):r;Rt(E==="/"||H.pathname?.startsWith(E),`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 "${E}" but pathname "${H.pathname}" was given in the \`location\` prop.`),R=H}else R=M;let B=R.pathname||"/",J=B;if(E!=="/"){let H=E.replace(/^\//,"").split("/");J="/"+B.replace(/^\//,"").split("/").slice(H.length).join("/")}let V=Eh(c,{pathname:J});xe(h||V!=null,`No routes matched location "${R.pathname}${R.search}${R.hash}" `),xe(V==null||V[V.length-1].route.element!==void 0||V[V.length-1].route.Component!==void 0||V[V.length-1].route.lazy!==void 0,`Matched leaf route at location "${R.pathname}${R.search}${R.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 L=h0(V&&V.map(H=>Object.assign({},H,{params:Object.assign({},S,H.params),pathname:Me([E,f.encodeLocation?f.encodeLocation(H.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:H.pathname]),pathnameBase:H.pathnameBase==="/"?E:Me([E,f.encodeLocation?f.encodeLocation(H.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:H.pathnameBase])})),d,o);return r&&L?z.createElement(Bn.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",unstable_mask:void 0,...R},navigationType:"POP"}},L):L}function s0(){let c=p0(),r=$v(c)?`${c.status} ${c.statusText}`:c instanceof Error?c.message:JSON.stringify(c),o=c instanceof Error?c.stack:null,f="rgba(200,200,200, 0.5)",d={padding:"0.5rem",backgroundColor:f},y={padding:"2px 4px",backgroundColor:f},S=null;return console.error("Error handled by React Router default ErrorBoundary:",c),S=z.createElement(z.Fragment,null,z.createElement("p",null,"💿 Hey developer 👋"),z.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",z.createElement("code",{style:y},"ErrorBoundary")," or"," ",z.createElement("code",{style:y},"errorElement")," prop on your route.")),z.createElement(z.Fragment,null,z.createElement("h2",null,"Unexpected Application Error!"),z.createElement("h3",{style:{fontStyle:"italic"}},r),o?z.createElement("pre",{style:d},o):null,S)}var r0=z.createElement(s0,null),Hh=class extends z.Component{constructor(c){super(c),this.state={location:c.location,revalidation:c.revalidation,error:c.error}}static getDerivedStateFromError(c){return{error:c}}static getDerivedStateFromProps(c,r){return r.location!==c.location||r.revalidation!=="idle"&&c.revalidation==="idle"?{error:c.error,location:c.location,revalidation:c.revalidation}:{error:c.error!==void 0?c.error:r.error,location:r.location,revalidation:c.revalidation||r.revalidation}}componentDidCatch(c,r){this.props.onError?this.props.onError(c,r):console.error("React Router caught the following error during render",c)}render(){let c=this.state.error;if(this.context&&typeof c=="object"&&c&&"digest"in c&&typeof c.digest=="string"){const o=a0(c.digest);o&&(c=o)}let r=c!==void 0?z.createElement(Ce.Provider,{value:this.props.routeContext},z.createElement($f.Provider,{value:c,children:this.props.component})):this.props.children;return this.context?z.createElement(o0,{error:c},r):r}};Hh.contextType=Oh;var Yf=new WeakMap;function o0({children:c,error:r}){let{basename:o}=z.useContext(me);if(typeof r=="object"&&r&&"digest"in r&&typeof r.digest=="string"){let f=l0(r.digest);if(f){let d=Yf.get(r);if(d)throw d;let y=Rh(f.location,o);if(Ah&&!Yf.get(r))if(y.isExternal||f.reloadDocument)window.location.href=y.absoluteURL||y.to;else{const S=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(y.to,{replace:f.replace}));throw Yf.set(r,S),S}return z.createElement("meta",{httpEquiv:"refresh",content:`0;url=${y.absoluteURL||y.to}`})}}return c}function d0({routeContext:c,match:r,children:o}){let f=z.useContext(Ha);return f&&f.static&&f.staticContext&&(r.route.errorElement||r.route.ErrorBoundary)&&(f.staticContext._deepestRenderedBoundaryId=r.route.id),z.createElement(Ce.Provider,{value:c},o)}function h0(c,r=[],o){let f=o?.state;if(c==null){if(!f)return null;if(f.errors)c=f.matches;else if(r.length===0&&!f.initialized&&f.matches.length>0)c=f.matches;else return null}let d=c,y=f?.errors;if(y!=null){let M=d.findIndex(R=>R.route.id&&y?.[R.route.id]!==void 0);Rt(M>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(y).join(",")}`),d=d.slice(0,Math.min(d.length,M+1))}let S=!1,O=-1;if(o&&f){S=f.renderFallback;for(let M=0;M=0?d=d.slice(0,O+1):d=[d[0]];break}}}}let E=o?.onError,h=f&&E?(M,R)=>{E(M,{location:f.location,params:f.matches?.[0]?.params??{},unstable_pattern:Wv(f.matches),errorInfo:R})}:void 0;return d.reduceRight((M,R,B)=>{let J,V=!1,L=null,H=null;f&&(J=y&&R.route.id?y[R.route.id]:void 0,L=R.route.errorElement||r0,S&&(O<0&&B===0?(Bh("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),V=!0,H=null):O===B&&(V=!0,H=R.route.hydrateFallbackElement||null)));let G=r.concat(d.slice(0,B+1)),$=()=>{let K;return J?K=L:V?K=H:R.route.Component?K=z.createElement(R.route.Component,null):R.route.element?K=R.route.element:K=M,z.createElement(d0,{match:R,routeContext:{outlet:M,matches:G,isDataRoute:f!=null},children:K})};return f&&(R.route.ErrorBoundary||R.route.errorElement||B===0)?z.createElement(Hh,{location:f.location,revalidation:f.revalidation,component:L,error:J,children:$(),routeContext:{outlet:null,matches:G,isDataRoute:!0},onError:h}):$()},null)}function Wf(c){return`${c} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function m0(c){let r=z.useContext(Ha);return Rt(r,Wf(c)),r}function y0(c){let r=z.useContext(ri);return Rt(r,Wf(c)),r}function v0(c){let r=z.useContext(Ce);return Rt(r,Wf(c)),r}function kf(c){let r=v0(c),o=r.matches[r.matches.length-1];return Rt(o.route.id,`${c} can only be used on routes that contain a unique "id"`),o.route.id}function g0(){return kf("useRouteId")}function p0(){let c=z.useContext($f),r=y0("useRouteError"),o=kf("useRouteError");return c!==void 0?c:r.errors?.[o]}function S0(){let{router:c}=m0("useNavigate"),r=kf("useNavigate"),o=z.useRef(!1);return Ch(()=>{o.current=!0}),z.useCallback(async(d,y={})=>{xe(o.current,Mh),o.current&&(typeof d=="number"?await c.navigate(d):await c.navigate(d,{fromRouteId:r,...y}))},[c,r])}var Sh={};function Bh(c,r,o){!r&&!Sh[c]&&(Sh[c]=!0,xe(!1,o))}z.memo(b0);function b0({routes:c,future:r,state:o,isStatic:f,onError:d}){return Uh(c,void 0,{state:o,isStatic:f,onError:d})}function E0({to:c,replace:r,state:o,relative:f}){Rt(Ba()," may be used only in the context of a component.");let{static:d}=z.useContext(me);xe(!d," must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.");let{matches:y}=z.useContext(Ce),{pathname:S}=Le(),O=jh(),E=si(c,wf(y),S,f==="path"),h=JSON.stringify(E);return z.useEffect(()=>{O(JSON.parse(h),{replace:r,state:o,relative:f})},[O,h,f,r,o]),null}function _0(c){return c0(c.context)}function Ua(c){Rt(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function T0({basename:c="/",children:r=null,location:o,navigationType:f="POP",navigator:d,static:y=!1,unstable_useTransitions:S}){Rt(!Ba(),"You cannot render a inside another . You should never have more than one in your app.");let O=c.replace(/^\/*/,"/"),E=z.useMemo(()=>({basename:O,navigator:d,static:y,unstable_useTransitions:S,future:{}}),[O,d,y,S]);typeof o=="string"&&(o=Wl(o));let{pathname:h="/",search:M="",hash:R="",state:B=null,key:J="default",unstable_mask:V}=o,L=z.useMemo(()=>{let H=nl(h,O);return H==null?null:{location:{pathname:H,search:M,hash:R,state:B,key:J,unstable_mask:V},navigationType:f}},[O,h,M,R,B,J,f,V]);return xe(L!=null,` is not able to match the URL "${h}${M}${R}" because it does not start with the basename, so the won't render anything.`),L==null?null:z.createElement(me.Provider,{value:E},z.createElement(Bn.Provider,{children:r,value:L}))}function z0({children:c,location:r}){return f0(Vf(c),r)}function Vf(c,r=[]){let o=[];return z.Children.forEach(c,(f,d)=>{if(!z.isValidElement(f))return;let y=[...r,d];if(f.type===z.Fragment){o.push.apply(o,Vf(f.props.children,y));return}Rt(f.type===Ua,`[${typeof f.type=="string"?f.type:f.type.name}] is not a component. All component children of must be a or `),Rt(!f.props.index||!f.props.children,"An index route cannot have child routes.");let S={id:f.props.id||y.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&&(S.children=Vf(f.props.children,y)),o.push(S)}),o}var ni="get",ui="application/x-www-form-urlencoded";function oi(c){return typeof HTMLElement<"u"&&c instanceof HTMLElement}function A0(c){return oi(c)&&c.tagName.toLowerCase()==="button"}function R0(c){return oi(c)&&c.tagName.toLowerCase()==="form"}function x0(c){return oi(c)&&c.tagName.toLowerCase()==="input"}function O0(c){return!!(c.metaKey||c.altKey||c.ctrlKey||c.shiftKey)}function N0(c,r){return c.button===0&&(!r||r==="_self")&&!O0(c)}var li=null;function D0(){if(li===null)try{new FormData(document.createElement("form"),0),li=!1}catch{li=!0}return li}var M0=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Lf(c){return c!=null&&!M0.has(c)?(xe(!1,`"${c}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${ui}"`),null):c}function C0(c,r){let o,f,d,y,S;if(R0(c)){let O=c.getAttribute("action");f=O?nl(O,r):null,o=c.getAttribute("method")||ni,d=Lf(c.getAttribute("enctype"))||ui,y=new FormData(c)}else if(A0(c)||x0(c)&&(c.type==="submit"||c.type==="image")){let O=c.form;if(O==null)throw new Error('Cannot submit a -
{timelineText}
- - -
-

Run diff

-
- - setDiffBaseline(e.target.value)} /> -
-
- - setDiffCandidate(e.target.value)} /> -
-
- - setDiffWindow(e.target.value)} /> -
-
- - setDiffEnv(e.target.value)} /> -
- -
{diffOut}
-
- -
-

Promote / rollback

-
- - setActRelease(e.target.value)} /> -
-
- - setActEnv(e.target.value)} /> -
-
- - setActWindow(e.target.value)} /> -
-
- - setActReason(e.target.value)} /> -
- - -
{actOut}
-
- + + + }> + } /> + } /> + } /> + } /> + + + ); } diff --git a/web/src/api.ts b/web/src/api.ts new file mode 100644 index 0000000..2b01bab --- /dev/null +++ b/web/src/api.ts @@ -0,0 +1,65 @@ +export type ReleaseRow = { + release_id: string; + agent_id: string; + version: string; + environment: string; + checksum: string; + created_at: string; +}; + +export type PromotedRow = { + agent_id: string; + environment: string; + release_id: string; +}; + +export type ActionRow = { + action_id: string; + action: string; + release_id: string; + agent_id: string; + environment: string; + baseline_release_id: string | null; + reason: string; + policy_passed: boolean; + policy_reasons: string[]; + created_at: string; + audit_seq: number | null; +}; + +export type TimelinePayload = { + releases: ReleaseRow[]; + promoted: PromotedRow[]; + actions: ActionRow[]; +}; + +export async function fetchJson(path: string, init?: RequestInit): Promise { + const headers = new Headers(init?.headers); + const token = import.meta.env.VITE_FLIGHTDECK_LOCAL_API_TOKEN; + if (typeof token === "string" && token.trim().length > 0 && !headers.has("Authorization")) { + headers.set("Authorization", `Bearer ${token.trim()}`); + } + const res = await fetch(path, { ...init, headers }); + const data: unknown = await res.json().catch(() => ({})); + if (!res.ok) { + const detail = + typeof data === "object" && data !== null && "detail" in data + ? String((data as { detail: unknown }).detail) + : JSON.stringify(data); + throw new Error(detail || `HTTP ${res.status}`); + } + return data as T; +} + +export async function loadTimeline(): Promise { + const [releases, promoted, actions] = await Promise.all([ + fetchJson<{ releases: ReleaseRow[] }>("/v1/releases"), + fetchJson<{ promoted: PromotedRow[] }>("/v1/promoted"), + fetchJson<{ actions: ActionRow[] }>("/v1/actions"), + ]); + return { + releases: releases.releases, + promoted: promoted.promoted, + actions: actions.actions, + }; +} diff --git a/web/src/components/AppShell.tsx b/web/src/components/AppShell.tsx new file mode 100644 index 0000000..affa7e4 --- /dev/null +++ b/web/src/components/AppShell.tsx @@ -0,0 +1,34 @@ +import { NavLink, Outlet } from "react-router-dom"; +import { TimelineRefreshProvider } from "../context/TimelineRefreshContext"; + +const navCls = ({ isActive }: { isActive: boolean }) => + `fd-nav__link${isActive ? " fd-nav__link--active" : ""}`; + +export function AppShell() { + return ( + +
+
+
+

FlightDeck

+

Local release governance

+
+ +
+
+ +
+
+
+ ); +} diff --git a/web/src/components/Badge.tsx b/web/src/components/Badge.tsx new file mode 100644 index 0000000..708a19e --- /dev/null +++ b/web/src/components/Badge.tsx @@ -0,0 +1,11 @@ +type Tone = "pass" | "fail" | "neutral"; + +const toneClass: Record = { + pass: "fd-badge fd-badge--pass", + fail: "fd-badge fd-badge--fail", + neutral: "fd-badge fd-badge--neutral", +}; + +export function Badge({ tone, children }: { tone: Tone; children: string }) { + return {children}; +} diff --git a/web/src/components/JsonPanel.tsx b/web/src/components/JsonPanel.tsx new file mode 100644 index 0000000..945ad91 --- /dev/null +++ b/web/src/components/JsonPanel.tsx @@ -0,0 +1,33 @@ +import { useId, useState } from "react"; + +type Props = { + title?: string; + value: string; + defaultOpen?: boolean; +}; + +export function JsonPanel({ title = "Raw JSON", value, defaultOpen = false }: Props) { + const id = useId(); + const [open, setOpen] = useState(defaultOpen); + return ( +
+ + {open ? ( +
+          {value}
+        
+ ) : null} +
+ ); +} diff --git a/web/src/context/TimelineRefreshContext.tsx b/web/src/context/TimelineRefreshContext.tsx new file mode 100644 index 0000000..3ba9638 --- /dev/null +++ b/web/src/context/TimelineRefreshContext.tsx @@ -0,0 +1,30 @@ +import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from "react"; + +type TimelineRefreshValue = { + /** Increments when server-side timeline data may have changed (e.g. promote/rollback). */ + generation: number; + /** Call after a successful mutation that affects releases, promoted pointers, or actions. */ + notifyTimelineMutated: () => void; +}; + +const TimelineRefreshContext = createContext(null); + +export function TimelineRefreshProvider({ children }: { children: ReactNode }) { + const [generation, setGeneration] = useState(0); + const notifyTimelineMutated = useCallback(() => { + setGeneration((g) => g + 1); + }, []); + const value = useMemo( + () => ({ generation, notifyTimelineMutated }), + [generation, notifyTimelineMutated], + ); + return {children}; +} + +export function useTimelineRefresh(): TimelineRefreshValue { + const ctx = useContext(TimelineRefreshContext); + if (!ctx) { + throw new Error("useTimelineRefresh must be used within TimelineRefreshProvider"); + } + return ctx; +} diff --git a/web/src/index.css b/web/src/index.css index 590eb1a..8528eac 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -1,62 +1,463 @@ :root { - font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; - line-height: 1.45; - color: #111; - background: #fafafa; + color-scheme: light; + --fd-bg: #f4f5f7; + --fd-surface: #ffffff; + --fd-surface-2: #fafbfc; + --fd-border: #e1e4e8; + --fd-border-strong: #c9ccd1; + --fd-text: #1a1d21; + --fd-muted: #5c6570; + --fd-accent: #0d6efd; + --fd-accent-hover: #0b5ed7; + --fd-pass-bg: #e8f5e9; + --fd-pass-fg: #1b5e20; + --fd-fail-bg: #ffebee; + --fd-fail-fg: #b71c1c; + --fd-radius: 10px; + --fd-radius-sm: 6px; + --fd-shadow: 0 1px 2px rgba(15, 23, 42, 0.06); + --fd-font: "Segoe UI Variable", "Segoe UI", system-ui, -apple-system, sans-serif; + --fd-mono: ui-monospace, "Cascadia Code", "Consolas", monospace; + font-family: var(--fd-font); + line-height: 1.5; + color: var(--fd-text); + background: var(--fd-bg); +} + +*, +*::before, +*::after { + box-sizing: border-box; } body { margin: 0; - padding: 1.25rem 1.5rem 2rem; + min-height: 100vh; +} + +.fd-shell { + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.fd-header { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + justify-content: space-between; + gap: 1rem 2rem; + padding: 1rem 1.5rem; + background: var(--fd-surface); + border-bottom: 1px solid var(--fd-border); + box-shadow: var(--fd-shadow); +} + +.fd-header__brand { + min-width: 0; } -h1 { - margin: 0 0 0.25rem; +.fd-header__title { + margin: 0; font-size: 1.35rem; + font-weight: 650; + letter-spacing: -0.02em; +} + +.fd-header__tagline { + margin: 0.15rem 0 0; + font-size: 0.875rem; + color: var(--fd-muted); +} + +.fd-nav { + display: flex; + gap: 0.25rem; + flex-wrap: wrap; } -h2 { - margin: 1.35rem 0 0.45rem; +.fd-nav__link { + padding: 0.45rem 0.85rem; + border-radius: var(--fd-radius-sm); + font-size: 0.9rem; + font-weight: 500; + color: var(--fd-muted); + text-decoration: none; + border: 1px solid transparent; + transition: + background 0.12s, + color 0.12s, + border-color 0.12s; +} + +.fd-nav__link:hover { + color: var(--fd-text); + background: var(--fd-surface-2); +} + +.fd-nav__link--active { + color: var(--fd-text); + background: var(--fd-surface-2); + border-color: var(--fd-border); +} + +.fd-main { + flex: 1; + width: 100%; + max-width: 1120px; + margin: 0 auto; + padding: 1.25rem 1.5rem 2.5rem; +} + +.fd-page-head { + display: flex; + flex-wrap: wrap; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + margin-bottom: 1.25rem; +} + +.fd-page-title { + margin: 0; + font-size: 1.25rem; + font-weight: 650; +} + +.fd-page-sub { + margin: 0.35rem 0 0; + max-width: 52ch; + font-size: 0.9rem; + color: var(--fd-muted); +} + +.fd-card { + background: var(--fd-surface); + border: 1px solid var(--fd-border); + border-radius: var(--fd-radius); + padding: 1rem 1.15rem; + margin-bottom: 1rem; + box-shadow: var(--fd-shadow); +} + +.fd-card__head { + margin-bottom: 0.75rem; +} + +.fd-card__title { + margin: 0; font-size: 1.05rem; + font-weight: 600; +} + +.fd-card__subtitle { + margin: 0 0 0.5rem; + font-size: 1rem; + font-weight: 600; +} + +.fd-card__desc { + margin: 0.35rem 0 0; + font-size: 0.85rem; + color: var(--fd-muted); +} + +.fd-table-wrap { + overflow-x: auto; + margin: 0 -0.15rem; +} + +.fd-table { + width: 100%; + border-collapse: collapse; + font-size: 0.875rem; +} + +.fd-table th, +.fd-table td { + text-align: left; + padding: 0.55rem 0.65rem; + border-bottom: 1px solid var(--fd-border); + vertical-align: top; +} + +.fd-table th { + font-weight: 600; + color: var(--fd-muted); + font-size: 0.78rem; + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.fd-table tbody tr:last-child td { + border-bottom: none; +} + +.fd-table tbody tr:hover td { + background: var(--fd-surface-2); +} + +.fd-mono { + font-family: var(--fd-mono); + font-size: 0.82rem; +} + +.fd-mono--sm { + font-size: 0.78rem; +} + +.fd-nowrap { + white-space: nowrap; +} + +.fd-reason { + max-width: 18rem; + word-break: break-word; +} + +.fd-empty { + margin: 0; + padding: 0.75rem 0; + color: var(--fd-muted); + font-size: 0.9rem; +} + +.fd-empty-cell { + padding: 0.85rem 0.65rem; + color: var(--fd-muted); + font-size: 0.9rem; + font-style: italic; } -section { - border: 1px solid #ddd; - border-radius: 8px; - padding: 0.85rem 1rem; - margin-bottom: 0.85rem; - background: #fff; +.fd-muted { + color: var(--fd-muted); + font-size: 0.9rem; +} + +.fd-samples { + margin: 0.5rem 0 1rem; } -label { +.fd-badge { display: inline-block; - min-width: 9.5rem; - margin-bottom: 0.35rem; + padding: 0.15rem 0.45rem; + border-radius: 999px; + font-size: 0.72rem; + font-weight: 700; + letter-spacing: 0.04em; +} + +.fd-badge--pass { + background: var(--fd-pass-bg); + color: var(--fd-pass-fg); +} + +.fd-badge--fail { + background: var(--fd-fail-bg); + color: var(--fd-fail-fg); +} + +.fd-badge--neutral { + background: var(--fd-surface-2); + color: var(--fd-muted); +} + +.fd-btn { + font: inherit; + cursor: pointer; + border-radius: var(--fd-radius-sm); + border: 1px solid var(--fd-border-strong); + background: var(--fd-surface); + color: var(--fd-text); + padding: 0.45rem 0.95rem; font-size: 0.9rem; + font-weight: 500; } -input { - margin-bottom: 0.45rem; - min-width: 16rem; - padding: 0.25rem 0.4rem; +.fd-btn:hover:not(:disabled) { + background: var(--fd-surface-2); +} + +.fd-btn:disabled { + opacity: 0.55; + cursor: not-allowed; +} + +.fd-btn--primary { + background: var(--fd-accent); + border-color: var(--fd-accent); + color: #fff; +} + +.fd-btn--primary:hover:not(:disabled) { + background: var(--fd-accent-hover); + border-color: var(--fd-accent-hover); } -button { - margin: 0.35rem 0.5rem 0.35rem 0; - padding: 0.35rem 0.65rem; +.fd-btn--ghost { + background: transparent; +} + +.fd-form-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); + gap: 0.85rem 1rem; +} + +.fd-field { + display: flex; + flex-direction: column; + gap: 0.3rem; +} + +.fd-field--full { + grid-column: 1 / -1; +} + +.fd-field__label { + font-size: 0.8rem; + font-weight: 600; + color: var(--fd-muted); +} + +.fd-input { + font: inherit; + padding: 0.45rem 0.55rem; + border: 1px solid var(--fd-border-strong); + border-radius: var(--fd-radius-sm); + background: var(--fd-surface); + color: var(--fd-text); +} + +.fd-input:focus { + outline: 2px solid rgba(13, 110, 253, 0.35); + outline-offset: 1px; +} + +.fd-actions { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 1rem; +} + +.fd-alert { + padding: 0.65rem 0.85rem; + border-radius: var(--fd-radius-sm); + font-size: 0.9rem; } -pre { +.fd-alert--error { + background: var(--fd-fail-bg); + color: var(--fd-fail-fg); + border: 1px solid rgba(183, 28, 28, 0.25); +} + +.fd-json-panel { + margin-top: 0.5rem; +} + +.fd-json-panel__toggle { + font: inherit; + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.35rem 0.5rem; + margin: 0; + border: none; + background: transparent; + color: var(--fd-accent); + cursor: pointer; + font-size: 0.88rem; + font-weight: 500; +} + +.fd-json-panel__toggle:hover { + text-decoration: underline; +} + +.fd-json-panel__chevron { + font-size: 0.7rem; + opacity: 0.85; +} + +.fd-json-panel__pre { + margin: 0.5rem 0 0; + padding: 0.75rem; + border-radius: var(--fd-radius-sm); + border: 1px solid var(--fd-border); + background: #0d1117; + color: #e6edf3; + font-family: var(--fd-mono); + font-size: 0.78rem; + line-height: 1.45; + overflow-x: auto; white-space: pre-wrap; - background: #f6f6f6; - border: 1px solid #eee; - padding: 0.65rem; - border-radius: 6px; + word-break: break-word; +} + +.fd-metric-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); + gap: 1rem; +} + +.fd-metric { + padding: 0.75rem; + border: 1px solid var(--fd-border); + border-radius: var(--fd-radius-sm); + background: var(--fd-surface-2); +} + +.fd-metric__label { + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--fd-muted); + margin-bottom: 0.45rem; +} + +.fd-metric__row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.35rem 0.5rem; + font-size: 0.88rem; +} + +.fd-metric__tag { + display: inline-block; + min-width: 1.25rem; + text-align: center; + font-size: 0.68rem; + font-weight: 700; + border-radius: 4px; + background: var(--fd-border); + color: var(--fd-muted); +} + +.fd-metric__arrow { + color: var(--fd-muted); +} + +.fd-metric__delta { + margin-top: 0.4rem; font-size: 0.82rem; + color: var(--fd-muted); } -.muted { - color: #555; - font-size: 0.9rem; - margin-bottom: 0.75rem; +.fd-inline { + margin-top: 0.25rem; +} + +.fd-reasons { + margin: 0 0 0.75rem; + padding-left: 1.2rem; + font-size: 0.88rem; + color: var(--fd-text); +} + +.fd-reasons li { + margin-bottom: 0.25rem; } diff --git a/web/src/pages/ActionsPage.tsx b/web/src/pages/ActionsPage.tsx new file mode 100644 index 0000000..a33d45b --- /dev/null +++ b/web/src/pages/ActionsPage.tsx @@ -0,0 +1,106 @@ +import { useState } from "react"; +import { fetchJson } from "../api"; +import { JsonPanel } from "../components/JsonPanel"; +import { useTimelineRefresh } from "../context/TimelineRefreshContext"; + +export function ActionsPage() { + const { notifyTimelineMutated } = useTimelineRefresh(); + const [actRelease, setActRelease] = useState(""); + const [actEnv, setActEnv] = useState("local"); + const [actWindow, setActWindow] = useState("7d"); + const [actReason, setActReason] = useState(""); + const [actOut, setActOut] = useState(null); + const [actErr, setActErr] = useState(null); + const [busy, setBusy] = useState(null); + + const runAction = async (path: "/v1/promote" | "/v1/rollback") => { + setActErr(null); + setActOut(null); + const reason = actReason.trim(); + if (!reason) { + setActErr("Reason is required."); + return; + } + const label = path === "/v1/promote" ? "promote" : "rollback"; + if (!window.confirm(`Confirm ${label} for this release?`)) { + return; + } + setBusy(path === "/v1/promote" ? "promote" : "rollback"); + try { + const data = await fetchJson(path, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + release_id: actRelease.trim(), + environment: actEnv.trim(), + window: actWindow.trim(), + reason, + actor: "react-ui", + }), + }); + setActOut(JSON.stringify(data, null, 2)); + notifyTimelineMutated(); + } catch (e) { + setActErr(String(e)); + } finally { + setBusy(null); + } + }; + + return ( + <> +
+
+

Promote & rollback

+

+ Mutations use the same HTTP contract as the CLI. When{" "} + FLIGHTDECK_LOCAL_API_TOKEN is set, include it via{" "} + VITE_FLIGHTDECK_LOCAL_API_TOKEN for local dev. +

+
+
+ +
+
+ + + + +
+
+ + +
+
+ + {actErr ?

{actErr}

: null} + {actOut ? : null} + + ); +} diff --git a/web/src/pages/DiffPage.tsx b/web/src/pages/DiffPage.tsx new file mode 100644 index 0000000..5fe4678 --- /dev/null +++ b/web/src/pages/DiffPage.tsx @@ -0,0 +1,220 @@ +import { useState } from "react"; +import { fetchJson } from "../api"; +import { Badge } from "../components/Badge"; +import { JsonPanel } from "../components/JsonPanel"; + +type DiffJson = Record; + +function isRecord(v: unknown): v is Record { + return typeof v === "object" && v !== null; +} + +function pickPolicy(data: DiffJson): { passed: boolean; reasons: string[] } | null { + const p = data.policy; + if (!isRecord(p)) return null; + const passed = p.passed; + const reasons = p.reasons; + return { + passed: passed === true, + reasons: Array.isArray(reasons) ? reasons.filter((x): x is string => typeof x === "string") : [], + }; +} + +function Metric({ + label, + baseline, + candidate, + delta, + suffix = "", +}: { + label: string; + baseline: string; + candidate: string; + delta?: string; + suffix?: string; +}) { + return ( +
+
{label}
+
+ + B {baseline} + {suffix} + + + → + + + C {candidate} + {suffix} + +
+ {delta ?
{delta}
: null} +
+ ); +} + +export function DiffPage() { + const [diffBaseline, setDiffBaseline] = useState(""); + const [diffCandidate, setDiffCandidate] = useState(""); + const [diffWindow, setDiffWindow] = useState("7d"); + const [diffEnv, setDiffEnv] = useState("local"); + const [diffOut, setDiffOut] = useState(null); + const [diffErr, setDiffErr] = useState(null); + const [busy, setBusy] = useState(false); + + const runDiff = async () => { + setDiffErr(null); + setDiffOut(null); + setBusy(true); + try { + const body = { + baseline_release_id: diffBaseline.trim(), + candidate_release_id: diffCandidate.trim(), + window: diffWindow.trim(), + environment: diffEnv.trim() || null, + }; + const data = await fetchJson("/v1/diff", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(body), + }); + setDiffOut(data); + } catch (e) { + setDiffErr(String(e)); + } finally { + setBusy(false); + } + }; + + const m = diffOut?.metrics; + const s = diffOut?.samples; + const metrics = isRecord(m) ? m : null; + const samples = isRecord(s) ? s : null; + const policy = diffOut ? pickPolicy(diffOut) : null; + + const num = (v: unknown) => (typeof v === "number" && Number.isFinite(v) ? String(v) : "—"); + const pct = (v: unknown) => + typeof v === "number" && Number.isFinite(v) ? `${(v * 100).toFixed(2)}%` : "—"; + + return ( + <> +
+
+

Run diff

+

+ Compare baseline vs candidate over a window. Same contract as{" "} + flightdeck release diff. +

+
+
+ +
+
+ + + + +
+
+ +
+
+ + {diffErr ?

{diffErr}

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

Summary

+ {policy ? ( +
+ Policy:{" "} + {policy.passed ? "PASS" : "FAIL"} +
+ ) : null} +
+ {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} + {metrics ? ( +
+ = 0 ? "+" : ""}${(metrics.delta_cost_per_run_pct * 100).toFixed(2)}% vs baseline)` + : "" + }` + : undefined + } + /> + + +
+ ) : null} +
+ + + ) : null} + + ); +} diff --git a/web/src/pages/OverviewPage.tsx b/web/src/pages/OverviewPage.tsx new file mode 100644 index 0000000..581bd20 --- /dev/null +++ b/web/src/pages/OverviewPage.tsx @@ -0,0 +1,209 @@ +import { useCallback, useEffect, useId, useState, type ReactNode } from "react"; +import type { ActionRow, PromotedRow, ReleaseRow, TimelinePayload } from "../api"; +import { loadTimeline } from "../api"; +import { useTimelineRefresh } from "../context/TimelineRefreshContext"; +import { Badge } from "../components/Badge"; +import { JsonPanel } from "../components/JsonPanel"; + +function shortId(id: string, keepStart = 10, keepEnd = 6) { + if (id.length <= keepStart + keepEnd + 1) return id; + return `${id.slice(0, keepStart)}…${id.slice(-keepEnd)}`; +} + +function TableShell({ + title, + description, + children, +}: { + title: string; + description?: string; + children: ReactNode; +}) { + const hid = useId(); + return ( +
+
+

+ {title} +

+ {description ?

{description}

: null} +
+
{children}
+
+ ); +} + +export function OverviewPage() { + const { generation } = useTimelineRefresh(); + const [data, setData] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + const refresh = useCallback(async () => { + setLoading(true); + setError(null); + try { + setData(await loadTimeline()); + } catch (e) { + setError(String(e)); + setData(null); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + void refresh(); + }, [generation, refresh]); + + const raw = + data === null + ? error ?? "" + : JSON.stringify( + { releases: data.releases, promoted: data.promoted, actions: data.actions }, + null, + 2, + ); + + return ( + <> +
+
+

Overview

+

Registered releases, promotion pointers, and recent ledger actions.

+
+ +
+ + {error && !loading ?

{error}

: null} + {loading ?

Loading…

: null} + + {data ? ( + <> + + + + + + + + + + + + + + {data.releases.length === 0 ? ( + + + + ) : ( + data.releases.map((r: ReleaseRow) => ( + + + + + + + + + )) + )} + +
Release IDAgentVersionEnvironmentChecksumCreated
+ No releases yet. +
+ + {shortId(r.release_id)} + + {r.agent_id}{r.version}{r.environment} + + {shortId(r.checksum, 8, 6)} + + {new Date(r.created_at).toLocaleString()}
+
+ + + + + + + + + + + + {data.promoted.length === 0 ? ( + + + + ) : ( + data.promoted.map((p: PromotedRow) => ( + + + + + + )) + )} + +
AgentEnvironmentActive release
+ No promotion pointers yet. +
{p.agent_id}{p.environment} + + {shortId(p.release_id)} + +
+
+ + + + + + + + + + + + + + + {data.actions.length === 0 ? ( + + + + ) : ( + data.actions.map((a: ActionRow) => ( + + + + + + + + + )) + )} + +
WhenActionPolicyReleaseEnvironmentReason
+ No actions yet. +
{new Date(a.created_at).toLocaleString()}{a.action} + + {a.policy_passed ? "PASS" : "FAIL"} + + + + {shortId(a.release_id)} + + {a.environment}{a.reason}
+
+ + + + ) : null} + + ); +}