From 5be20638edda46814482595312fe144db2062703 Mon Sep 17 00:00:00 2001 From: zendaya Date: Sat, 2 May 2026 23:52:22 +0200 Subject: [PATCH 1/2] feat: enhance run events API with session_id and span_id filters, and add export functionality - Introduced `session_id` and `span_id` query parameters for `GET /v1/runs` and `GET /v1/runs/export`, allowing users to filter run events based on these identifiers. - Updated the CLI commands `flightdeck runs list` and `flightdeck runs export` to support the new filters, enhancing the forensics capabilities of the platform. - Added a new endpoint `GET /v1/runs/export` to provide an NDJSON stream of filtered run events, improving data export options for users. - Enhanced documentation to reflect the new features and updated examples for clarity. - Updated tests to ensure the correct functionality of the new filters and export capabilities. This update significantly improves the ability to analyze and export run events, making it easier for users to work with specific data sets. --- CHANGELOG.md | 12 +- README.md | 2 +- RELEASE_NOTES.md | 6 +- ROADMAP.md | 24 ++-- docs/cli.md | 6 +- docs/http-api.md | 26 +++- docs/sdk.md | 8 +- docs/web-ui.md | 2 + examples/README.md | 4 +- examples/fleet/README.md | 10 +- examples/fleet/workspace-staging.example.yaml | 2 +- src/flightdeck/cli/main.py | 22 +++- src/flightdeck/models.py | 2 +- src/flightdeck/operations.py | 16 ++- src/flightdeck/sdk/client.py | 119 ++++++++++++++++++ src/flightdeck/server/routes/read.py | 60 +++++++++ src/flightdeck/storage.py | 8 ++ tests/test_spine.py | 38 +++++- web/e2e/smoke.spec.ts | 4 +- web/src/App.tsx | 2 + web/src/api.ts | 84 +++++++++++++ web/src/components/AppShell.tsx | 3 + 22 files changed, 421 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d858f32..09e80e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to FlightDeck will be documented in this file. This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0**, documented CLI behavior (**[README.md](https://github.com/flightdeckdev/flightdeck/blob/main/README.md)** on the canonical **`main`** branch), committed **`schemas/v1/`**, and **`POST /v1/events`** payloads with **`api_version` `v1`** are treated as stable public contracts unless a release notes a semver-major bump. +## Unreleased + +### Added + +- **`GET /v1/runs/export`** — NDJSON stream of the same filtered slice as **`GET /v1/runs`** (optional response headers when truncated). +- **`session_id`** / **`span_id`** query filters on **`GET /v1/runs`**, matching CLI/SDK, and **`offset`** pagination on run listings (with **`runs list`** / **`runs export`**). +- **Web Runs** page — query **`GET /v1/runs`** from the bundled UI. + ## 1.1.2 - 2026-05-03 ### Added @@ -21,7 +29,7 @@ This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0** - **`GET /v1/workspace`:** read-only JSON for operators and the web UI — **`promotion_requires_approval`**, **`pricing_catalog_configured`**, **`server_version`** (normative schema + Python SDK helper). - **Web Actions:** Promote flow uses workspace flags — direct **`POST /v1/promote`** when approval is off; **request → list pending → confirm** when **`promotion_requires_approval`** is on, with clearer errors. -- **Docs:** README / **release-artifact** / **examples** / **web-ui** / **http-api** / **sdk** updates for Phase 1 remainder; optional **`docs/pricing-catalog.md`**; **`examples/ci/promote_with_approval.sh`** and CI README **GitHub Actions** pattern for approval-gated promote. +- **Docs:** README / **release-artifact** / **examples** / **web-ui** / **http-api** / **sdk** updates for the **v1.1.x** remainder; optional **`docs/pricing-catalog.md`**; **`examples/ci/promote_with_approval.sh`** and CI README **GitHub Actions** pattern for approval-gated promote. ### Changed @@ -50,7 +58,7 @@ This project follows [Semantic Versioning](https://semver.org/). From **v1.0.0** - **CLI `flightdeck doctor --backup PATH`:** SQLite online backup of the workspace database to **`PATH`** (parent directories created; file overwritten if present), then the usual doctor checks. - **Examples:** **[examples/integration/emit_sample_events.node.mjs](examples/integration/emit_sample_events.node.mjs)** — **`POST /v1/events`** sample using built-in **`fetch`** (Node 18+); **[examples/integration/README.md](examples/integration/README.md)** adds **`curl`** + **`jq`** example. - **Docs:** **[examples/deploy/README.md](examples/deploy/README.md)** — Compose **`/health`** healthcheck and **`doctor --backup`** / cron scheduling notes. -- **Roadmap:** **Phase 0** declared **closed**; **catalog-level** multi-provider pricing normalization called out under **Phase 1** build items. +- **Roadmap:** **Phase 0** declared **closed**; **catalog-level** multi-provider pricing normalization called out under **mid-term productization** build items. - **Tests:** **`test_doctor_backup_writes_valid_sqlite`** in **`tests/test_cli.py`**. ### Changed diff --git a/README.md b/README.md index a01c8dc..74c0a69 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Small prompt or model changes can silently move **cost**, **latency**, and **err FlightDeck is **local-first** and ships as a Python CLI backed by SQLite. **v1.0.0** froze **SemVer-stable public contracts** for the documented CLI, committed **`schemas/v1/`**, -and **`POST /v1/events`** with **`api_version` `v1`**. **v1.1.x** adds Phase 1 slices (optional pricing catalog on diffs, +and **`POST /v1/events`** with **`api_version` `v1`**. **v1.1.x** adds catalog-aware diffs, approval flows, and forensics slices (optional pricing catalog on diffs, promotion request/confirm, read-only runs listing, **`GET /v1/workspace`** for UI and automation, Helm/fleet examples) without breaking those v1.0 shapes. See **[RELEASE_NOTES.md](RELEASE_NOTES.md)** and **[CHANGELOG.md](CHANGELOG.md)**. The product scope is still intentionally narrow (release governance, not a hosted agent platform). diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 79da562..65e08d4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,7 +4,7 @@ High-level notes for **shipping FlightDeck**. Detailed history: **[CHANGELOG.md] Narrative docs (including the CLI reference) are maintained on **[github.com/flightdeckdev/flightdeck](https://github.com/flightdeckdev/flightdeck)** `main`; this file and **`schemas/`** ship in minimal clones. -## v1.1.2 — Forensics filters, JSONL export, Phase 1 closure slice +## v1.1.2 — Forensics filters, JSONL export, productization closure slice Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): optional **`trace_id`** filter on **`GET /v1/runs`**, **`flightdeck runs list --trace-id`**, and SDK **`list_runs(trace_id=…)`** (exact match on **`RunEvent.request.trace_id`**); **`flightdeck runs export`** writes the same filtered slice as JSONL (stdout or **`-o`**, **`--limit`** up to **500**, stderr warning when truncated). **Stable contracts:** additive HTTP query param and CLI command only. @@ -12,13 +12,13 @@ Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): optional **`trace_id`** fi Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): **`GET /v1/workspace`** exposes non-secret workspace flags (**`promotion_requires_approval`**, **`pricing_catalog_configured`**, **`server_version`**) for scripting and the **Actions** page; web **Promote** follows the two-step request/confirm path when approval is required; expanded operator docs and **`examples/ci/promote_with_approval.sh`** + README workflow snippet. **Stable contracts:** additive HTTP and JSON Schema only. -## v1.1.0 — Phase 1 first slice (catalog, approval, runs, Helm, fleet) +## v1.1.0 — Catalog, approval, runs, Helm, fleet (first v1.1 slice) Minor release (see **[CHANGELOG.md](CHANGELOG.md)**): optional **`pricing_catalog_path`** + **`PricingCatalog`** YAML for cross-vendor comparable **`pricing.catalog`** lines on diffs; **`pricing.hints`** for multi-version and model-name diagnostics; **`promotion_requires_approval`** with **`POST /v1/promote/request`** / **`POST /v1/promote/confirm`** / **`GET /v1/promotion-requests`** and matching CLI; **`GET /v1/runs`** and **`flightdeck runs list`**; SQLite migration **v4** (`promotion_requests`); reference **Helm** chart and **fleet** docs under **`examples/`**. **Stable contracts:** additive HTTP and CLI surfaces; existing **`v1`** event and release payloads unchanged. ## v1.0.6 — Phase 0 closure (backup, cross-language emitters, roadmap) -Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): **`flightdeck doctor --backup PATH`** performs a SQLite online backup of the workspace DB; **[examples/integration/](examples/integration/README.md)** gains **`curl`** and a **Node** **`emit_sample_events.node.mjs`** path for **`POST /v1/events`**; **[examples/deploy/README.md](examples/deploy/README.md)** documents the Compose **`/health`** healthcheck and backup scheduling. **ROADMAP:** **Phase 0** is **closed**; **catalog-level** multi-provider pricing normalization is an explicit **Phase 1** build item. **Stable contracts:** additive CLI flag and HTTP field **`pricing.warnings`** (from **v1.0.5**) remain backward-compatible. +Patch release (see **[CHANGELOG.md](CHANGELOG.md)**): **`flightdeck doctor --backup PATH`** performs a SQLite online backup of the workspace DB; **[examples/integration/](examples/integration/README.md)** gains **`curl`** and a **Node** **`emit_sample_events.node.mjs`** path for **`POST /v1/events`**; **[examples/deploy/README.md](examples/deploy/README.md)** documents the Compose **`/health`** healthcheck and backup scheduling. **ROADMAP:** **Phase 0** is **closed**; **catalog-level** multi-provider pricing normalization is an explicit **mid-term** build item. **Stable contracts:** additive CLI flag and HTTP field **`pricing.warnings`** (from **v1.0.5**) remain backward-compatible. ## v1.0.5 — Diff JSON output, pricing warnings, metrics in Overview diff --git a/ROADMAP.md b/ROADMAP.md index ac37800..2f98be7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -14,7 +14,7 @@ This roadmap is meant to be clear from **what is already shipped** to **near-ter - **Economic + operational governance:** immutable pricing imports, trusted `release diff`, policy-gated `promote` and `rollback`. - **Audit trail:** promotion/rollback history with stable sequencing (`audit_seq`) and integrity checks via `doctor`. - **Evidence ingestion:** `runs ingest` from JSONL/JSON arrays plus stable `POST /v1/events` contracts (`schemas/v1/`); **`GET /v1/runs`**, **`runs list`**, optional **`trace_id`** filter, and **`runs export`** (JSONL) for operator forensics. -- **Local API + UI:** `flightdeck serve` routes and web UI (Overview with ledger metrics, Diff, Promote) in `src/flightdeck/server/static/`. +- **Local API + UI:** `flightdeck serve` routes and web UI (Overview with ledger metrics, Diff, Runs, Promote) in `src/flightdeck/server/static/`. - **SDK and tooling:** Python sync/async clients with retries/batching and `flightdeck-quickstart-verify`. --- @@ -23,13 +23,13 @@ This roadmap is meant to be clear from **what is already shipped** to **near-ter Further work after **v1.1.2** — **OTLP-oriented** telemetry, **replay-style web** forensics, deeper **catalog lifecycle** governance, and **cross-workspace / fleet** product surfaces stay on **Phase 2 / mid-term** (see gaps table below). Track **[CHANGELOG.md](CHANGELOG.md)**. -**v1.1.2** (patch, shipped): **`trace_id`** filter on **`GET /v1/runs`**, **`flightdeck runs list --trace-id`**, and SDK **`list_runs`**, plus **`flightdeck runs export`** (JSONL, stderr warning when truncated); **[examples/README.md](examples/README.md)** adds a **Phase 1 readiness checklist**; **Phase 1 status** (this document) records closure of the scoped productization tranche. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. +**v1.1.2** (patch, shipped): **`trace_id`** filter on **`GET /v1/runs`**, **`flightdeck runs list --trace-id`**, and SDK **`list_runs`**, plus **`flightdeck runs export`** (JSONL, stderr warning when truncated); **[examples/README.md](examples/README.md)** adds a **readiness checklist**; **productization tranche status** (this document) records closure of the scoped local-first slice. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. **v1.1.1** (patch, shipped): **`GET /v1/workspace`** (read-only flags + **`server_version`**); web **Actions** page uses it for **direct promote** vs **request / pending list / confirm**; operator docs refresh (**README**, **release-artifact**, **examples**, **web-ui**, **http-api**, **sdk**, **`docs/pricing-catalog.md`**, **SECURITY**); **`examples/ci/promote_with_approval.sh`** and **`github-actions/promote-approval-twostep.yml`**; CI runs a second Playwright pass with **`FD_E2E_FORCE_APPROVAL`**. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. -**v1.1.0** (minor, shipped): Phase 1 first slice — **`pricing_catalog_path`** + **`pricing.catalog`** / **`pricing.hints`** on diffs; **`promotion_requires_approval`** + promote **request/confirm** (HTTP + CLI) + **`GET /v1/promotion-requests`**; **`GET /v1/runs`** / **`runs list`**; **Helm** reference chart; **`examples/fleet/`**; SQLite migration **v4**. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. +**v1.1.0** (minor, shipped): **`pricing_catalog_path`** + **`pricing.catalog`** / **`pricing.hints`** on diffs; **`promotion_requires_approval`** + promote **request/confirm** (HTTP + CLI) + **`GET /v1/promotion-requests`**; **`GET /v1/runs`** / **`runs list`**; **Helm** reference chart; **`examples/fleet/`**; SQLite migration **v4**. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. -**v1.0.6** (patch, shipped): Phase 0 closure — **`flightdeck release diff --output json`** (same shape as **`POST /v1/diff`**); **`pricing.warnings`** when a release model has no row in its pricing table (CLI **`WARNING:`** lines + web Diff); **Overview** ledger metrics card (**`GET /v1/metrics`**); **`curl`** + **Node** samples under **[examples/integration/](examples/integration/README.md)**; **`flightdeck doctor --backup PATH`** (SQLite online backup); **[examples/deploy/](examples/deploy/README.md)** documents Compose **`/health`** healthcheck and backup scheduling. **Phase 0** is declared **closed**; **catalog-level** multi-provider normalization moves to **Phase 1**. See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. No breaking changes to stable CLI, HTTP, or **`api_version` `v1`** contracts. +**v1.0.6** (patch, shipped): Phase 0 closure — **`flightdeck release diff --output json`** (same shape as **`POST /v1/diff`**); **`pricing.warnings`** when a release model has no row in its pricing table (CLI **`WARNING:`** lines + web Diff); **Overview** ledger metrics card (**`GET /v1/metrics`**); **`curl`** + **Node** samples under **[examples/integration/](examples/integration/README.md)**; **`flightdeck doctor --backup PATH`** (SQLite online backup); **[examples/deploy/](examples/deploy/README.md)** documents Compose **`/health`** healthcheck and backup scheduling. **Phase 0** is declared **closed**; **catalog-level** multi-provider normalization moves to **mid-term productization** (the **v1.1.x** track). See **[CHANGELOG.md](CHANGELOG.md)** and **[RELEASE_NOTES.md](RELEASE_NOTES.md)**. No breaking changes to stable CLI, HTTP, or **`api_version` `v1`** contracts. --- @@ -79,7 +79,7 @@ Shipped on **`main`**: **Phase 0 is closed** as of **v1.0.6** for the local-first wedge (immutable releases, evidence ingest, diff + policy gate, promote/rollback, audit, CI/deploy/integration references, metrics, diagnostics, and operator backup ergonomics). -**Carried forward to Phase 1** (see gaps table): **catalog-level** multi-provider pricing normalization (single comparable unit across vendors), deeper **fleet** ergonomics, and **OTLP-oriented** telemetry — not blocking further patch releases on the Phase 0 spine. +**Carried forward** (see gaps table): **catalog-level** multi-provider pricing normalization (single comparable unit across vendors), deeper **fleet** ergonomics, and **OTLP-oriented** telemetry — not blocking further patch releases on the Phase 0 spine. ### Phase-0 success signals @@ -90,19 +90,19 @@ Shipped on **`main`**: --- -## Phase 1: Productization (mid term, roughly quarters) +## Mid term: Productization (v1.1.x tranche, roughly quarters) Goal: move from solid local tooling to repeatable production usage patterns. -**v1.1.0** ships the first tranche: catalog + hints on diffs, approval-gated promote (HTTP + CLI), read-only runs listing, Helm + fleet reference docs, and migration **v4**. **v1.1.1** closes the **web + discovery** gap for approval (**`GET /v1/workspace`**, Actions UX) and refreshes team docs / CI examples. **v1.1.2** adds **CLI/HTTP forensics** filters and **JSONL export**; see **Phase 1 status** below. +**v1.1.0** ships the first tranche: catalog + hints on diffs, approval-gated promote (HTTP + CLI), read-only runs listing, Helm + fleet reference docs, and migration **v4**. **v1.1.1** closes the **web + discovery** gap for approval (**`GET /v1/workspace`**, Actions UX) and refreshes team docs / CI examples. **v1.1.2** adds **CLI/HTTP forensics** filters and **JSONL export**; see **tranche status** below. -### Phase 1 progress (post–v1.1.0 / v1.1.1) +### Productization progress (post–v1.1.0 / v1.1.1) - **Approval workflow:** **v1.1.0** added HTTP + CLI + **`GET /v1/promotion-requests`**. **v1.1.1** adds **`GET /v1/workspace`** and a first-class **web** path (request → pending table → confirm) keyed off `promotion_requires_approval`, plus **`examples/ci/promote_with_approval.sh`** and a **`workflow_dispatch`** sample workflow. - **Operator narrative:** README / **examples** index / **web-ui** / **release-artifact** / **http-api** / **sdk** / optional **`docs/pricing-catalog.md`** describe catalog fields, runs listing, and the two promote modes. - **Forensics (v1.1.2):** **`trace_id`** filter and **`runs export`** (JSONL). **Still open** for replay-style **web** views and richer export semantics if needed; **OTLP** and **catalog lifecycle** depth remain mid-term (gaps table). -### Build in this phase +### Build targets - Human-in-the-loop approval workflow on top of policy gates (without requiring a hosted control plane). - **Catalog-level multi-provider pricing normalization** — single comparable tariff unit across vendors; additive to today's per-provider **`pricing import`** tables and **`pricing.prices`** / **`pricing.warnings`** diagnostics. @@ -111,15 +111,15 @@ Goal: move from solid local tooling to repeatable production usage patterns. - 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 +### 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 1 status +### Productization tranche status -**Phase 1 is closed** as of **v1.1.2** for the **scoped local-first productization tranche** shipped across **v1.1.0–v1.1.2**: catalog-aware diffs + hints, approval-gated promote (HTTP + CLI + web), read-only runs forensics (**`GET /v1/runs`**, **`runs list`**, **`trace_id`**, **`runs export`**), reference **Helm** / **Compose** / **fleet** examples, SQLite migration **v4**, **`GET /v1/workspace`**, and a discoverability **checklist** for the Phase-1 **readiness signals** in **[examples/README.md](examples/README.md)**. +**The scoped v1.1.0–v1.1.2 tranche is closed** as of **v1.1.2**: catalog-aware diffs + hints, approval-gated promote (HTTP + CLI + web), read-only runs forensics (**`GET /v1/runs`**, **`runs list`**, **`trace_id`**, **`runs export`**), reference **Helm** / **Compose** / **fleet** examples, SQLite migration **v4**, **`GET /v1/workspace`**, and a discoverability **checklist** for the **readiness signals** in **[examples/README.md](examples/README.md)**. **Carried forward to Phase 2 / mid-term** (explicitly not claimed by this closure): **OTLP-oriented** correlated telemetry; **cross-workspace / fleet** governance products; **replay-style** forensics UI beyond CLI/HTTP listing; **enterprise-grade** identity beyond documented bearer + loopback patterns; deeper **catalog lifecycle** governance (for example version skew hints). diff --git a/docs/cli.md b/docs/cli.md index 6baaa89..6365039 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -453,17 +453,17 @@ Subgroup for ingesting, listing, and exporting run events. Print ingested events for a release (newest first), truncated to `--limit`. ```bash -flightdeck runs list RELEASE_ID --window WINDOW [--env ENV] [--tenant …] [--task …] [--trace-id ID] [--limit N] [--output json] +flightdeck runs list RELEASE_ID --window WINDOW [--env ENV] [--tenant …] [--task …] [--trace-id ID] [--session-id ID] [--span-id ID] [--offset N] [--limit N] [--output json] ``` -`--trace-id` filters to events whose ingested `request.trace_id` equals the given string (exact match), same as the `trace_id` query parameter on `GET /v1/runs`. +`--trace-id`, `--session-id`, and `--span-id` filter to exact matches on ingested `request.*` fields (same query names as `GET /v1/runs`). **`--offset`** skips that many newest-matching events before applying **`--limit`**. ### `flightdeck runs export` Write the same filtered slice as `runs list` (newest first) as **JSONL** — one `RunEvent` JSON object per line. Default **`--limit`** is **500** (maximum **500**). If more events match the window and filters, only the first **`--limit`** lines are written and a **`WARNING:`** line is printed to **stderr** with `exported` / `matching` counts. ```bash -flightdeck runs export RELEASE_ID --window WINDOW [-o export.jsonl] [--env ENV] [--tenant …] [--task …] [--trace-id ID] [--limit N] +flightdeck runs export RELEASE_ID --window WINDOW [-o export.jsonl] [--env ENV] [--tenant …] [--task …] [--trace-id ID] [--session-id ID] [--span-id ID] [--offset N] [--limit N] ``` With **`-o` / `--output`**, writes UTF-8 JSONL to that path; without it, writes to **stdout** (suitable for pipes). diff --git a/docs/http-api.md b/docs/http-api.md index 8754fb6..12a06e4 100644 --- a/docs/http-api.md +++ b/docs/http-api.md @@ -22,7 +22,7 @@ Two access tiers: | Route | No token configured | `FLIGHTDECK_LOCAL_API_TOKEN` set | |-------|--------------------|---------------------------------| | `GET /health` | open | open | -| `GET /v1/*` (reads, including `GET /v1/workspace`, `GET /v1/metrics`, `GET /v1/runs`, `GET /v1/promotion-requests`) | open | open | +| `GET /v1/*` (reads, including `GET /v1/workspace`, `GET /v1/metrics`, `GET /v1/runs`, `GET /v1/runs/export`, `GET /v1/promotion-requests`) | open | open | | `POST /v1/events` | open† | open (no Bearer required) | | `POST /v1/diff` | open | open | | `POST /v1/promote` | loopback only | `Authorization: Bearer ` required | @@ -201,7 +201,7 @@ doctor` checks that the sequence has no gaps. ## `GET /v1/promotion-requests` -List promotion approval requests (Phase 1). Newest first. +List promotion approval requests. Newest first. **Query parameters** @@ -250,6 +250,9 @@ Read-only forensics: return a slice of ingested run events for one release (newe | `tenant_id` | string | — | Optional filter | | `task_id` | string | — | Optional filter | | `trace_id` | string | — | Optional filter: exact match on `RunEvent.request.trace_id` (ingested JSON path `request.trace_id`) | +| `session_id` | string | — | Optional filter: exact match on `request.session_id` | +| `span_id` | string | — | Optional filter: exact match on `request.span_id` | +| `offset` | integer | 0 | Skip this many newest-matching events before returning the page (0–500000) | | `limit` | integer | 100 | Max events returned (1–500) | **Response** @@ -259,7 +262,9 @@ Read-only forensics: return a slice of ingested run events for one release (newe "release_id": "rel_abc", "since": "2026-04-25T12:00:00+00:00", "until": "2026-05-02T12:00:00+00:00", - "filters": { "environment": "local", "tenant_id": null, "task_id": null, "trace_id": null }, + "filters": { "environment": "local", "tenant_id": null, "task_id": null, "trace_id": null, "session_id": null, "span_id": null }, + "offset": 0, + "limit": 10, "matched_total": 42, "returned": 10, "truncated": true, @@ -271,6 +276,21 @@ Each element of `events` is a `RunEvent` object (`schemas/v1/run_event.schema.js --- +## `GET /v1/runs/export` + +Same **query parameters** and filter semantics as **`GET /v1/runs`** (defaults: **`offset`** `0`, **`limit`** `500`). Response body is **NDJSON**: one JSON object per line, each a `RunEvent` (`schemas/v1/run_event.schema.json`). **`Content-Type`:** `application/x-ndjson`. + +Response headers (non-secret hints for clients): + +| Header | Meaning | +|--------|---------| +| `X-Flightdeck-Matched-Total` | Count of events matching filters in the window | +| `X-Flightdeck-Returned` | Lines in this response body | +| `X-Flightdeck-Offset` | `offset` query value used | +| `X-Flightdeck-Truncated` | `true` if more matching events exist after this page | + +--- + ## `POST /v1/events` Ingest `RunEvent` records (runtime evidence for diff and policy evaluation). diff --git a/docs/sdk.md b/docs/sdk.md index 3005526..ff301f1 100644 --- a/docs/sdk.md +++ b/docs/sdk.md @@ -177,9 +177,13 @@ as `post_promote`. `GET /v1/promotion-requests`. -### `list_runs(*, release_id, window, environment=None, tenant_id=None, task_id=None, trace_id=None, limit=100) -> dict` +### `list_runs(*, release_id, window, environment=None, tenant_id=None, task_id=None, trace_id=None, session_id=None, span_id=None, offset=0, limit=100) -> dict` -`GET /v1/runs` — read-only event slice for forensics. When `trace_id` is set, only events whose `request.trace_id` matches are returned. +`GET /v1/runs` — read-only event slice for forensics. Optional `trace_id`, `session_id`, and `span_id` filter on `request.*` (exact match). `offset` skips the newest matching events before returning up to `limit` rows. + +### `fetch_runs_export_ndjson(*, release_id, window, …) -> tuple[bytes, dict[str, str]]` + +`GET /v1/runs/export` — same filters as `list_runs`; returns the raw **NDJSON** body and selected `X-Flightdeck-*` response headers. ## Async usage diff --git a/docs/web-ui.md b/docs/web-ui.md index a5431f3..3de3f2a 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -18,6 +18,7 @@ The app uses **HashRouter** (`react-router-dom`) so all navigation stays within |-----------|-----------|-----------|-------| | `#/` | `OverviewPage` | `GET /v1/releases`, `GET /v1/promoted`, `GET /v1/actions`, `GET /v1/metrics` (parallel where applicable) | Ledger metrics card is read-only counters | | `#/diff` | `DiffPage` | `POST /v1/diff` | Renders `pricing.warnings`, optional **`pricing.catalog`** / **`pricing.hints`**, per-1k prices when present | +| `#/runs` | `RunsPage` | `GET /v1/releases` (for datalist), `GET /v1/runs`, `GET /v1/runs/export` | Forensics: filters, table, NDJSON download | | `#/actions` | `ActionsPage` | `GET /v1/workspace`, `GET /v1/promotion-requests` (when `promotion_requires_approval`), `POST /v1/promote` **or** `POST /v1/promote/request` + `POST /v1/promote/confirm`, `POST /v1/rollback` | Workspace strip shows server version + mode; see **ActionsPage** below | | `#/*` (any other) | — | Redirects to `#/` | | @@ -39,6 +40,7 @@ App (HashRouter) ├── SecurityStatusBar (below header, above main content) ├── OverviewPage (route: #/) ├── DiffPage (route: #/diff) + ├── RunsPage (route: #/runs) └── ActionsPage (route: #/actions; redirects → #/ when UI_READ_ONLY) ``` diff --git a/examples/README.md b/examples/README.md index 4f76ac1..30c89dc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -12,9 +12,9 @@ This folder holds **copy-pasteable** references for wiring FlightDeck into a rea 6. **Run the server** in a container or compose stack — see [deploy/](deploy/README.md). The bundled UI calls **`GET /v1/workspace`** to choose direct promote vs request/confirm. 7. **Triage runs** with **`flightdeck runs list`** / **`runs export`** or **`GET /v1/runs`**, and **observe** aggregate ledger size with **`GET /v1/metrics`** (JSON counters; read-only, same access tier as other `GET /v1/*` routes). -## Phase 1 readiness (quick checklist) +## Readiness checklist (quick pass) -Use this as a **discoverability** pass for the **[ROADMAP.md](../ROADMAP.md)** Phase-1 readiness signals (not a product guarantee): +Use this as a **discoverability** pass for the **[ROADMAP.md](../ROADMAP.md)** readiness signals (not a product guarantee): | Signal | Where to start | |--------|----------------| diff --git a/examples/fleet/README.md b/examples/fleet/README.md index 5ba3a37..b8f57fc 100644 --- a/examples/fleet/README.md +++ b/examples/fleet/README.md @@ -1,4 +1,4 @@ -# Multi-workspace operator patterns (Phase 1) +# Multi-workspace operator patterns FlightDeck is **local-first**: one `flightdeck.yaml` + SQLite database per working directory. “Fleet” ergonomics here means **repeatable layouts** and naming—not a hosted control plane. @@ -19,3 +19,11 @@ Set `promotion_requires_approval: true` in `flightdeck.yaml` to require `release ## Template See `workspace-staging.example.yaml` for a copy-paste starting point; copy to your repo root as `flightdeck.yaml` and edit paths. + +## Multi-workspace aggregation (read-model) + +Prefer **per-workspace** ledgers and **pull**-based joins in your own tooling: + +- **`flightdeck runs export`** / **`GET /v1/runs`** (and **`GET /v1/runs/export`** when using `flightdeck serve`) — JSONL or JSON per workspace. +- **`GET /v1/metrics`** — coarse counters per workspace. +- **CI** — one job per workspace directory (see [examples/ci/README.md](../ci/README.md)). diff --git a/examples/fleet/workspace-staging.example.yaml b/examples/fleet/workspace-staging.example.yaml index ceee19b..fb63e8f 100644 --- a/examples/fleet/workspace-staging.example.yaml +++ b/examples/fleet/workspace-staging.example.yaml @@ -6,6 +6,6 @@ diff: min_candidate_runs: 500 min_baseline_runs: 500 min_low_runs: 50 -# Optional Phase 1 fields (uncomment and adjust paths): +# Optional catalog / pricing fields (uncomment and adjust paths): # pricing_catalog_path: examples/pricing/catalog.sample.yaml # promotion_requires_approval: true diff --git a/src/flightdeck/cli/main.py b/src/flightdeck/cli/main.py index 5f8924e..9a3a3de 100644 --- a/src/flightdeck/cli/main.py +++ b/src/flightdeck/cli/main.py @@ -323,7 +323,10 @@ def runs() -> None: @click.option("--tenant", "tenant_id", default=None) @click.option("--task", "task_id", default=None) @click.option("--trace-id", "trace_id", default=None, help="Filter to events whose request.trace_id matches (exact).") -@click.option("--limit", default=100, show_default=True, type=int) +@click.option("--session-id", "session_id", default=None, help="Filter to request.session_id (exact).") +@click.option("--span-id", "span_id", default=None, help="Filter to request.span_id (exact).") +@click.option("--offset", default=0, show_default=True, type=click.IntRange(0, 500_000)) +@click.option("--limit", default=100, show_default=True, type=click.IntRange(1, 500)) @click.option( "--output", "output_format", @@ -338,6 +341,9 @@ def runs_list( tenant_id: str | None, task_id: str | None, trace_id: str | None, + session_id: str | None, + span_id: str | None, + offset: int, limit: int, output_format: str, ) -> None: @@ -355,6 +361,9 @@ def runs_list( tenant_id=tenant_id, task_id=task_id, trace_id=trace_id, + session_id=session_id, + span_id=span_id, + offset=offset, limit=limit, ) except OperationError as e: @@ -377,6 +386,9 @@ def runs_list( @click.option("--tenant", "tenant_id", default=None) @click.option("--task", "task_id", default=None) @click.option("--trace-id", "trace_id", default=None, help="Filter to events whose request.trace_id matches (exact).") +@click.option("--session-id", "session_id", default=None, help="Filter to request.session_id (exact).") +@click.option("--span-id", "span_id", default=None, help="Filter to request.span_id (exact).") +@click.option("--offset", default=0, show_default=True, type=click.IntRange(0, 500_000)) @click.option("--limit", default=500, show_default=True, type=click.IntRange(1, 500)) @click.option( "-o", @@ -393,6 +405,9 @@ def runs_export( tenant_id: str | None, task_id: str | None, trace_id: str | None, + session_id: str | None, + span_id: str | None, + offset: int, limit: int, output_path: Path | None, ) -> None: @@ -410,6 +425,9 @@ def runs_export( tenant_id=tenant_id, task_id=task_id, trace_id=trace_id, + session_id=session_id, + span_id=span_id, + offset=offset, limit=limit, ) except OperationError as e: @@ -425,7 +443,7 @@ def runs_export( if payload["truncated"]: click.echo( f"WARNING: exported {payload['returned']} of {payload['matched_total']} matching events " - f"(cap --limit {limit}).", + f"(offset={payload['offset']}, --limit {limit}).", err=True, ) diff --git a/src/flightdeck/models.py b/src/flightdeck/models.py index 385420c..ef27555 100644 --- a/src/flightdeck/models.py +++ b/src/flightdeck/models.py @@ -262,7 +262,7 @@ class PromotionRecord(BaseModel): class PromotionRequestRecord(BaseModel): - """Pending human approval before ``commit_promotion`` (Phase 1).""" + """Pending human approval before ``commit_promotion``.""" request_id: str status: Literal["pending", "completed", "cancelled"] = "pending" diff --git a/src/flightdeck/operations.py b/src/flightdeck/operations.py index d8edd08..f1986d7 100644 --- a/src/flightdeck/operations.py +++ b/src/flightdeck/operations.py @@ -721,6 +721,9 @@ def query_run_events_page( tenant_id: str | None, task_id: str | None, trace_id: str | None = None, + session_id: str | None = None, + span_id: str | None = None, + offset: int = 0, limit: int, ) -> dict[str, object]: """Read-only slice of run events for forensics (newest-first truncation).""" @@ -728,6 +731,9 @@ def query_run_events_page( raise OperationError(f"Unknown release: {release_id}") env = environment or cfg.default_environment tid = (trace_id or "").strip() or None + sid = (session_id or "").strip() or None + spid = (span_id or "").strip() or None + off = max(0, int(offset)) try: delta = parse_window(window) except ValueError as e: @@ -742,10 +748,12 @@ def query_run_events_page( task_id=task_id, environment=env, trace_id=tid, + session_id=sid, + span_id=spid, ) events_sorted = sorted(events, key=lambda e: e.timestamp, reverse=True) lim = max(1, min(500, limit)) - page = events_sorted[:lim] + page = events_sorted[off : off + lim] return { "release_id": release_id, "since": since.isoformat(), @@ -755,10 +763,14 @@ def query_run_events_page( "tenant_id": tenant_id, "task_id": task_id, "trace_id": tid, + "session_id": sid, + "span_id": spid, }, + "offset": off, + "limit": lim, "matched_total": len(events), "returned": len(page), - "truncated": len(events) > len(page), + "truncated": off + len(page) < len(events), "events": [e.model_dump(mode="json") for e in page], } diff --git a/src/flightdeck/sdk/client.py b/src/flightdeck/sdk/client.py index d45a4ac..d883ac1 100644 --- a/src/flightdeck/sdk/client.py +++ b/src/flightdeck/sdk/client.py @@ -185,12 +185,16 @@ def list_runs( tenant_id: str | None = None, task_id: str | None = None, trace_id: str | None = None, + session_id: str | None = None, + span_id: str | None = None, + offset: int = 0, limit: int = 100, ) -> dict[str, Any]: params: dict[str, str | int] = { "release_id": release_id, "window": window, "limit": limit, + "offset": offset, } if environment is not None: params["environment"] = environment @@ -200,6 +204,10 @@ def list_runs( params["task_id"] = task_id if trace_id is not None: params["trace_id"] = trace_id + if session_id is not None: + params["session_id"] = session_id + if span_id is not None: + params["span_id"] = span_id resp = self._request_with_retry( "GET", "/v1/runs", @@ -209,6 +217,58 @@ def list_runs( resp.raise_for_status() return resp.json() + def fetch_runs_export_ndjson( + self, + *, + release_id: str, + window: str, + environment: str | None = None, + tenant_id: str | None = None, + task_id: str | None = None, + trace_id: str | None = None, + session_id: str | None = None, + span_id: str | None = None, + offset: int = 0, + limit: int = 500, + ) -> tuple[bytes, dict[str, str]]: + """GET /v1/runs/export — returns raw NDJSON body and selected response headers.""" + params: dict[str, str | int] = { + "release_id": release_id, + "window": window, + "limit": limit, + "offset": offset, + } + if environment is not None: + params["environment"] = environment + if tenant_id is not None: + params["tenant_id"] = tenant_id + if task_id is not None: + params["task_id"] = task_id + if trace_id is not None: + params["trace_id"] = trace_id + if session_id is not None: + params["session_id"] = session_id + if span_id is not None: + params["span_id"] = span_id + resp = self._request_with_retry( + "GET", + "/v1/runs/export", + params=params, + headers=self._auth_headers() or None, + ) + resp.raise_for_status() + hdrs = { + k: resp.headers[k] + for k in ( + "X-Flightdeck-Matched-Total", + "X-Flightdeck-Returned", + "X-Flightdeck-Offset", + "X-Flightdeck-Truncated", + ) + if k in resp.headers + } + return (resp.content, hdrs) + def post_rollback( self, *, @@ -442,12 +502,16 @@ async def list_runs( tenant_id: str | None = None, task_id: str | None = None, trace_id: str | None = None, + session_id: str | None = None, + span_id: str | None = None, + offset: int = 0, limit: int = 100, ) -> dict[str, Any]: params: dict[str, str | int] = { "release_id": release_id, "window": window, "limit": limit, + "offset": offset, } if environment is not None: params["environment"] = environment @@ -457,6 +521,10 @@ async def list_runs( params["task_id"] = task_id if trace_id is not None: params["trace_id"] = trace_id + if session_id is not None: + params["session_id"] = session_id + if span_id is not None: + params["span_id"] = span_id resp = await self._request_with_retry( "GET", "/v1/runs", @@ -466,6 +534,57 @@ async def list_runs( resp.raise_for_status() return resp.json() + async def fetch_runs_export_ndjson( + self, + *, + release_id: str, + window: str, + environment: str | None = None, + tenant_id: str | None = None, + task_id: str | None = None, + trace_id: str | None = None, + session_id: str | None = None, + span_id: str | None = None, + offset: int = 0, + limit: int = 500, + ) -> tuple[bytes, dict[str, str]]: + params: dict[str, str | int] = { + "release_id": release_id, + "window": window, + "limit": limit, + "offset": offset, + } + if environment is not None: + params["environment"] = environment + if tenant_id is not None: + params["tenant_id"] = tenant_id + if task_id is not None: + params["task_id"] = task_id + if trace_id is not None: + params["trace_id"] = trace_id + if session_id is not None: + params["session_id"] = session_id + if span_id is not None: + params["span_id"] = span_id + resp = await self._request_with_retry( + "GET", + "/v1/runs/export", + params=params, + headers=self._auth_headers() or None, + ) + resp.raise_for_status() + hdrs = { + k: resp.headers[k] + for k in ( + "X-Flightdeck-Matched-Total", + "X-Flightdeck-Returned", + "X-Flightdeck-Offset", + "X-Flightdeck-Truncated", + ) + if k in resp.headers + } + return (resp.content, hdrs) + async def post_rollback( self, *, diff --git a/src/flightdeck/server/routes/read.py b/src/flightdeck/server/routes/read.py index 10aed63..8913fcd 100644 --- a/src/flightdeck/server/routes/read.py +++ b/src/flightdeck/server/routes/read.py @@ -1,6 +1,9 @@ from __future__ import annotations +import json + from fastapi import APIRouter, HTTPException, Query, Request +from fastapi.responses import StreamingResponse from flightdeck import __version__ as flightdeck_version from flightdeck.models import PromotionRequestRecord, WorkspacePublic @@ -81,6 +84,9 @@ def get_runs( tenant_id: str | None = Query(default=None), task_id: str | None = Query(default=None), trace_id: str | None = Query(default=None), + session_id: str | None = Query(default=None), + span_id: str | None = Query(default=None), + offset: int = Query(default=0, ge=0, le=500_000), limit: int = Query(default=100, ge=1, le=500), ) -> dict[str, object]: cfg, storage = ensure_app_state(request) @@ -94,7 +100,61 @@ def get_runs( tenant_id=tenant_id, task_id=task_id, trace_id=trace_id, + session_id=session_id, + span_id=span_id, + offset=offset, limit=limit, ) except OperationError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc + + +@router.get("/v1/runs/export") +def get_runs_export( + request: Request, + release_id: str = Query(..., min_length=1), + window: str = Query(..., min_length=1), + environment: str | None = Query(default=None), + tenant_id: str | None = Query(default=None), + task_id: str | None = Query(default=None), + trace_id: str | None = Query(default=None), + session_id: str | None = Query(default=None), + span_id: str | None = Query(default=None), + offset: int = Query(default=0, ge=0, le=500_000), + limit: int = Query(default=500, ge=1, le=500), +) -> StreamingResponse: + """NDJSON stream of the same filtered slice as ``GET /v1/runs`` (read tier).""" + cfg, storage = ensure_app_state(request) + try: + payload = query_run_events_page( + cfg=cfg, + storage=storage, + release_id=release_id, + window=window, + environment=environment, + tenant_id=tenant_id, + task_id=task_id, + trace_id=trace_id, + session_id=session_id, + span_id=span_id, + offset=offset, + limit=limit, + ) + except OperationError as exc: + raise HTTPException(status_code=400, detail=str(exc)) from exc + + def body_iter(): + for ev in payload["events"]: + yield json.dumps(ev, sort_keys=True) + "\n" + + headers: dict[str, str] = { + "X-Flightdeck-Matched-Total": str(payload["matched_total"]), + "X-Flightdeck-Returned": str(payload["returned"]), + "X-Flightdeck-Offset": str(payload["offset"]), + "X-Flightdeck-Truncated": "true" if payload["truncated"] else "false", + } + return StreamingResponse( + body_iter(), + media_type="application/x-ndjson", + headers=headers, + ) diff --git a/src/flightdeck/storage.py b/src/flightdeck/storage.py index 7317498..9d10dca 100644 --- a/src/flightdeck/storage.py +++ b/src/flightdeck/storage.py @@ -802,6 +802,8 @@ def query_runs( task_id: str | None = None, environment: str | None = None, trace_id: str | None = None, + session_id: str | None = None, + span_id: str | None = None, ) -> list[RunEvent]: clauses: list[str] = ["release_id = ?", "timestamp >= ?", "timestamp < ?"] params: list[Any] = [release_id, since.isoformat(), until.isoformat()] @@ -818,6 +820,12 @@ def query_runs( if trace_id: clauses.append("json_extract(event_json, '$.request.trace_id') = ?") params.append(trace_id) + if session_id: + clauses.append("json_extract(event_json, '$.request.session_id') = ?") + params.append(session_id) + if span_id: + clauses.append("json_extract(event_json, '$.request.span_id') = ?") + params.append(span_id) where = " AND ".join(clauses) diff --git a/tests/test_spine.py b/tests/test_spine.py index 4dea443..7da7d1a 100644 --- a/tests/test_spine.py +++ b/tests/test_spine.py @@ -1,7 +1,7 @@ from __future__ import annotations import json -from datetime import datetime, timezone +from datetime import datetime, timedelta, timezone from pathlib import Path import yaml @@ -76,16 +76,26 @@ def write_events( model: str = "gpt-4.1-mini", trace_id: str | None = None, trace_ids: list[str | None] | None = None, + session_id: str | None = None, + session_ids: list[str | None] | None = None, + span_id: str | None = None, + span_ids: list[str | None] | None = None, + stagger_ts: bool = False, ) -> Path: if trace_ids is not None and len(trace_ids) != n: raise ValueError("trace_ids length must equal n") + if session_ids is not None and len(session_ids) != n: + raise ValueError("session_ids length must equal n") + if span_ids is not None and len(span_ids) != n: + raise ValueError("span_ids length must equal n") p = tmp_path / f"events_{release_id}.jsonl" lines = [] for i in range(n): + ts_i = ts + timedelta(seconds=i) if stagger_ts else ts e = { "api_version": "v1", "type": "run_end", - "timestamp": ts.isoformat(), + "timestamp": ts_i.isoformat(), "workspace_id": "ws_local", "agent_id": agent_id, "release_id": release_id, @@ -112,8 +122,30 @@ def write_events( tid = trace_id else: tid = None + + if session_ids is not None: + sid_i = session_ids[i] + elif session_id is not None: + sid_i = session_id + else: + sid_i = None + + if span_ids is not None: + sp_i = span_ids[i] + elif span_id is not None: + sp_i = span_id + else: + sp_i = None + + req: dict[str, str] = {} if tid is not None: - e["request"] = {"trace_id": tid} + req["trace_id"] = tid + if sid_i is not None: + req["session_id"] = sid_i + if sp_i is not None: + req["span_id"] = sp_i + if req: + e["request"] = req lines.append(json.dumps(e)) p.write_text("\n".join(lines) + "\n", encoding="utf-8") return p diff --git a/web/e2e/smoke.spec.ts b/web/e2e/smoke.spec.ts index 9a4d5a9..455cbc3 100644 --- a/web/e2e/smoke.spec.ts +++ b/web/e2e/smoke.spec.ts @@ -11,9 +11,11 @@ test("home loads FlightDeck shell and overview tables", async ({ page }) => { await expect(page.getByText("No releases yet.")).toBeVisible(); }); -test("hash routes reach diff and promote pages", async ({ page }) => { +test("hash routes reach diff, runs, and promote pages", async ({ page }) => { await page.goto("/#/diff"); await expect(page.getByRole("heading", { name: "Run diff", level: 2 })).toBeVisible(); + await page.goto("/#/runs"); + await expect(page.getByRole("heading", { name: "Run events", level: 2 })).toBeVisible(); await page.goto("/#/actions"); await expect(page.getByRole("heading", { name: "Promote & rollback", level: 2 })).toBeVisible(); }); diff --git a/web/src/App.tsx b/web/src/App.tsx index db28abe..4f18d6c 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -3,6 +3,7 @@ import { AppShell } from "./components/AppShell"; import { ActionsPage } from "./pages/ActionsPage"; import { DiffPage } from "./pages/DiffPage"; import { OverviewPage } from "./pages/OverviewPage"; +import { RunsPage } from "./pages/RunsPage"; import { UI_READ_ONLY } from "./uiConfig"; export function App() { @@ -12,6 +13,7 @@ export function App() { }> } /> } /> + } /> : } /> } /> diff --git a/web/src/api.ts b/web/src/api.ts index 7bc99cd..eca5fd8 100644 --- a/web/src/api.ts +++ b/web/src/api.ts @@ -188,6 +188,90 @@ export async function fetchMetrics(): Promise { return fetchJson("/v1/metrics"); } +/** Response shape for `GET /v1/runs` (see `query_run_events_page` on the server). */ +export type RunsListPayload = { + release_id: string; + since: string; + until: string; + filters: Record; + offset: number; + limit: number; + matched_total: number; + returned: number; + truncated: boolean; + events: Record[]; +}; + +export async function fetchRuns(params: { + release_id: string; + window: string; + environment?: string; + tenant_id?: string; + task_id?: string; + trace_id?: string; + session_id?: string; + span_id?: string; + offset?: number; + limit?: number; +}): Promise { + const sp = new URLSearchParams(); + sp.set("release_id", params.release_id); + sp.set("window", params.window); + if (params.environment != null && params.environment !== "") sp.set("environment", params.environment); + if (params.tenant_id != null && params.tenant_id !== "") sp.set("tenant_id", params.tenant_id); + if (params.task_id != null && params.task_id !== "") sp.set("task_id", params.task_id); + if (params.trace_id != null && params.trace_id !== "") sp.set("trace_id", params.trace_id); + if (params.session_id != null && params.session_id !== "") sp.set("session_id", params.session_id); + if (params.span_id != null && params.span_id !== "") sp.set("span_id", params.span_id); + if (params.offset != null) sp.set("offset", String(params.offset)); + sp.set("limit", String(params.limit ?? 100)); + return fetchJson(`/v1/runs?${sp.toString()}`); +} + +/** `GET /v1/runs/export` — NDJSON body (read tier; same query params as `fetchRuns`). */ +export async function fetchRunsExportBlob(params: { + release_id: string; + window: string; + environment?: string; + tenant_id?: string; + task_id?: string; + trace_id?: string; + session_id?: string; + span_id?: string; + offset?: number; + limit?: number; +}): Promise<{ blob: Blob }> { + const sp = new URLSearchParams(); + sp.set("release_id", params.release_id); + sp.set("window", params.window); + if (params.environment != null && params.environment !== "") sp.set("environment", params.environment); + if (params.tenant_id != null && params.tenant_id !== "") sp.set("tenant_id", params.tenant_id); + if (params.task_id != null && params.task_id !== "") sp.set("task_id", params.task_id); + if (params.trace_id != null && params.trace_id !== "") sp.set("trace_id", params.trace_id); + if (params.session_id != null && params.session_id !== "") sp.set("session_id", params.session_id); + if (params.span_id != null && params.span_id !== "") sp.set("span_id", params.span_id); + if (params.offset != null) sp.set("offset", String(params.offset)); + sp.set("limit", String(params.limit ?? 500)); + const headers = new 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(`/v1/runs/export?${sp.toString()}`, { headers }); + const text = await res.text(); + if (!res.ok) { + let msg = `HTTP ${res.status}`; + try { + const j = JSON.parse(text) as { detail?: unknown }; + if (typeof j.detail === "string") msg = j.detail; + } catch { + if (text.trim()) msg = text.slice(0, 500); + } + throw new Error(msg); + } + return { blob: new Blob([text], { type: "application/x-ndjson" }) }; +} + export async function loadTimeline(): Promise { const [releases, promoted, actions] = await Promise.all([ fetchJson<{ releases: ReleaseRow[] }>("/v1/releases"), diff --git a/web/src/components/AppShell.tsx b/web/src/components/AppShell.tsx index f6084f8..9e7666e 100644 --- a/web/src/components/AppShell.tsx +++ b/web/src/components/AppShell.tsx @@ -22,6 +22,9 @@ export function AppShell() { Diff + + Runs + {UI_READ_ONLY ? null : ( Promote From cc7465e9b656bc55e0391461cc3644bfe6e7c3a2 Mon Sep 17 00:00:00 2001 From: zendaya Date: Sat, 2 May 2026 23:52:32 +0200 Subject: [PATCH 2/2] Remove obsolete JavaScript asset file `index-DjScmcgK.js` from static assets directory. --- .../server/static/assets/index-7m5ayZhE.js | 11 + .../server/static/assets/index-DjScmcgK.js | 11 - src/flightdeck/server/static/index.html | 2 +- ...se1_features.py => test_operator_slice.py} | 83 +++++- web/src/pages/RunsPage.tsx | 272 ++++++++++++++++++ 5 files changed, 366 insertions(+), 13 deletions(-) create mode 100644 src/flightdeck/server/static/assets/index-7m5ayZhE.js delete mode 100644 src/flightdeck/server/static/assets/index-DjScmcgK.js rename tests/{test_phase1_features.py => test_operator_slice.py} (80%) create mode 100644 web/src/pages/RunsPage.tsx diff --git a/src/flightdeck/server/static/assets/index-7m5ayZhE.js b/src/flightdeck/server/static/assets/index-7m5ayZhE.js new file mode 100644 index 0000000..7e0b7eb --- /dev/null +++ b/src/flightdeck/server/static/assets/index-7m5ayZhE.js @@ -0,0 +1,11 @@ +(function(){const s=document.createElement("link").relList;if(s&&s.supports&&s.supports("modulepreload"))return;for(const m of document.querySelectorAll('link[rel="modulepreload"]'))f(m);new MutationObserver(m=>{for(const h of m)if(h.type==="childList")for(const b of h.addedNodes)b.tagName==="LINK"&&b.rel==="modulepreload"&&f(b)}).observe(document,{childList:!0,subtree:!0});function d(m){const h={};return m.integrity&&(h.integrity=m.integrity),m.referrerPolicy&&(h.referrerPolicy=m.referrerPolicy),m.crossOrigin==="use-credentials"?h.credentials="include":m.crossOrigin==="anonymous"?h.credentials="omit":h.credentials="same-origin",h}function f(m){if(m.ep)return;m.ep=!0;const h=d(m);fetch(m.href,h)}})();var qf={exports:{}},Un={};var cm;function gy(){if(cm)return Un;cm=1;var i=Symbol.for("react.transitional.element"),s=Symbol.for("react.fragment");function d(f,m,h){var b=null;if(h!==void 0&&(b=""+h),m.key!==void 0&&(b=""+m.key),"key"in m){h={};for(var z in m)z!=="key"&&(h[z]=m[z])}else h=m;return m=h.ref,{$$typeof:i,type:f,key:b,ref:m!==void 0?m:null,props:h}}return Un.Fragment=s,Un.jsx=d,Un.jsxs=d,Un}var fm;function Sy(){return fm||(fm=1,qf.exports=gy()),qf.exports}var r=Sy(),Bf={exports:{}},ee={};var sm;function by(){if(sm)return ee;sm=1;var i=Symbol.for("react.transitional.element"),s=Symbol.for("react.portal"),d=Symbol.for("react.fragment"),f=Symbol.for("react.strict_mode"),m=Symbol.for("react.profiler"),h=Symbol.for("react.consumer"),b=Symbol.for("react.context"),z=Symbol.for("react.forward_ref"),S=Symbol.for("react.suspense"),y=Symbol.for("react.memo"),C=Symbol.for("react.lazy"),T=Symbol.for("react.activity"),Y=Symbol.iterator;function H(p){return p===null||typeof p!="object"?null:(p=Y&&p[Y]||p["@@iterator"],typeof p=="function"?p:null)}var Q={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Z=Object.assign,q={};function V(p,A,B){this.props=p,this.context=A,this.refs=q,this.updater=B||Q}V.prototype.isReactComponent={},V.prototype.setState=function(p,A){if(typeof p!="object"&&typeof p!="function"&&p!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,p,A,"setState")},V.prototype.forceUpdate=function(p){this.updater.enqueueForceUpdate(this,p,"forceUpdate")};function W(){}W.prototype=V.prototype;function X(p,A,B){this.props=p,this.context=A,this.refs=q,this.updater=B||Q}var w=X.prototype=new W;w.constructor=X,Z(w,V.prototype),w.isPureReactComponent=!0;var ae=Array.isArray;function te(){}var G={H:null,A:null,T:null,S:null},ie=Object.prototype.hasOwnProperty;function de(p,A,B){var K=B.ref;return{$$typeof:i,type:p,key:A,ref:K!==void 0?K:null,props:B}}function qe(p,A){return de(p.type,A,p.props)}function Xe(p){return typeof p=="object"&&p!==null&&p.$$typeof===i}function Oe(p){var A={"=":"=0",":":"=2"};return"$"+p.replace(/[=:]/g,function(B){return A[B]})}var We=/\/+/g;function Fe(p,A){return typeof p=="object"&&p!==null&&p.key!=null?Oe(""+p.key):A.toString(36)}function je(p){switch(p.status){case"fulfilled":return p.value;case"rejected":throw p.reason;default:switch(typeof p.status=="string"?p.then(te,te):(p.status="pending",p.then(function(A){p.status==="pending"&&(p.status="fulfilled",p.value=A)},function(A){p.status==="pending"&&(p.status="rejected",p.reason=A)})),p.status){case"fulfilled":return p.value;case"rejected":throw p.reason}}throw p}function x(p,A,B,K,P){var ce=typeof p;(ce==="undefined"||ce==="boolean")&&(p=null);var ge=!1;if(p===null)ge=!0;else switch(ce){case"bigint":case"string":case"number":ge=!0;break;case"object":switch(p.$$typeof){case i:case s:ge=!0;break;case C:return ge=p._init,x(ge(p._payload),A,B,K,P)}}if(ge)return P=P(p),ge=K===""?"."+Fe(p,0):K,ae(P)?(B="",ge!=null&&(B=ge.replace(We,"$&/")+"/"),x(P,A,B,"",function(Ga){return Ga})):P!=null&&(Xe(P)&&(P=qe(P,B+(P.key==null||p&&p.key===P.key?"":(""+P.key).replace(We,"$&/")+"/")+ge)),A.push(P)),1;ge=0;var Pe=K===""?".":K+":";if(ae(p))for(var Me=0;Me>>1,he=x[ue];if(0>>1;uem(B,$))Km(P,B)?(x[ue]=P,x[K]=$,ue=K):(x[ue]=B,x[A]=$,ue=A);else if(Km(P,$))x[ue]=P,x[K]=$,ue=K;else break e}}return L}function m(x,L){var $=x.sortIndex-L.sortIndex;return $!==0?$:x.id-L.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var h=performance;i.unstable_now=function(){return h.now()}}else{var b=Date,z=b.now();i.unstable_now=function(){return b.now()-z}}var S=[],y=[],C=1,T=null,Y=3,H=!1,Q=!1,Z=!1,q=!1,V=typeof setTimeout=="function"?setTimeout:null,W=typeof clearTimeout=="function"?clearTimeout:null,X=typeof setImmediate<"u"?setImmediate:null;function w(x){for(var L=d(y);L!==null;){if(L.callback===null)f(y);else if(L.startTime<=x)f(y),L.sortIndex=L.expirationTime,s(S,L);else break;L=d(y)}}function ae(x){if(Z=!1,w(x),!Q)if(d(S)!==null)Q=!0,te||(te=!0,Oe());else{var L=d(y);L!==null&&je(ae,L.startTime-x)}}var te=!1,G=-1,ie=5,de=-1;function qe(){return q?!0:!(i.unstable_now()-dex&&qe());){var ue=T.callback;if(typeof ue=="function"){T.callback=null,Y=T.priorityLevel;var he=ue(T.expirationTime<=x);if(x=i.unstable_now(),typeof he=="function"){T.callback=he,w(x),L=!0;break t}T===d(S)&&f(S),w(x)}else f(S);T=d(S)}if(T!==null)L=!0;else{var p=d(y);p!==null&&je(ae,p.startTime-x),L=!1}}break e}finally{T=null,Y=$,H=!1}L=void 0}}finally{L?Oe():te=!1}}}var Oe;if(typeof X=="function")Oe=function(){X(Xe)};else if(typeof MessageChannel<"u"){var We=new MessageChannel,Fe=We.port2;We.port1.onmessage=Xe,Oe=function(){Fe.postMessage(null)}}else Oe=function(){V(Xe,0)};function je(x,L){G=V(function(){x(i.unstable_now())},L)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(x){x.callback=null},i.unstable_forceFrameRate=function(x){0>x||125ue?(x.sortIndex=$,s(y,x),d(S)===null&&x===d(y)&&(Z?(W(G),G=-1):Z=!0,je(ae,$-ue))):(x.sortIndex=he,s(S,x),Q||H||(Q=!0,te||(te=!0,Oe()))),x},i.unstable_shouldYield=qe,i.unstable_wrapCallback=function(x){var L=Y;return function(){var $=Y;Y=L;try{return x.apply(this,arguments)}finally{Y=$}}}})(Gf)),Gf}var dm;function Ey(){return dm||(dm=1,Yf.exports=_y()),Yf.exports}var wf={exports:{}},Ie={};var mm;function xy(){if(mm)return Ie;mm=1;var i=Wf();function s(S){var y="https://react.dev/errors/"+S;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(s){console.error(s)}}return i(),wf.exports=xy(),wf.exports}var vm;function jy(){if(vm)return Hn;vm=1;var i=Ey(),s=Wf(),d=Ny();function f(e){var t="https://react.dev/errors/"+e;if(1he||(e.current=ue[he],ue[he]=null,he--)}function B(e,t){he++,ue[he]=e.current,e.current=t}var K=p(null),P=p(null),ce=p(null),ge=p(null);function Pe(e,t){switch(B(ce,t),B(P,e),B(K,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Od(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Od(t),e=Cd(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}A(K),B(K,e)}function Me(){A(K),A(P),A(ce)}function Ga(e){e.memoizedState!==null&&B(ge,e);var t=K.current,l=Cd(t,e.type);t!==l&&(B(P,e),B(K,l))}function Qn(e){P.current===e&&(A(K),A(P)),ge.current===e&&(A(ge),On._currentValue=$)}var pi,us;function Cl(e){if(pi===void 0)try{throw Error()}catch(l){var t=l.stack.trim().match(/\n( *(at )?)/);pi=t&&t[1]||"",us=-1)":-1n||v[a]!==j[n]){var D=` +`+v[a].replace(" at new "," at ");return e.displayName&&D.includes("")&&(D=D.replace("",e.displayName)),D}while(1<=a&&0<=n);break}}}finally{gi=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?Cl(l):""}function km(e,t){switch(e.tag){case 26:case 27:case 5:return Cl(e.type);case 16:return Cl("Lazy");case 13:return e.child!==t&&t!==null?Cl("Suspense Fallback"):Cl("Suspense");case 19:return Cl("SuspenseList");case 0:case 15:return Si(e.type,!1);case 11:return Si(e.type.render,!1);case 1:return Si(e.type,!0);case 31:return Cl("Activity");default:return""}}function is(e){try{var t="",l=null;do t+=km(e,l),l=e,e=e.return;while(e);return t}catch(a){return` +Error generating stack: `+a.message+` +`+a.stack}}var bi=Object.prototype.hasOwnProperty,_i=i.unstable_scheduleCallback,Ei=i.unstable_cancelCallback,Wm=i.unstable_shouldYield,Fm=i.unstable_requestPaint,ft=i.unstable_now,Im=i.unstable_getCurrentPriorityLevel,cs=i.unstable_ImmediatePriority,fs=i.unstable_UserBlockingPriority,Zn=i.unstable_NormalPriority,Pm=i.unstable_LowPriority,ss=i.unstable_IdlePriority,eh=i.log,th=i.unstable_setDisableYieldValue,wa=null,st=null;function il(e){if(typeof eh=="function"&&th(e),st&&typeof st.setStrictMode=="function")try{st.setStrictMode(wa,e)}catch{}}var rt=Math.clz32?Math.clz32:nh,lh=Math.log,ah=Math.LN2;function nh(e){return e>>>=0,e===0?32:31-(lh(e)/ah|0)|0}var Vn=256,Kn=262144,Jn=4194304;function Dl(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function $n(e,t,l){var a=e.pendingLanes;if(a===0)return 0;var n=0,u=e.suspendedLanes,c=e.pingedLanes;e=e.warmLanes;var o=a&134217727;return o!==0?(a=o&~u,a!==0?n=Dl(a):(c&=o,c!==0?n=Dl(c):l||(l=o&~e,l!==0&&(n=Dl(l))))):(o=a&~u,o!==0?n=Dl(o):c!==0?n=Dl(c):l||(l=a&~e,l!==0&&(n=Dl(l)))),n===0?0:t!==0&&t!==n&&(t&u)===0&&(u=n&-n,l=t&-t,u>=l||u===32&&(l&4194048)!==0)?t:n}function Xa(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function uh(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function rs(){var e=Jn;return Jn<<=1,(Jn&62914560)===0&&(Jn=4194304),e}function xi(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function Qa(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function ih(e,t,l,a,n,u){var c=e.pendingLanes;e.pendingLanes=l,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=l,e.entangledLanes&=l,e.errorRecoveryDisabledLanes&=l,e.shellSuspendCounter=0;var o=e.entanglements,v=e.expirationTimes,j=e.hiddenUpdates;for(l=c&~l;0"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var dh=/[\n"\\]/g;function bt(e){return e.replace(dh,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function zi(e,t,l,a,n,u,c,o){e.name="",c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"?e.type=c:e.removeAttribute("type"),t!=null?c==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+St(t)):e.value!==""+St(t)&&(e.value=""+St(t)):c!=="submit"&&c!=="reset"||e.removeAttribute("value"),t!=null?Oi(e,c,St(t)):l!=null?Oi(e,c,St(l)):a!=null&&e.removeAttribute("value"),n==null&&u!=null&&(e.defaultChecked=!!u),n!=null&&(e.checked=n&&typeof n!="function"&&typeof n!="symbol"),o!=null&&typeof o!="function"&&typeof o!="symbol"&&typeof o!="boolean"?e.name=""+St(o):e.removeAttribute("name")}function xs(e,t,l,a,n,u,c,o){if(u!=null&&typeof u!="function"&&typeof u!="symbol"&&typeof u!="boolean"&&(e.type=u),t!=null||l!=null){if(!(u!=="submit"&&u!=="reset"||t!=null)){Ai(e);return}l=l!=null?""+St(l):"",t=t!=null?""+St(t):l,o||t===e.value||(e.value=t),e.defaultValue=t}a=a??n,a=typeof a!="function"&&typeof a!="symbol"&&!!a,e.checked=o?e.checked:!!a,e.defaultChecked=!!a,c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"&&(e.name=c),Ai(e)}function Oi(e,t,l){t==="number"&&Fn(e.ownerDocument)===e||e.defaultValue===""+l||(e.defaultValue=""+l)}function aa(e,t,l,a){if(e=e.options,t){t={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Hi=!1;if(Qt)try{var Ja={};Object.defineProperty(Ja,"passive",{get:function(){Hi=!0}}),window.addEventListener("test",Ja,Ja),window.removeEventListener("test",Ja,Ja)}catch{Hi=!1}var fl=null,qi=null,Pn=null;function Os(){if(Pn)return Pn;var e,t=qi,l=t.length,a,n="value"in fl?fl.value:fl.textContent,u=n.length;for(e=0;e=Wa),qs=" ",Bs=!1;function Ls(e,t){switch(e){case"keyup":return Gh.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Ys(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ca=!1;function Xh(e,t){switch(e){case"compositionend":return Ys(t);case"keypress":return t.which!==32?null:(Bs=!0,qs);case"textInput":return e=t.data,e===qs&&Bs?null:e;default:return null}}function Qh(e,t){if(ca)return e==="compositionend"||!wi&&Ls(e,t)?(e=Os(),Pn=qi=fl=null,ca=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:l,offset:t-e};e=a}e:{for(;l;){if(l.nextSibling){l=l.nextSibling;break e}l=l.parentNode}l=void 0}l=Js(l)}}function ks(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?ks(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Ws(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Fn(e.document);t instanceof e.HTMLIFrameElement;){try{var l=typeof t.contentWindow.location.href=="string"}catch{l=!1}if(l)e=t.contentWindow;else break;t=Fn(e.document)}return t}function Zi(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var Fh=Qt&&"documentMode"in document&&11>=document.documentMode,fa=null,Vi=null,en=null,Ki=!1;function Fs(e,t,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;Ki||fa==null||fa!==Fn(a)||(a=fa,"selectionStart"in a&&Zi(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),en&&Pa(en,a)||(en=a,a=Ku(Vi,"onSelect"),0>=c,n-=c,qt=1<<32-rt(t)+n|l<ne?(oe=k,k=null):oe=k.sibling;var ye=R(E,k,N[ne],M);if(ye===null){k===null&&(k=oe);break}e&&k&&ye.alternate===null&&t(E,k),g=u(ye,g,ne),ve===null?F=ye:ve.sibling=ye,ve=ye,k=oe}if(ne===N.length)return l(E,k),me&&Vt(E,ne),F;if(k===null){for(;nene?(oe=k,k=null):oe=k.sibling;var Ol=R(E,k,ye.value,M);if(Ol===null){k===null&&(k=oe);break}e&&k&&Ol.alternate===null&&t(E,k),g=u(Ol,g,ne),ve===null?F=Ol:ve.sibling=Ol,ve=Ol,k=oe}if(ye.done)return l(E,k),me&&Vt(E,ne),F;if(k===null){for(;!ye.done;ne++,ye=N.next())ye=U(E,ye.value,M),ye!==null&&(g=u(ye,g,ne),ve===null?F=ye:ve.sibling=ye,ve=ye);return me&&Vt(E,ne),F}for(k=a(k);!ye.done;ne++,ye=N.next())ye=O(k,E,ne,ye.value,M),ye!==null&&(e&&ye.alternate!==null&&k.delete(ye.key===null?ne:ye.key),g=u(ye,g,ne),ve===null?F=ye:ve.sibling=ye,ve=ye);return e&&k.forEach(function(py){return t(E,py)}),me&&Vt(E,ne),F}function xe(E,g,N,M){if(typeof N=="object"&&N!==null&&N.type===Z&&N.key===null&&(N=N.props.children),typeof N=="object"&&N!==null){switch(N.$$typeof){case H:e:{for(var F=N.key;g!==null;){if(g.key===F){if(F=N.type,F===Z){if(g.tag===7){l(E,g.sibling),M=n(g,N.props.children),M.return=E,E=M;break e}}else if(g.elementType===F||typeof F=="object"&&F!==null&&F.$$typeof===ie&&Ql(F)===g.type){l(E,g.sibling),M=n(g,N.props),cn(M,N),M.return=E,E=M;break e}l(E,g);break}else t(E,g);g=g.sibling}N.type===Z?(M=Ll(N.props.children,E.mode,M,N.key),M.return=E,E=M):(M=su(N.type,N.key,N.props,null,E.mode,M),cn(M,N),M.return=E,E=M)}return c(E);case Q:e:{for(F=N.key;g!==null;){if(g.key===F)if(g.tag===4&&g.stateNode.containerInfo===N.containerInfo&&g.stateNode.implementation===N.implementation){l(E,g.sibling),M=n(g,N.children||[]),M.return=E,E=M;break e}else{l(E,g);break}else t(E,g);g=g.sibling}M=Pi(N,E.mode,M),M.return=E,E=M}return c(E);case ie:return N=Ql(N),xe(E,g,N,M)}if(je(N))return J(E,g,N,M);if(Oe(N)){if(F=Oe(N),typeof F!="function")throw Error(f(150));return N=F.call(N),I(E,g,N,M)}if(typeof N.then=="function")return xe(E,g,yu(N),M);if(N.$$typeof===X)return xe(E,g,du(E,N),M);pu(E,N)}return typeof N=="string"&&N!==""||typeof N=="number"||typeof N=="bigint"?(N=""+N,g!==null&&g.tag===6?(l(E,g.sibling),M=n(g,N),M.return=E,E=M):(l(E,g),M=Ii(N,E.mode,M),M.return=E,E=M),c(E)):l(E,g)}return function(E,g,N,M){try{un=0;var F=xe(E,g,N,M);return Sa=null,F}catch(k){if(k===ga||k===hu)throw k;var ve=dt(29,k,null,E.mode);return ve.lanes=M,ve.return=E,ve}}}var Vl=br(!0),_r=br(!1),ml=!1;function oc(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function dc(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function hl(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function vl(e,t,l){var a=e.updateQueue;if(a===null)return null;if(a=a.shared,(pe&2)!==0){var n=a.pending;return n===null?t.next=t:(t.next=n.next,n.next=t),a.pending=t,t=fu(e),nr(e,null,l),t}return cu(e,a,t,l),fu(e)}function fn(e,t,l){if(t=t.updateQueue,t!==null&&(t=t.shared,(l&4194048)!==0)){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,ds(e,l)}}function mc(e,t){var l=e.updateQueue,a=e.alternate;if(a!==null&&(a=a.updateQueue,l===a)){var n=null,u=null;if(l=l.firstBaseUpdate,l!==null){do{var c={lane:l.lane,tag:l.tag,payload:l.payload,callback:null,next:null};u===null?n=u=c:u=u.next=c,l=l.next}while(l!==null);u===null?n=u=t:u=u.next=t}else n=u=t;l={baseState:a.baseState,firstBaseUpdate:n,lastBaseUpdate:u,shared:a.shared,callbacks:a.callbacks},e.updateQueue=l;return}e=l.lastBaseUpdate,e===null?l.firstBaseUpdate=t:e.next=t,l.lastBaseUpdate=t}var hc=!1;function sn(){if(hc){var e=pa;if(e!==null)throw e}}function rn(e,t,l,a){hc=!1;var n=e.updateQueue;ml=!1;var u=n.firstBaseUpdate,c=n.lastBaseUpdate,o=n.shared.pending;if(o!==null){n.shared.pending=null;var v=o,j=v.next;v.next=null,c===null?u=j:c.next=j,c=v;var D=e.alternate;D!==null&&(D=D.updateQueue,o=D.lastBaseUpdate,o!==c&&(o===null?D.firstBaseUpdate=j:o.next=j,D.lastBaseUpdate=v))}if(u!==null){var U=n.baseState;c=0,D=j=v=null,o=u;do{var R=o.lane&-536870913,O=R!==o.lane;if(O?(re&R)===R:(a&R)===R){R!==0&&R===ya&&(hc=!0),D!==null&&(D=D.next={lane:0,tag:o.tag,payload:o.payload,callback:null,next:null});e:{var J=e,I=o;R=t;var xe=l;switch(I.tag){case 1:if(J=I.payload,typeof J=="function"){U=J.call(xe,U,R);break e}U=J;break e;case 3:J.flags=J.flags&-65537|128;case 0:if(J=I.payload,R=typeof J=="function"?J.call(xe,U,R):J,R==null)break e;U=T({},U,R);break e;case 2:ml=!0}}R=o.callback,R!==null&&(e.flags|=64,O&&(e.flags|=8192),O=n.callbacks,O===null?n.callbacks=[R]:O.push(R))}else O={lane:R,tag:o.tag,payload:o.payload,callback:o.callback,next:null},D===null?(j=D=O,v=U):D=D.next=O,c|=R;if(o=o.next,o===null){if(o=n.shared.pending,o===null)break;O=o,o=O.next,O.next=null,n.lastBaseUpdate=O,n.shared.pending=null}}while(!0);D===null&&(v=U),n.baseState=v,n.firstBaseUpdate=j,n.lastBaseUpdate=D,u===null&&(n.shared.lanes=0),bl|=c,e.lanes=c,e.memoizedState=U}}function Er(e,t){if(typeof e!="function")throw Error(f(191,e));e.call(t)}function xr(e,t){var l=e.callbacks;if(l!==null)for(e.callbacks=null,e=0;eu?u:8;var c=x.T,o={};x.T=o,Mc(e,!1,t,l);try{var v=n(),j=x.S;if(j!==null&&j(o,v),v!==null&&typeof v=="object"&&typeof v.then=="function"){var D=iv(v,a);mn(e,t,D,pt(e))}else mn(e,t,a,pt(e))}catch(U){mn(e,t,{then:function(){},status:"rejected",reason:U},pt())}finally{L.p=u,c!==null&&o.types!==null&&(c.types=o.types),x.T=c}}function dv(){}function Cc(e,t,l,a){if(e.tag!==5)throw Error(f(476));var n=to(e).queue;eo(e,n,t,$,l===null?dv:function(){return lo(e),l(a)})}function to(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:$,baseState:$,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:kt,lastRenderedState:$},next:null};var l={};return t.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:kt,lastRenderedState:l},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function lo(e){var t=to(e);t.next===null&&(t=e.alternate.memoizedState),mn(e,t.next.queue,{},pt())}function Dc(){return Je(On)}function ao(){return He().memoizedState}function no(){return He().memoizedState}function mv(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var l=pt();e=hl(l);var a=vl(t,e,l);a!==null&&(ct(a,t,l),fn(a,t,l)),t={cache:cc()},e.payload=t;return}t=t.return}}function hv(e,t,l){var a=pt();l={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null},Ru(e)?io(t,l):(l=Wi(e,t,l,a),l!==null&&(ct(l,e,a),co(l,t,a)))}function uo(e,t,l){var a=pt();mn(e,t,l,a)}function mn(e,t,l,a){var n={lane:a,revertLane:0,gesture:null,action:l,hasEagerState:!1,eagerState:null,next:null};if(Ru(e))io(t,n);else{var u=e.alternate;if(e.lanes===0&&(u===null||u.lanes===0)&&(u=t.lastRenderedReducer,u!==null))try{var c=t.lastRenderedState,o=u(c,l);if(n.hasEagerState=!0,n.eagerState=o,ot(o,c))return cu(e,t,n,0),Ne===null&&iu(),!1}catch{}if(l=Wi(e,t,n,a),l!==null)return ct(l,e,a),co(l,t,a),!0}return!1}function Mc(e,t,l,a){if(a={lane:2,revertLane:df(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},Ru(e)){if(t)throw Error(f(479))}else t=Wi(e,l,a,2),t!==null&&ct(t,e,2)}function Ru(e){var t=e.alternate;return e===le||t!==null&&t===le}function io(e,t){_a=bu=!0;var l=e.pending;l===null?t.next=t:(t.next=l.next,l.next=t),e.pending=t}function co(e,t,l){if((l&4194048)!==0){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,ds(e,l)}}var hn={readContext:Je,use:xu,useCallback:Ce,useContext:Ce,useEffect:Ce,useImperativeHandle:Ce,useLayoutEffect:Ce,useInsertionEffect:Ce,useMemo:Ce,useReducer:Ce,useRef:Ce,useState:Ce,useDebugValue:Ce,useDeferredValue:Ce,useTransition:Ce,useSyncExternalStore:Ce,useId:Ce,useHostTransitionStatus:Ce,useFormState:Ce,useActionState:Ce,useOptimistic:Ce,useMemoCache:Ce,useCacheRefresh:Ce};hn.useEffectEvent=Ce;var fo={readContext:Je,use:xu,useCallback:function(e,t){return et().memoizedState=[e,t===void 0?null:t],e},useContext:Je,useEffect:Vr,useImperativeHandle:function(e,t,l){l=l!=null?l.concat([e]):null,ju(4194308,4,kr.bind(null,t,e),l)},useLayoutEffect:function(e,t){return ju(4194308,4,e,t)},useInsertionEffect:function(e,t){ju(4,2,e,t)},useMemo:function(e,t){var l=et();t=t===void 0?null:t;var a=e();if(Kl){il(!0);try{e()}finally{il(!1)}}return l.memoizedState=[a,t],a},useReducer:function(e,t,l){var a=et();if(l!==void 0){var n=l(t);if(Kl){il(!0);try{l(t)}finally{il(!1)}}}else n=t;return a.memoizedState=a.baseState=n,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},a.queue=e,e=e.dispatch=hv.bind(null,le,e),[a.memoizedState,e]},useRef:function(e){var t=et();return e={current:e},t.memoizedState=e},useState:function(e){e=Tc(e);var t=e.queue,l=uo.bind(null,le,t);return t.dispatch=l,[e.memoizedState,l]},useDebugValue:zc,useDeferredValue:function(e,t){var l=et();return Oc(l,e,t)},useTransition:function(){var e=Tc(!1);return e=eo.bind(null,le,e.queue,!0,!1),et().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,l){var a=le,n=et();if(me){if(l===void 0)throw Error(f(407));l=l()}else{if(l=t(),Ne===null)throw Error(f(349));(re&127)!==0||zr(a,t,l)}n.memoizedState=l;var u={value:l,getSnapshot:t};return n.queue=u,Vr(Cr.bind(null,a,u,e),[e]),a.flags|=2048,xa(9,{destroy:void 0},Or.bind(null,a,u,l,t),null),l},useId:function(){var e=et(),t=Ne.identifierPrefix;if(me){var l=Bt,a=qt;l=(a&~(1<<32-rt(a)-1)).toString(32)+l,t="_"+t+"R_"+l,l=_u++,0<\/script>",u=u.removeChild(u.firstChild);break;case"select":u=typeof a.is=="string"?c.createElement("select",{is:a.is}):c.createElement("select"),a.multiple?u.multiple=!0:a.size&&(u.size=a.size);break;default:u=typeof a.is=="string"?c.createElement(n,{is:a.is}):c.createElement(n)}}u[Ve]=t,u[tt]=a;e:for(c=t.child;c!==null;){if(c.tag===5||c.tag===6)u.appendChild(c.stateNode);else if(c.tag!==4&&c.tag!==27&&c.child!==null){c.child.return=c,c=c.child;continue}if(c===t)break e;for(;c.sibling===null;){if(c.return===null||c.return===t)break e;c=c.return}c.sibling.return=c.return,c=c.sibling}t.stateNode=u;e:switch(ke(u,n,a),n){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break e;case"img":a=!0;break e;default:a=!1}a&&Ft(t)}}return Re(t),Jc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,l),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==a&&Ft(t);else{if(typeof a!="string"&&t.stateNode===null)throw Error(f(166));if(e=ce.current,ha(t)){if(e=t.stateNode,l=t.memoizedProps,a=null,n=Ke,n!==null)switch(n.tag){case 27:case 5:a=n.memoizedProps}e[Ve]=t,e=!!(e.nodeValue===l||a!==null&&a.suppressHydrationWarning===!0||Ad(e.nodeValue,l)),e||ol(t,!0)}else e=Ju(e).createTextNode(a),e[Ve]=t,t.stateNode=e}return Re(t),null;case 31:if(l=t.memoizedState,e===null||e.memoizedState!==null){if(a=ha(t),l!==null){if(e===null){if(!a)throw Error(f(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(f(557));e[Ve]=t}else Yl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Re(t),e=!1}else l=ac(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=l),e=!0;if(!e)return t.flags&256?(ht(t),t):(ht(t),null);if((t.flags&128)!==0)throw Error(f(558))}return Re(t),null;case 13:if(a=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(n=ha(t),a!==null&&a.dehydrated!==null){if(e===null){if(!n)throw Error(f(318));if(n=t.memoizedState,n=n!==null?n.dehydrated:null,!n)throw Error(f(317));n[Ve]=t}else Yl(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;Re(t),n=!1}else n=ac(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),n=!0;if(!n)return t.flags&256?(ht(t),t):(ht(t),null)}return ht(t),(t.flags&128)!==0?(t.lanes=l,t):(l=a!==null,e=e!==null&&e.memoizedState!==null,l&&(a=t.child,n=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(n=a.alternate.memoizedState.cachePool.pool),u=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(u=a.memoizedState.cachePool.pool),u!==n&&(a.flags|=2048)),l!==e&&l&&(t.child.flags|=8192),Du(t,t.updateQueue),Re(t),null);case 4:return Me(),e===null&&yf(t.stateNode.containerInfo),Re(t),null;case 10:return Jt(t.type),Re(t),null;case 19:if(A(Ue),a=t.memoizedState,a===null)return Re(t),null;if(n=(t.flags&128)!==0,u=a.rendering,u===null)if(n)yn(a,!1);else{if(De!==0||e!==null&&(e.flags&128)!==0)for(e=t.child;e!==null;){if(u=Su(e),u!==null){for(t.flags|=128,yn(a,!1),e=u.updateQueue,t.updateQueue=e,Du(t,e),t.subtreeFlags=0,e=l,l=t.child;l!==null;)ur(l,e),l=l.sibling;return B(Ue,Ue.current&1|2),me&&Vt(t,a.treeForkCount),t.child}e=e.sibling}a.tail!==null&&ft()>Bu&&(t.flags|=128,n=!0,yn(a,!1),t.lanes=4194304)}else{if(!n)if(e=Su(u),e!==null){if(t.flags|=128,n=!0,e=e.updateQueue,t.updateQueue=e,Du(t,e),yn(a,!0),a.tail===null&&a.tailMode==="hidden"&&!u.alternate&&!me)return Re(t),null}else 2*ft()-a.renderingStartTime>Bu&&l!==536870912&&(t.flags|=128,n=!0,yn(a,!1),t.lanes=4194304);a.isBackwards?(u.sibling=t.child,t.child=u):(e=a.last,e!==null?e.sibling=u:t.child=u,a.last=u)}return a.tail!==null?(e=a.tail,a.rendering=e,a.tail=e.sibling,a.renderingStartTime=ft(),e.sibling=null,l=Ue.current,B(Ue,n?l&1|2:l&1),me&&Vt(t,a.treeForkCount),e):(Re(t),null);case 22:case 23:return ht(t),yc(),a=t.memoizedState!==null,e!==null?e.memoizedState!==null!==a&&(t.flags|=8192):a&&(t.flags|=8192),a?(l&536870912)!==0&&(t.flags&128)===0&&(Re(t),t.subtreeFlags&6&&(t.flags|=8192)):Re(t),l=t.updateQueue,l!==null&&Du(t,l.retryQueue),l=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(l=e.memoizedState.cachePool.pool),a=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(a=t.memoizedState.cachePool.pool),a!==l&&(t.flags|=2048),e!==null&&A(Xl),null;case 24:return l=null,e!==null&&(l=e.memoizedState.cache),t.memoizedState.cache!==l&&(t.flags|=2048),Jt(Be),Re(t),null;case 25:return null;case 30:return null}throw Error(f(156,t.tag))}function Sv(e,t){switch(tc(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Jt(Be),Me(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return Qn(t),null;case 31:if(t.memoizedState!==null){if(ht(t),t.alternate===null)throw Error(f(340));Yl()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(ht(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(f(340));Yl()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return A(Ue),null;case 4:return Me(),null;case 10:return Jt(t.type),null;case 22:case 23:return ht(t),yc(),e!==null&&A(Xl),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Jt(Be),null;case 25:return null;default:return null}}function Mo(e,t){switch(tc(t),t.tag){case 3:Jt(Be),Me();break;case 26:case 27:case 5:Qn(t);break;case 4:Me();break;case 31:t.memoizedState!==null&&ht(t);break;case 13:ht(t);break;case 19:A(Ue);break;case 10:Jt(t.type);break;case 22:case 23:ht(t),yc(),e!==null&&A(Xl);break;case 24:Jt(Be)}}function pn(e,t){try{var l=t.updateQueue,a=l!==null?l.lastEffect:null;if(a!==null){var n=a.next;l=n;do{if((l.tag&e)===e){a=void 0;var u=l.create,c=l.inst;a=u(),c.destroy=a}l=l.next}while(l!==n)}}catch(o){be(t,t.return,o)}}function gl(e,t,l){try{var a=t.updateQueue,n=a!==null?a.lastEffect:null;if(n!==null){var u=n.next;a=u;do{if((a.tag&e)===e){var c=a.inst,o=c.destroy;if(o!==void 0){c.destroy=void 0,n=t;var v=l,j=o;try{j()}catch(D){be(n,v,D)}}}a=a.next}while(a!==u)}}catch(D){be(t,t.return,D)}}function Uo(e){var t=e.updateQueue;if(t!==null){var l=e.stateNode;try{xr(t,l)}catch(a){be(e,e.return,a)}}}function Ho(e,t,l){l.props=Jl(e.type,e.memoizedProps),l.state=e.memoizedState;try{l.componentWillUnmount()}catch(a){be(e,t,a)}}function gn(e,t){try{var l=e.ref;if(l!==null){switch(e.tag){case 26:case 27:case 5:var a=e.stateNode;break;case 30:a=e.stateNode;break;default:a=e.stateNode}typeof l=="function"?e.refCleanup=l(a):l.current=a}}catch(n){be(e,t,n)}}function Lt(e,t){var l=e.ref,a=e.refCleanup;if(l!==null)if(typeof a=="function")try{a()}catch(n){be(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof l=="function")try{l(null)}catch(n){be(e,t,n)}else l.current=null}function qo(e){var t=e.type,l=e.memoizedProps,a=e.stateNode;try{e:switch(t){case"button":case"input":case"select":case"textarea":l.autoFocus&&a.focus();break e;case"img":l.src?a.src=l.src:l.srcSet&&(a.srcset=l.srcSet)}}catch(n){be(e,e.return,n)}}function $c(e,t,l){try{var a=e.stateNode;wv(a,e.type,l,t),a[tt]=t}catch(n){be(e,e.return,n)}}function Bo(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&jl(e.type)||e.tag===4}function kc(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Bo(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&jl(e.type)||e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Wc(e,t,l){var a=e.tag;if(a===5||a===6)e=e.stateNode,t?(l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l).insertBefore(e,t):(t=l.nodeType===9?l.body:l.nodeName==="HTML"?l.ownerDocument.body:l,t.appendChild(e),l=l._reactRootContainer,l!=null||t.onclick!==null||(t.onclick=Xt));else if(a!==4&&(a===27&&jl(e.type)&&(l=e.stateNode,t=null),e=e.child,e!==null))for(Wc(e,t,l),e=e.sibling;e!==null;)Wc(e,t,l),e=e.sibling}function Mu(e,t,l){var a=e.tag;if(a===5||a===6)e=e.stateNode,t?l.insertBefore(e,t):l.appendChild(e);else if(a!==4&&(a===27&&jl(e.type)&&(l=e.stateNode),e=e.child,e!==null))for(Mu(e,t,l),e=e.sibling;e!==null;)Mu(e,t,l),e=e.sibling}function Lo(e){var t=e.stateNode,l=e.memoizedProps;try{for(var a=e.type,n=t.attributes;n.length;)t.removeAttributeNode(n[0]);ke(t,a,l),t[Ve]=e,t[tt]=l}catch(u){be(e,e.return,u)}}var It=!1,Ge=!1,Fc=!1,Yo=typeof WeakSet=="function"?WeakSet:Set,Ze=null;function bv(e,t){if(e=e.containerInfo,Sf=ei,e=Ws(e),Zi(e)){if("selectionStart"in e)var l={start:e.selectionStart,end:e.selectionEnd};else e:{l=(l=e.ownerDocument)&&l.defaultView||window;var a=l.getSelection&&l.getSelection();if(a&&a.rangeCount!==0){l=a.anchorNode;var n=a.anchorOffset,u=a.focusNode;a=a.focusOffset;try{l.nodeType,u.nodeType}catch{l=null;break e}var c=0,o=-1,v=-1,j=0,D=0,U=e,R=null;t:for(;;){for(var O;U!==l||n!==0&&U.nodeType!==3||(o=c+n),U!==u||a!==0&&U.nodeType!==3||(v=c+a),U.nodeType===3&&(c+=U.nodeValue.length),(O=U.firstChild)!==null;)R=U,U=O;for(;;){if(U===e)break t;if(R===l&&++j===n&&(o=c),R===u&&++D===a&&(v=c),(O=U.nextSibling)!==null)break;U=R,R=U.parentNode}U=O}l=o===-1||v===-1?null:{start:o,end:v}}else l=null}l=l||{start:0,end:0}}else l=null;for(bf={focusedElem:e,selectionRange:l},ei=!1,Ze=t;Ze!==null;)if(t=Ze,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,Ze=e;else for(;Ze!==null;){switch(t=Ze,u=t.alternate,e=t.flags,t.tag){case 0:if((e&4)!==0&&(e=t.updateQueue,e=e!==null?e.events:null,e!==null))for(l=0;l title"))),ke(u,a,l),u[Ve]=e,Qe(u),a=u;break e;case"link":var c=Vd("link","href",n).get(a+(l.href||""));if(c){for(var o=0;oxe&&(c=xe,xe=I,I=c);var E=$s(o,I),g=$s(o,xe);if(E&&g&&(O.rangeCount!==1||O.anchorNode!==E.node||O.anchorOffset!==E.offset||O.focusNode!==g.node||O.focusOffset!==g.offset)){var N=U.createRange();N.setStart(E.node,E.offset),O.removeAllRanges(),I>xe?(O.addRange(N),O.extend(g.node,g.offset)):(N.setEnd(g.node,g.offset),O.addRange(N))}}}}for(U=[],O=o;O=O.parentNode;)O.nodeType===1&&U.push({element:O,left:O.scrollLeft,top:O.scrollTop});for(typeof o.focus=="function"&&o.focus(),o=0;ol?32:l,x.T=null,l=nf,nf=null;var u=El,c=al;if(we=0,Aa=El=null,al=0,(pe&6)!==0)throw Error(f(331));var o=pe;if(pe|=4,Wo(u.current),Jo(u,u.current,c,l),pe=o,Nn(0,!1),st&&typeof st.onPostCommitFiberRoot=="function")try{st.onPostCommitFiberRoot(wa,u)}catch{}return!0}finally{L.p=n,x.T=a,hd(e,t)}}function yd(e,t,l){t=Et(l,t),t=Bc(e.stateNode,t,2),e=vl(e,t,2),e!==null&&(Qa(e,2),Yt(e))}function be(e,t,l){if(e.tag===3)yd(e,e,l);else for(;t!==null;){if(t.tag===3){yd(t,e,l);break}else if(t.tag===1){var a=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(_l===null||!_l.has(a))){e=Et(l,e),l=po(2),a=vl(t,l,2),a!==null&&(go(l,a,t,e),Qa(a,2),Yt(a));break}}t=t.return}}function sf(e,t,l){var a=e.pingCache;if(a===null){a=e.pingCache=new xv;var n=new Set;a.set(t,n)}else n=a.get(t),n===void 0&&(n=new Set,a.set(t,n));n.has(l)||(ef=!0,n.add(l),e=Av.bind(null,e,t,l),t.then(e,e))}function Av(e,t,l){var a=e.pingCache;a!==null&&a.delete(t),e.pingedLanes|=e.suspendedLanes&l,e.warmLanes&=~l,Ne===e&&(re&l)===l&&(De===4||De===3&&(re&62914560)===re&&300>ft()-qu?(pe&2)===0&&za(e,0):tf|=l,Ra===re&&(Ra=0)),Yt(e)}function pd(e,t){t===0&&(t=rs()),e=Bl(e,t),e!==null&&(Qa(e,t),Yt(e))}function zv(e){var t=e.memoizedState,l=0;t!==null&&(l=t.retryLane),pd(e,l)}function Ov(e,t){var l=0;switch(e.tag){case 31:case 13:var a=e.stateNode,n=e.memoizedState;n!==null&&(l=n.retryLane);break;case 19:a=e.stateNode;break;case 22:a=e.stateNode._retryCache;break;default:throw Error(f(314))}a!==null&&a.delete(t),pd(e,l)}function Cv(e,t){return _i(e,t)}var Qu=null,Ca=null,rf=!1,Zu=!1,of=!1,Nl=0;function Yt(e){e!==Ca&&e.next===null&&(Ca===null?Qu=Ca=e:Ca=Ca.next=e),Zu=!0,rf||(rf=!0,Mv())}function Nn(e,t){if(!of&&Zu){of=!0;do for(var l=!1,a=Qu;a!==null;){if(e!==0){var n=a.pendingLanes;if(n===0)var u=0;else{var c=a.suspendedLanes,o=a.pingedLanes;u=(1<<31-rt(42|e)+1)-1,u&=n&~(c&~o),u=u&201326741?u&201326741|1:u?u|2:0}u!==0&&(l=!0,_d(a,u))}else u=re,u=$n(a,a===Ne?u:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(u&3)===0||Xa(a,u)||(l=!0,_d(a,u));a=a.next}while(l);of=!1}}function Dv(){gd()}function gd(){Zu=rf=!1;var e=0;Nl!==0&&Qv()&&(e=Nl);for(var t=ft(),l=null,a=Qu;a!==null;){var n=a.next,u=Sd(a,t);u===0?(a.next=null,l===null?Qu=n:l.next=n,n===null&&(Ca=l)):(l=a,(e!==0||(u&3)!==0)&&(Zu=!0)),a=n}we!==0&&we!==5||Nn(e),Nl!==0&&(Nl=0)}function Sd(e,t){for(var l=e.suspendedLanes,a=e.pingedLanes,n=e.expirationTimes,u=e.pendingLanes&-62914561;0o)break;var D=v.transferSize,U=v.initiatorType;D&&zd(U)&&(v=v.responseEnd,c+=D*(v"u"?null:document;function wd(e,t,l){var a=Da;if(a&&typeof t=="string"&&t){var n=bt(t);n='link[rel="'+e+'"][href="'+n+'"]',typeof l=="string"&&(n+='[crossorigin="'+l+'"]'),Gd.has(n)||(Gd.add(n),e={rel:e,crossOrigin:l,href:t},a.querySelector(n)===null&&(t=a.createElement("link"),ke(t,"link",e),Qe(t),a.head.appendChild(t)))}}function Iv(e){nl.D(e),wd("dns-prefetch",e,null)}function Pv(e,t){nl.C(e,t),wd("preconnect",e,t)}function ey(e,t,l){nl.L(e,t,l);var a=Da;if(a&&e&&t){var n='link[rel="preload"][as="'+bt(t)+'"]';t==="image"&&l&&l.imageSrcSet?(n+='[imagesrcset="'+bt(l.imageSrcSet)+'"]',typeof l.imageSizes=="string"&&(n+='[imagesizes="'+bt(l.imageSizes)+'"]')):n+='[href="'+bt(e)+'"]';var u=n;switch(t){case"style":u=Ma(e);break;case"script":u=Ua(e)}At.has(u)||(e=T({rel:"preload",href:t==="image"&&l&&l.imageSrcSet?void 0:e,as:t},l),At.set(u,e),a.querySelector(n)!==null||t==="style"&&a.querySelector(An(u))||t==="script"&&a.querySelector(zn(u))||(t=a.createElement("link"),ke(t,"link",e),Qe(t),a.head.appendChild(t)))}}function ty(e,t){nl.m(e,t);var l=Da;if(l&&e){var a=t&&typeof t.as=="string"?t.as:"script",n='link[rel="modulepreload"][as="'+bt(a)+'"][href="'+bt(e)+'"]',u=n;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":u=Ua(e)}if(!At.has(u)&&(e=T({rel:"modulepreload",href:e},t),At.set(u,e),l.querySelector(n)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(l.querySelector(zn(u)))return}a=l.createElement("link"),ke(a,"link",e),Qe(a),l.head.appendChild(a)}}}function ly(e,t,l){nl.S(e,t,l);var a=Da;if(a&&e){var n=ta(a).hoistableStyles,u=Ma(e);t=t||"default";var c=n.get(u);if(!c){var o={loading:0,preload:null};if(c=a.querySelector(An(u)))o.loading=5;else{e=T({rel:"stylesheet",href:e,"data-precedence":t},l),(l=At.get(u))&&Rf(e,l);var v=c=a.createElement("link");Qe(v),ke(v,"link",e),v._p=new Promise(function(j,D){v.onload=j,v.onerror=D}),v.addEventListener("load",function(){o.loading|=1}),v.addEventListener("error",function(){o.loading|=2}),o.loading|=4,ku(c,t,a)}c={type:"stylesheet",instance:c,count:1,state:o},n.set(u,c)}}}function ay(e,t){nl.X(e,t);var l=Da;if(l&&e){var a=ta(l).hoistableScripts,n=Ua(e),u=a.get(n);u||(u=l.querySelector(zn(n)),u||(e=T({src:e,async:!0},t),(t=At.get(n))&&Af(e,t),u=l.createElement("script"),Qe(u),ke(u,"link",e),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function ny(e,t){nl.M(e,t);var l=Da;if(l&&e){var a=ta(l).hoistableScripts,n=Ua(e),u=a.get(n);u||(u=l.querySelector(zn(n)),u||(e=T({src:e,async:!0,type:"module"},t),(t=At.get(n))&&Af(e,t),u=l.createElement("script"),Qe(u),ke(u,"link",e),l.head.appendChild(u)),u={type:"script",instance:u,count:1,state:null},a.set(n,u))}}function Xd(e,t,l,a){var n=(n=ce.current)?$u(n):null;if(!n)throw Error(f(446));switch(e){case"meta":case"title":return null;case"style":return typeof l.precedence=="string"&&typeof l.href=="string"?(t=Ma(l.href),l=ta(n).hoistableStyles,a=l.get(t),a||(a={type:"style",instance:null,count:0,state:null},l.set(t,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(l.rel==="stylesheet"&&typeof l.href=="string"&&typeof l.precedence=="string"){e=Ma(l.href);var u=ta(n).hoistableStyles,c=u.get(e);if(c||(n=n.ownerDocument||n,c={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},u.set(e,c),(u=n.querySelector(An(e)))&&!u._p&&(c.instance=u,c.state.loading=5),At.has(e)||(l={rel:"preload",as:"style",href:l.href,crossOrigin:l.crossOrigin,integrity:l.integrity,media:l.media,hrefLang:l.hrefLang,referrerPolicy:l.referrerPolicy},At.set(e,l),u||uy(n,e,l,c.state))),t&&a===null)throw Error(f(528,""));return c}if(t&&a!==null)throw Error(f(529,""));return null;case"script":return t=l.async,l=l.src,typeof l=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=Ua(l),l=ta(n).hoistableScripts,a=l.get(t),a||(a={type:"script",instance:null,count:0,state:null},l.set(t,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(f(444,e))}}function Ma(e){return'href="'+bt(e)+'"'}function An(e){return'link[rel="stylesheet"]['+e+"]"}function Qd(e){return T({},e,{"data-precedence":e.precedence,precedence:null})}function uy(e,t,l,a){e.querySelector('link[rel="preload"][as="style"]['+t+"]")?a.loading=1:(t=e.createElement("link"),a.preload=t,t.addEventListener("load",function(){return a.loading|=1}),t.addEventListener("error",function(){return a.loading|=2}),ke(t,"link",l),Qe(t),e.head.appendChild(t))}function Ua(e){return'[src="'+bt(e)+'"]'}function zn(e){return"script[async]"+e}function Zd(e,t,l){if(t.count++,t.instance===null)switch(t.type){case"style":var a=e.querySelector('style[data-href~="'+bt(l.href)+'"]');if(a)return t.instance=a,Qe(a),a;var n=T({},l,{"data-href":l.href,"data-precedence":l.precedence,href:null,precedence:null});return a=(e.ownerDocument||e).createElement("style"),Qe(a),ke(a,"style",n),ku(a,l.precedence,e),t.instance=a;case"stylesheet":n=Ma(l.href);var u=e.querySelector(An(n));if(u)return t.state.loading|=4,t.instance=u,Qe(u),u;a=Qd(l),(n=At.get(n))&&Rf(a,n),u=(e.ownerDocument||e).createElement("link"),Qe(u);var c=u;return c._p=new Promise(function(o,v){c.onload=o,c.onerror=v}),ke(u,"link",a),t.state.loading|=4,ku(u,l.precedence,e),t.instance=u;case"script":return u=Ua(l.src),(n=e.querySelector(zn(u)))?(t.instance=n,Qe(n),n):(a=l,(n=At.get(u))&&(a=T({},l),Af(a,n)),e=e.ownerDocument||e,n=e.createElement("script"),Qe(n),ke(n,"link",a),e.head.appendChild(n),t.instance=n);case"void":return null;default:throw Error(f(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(a=t.instance,t.state.loading|=4,ku(a,l.precedence,e));return t.instance}function ku(e,t,l){for(var a=l.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),n=a.length?a[a.length-1]:null,u=n,c=0;c title"):null)}function iy(e,t,l){if(l===1||t.itemProp!=null)return!1;switch(e){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;return t.rel==="stylesheet"?(e=t.disabled,typeof t.precedence=="string"&&e==null):!0;case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function Jd(e){return!(e.type==="stylesheet"&&(e.state.loading&3)===0)}function cy(e,t,l,a){if(l.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(l.state.loading&4)===0){if(l.instance===null){var n=Ma(a.href),u=t.querySelector(An(n));if(u){t=u._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(e.count++,e=Fu.bind(e),t.then(e,e)),l.state.loading|=4,l.instance=u,Qe(u);return}u=t.ownerDocument||t,a=Qd(a),(n=At.get(n))&&Rf(a,n),u=u.createElement("link"),Qe(u);var c=u;c._p=new Promise(function(o,v){c.onload=o,c.onerror=v}),ke(u,"link",a),l.instance=u}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(l,t),(t=l.state.preload)&&(l.state.loading&3)===0&&(e.count++,l=Fu.bind(e),t.addEventListener("load",l),t.addEventListener("error",l))}}var zf=0;function fy(e,t){return e.stylesheets&&e.count===0&&Pu(e,e.stylesheets),0zf?50:800)+t);return e.unsuspend=l,function(){e.unsuspend=null,clearTimeout(a),clearTimeout(n)}}:null}function Fu(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Pu(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Iu=null;function Pu(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Iu=new Map,t.forEach(sy,e),Iu=null,Fu.call(e))}function sy(e,t){if(!(t.state.loading&4)){var l=Iu.get(e);if(l)var a=l.get(null);else{l=new Map,Iu.set(e,l);for(var n=e.querySelectorAll("link[data-precedence],style[data-precedence]"),u=0;u"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(s){console.error(s)}}return i(),Lf.exports=jy(),Lf.exports}var Ry=Ty();var pm="popstate";function gm(i){return typeof i=="object"&&i!=null&&"pathname"in i&&"search"in i&&"hash"in i&&"state"in i&&"key"in i}function Ay(i={}){function s(m,h){let{pathname:b="/",search:z="",hash:S=""}=Fl(m.location.hash.substring(1));return!b.startsWith("/")&&!b.startsWith(".")&&(b="/"+b),Jf("",{pathname:b,search:z,hash:S},h.state&&h.state.usr||null,h.state&&h.state.key||"default")}function d(m,h){let b=m.document.querySelector("base"),z="";if(b&&b.getAttribute("href")){let S=m.location.href,y=S.indexOf("#");z=y===-1?S:S.slice(0,y)}return z+"#"+(typeof h=="string"?h:Yn(h))}function f(m,h){Ot(m.pathname.charAt(0)==="/",`relative pathnames are not supported in hash history.push(${JSON.stringify(h)})`)}return Oy(s,d,f,i)}function ze(i,s){if(i===!1||i===null||typeof i>"u")throw new Error(s)}function Ot(i,s){if(!i){typeof console<"u"&&console.warn(s);try{throw new Error(s)}catch{}}}function zy(){return Math.random().toString(36).substring(2,10)}function Sm(i,s){return{usr:i.state,key:i.key,idx:s,masked:i.unstable_mask?{pathname:i.pathname,search:i.search,hash:i.hash}:void 0}}function Jf(i,s,d=null,f,m){return{pathname:typeof i=="string"?i:i.pathname,search:"",hash:"",...typeof s=="string"?Fl(s):s,state:d,key:s&&s.key||f||zy(),unstable_mask:m}}function Yn({pathname:i="/",search:s="",hash:d=""}){return s&&s!=="?"&&(i+=s.charAt(0)==="?"?s:"?"+s),d&&d!=="#"&&(i+=d.charAt(0)==="#"?d:"#"+d),i}function Fl(i){let s={};if(i){let d=i.indexOf("#");d>=0&&(s.hash=i.substring(d),i=i.substring(0,d));let f=i.indexOf("?");f>=0&&(s.search=i.substring(f),i=i.substring(0,f)),i&&(s.pathname=i)}return s}function Oy(i,s,d,f={}){let{window:m=document.defaultView,v5Compat:h=!1}=f,b=m.history,z="POP",S=null,y=C();y==null&&(y=0,b.replaceState({...b.state,idx:y},""));function C(){return(b.state||{idx:null}).idx}function T(){z="POP";let q=C(),V=q==null?null:q-y;y=q,S&&S({action:z,location:Z.location,delta:V})}function Y(q,V){z="PUSH";let W=gm(q)?q:Jf(Z.location,q,V);d&&d(W,q),y=C()+1;let X=Sm(W,y),w=Z.createHref(W.unstable_mask||W);try{b.pushState(X,"",w)}catch(ae){if(ae instanceof DOMException&&ae.name==="DataCloneError")throw ae;m.location.assign(w)}h&&S&&S({action:z,location:Z.location,delta:1})}function H(q,V){z="REPLACE";let W=gm(q)?q:Jf(Z.location,q,V);d&&d(W,q),y=C();let X=Sm(W,y),w=Z.createHref(W.unstable_mask||W);b.replaceState(X,"",w),h&&S&&S({action:z,location:Z.location,delta:0})}function Q(q){return Cy(q)}let Z={get action(){return z},get location(){return i(m,b)},listen(q){if(S)throw new Error("A history only accepts one active listener");return m.addEventListener(pm,T),S=q,()=>{m.removeEventListener(pm,T),S=null}},createHref(q){return s(m,q)},createURL:Q,encodeLocation(q){let V=Q(q);return{pathname:V.pathname,search:V.search,hash:V.hash}},push:Y,replace:H,go(q){return b.go(q)}};return Z}function Cy(i,s=!1){let d="http://localhost";typeof window<"u"&&(d=window.location.origin!=="null"?window.location.origin:window.location.href),ze(d,"No window.location.(origin|href) available to create URL");let f=typeof i=="string"?i:Yn(i);return f=f.replace(/ $/,"%20"),!s&&f.startsWith("//")&&(f=d+f),new URL(f,d)}function jm(i,s,d="/"){return Dy(i,s,d,!1)}function Dy(i,s,d,f){let m=typeof s=="string"?Fl(s):s,h=ul(m.pathname||"/",d);if(h==null)return null;let b=Tm(i);My(b);let z=null;for(let S=0;z==null&&S{let C={relativePath:y===void 0?b.path||"":y,caseSensitive:b.caseSensitive===!0,childrenIndex:z,route:b};if(C.relativePath.startsWith("/")){if(!C.relativePath.startsWith(f)&&S)return;ze(C.relativePath.startsWith(f),`Absolute route path "${C.relativePath}" nested under path "${f}" is not valid. An absolute child route path must start with the combined path of all its parent routes.`),C.relativePath=C.relativePath.slice(f.length)}let T=Ut([f,C.relativePath]),Y=d.concat(C);b.children&&b.children.length>0&&(ze(b.index!==!0,`Index routes must not have child routes. Please remove all child routes from route path "${T}".`),Tm(b.children,s,Y,T,S)),!(b.path==null&&!b.index)&&s.push({path:T,score:Gy(T,b.index),routesMeta:Y})};return i.forEach((b,z)=>{if(b.path===""||!b.path?.includes("?"))h(b,z);else for(let S of Rm(b.path))h(b,z,!0,S)}),s}function Rm(i){let s=i.split("/");if(s.length===0)return[];let[d,...f]=s,m=d.endsWith("?"),h=d.replace(/\?$/,"");if(f.length===0)return m?[h,""]:[h];let b=Rm(f.join("/")),z=[];return z.push(...b.map(S=>S===""?h:[h,S].join("/"))),m&&z.push(...b),z.map(S=>i.startsWith("/")&&S===""?"/":S)}function My(i){i.sort((s,d)=>s.score!==d.score?d.score-s.score:wy(s.routesMeta.map(f=>f.childrenIndex),d.routesMeta.map(f=>f.childrenIndex)))}var Uy=/^:[\w-]+$/,Hy=3,qy=2,By=1,Ly=10,Yy=-2,bm=i=>i==="*";function Gy(i,s){let d=i.split("/"),f=d.length;return d.some(bm)&&(f+=Yy),s&&(f+=qy),d.filter(m=>!bm(m)).reduce((m,h)=>m+(Uy.test(h)?Hy:h===""?By:Ly),f)}function wy(i,s){return i.length===s.length&&i.slice(0,-1).every((f,m)=>f===s[m])?i[i.length-1]-s[s.length-1]:0}function Xy(i,s,d=!1){let{routesMeta:f}=i,m={},h="/",b=[];for(let z=0;z{if(C==="*"){let Q=z[Y]||"";b=h.slice(0,h.length-Q.length).replace(/(.)\/+$/,"$1")}const H=z[Y];return T&&!H?y[C]=void 0:y[C]=(H||"").replace(/%2F/g,"/"),y},{}),pathname:h,pathnameBase:b,pattern:i}}function Qy(i,s=!1,d=!0){Ot(i==="*"||!i.endsWith("*")||i.endsWith("/*"),`Route path "${i}" will be treated as if it were "${i.replace(/\*$/,"/*")}" because the \`*\` character must always follow a \`/\` in the pattern. To get rid of this warning, please change the route path to "${i.replace(/\*$/,"/*")}".`);let f=[],m="^"+i.replace(/\/*\*?$/,"").replace(/^\/*/,"/").replace(/[\\.*+^${}|()[\]]/g,"\\$&").replace(/\/:([\w-]+)(\?)?/g,(b,z,S,y,C)=>{if(f.push({paramName:z,isOptional:S!=null}),S){let T=C.charAt(y+b.length);return T&&T!=="/"?"/([^\\/]*)":"(?:/([^\\/]*))?"}return"/([^\\/]+)"}).replace(/\/([\w-]+)\?(\/|$)/g,"(/$1)?$2");return i.endsWith("*")?(f.push({paramName:"*"}),m+=i==="*"||i==="/*"?"(.*)$":"(?:\\/(.+)|\\/*)$"):d?m+="\\/*$":i!==""&&i!=="/"&&(m+="(?:(?=\\/|$))"),[new RegExp(m,s?void 0:"i"),f]}function Zy(i){try{return i.split("/").map(s=>decodeURIComponent(s).replace(/\//g,"%2F")).join("/")}catch(s){return Ot(!1,`The URL path "${i}" could not be decoded because it is a malformed URL segment. This is probably due to a bad percent encoding (${s}).`),i}}function ul(i,s){if(s==="/")return i;if(!i.toLowerCase().startsWith(s.toLowerCase()))return null;let d=s.endsWith("/")?s.length-1:s.length,f=i.charAt(d);return f&&f!=="/"?null:i.slice(d)||"/"}var Vy=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;function Ky(i,s="/"){let{pathname:d,search:f="",hash:m=""}=typeof i=="string"?Fl(i):i,h;return d?(d=Am(d),d.startsWith("/")?h=_m(d.substring(1),"/"):h=_m(d,s)):h=s,{pathname:h,search:ky(f),hash:Wy(m)}}function _m(i,s){let d=mi(s).split("/");return i.split("/").forEach(m=>{m===".."?d.length>1&&d.pop():m!=="."&&d.push(m)}),d.length>1?d.join("/"):"/"}function Xf(i,s,d,f){return`Cannot include a '${i}' character in a manually specified \`to.${s}\` field [${JSON.stringify(f)}]. Please separate it out to the \`to.${d}\` field. Alternatively you may provide the full path as a string in and the router will parse it for you.`}function Jy(i){return i.filter((s,d)=>d===0||s.route.path&&s.route.path.length>0)}function Ff(i){let s=Jy(i);return s.map((d,f)=>f===s.length-1?d.pathname:d.pathnameBase)}function hi(i,s,d,f=!1){let m;typeof i=="string"?m=Fl(i):(m={...i},ze(!m.pathname||!m.pathname.includes("?"),Xf("?","pathname","search",m)),ze(!m.pathname||!m.pathname.includes("#"),Xf("#","pathname","hash",m)),ze(!m.search||!m.search.includes("#"),Xf("#","search","hash",m)));let h=i===""||m.pathname==="",b=h?"/":m.pathname,z;if(b==null)z=d;else{let T=s.length-1;if(!f&&b.startsWith("..")){let Y=b.split("/");for(;Y[0]==="..";)Y.shift(),T-=1;m.pathname=Y.join("/")}z=T>=0?s[T]:"/"}let S=Ky(m,z),y=b&&b!=="/"&&b.endsWith("/"),C=(h||b===".")&&d.endsWith("/");return!S.pathname.endsWith("/")&&(y||C)&&(S.pathname+="/"),S}var Am=i=>i.replace(/\/\/+/g,"/"),Ut=i=>Am(i.join("/")),mi=i=>i.replace(/\/+$/,""),$y=i=>mi(i).replace(/^\/*/,"/"),ky=i=>!i||i==="?"?"":i.startsWith("?")?i:"?"+i,Wy=i=>!i||i==="#"?"":i.startsWith("#")?i:"#"+i,Fy=class{constructor(i,s,d,f=!1){this.status=i,this.statusText=s||"",this.internal=f,d instanceof Error?(this.data=d.toString(),this.error=d):this.data=d}};function Iy(i){return i!=null&&typeof i.status=="number"&&typeof i.statusText=="string"&&typeof i.internal=="boolean"&&"data"in i}function Py(i){let s=i.map(d=>d.route.path).filter(Boolean);return Ut(s)||"/"}var zm=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u";function Om(i,s){let d=i;if(typeof d!="string"||!Vy.test(d))return{absoluteURL:void 0,isExternal:!1,to:d};let f=d,m=!1;if(zm)try{let h=new URL(window.location.href),b=d.startsWith("//")?new URL(h.protocol+d):new URL(d),z=ul(b.pathname,s);b.origin===h.origin&&z!=null?d=z+b.search+b.hash:m=!0}catch{Ot(!1,` contains an invalid URL which will probably break when clicked - please update to a valid URL path.`)}return{absoluteURL:f,isExternal:m,to:d}}Object.getOwnPropertyNames(Object.prototype).sort().join("\0");var Cm=["POST","PUT","PATCH","DELETE"];new Set(Cm);var e0=["GET",...Cm];new Set(e0);var La=_.createContext(null);La.displayName="DataRouter";var vi=_.createContext(null);vi.displayName="DataRouterState";var Dm=_.createContext(!1);function t0(){return _.useContext(Dm)}var Mm=_.createContext({isTransitioning:!1});Mm.displayName="ViewTransition";var l0=_.createContext(new Map);l0.displayName="Fetchers";var a0=_.createContext(null);a0.displayName="Await";var gt=_.createContext(null);gt.displayName="Navigation";var wn=_.createContext(null);wn.displayName="Location";var Ht=_.createContext({outlet:null,matches:[],isDataRoute:!1});Ht.displayName="Route";var If=_.createContext(null);If.displayName="RouteError";var Um="REACT_ROUTER_ERROR",n0="REDIRECT",u0="ROUTE_ERROR_RESPONSE";function i0(i){if(i.startsWith(`${Um}:${n0}:{`))try{let s=JSON.parse(i.slice(28));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string"&&typeof s.location=="string"&&typeof s.reloadDocument=="boolean"&&typeof s.replace=="boolean")return s}catch{}}function c0(i){if(i.startsWith(`${Um}:${u0}:{`))try{let s=JSON.parse(i.slice(40));if(typeof s=="object"&&s&&typeof s.status=="number"&&typeof s.statusText=="string")return new Fy(s.status,s.statusText,s.data)}catch{}}function f0(i,{relative:s}={}){ze(Ya(),"useHref() may be used only in the context of a component.");let{basename:d,navigator:f}=_.useContext(gt),{hash:m,pathname:h,search:b}=Xn(i,{relative:s}),z=h;return d!=="/"&&(z=h==="/"?d:Ut([d,h])),f.createHref({pathname:z,search:b,hash:m})}function Ya(){return _.useContext(wn)!=null}function Gt(){return ze(Ya(),"useLocation() may be used only in the context of a component."),_.useContext(wn).location}var Hm="You should call navigate() in a React.useEffect(), not when your component is first rendered.";function qm(i){_.useContext(gt).static||_.useLayoutEffect(i)}function Bm(){let{isDataRoute:i}=_.useContext(Ht);return i?x0():s0()}function s0(){ze(Ya(),"useNavigate() may be used only in the context of a component.");let i=_.useContext(La),{basename:s,navigator:d}=_.useContext(gt),{matches:f}=_.useContext(Ht),{pathname:m}=Gt(),h=JSON.stringify(Ff(f)),b=_.useRef(!1);return qm(()=>{b.current=!0}),_.useCallback((S,y={})=>{if(Ot(b.current,Hm),!b.current)return;if(typeof S=="number"){d.go(S);return}let C=hi(S,JSON.parse(h),m,y.relative==="path");i==null&&s!=="/"&&(C.pathname=C.pathname==="/"?s:Ut([s,C.pathname])),(y.replace?d.replace:d.push)(C,y.state,y)},[s,d,h,m,i])}var r0=_.createContext(null);function o0(i){let s=_.useContext(Ht).outlet;return _.useMemo(()=>s&&_.createElement(r0.Provider,{value:i},s),[s,i])}function Xn(i,{relative:s}={}){let{matches:d}=_.useContext(Ht),{pathname:f}=Gt(),m=JSON.stringify(Ff(d));return _.useMemo(()=>hi(i,JSON.parse(m),f,s==="path"),[i,m,f,s])}function d0(i,s){return Lm(i,s)}function Lm(i,s,d){ze(Ya(),"useRoutes() may be used only in the context of a component.");let{navigator:f}=_.useContext(gt),{matches:m}=_.useContext(Ht),h=m[m.length-1],b=h?h.params:{},z=h?h.pathname:"/",S=h?h.pathnameBase:"/",y=h&&h.route;{let q=y&&y.path||"";Gm(z,!y||q.endsWith("*")||q.endsWith("*?"),`You rendered descendant (or called \`useRoutes()\`) at "${z}" (under ) but the parent route path has no trailing "*". This means if you navigate deeper, the parent won't match anymore and therefore the child routes will never render. + +Please change the parent to .`)}let C=Gt(),T;if(s){let q=typeof s=="string"?Fl(s):s;ze(S==="/"||q.pathname?.startsWith(S),`When overriding the location using \`\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${S}" but pathname "${q.pathname}" was given in the \`location\` prop.`),T=q}else T=C;let Y=T.pathname||"/",H=Y;if(S!=="/"){let q=S.replace(/^\//,"").split("/");H="/"+Y.replace(/^\//,"").split("/").slice(q.length).join("/")}let Q=jm(i,{pathname:H});Ot(y||Q!=null,`No routes matched location "${T.pathname}${T.search}${T.hash}" `),Ot(Q==null||Q[Q.length-1].route.element!==void 0||Q[Q.length-1].route.Component!==void 0||Q[Q.length-1].route.lazy!==void 0,`Matched leaf route at location "${T.pathname}${T.search}${T.hash}" does not have an element or Component. This means it will render an with a null value by default resulting in an "empty" page.`);let Z=p0(Q&&Q.map(q=>Object.assign({},q,{params:Object.assign({},b,q.params),pathname:Ut([S,f.encodeLocation?f.encodeLocation(q.pathname.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:q.pathname]),pathnameBase:q.pathnameBase==="/"?S:Ut([S,f.encodeLocation?f.encodeLocation(q.pathnameBase.replace(/%/g,"%25").replace(/\?/g,"%3F").replace(/#/g,"%23")).pathname:q.pathnameBase])})),m,d);return s&&Z?_.createElement(wn.Provider,{value:{location:{pathname:"/",search:"",hash:"",state:null,key:"default",unstable_mask:void 0,...T},navigationType:"POP"}},Z):Z}function m0(){let i=E0(),s=Iy(i)?`${i.status} ${i.statusText}`:i instanceof Error?i.message:JSON.stringify(i),d=i instanceof Error?i.stack:null,f="rgba(200,200,200, 0.5)",m={padding:"0.5rem",backgroundColor:f},h={padding:"2px 4px",backgroundColor:f},b=null;return console.error("Error handled by React Router default ErrorBoundary:",i),b=_.createElement(_.Fragment,null,_.createElement("p",null,"💿 Hey developer 👋"),_.createElement("p",null,"You can provide a way better UX than this when your app throws errors by providing your own ",_.createElement("code",{style:h},"ErrorBoundary")," or"," ",_.createElement("code",{style:h},"errorElement")," prop on your route.")),_.createElement(_.Fragment,null,_.createElement("h2",null,"Unexpected Application Error!"),_.createElement("h3",{style:{fontStyle:"italic"}},s),d?_.createElement("pre",{style:m},d):null,b)}var h0=_.createElement(m0,null),Ym=class extends _.Component{constructor(i){super(i),this.state={location:i.location,revalidation:i.revalidation,error:i.error}}static getDerivedStateFromError(i){return{error:i}}static getDerivedStateFromProps(i,s){return s.location!==i.location||s.revalidation!=="idle"&&i.revalidation==="idle"?{error:i.error,location:i.location,revalidation:i.revalidation}:{error:i.error!==void 0?i.error:s.error,location:s.location,revalidation:i.revalidation||s.revalidation}}componentDidCatch(i,s){this.props.onError?this.props.onError(i,s):console.error("React Router caught the following error during render",i)}render(){let i=this.state.error;if(this.context&&typeof i=="object"&&i&&"digest"in i&&typeof i.digest=="string"){const d=c0(i.digest);d&&(i=d)}let s=i!==void 0?_.createElement(Ht.Provider,{value:this.props.routeContext},_.createElement(If.Provider,{value:i,children:this.props.component})):this.props.children;return this.context?_.createElement(v0,{error:i},s):s}};Ym.contextType=Dm;var Qf=new WeakMap;function v0({children:i,error:s}){let{basename:d}=_.useContext(gt);if(typeof s=="object"&&s&&"digest"in s&&typeof s.digest=="string"){let f=i0(s.digest);if(f){let m=Qf.get(s);if(m)throw m;let h=Om(f.location,d);if(zm&&!Qf.get(s))if(h.isExternal||f.reloadDocument)window.location.href=h.absoluteURL||h.to;else{const b=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(h.to,{replace:f.replace}));throw Qf.set(s,b),b}return _.createElement("meta",{httpEquiv:"refresh",content:`0;url=${h.absoluteURL||h.to}`})}}return i}function y0({routeContext:i,match:s,children:d}){let f=_.useContext(La);return f&&f.static&&f.staticContext&&(s.route.errorElement||s.route.ErrorBoundary)&&(f.staticContext._deepestRenderedBoundaryId=s.route.id),_.createElement(Ht.Provider,{value:i},d)}function p0(i,s=[],d){let f=d?.state;if(i==null){if(!f)return null;if(f.errors)i=f.matches;else if(s.length===0&&!f.initialized&&f.matches.length>0)i=f.matches;else return null}let m=i,h=f?.errors;if(h!=null){let C=m.findIndex(T=>T.route.id&&h?.[T.route.id]!==void 0);ze(C>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(h).join(",")}`),m=m.slice(0,Math.min(m.length,C+1))}let b=!1,z=-1;if(d&&f){b=f.renderFallback;for(let C=0;C=0?m=m.slice(0,z+1):m=[m[0]];break}}}}let S=d?.onError,y=f&&S?(C,T)=>{S(C,{location:f.location,params:f.matches?.[0]?.params??{},unstable_pattern:Py(f.matches),errorInfo:T})}:void 0;return m.reduceRight((C,T,Y)=>{let H,Q=!1,Z=null,q=null;f&&(H=h&&T.route.id?h[T.route.id]:void 0,Z=T.route.errorElement||h0,b&&(z<0&&Y===0?(Gm("route-fallback",!1,"No `HydrateFallback` element provided to render during initial hydration"),Q=!0,q=null):z===Y&&(Q=!0,q=T.route.hydrateFallbackElement||null)));let V=s.concat(m.slice(0,Y+1)),W=()=>{let X;return H?X=Z:Q?X=q:T.route.Component?X=_.createElement(T.route.Component,null):T.route.element?X=T.route.element:X=C,_.createElement(y0,{match:T,routeContext:{outlet:C,matches:V,isDataRoute:f!=null},children:X})};return f&&(T.route.ErrorBoundary||T.route.errorElement||Y===0)?_.createElement(Ym,{location:f.location,revalidation:f.revalidation,component:Z,error:H,children:W(),routeContext:{outlet:null,matches:V,isDataRoute:!0},onError:y}):W()},null)}function Pf(i){return`${i} must be used within a data router. See https://reactrouter.com/en/main/routers/picking-a-router.`}function g0(i){let s=_.useContext(La);return ze(s,Pf(i)),s}function S0(i){let s=_.useContext(vi);return ze(s,Pf(i)),s}function b0(i){let s=_.useContext(Ht);return ze(s,Pf(i)),s}function es(i){let s=b0(i),d=s.matches[s.matches.length-1];return ze(d.route.id,`${i} can only be used on routes that contain a unique "id"`),d.route.id}function _0(){return es("useRouteId")}function E0(){let i=_.useContext(If),s=S0("useRouteError"),d=es("useRouteError");return i!==void 0?i:s.errors?.[d]}function x0(){let{router:i}=g0("useNavigate"),s=es("useNavigate"),d=_.useRef(!1);return qm(()=>{d.current=!0}),_.useCallback(async(m,h={})=>{Ot(d.current,Hm),d.current&&(typeof m=="number"?await i.navigate(m):await i.navigate(m,{fromRouteId:s,...h}))},[i,s])}var Em={};function Gm(i,s,d){!s&&!Em[i]&&(Em[i]=!0,Ot(!1,d))}_.memo(N0);function N0({routes:i,future:s,state:d,isStatic:f,onError:m}){return Lm(i,void 0,{state:d,isStatic:f,onError:m})}function j0({to:i,replace:s,state:d,relative:f}){ze(Ya()," may be used only in the context of a component.");let{static:m}=_.useContext(gt);Ot(!m," must not be used on the initial render in a . This is a no-op, but you should modify your code so the is only ever rendered in response to some user interaction or state change.");let{matches:h}=_.useContext(Ht),{pathname:b}=Gt(),z=Bm(),S=hi(i,Ff(h),b,f==="path"),y=JSON.stringify(S);return _.useEffect(()=>{z(JSON.parse(y),{replace:s,state:d,relative:f})},[z,y,f,s,d]),null}function T0(i){return o0(i.context)}function Wl(i){ze(!1,"A is only ever to be used as the child of element, never rendered directly. Please wrap your in a .")}function R0({basename:i="/",children:s=null,location:d,navigationType:f="POP",navigator:m,static:h=!1,unstable_useTransitions:b}){ze(!Ya(),"You cannot render a inside another . You should never have more than one in your app.");let z=i.replace(/^\/*/,"/"),S=_.useMemo(()=>({basename:z,navigator:m,static:h,unstable_useTransitions:b,future:{}}),[z,m,h,b]);typeof d=="string"&&(d=Fl(d));let{pathname:y="/",search:C="",hash:T="",state:Y=null,key:H="default",unstable_mask:Q}=d,Z=_.useMemo(()=>{let q=ul(y,z);return q==null?null:{location:{pathname:q,search:C,hash:T,state:Y,key:H,unstable_mask:Q},navigationType:f}},[z,y,C,T,Y,H,f,Q]);return Ot(Z!=null,` is not able to match the URL "${y}${C}${T}" because it does not start with the basename, so the won't render anything.`),Z==null?null:_.createElement(gt.Provider,{value:S},_.createElement(wn.Provider,{children:s,value:Z}))}function A0({children:i,location:s}){return d0($f(i),s)}function $f(i,s=[]){let d=[];return _.Children.forEach(i,(f,m)=>{if(!_.isValidElement(f))return;let h=[...s,m];if(f.type===_.Fragment){d.push.apply(d,$f(f.props.children,h));return}ze(f.type===Wl,`[${typeof f.type=="string"?f.type:f.type.name}] is not a component. All component children of must be a or `),ze(!f.props.index||!f.props.children,"An index route cannot have child routes.");let b={id:f.props.id||h.join("-"),caseSensitive:f.props.caseSensitive,element:f.props.element,Component:f.props.Component,index:f.props.index,path:f.props.path,middleware:f.props.middleware,loader:f.props.loader,action:f.props.action,hydrateFallbackElement:f.props.hydrateFallbackElement,HydrateFallback:f.props.HydrateFallback,errorElement:f.props.errorElement,ErrorBoundary:f.props.ErrorBoundary,hasErrorBoundary:f.props.hasErrorBoundary===!0||f.props.ErrorBoundary!=null||f.props.errorElement!=null,shouldRevalidate:f.props.shouldRevalidate,handle:f.props.handle,lazy:f.props.lazy};f.props.children&&(b.children=$f(f.props.children,h)),d.push(b)}),d}var ri="get",oi="application/x-www-form-urlencoded";function yi(i){return typeof HTMLElement<"u"&&i instanceof HTMLElement}function z0(i){return yi(i)&&i.tagName.toLowerCase()==="button"}function O0(i){return yi(i)&&i.tagName.toLowerCase()==="form"}function C0(i){return yi(i)&&i.tagName.toLowerCase()==="input"}function D0(i){return!!(i.metaKey||i.altKey||i.ctrlKey||i.shiftKey)}function M0(i,s){return i.button===0&&(!s||s==="_self")&&!D0(i)}var ci=null;function U0(){if(ci===null)try{new FormData(document.createElement("form"),0),ci=!1}catch{ci=!0}return ci}var H0=new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);function Zf(i){return i!=null&&!H0.has(i)?(Ot(!1,`"${i}" is not a valid \`encType\` for \`
\`/\`\` and will default to "${oi}"`),null):i}function q0(i,s){let d,f,m,h,b;if(O0(i)){let z=i.getAttribute("action");f=z?ul(z,s):null,d=i.getAttribute("method")||ri,m=Zf(i.getAttribute("enctype"))||oi,h=new FormData(i)}else if(z0(i)||C0(i)&&(i.type==="submit"||i.type==="image")){let z=i.form;if(z==null)throw new Error('Cannot submit a + + + {rawErr ?

{rawErr}

: null} + + + {result ? ( +
+
+

Results

+

+ matched_total={result.matched_total} returned={result.returned} truncated= + {String(result.truncated)} offset={result.offset} +

+
+
+ + + + + + + + + + {result.events.length === 0 ? ( + + + + ) : ( + result.events.map((ev, idx) => { + const runId = typeof ev.run_id === "string" ? ev.run_id : ""; + const ts = typeof ev.timestamp === "string" ? ev.timestamp : ""; + const agent = typeof ev.agent_id === "string" ? ev.agent_id : ""; + return ( + + + + + + ); + }) + )} + +
Run IDTimestampAgent
+ No events in this page. +
{shortId(runId)}{ts}{agent}
+
+ +
+ ) : null} + + ); +}