<!-- nornir:class: design -->
# dwarves — the Forge(live) **run-picker**
> A follow-up deferred in `3e082eb` (the `GetRunReport` + `dwarves-embed` live tab): the
> live pane rendered only the **newest** run (or the one pinned via `$DWARVES_RUN_ID`).
> This adds a **run-picker** — a dropdown of recent runs — so a viewer chooses which
> historized run to render, live, from the UI. **No proto change** (the `History` RPC
> already carried the run list), **additive** (unpinned still ⇒ newest), and it **reuses**
> the same `GetRunReport` → `ForgePipelineAdapter` → `PipelineView` path.

## The one-call fetch (`dwarves-embed::fetch_forge_split`)
The pane's fetch returns **both** halves in a SINGLE connection, with the two legs **split**:
- **`fetch_forge_split(endpoint, token, run_id, history_limit) -> Result<ForgeFetch>`** —
one `History(limit)` call feeds **both** the picker list *and* newest-run selection (when
`run_id` is unpinned), then one `GetRunReport(id)` fetches the selected run.
`ForgeFetch { runs: Vec<RunRow>, report: Result<RunReport> }` keeps the legs apart: the
**outer `Err`** is the LIST leg failing (couldn't connect / `History` errored ⇒ genuinely
unavailable); a `report: Err(…)` is only the SELECTED run's report failing (a bad pin / a
run that vanished) while the run list is fresh.
- **`fetch_forge(…) -> (Vec<RunRow>, RunReport)`** folds the split back to the strict
all-or-nothing pair (a report error ⇒ overall `Err`); **`fetch_run_report`** is a thin
wrapper over *that*. No twin — three functions, one fetch body.
## Report-error resilience — a bad pin never blanks the picker
When `History` succeeds but the pinned run's `GetRunReport` fails, the pane lands in a
distinct **`report_error`** `Conn` state instead of collapsing to `unavailable`:
- `apply_fetch(Result<ForgeFetch>)` (split out of `refresh` so the transitions are
**drivable headless**) repopulates `runs` from the fresh `History` list *before* looking
at the report — so the dropdown stays usable to switch to a working run. Only when the
forge has **no runs at all** does a report error stay the plain `unavailable`/no-runs state.
- The control strip shows an amber *"run <id> unavailable — pick another run"* (the
error on hover); `state_json.status == "report_error"` with `run_count` intact. Picking a
good run re-fetches and clears the error. Previously the whole result was discarded on a
report error, throwing the freshly-fetched list away — the fix that closed the last
run-picker deferral.
## The pure mapping (`run_picks_from_wire`)
- **`RunPick { run_id, pipeline, status }`** — one dropdown entry, `Serialize` for
`state_json`.
- **`run_picks_from_wire(&[RunRow]) -> Vec<RunPick>`** — pure, order-preserving
(newest-first, as the server returns), so the picker list is unit-testable with **no
server**.
## The pane (`ForgePanel`)
- Two new fields: `runs: Vec<RunPick>` (the offered list) + `history_limit: u32`
(`DEFAULT_HISTORY_LIMIT = 20`). `refresh()` → `apply_fetch()` repopulates `runs` from the
fresh `History` list whenever the list leg succeeds; a **dead-server fetch error leaves
the list as-is** (a blip doesn't blank the dropdown), and a **report-only error keeps the
fresh list** (the `report_error` state above).
- **UI:** an `egui::ComboBox` in the control strip — `● newest` plus one entry per run
(`run_id · pipeline · status`). Selecting an entry pins its `run_id` and re-fetches;
`● newest` clears the pin. The click drives the **exact same seam** a robot uses.
- **`state_json`** gains `selected_run_id` (the pin; `null` ⇒ newest), `runs`,
`run_count`, `history_limit` — the robot-observable picker surface.
- **`update_json`** already pinned `{"run_id": "…"}` (`""` ⇒ newest); now also
`{"history_limit": N}` resizes the list. Both re-fetch.
## Why this shape
- **Reuse, no twin (L5).** `fetch_run_report` becomes a wrapper over `fetch_forge`; the
render path (`GetRunReport` → `ForgePipelineAdapter::from_report` → `PipelineView`) is
untouched. No new RPC, no new viewer.
- **Additive (L2).** The default (unpinned ⇒ newest, auto-poll) is byte-for-byte the
prior behaviour; the picker is purely additional surface.
- **Robot-tested (L4).** The picker (list + pin + clear) *and* the `report_error` recovery
drive headless over `state_json` (`apply_fetch` → `state_json`), closing the coverage gap
on the newest UI surface (the live-forge tab was mounted but not yet robot-ledgered).
## Deferred
- The picker lists **`History(limit)`** runs (default 20) — no paging / infinite scroll.
- No live-search/filter box over the list yet (fine at 20; add a filter if `limit` grows).
- Selection is by `run_id` only; a richer label (project + finished-at) is cosmetic.