From cb6415254ddab0edb0dfde1ca5f46baac8f0d978 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 18 May 2026 07:09:42 +0000 Subject: [PATCH] docs: add pricing check command, OverviewPage hero/filters, and UI deep-link reference - docs/cli.md: add flightdeck pricing check section (--max-age-days, --fail, output format, CI use note); links back to pricing-catalog.md staleness guardrails - docs/web-ui.md: update routing table with URL search params for all pages (#/ ?release=, #/diff ?baseline/candidate/window/environment, #/runs ?release_id, #/actions ?release_id/environment/window); add deep-link explanation paragraph - docs/web-ui.md: rewrite OverviewPage section to document focused-release hero, release table client-side filters, collapsible ledger metrics panel, ReleaseLifecycleStrip - docs/web-ui.md: add urlSearch.ts section (pickTrimmedSearch, searchParamsFromRecord) - docs/web-ui.md: add CopyTextButton and ReleaseLifecycleStrip to shared components - docs/web-ui.md: add new CSS utility classes to token table (fd-lifecycle-strip, fd-release-hero, fd-copy-btn, fd-card--collapse, fd-collapse-head, fd-filter-row, fd-table-toolbar, fd-table-actions, fd-cell-stack, fd-cell-inline) Co-authored-by: Gottam Sai Bharath --- docs/cli.md | 40 +++++++++++++++ docs/web-ui.md | 130 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 161 insertions(+), 9 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 5591085..0879767 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -412,6 +412,46 @@ flightdeck pricing show --provider PROVIDER --version VERSION Both flags are required. If the table does not exist, exits 1 with an error message. +### `flightdeck pricing check` + +Check the age of any **`flightdeck-bundled-*`** pricing snapshots currently in the ledger. +Bundled snapshots use a calendar anchor (`YYYY-MM-01`) derived from the version string; the +command computes how many days have elapsed since that anchor date (in UTC) and reports +whether each table is within the acceptable age window. + +```bash +flightdeck pricing check [--max-age-days N] [--fail] +``` + +| Option | Default | Description | +|--------|---------|-------------| +| `--max-age-days` | `90` | Warn (and optionally fail) when a bundled snapshot anchor is older than this many days | +| `--fail` | off | Exit with code **1** if any bundled snapshot exceeds `--max-age-days` | + +If the ledger contains no `flightdeck-bundled-*` tables (for example when the workspace +was initialized with `--no-bundled-pricing`), the command prints a single informational +line and exits 0. + +**Output format — each bundled table is printed on its own line:** + +``` +OK flightdeck-bundled-2026-05 (~17 days old; max 90) +``` + +or, when stale (written to **stderr**): + +``` +STALE flightdeck-bundled-2025-12 (anchor 2025-12-01, ~168 days old; max 90) +``` + +`STALE` lines are written to stderr; `OK` lines to stdout. If `--fail` is given and any +table is stale, the process exits 1 after printing all lines. + +**CI use:** add `flightdeck pricing check --fail` to a periodic job or pre-release gate to +detect when bundled tables have aged past your allowed threshold. See +[pricing-catalog.md § Bundled snapshot](pricing-catalog.md) for maintainer cadence and +how diff responses surface stale-snapshot warnings automatically. + --- ## `flightdeck policy` diff --git a/docs/web-ui.md b/docs/web-ui.md index 5dd4df4..6b4b407 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -54,13 +54,21 @@ The app uses **HashRouter** (`react-router-dom`) so all navigation stays within | Hash path | Component | HTTP calls | Notes | |-----------|-----------|-----------|-------| -| `#/` | `OverviewPage` | `GET /v1/releases`, `GET /v1/promoted`, `GET /v1/actions`, `GET /v1/metrics` (parallel where applicable) | Ledger metrics (read-only); short per-counter hints; skeleton on first load; **auto-refresh** every 30s when the tab is visible + on timeline **`generation`** bump; links to Diff/Runs | -| `#/diff` | `DiffPage` | `POST /v1/diff` | Sections: policy gate (incl. `evaluated_at`), evidence window, pricing/catalog/hints (incl. provider/version skew callout when sides differ), per-1k prices when present, cost/quality rollups; raw JSON panel | -| `#/runs` | `RunsPage` | `GET /v1/releases` (for datalist), `GET /v1/runs`, `GET /v1/runs/export` | Forensics: filters, table (trace/status, trace band rows or **Group by trace_id**), **View** drawer (focus trap, session/span ids), typed **run-query error** card with **Retry**, empty/offset/truncation hints, NDJSON download | +| `#/` | `OverviewPage` | `GET /v1/releases`, `GET /v1/promoted`, `GET /v1/actions`, `GET /v1/metrics` (parallel where applicable) | Ledger metrics (collapsible, read-only); short per-counter hints; skeleton on first load; **auto-refresh** every 30s when the tab is visible + on timeline **`generation`** bump; links to Diff/Runs. URL param **`?release=`** activates the **focused-release hero**. | +| `#/diff` | `DiffPage` | `POST /v1/diff` | Sections: policy gate (incl. `evaluated_at`), evidence window, pricing/catalog/hints (incl. provider/version skew callout when sides differ), per-1k prices when present, cost/quality rollups; raw JSON panel. URL params **`?baseline=&candidate=&window=7d&environment=prod`** prefill the form. | +| `#/runs` | `RunsPage` | `GET /v1/releases` (for datalist), `GET /v1/runs`, `GET /v1/runs/export` | Forensics: filters, table (trace/status, trace band rows or **Group by trace_id**), **View** drawer (focus trap, session/span ids), typed **run-query error** card with **Retry**, empty/offset/truncation hints, NDJSON download. URL params **`?release_id=&window=7d&environment=prod`** prefill the filters. | | `#/settings` | `SettingsPage` | *(none)* | **Color theme** (Light / Dark / System) via `ThemeToggle`; more preferences later. | -| `#/actions` | `ActionsPage` | `GET /v1/workspace`, `GET /v1/promotion-requests` (when `promotion_requires_approval`), `POST /v1/promote` **or** `POST /v1/promote/request` + `POST /v1/promote/confirm`, `POST /v1/rollback` | Workspace skeleton then strip; approval path: numbered steps, pending **Refresh list** / **Use for confirm**; **Rollback** danger-styled; see **ActionsPage** below | +| `#/actions` | `ActionsPage` | `GET /v1/workspace`, `GET /v1/promotion-requests` (when `promotion_requires_approval`), `POST /v1/promote` **or** `POST /v1/promote/request` + `POST /v1/promote/confirm`, `POST /v1/rollback` | Workspace skeleton then strip; approval path: numbered steps, pending **Refresh list** / **Use for confirm**; **Rollback** danger-styled; see **ActionsPage** below. URL params **`?release_id=&environment=prod&window=7d`** prefill the form. | | `#/*` (any other) | — | Redirects to `#/` | | +**URL deep-linking:** all page query parameters are read from the hash-router search string +on mount. Pages do **not** auto-submit — they prefill form fields; the user still clicks the +action button to fire the request. Deep links are generated throughout the UI (every row in +the Overview releases table has **Diff**, **Runs**, and **Promote** shortcuts; the +focused-release hero also renders those buttons). See +[`urlSearch.ts`](#urlsearchts-websrcurlsearchts) for the helpers that construct and parse +search strings. + `App.tsx` declares the route tree. `AppShell` is the layout wrapper rendered for all routes. When `VITE_FLIGHTDECK_UI_READ_ONLY=true` is set at build time, the `#/actions` route @@ -172,23 +180,53 @@ fail. This is a configuration hint only — the server enforces the actual gate. ## `OverviewPage` (`web/src/pages/OverviewPage.tsx`) -Read-only dashboard. Renders a **Ledger metrics** card from `fetchMetrics()` plus three tables from `loadTimeline()` output: +Read-only dashboard. Renders a **`ReleaseLifecycleStrip`** workflow nav, an optional +**focused-release hero**, and then four blocks from `loadTimeline()` + `fetchMetrics()`: | Block | Source | Content | |-------|--------|---------| -| Ledger metrics | `GET /v1/metrics` | Releases, pricing tables, run events, promoted pointers, and actions totals (plus `actions_by_action` breakdown), `schema_version`, `generated_at` | -| Releases | `GET /v1/releases` | Release ID, Agent, Version, Environment, Checksum, Created | -| Promoted | `GET /v1/promoted` | Agent, Environment, Active release | +| Promoted releases | `GET /v1/promoted` | Agent, Environment, Active release, Version; Copy-ID button | +| Registered releases | `GET /v1/releases` | Agent/version/env, Release ID (copy + focus link), Checksum, Created, Shortcuts (Diff / Runs / Promote) | | Recent actions | `GET /v1/actions` | When, Action, Policy (PASS/FAIL badge), Release, Environment, Reason | +| Ledger metrics | `GET /v1/metrics` | Collapsible card: releases, pricing tables, run events, promoted pointers, actions totals + `actions_by_action` breakdown, `schema_version`, `generated_at` | Long IDs are abbreviated with `shortId(id, keepStart, keepEnd)` and shown in full on hover via the HTML `title` attribute. +**Release table filters:** a toolbar above the Registered releases table exposes three +client-side filters that narrow the displayed rows without an additional API call: + +| Filter | Behaviour | +|--------|-----------| +| Agent contains | Case-insensitive substring match on `agent_id` | +| Environment contains | Case-insensitive substring match on `environment` | +| Promotion | `All` / `Live (promoted)` / `Not promoted` — based on whether the row's `release_id` matches the current promoted pointer for that agent/environment pair | + +**Focused-release hero:** when the URL includes `?release=`, the page looks up +that release from the already-loaded data and renders a hero block above the tables: + +- **Title:** ` v ()` +- **Meta:** abbreviated release ID with a **Copy ID** button, abbreviated checksum, and the + currently promoted baseline for the same agent/environment pair (or a note if none). +- **Action buttons:** **Open diff** (links to `#/diff?baseline=&candidate=&…`), + **Open runs** (links to `#/runs?release_id=&…`), **Promote** (links to + `#/actions?release_id=&…`; hidden in `UI_READ_ONLY` mode), **Clear focus** (removes + `?release` from the URL). +- When the `?release` value does not match any registered release, a warning banner is shown + with a **Clear** button. + +Each release ID in the table is a link that sets `?release=` to open the focused hero. +The promoted releases table does the same for the active promoted ID. + **Refresh:** while the document tab is visible, the page **auto-polls** metrics and the -timeline on an interval and uses **silent** fetches after the first load. The `generation` +timeline every 30 seconds and uses **silent** fetches after the first load. The `generation` counter from `TimelineRefreshContext` triggers an immediate refresh after mutations from `ActionsPage`. +**Ledger metrics panel:** collapsed by default; click the **▸ Ledger metrics** toggle to +expand. Shows per-counter metric cards with hints. Uses `aria-expanded` / `aria-controls` +and a `data-testid="ledger-metrics-toggle"` for E2E targeting. + --- ## `DiffPage` (`web/src/pages/DiffPage.tsx`) @@ -369,6 +407,28 @@ Calls `GET /v1/promotion-requests` with optional query parameters. Used by `Acti --- +## `urlSearch.ts` (`web/src/urlSearch.ts`) + +Thin helpers for reading and writing hash-router search strings. Used by all pages that +support URL deep-linking. + +| Export | Signature | Description | +|--------|-----------|-------------| +| `pickTrimmedSearch` | `(searchParams, key) => string` | Returns `searchParams.get(key).trim()` or `""` when the key is absent. Prevents whitespace-only values from being treated as filled form fields. | +| `searchParamsFromRecord` | `(rec: Record) => string` | Builds a query string (e.g. `"?baseline=rel_abc&window=7d"`) from an object, omitting keys whose value is `""`. Returns `""` (not `"?"`) when all values are empty. | + +**Usage pattern in pages:** + +```ts +// Read on mount +const baseline = pickTrimmedSearch(searchParams, "baseline"); + +// Write when building a shortcut link +const href = `/diff${searchParamsFromRecord({ baseline, candidate, window: "7d" })}`; +``` + +--- + ## Shared components ### `Badge` (`web/src/components/Badge.tsx`) @@ -394,6 +454,44 @@ Collapsible raw-JSON viewer. Props: Uses `aria-expanded` and `aria-controls` for accessibility. Toggle state is local (`useState`). +### `CopyTextButton` (`web/src/components/CopyTextButton.tsx`) + +One-click clipboard helper. Renders a `