Gunnar

nordisk/dwarves

.nornir/facett-brokkrview-plan.md

<!-- nornir:class: design -->
# Plan: `facett-brokkrview` — the forge panel as a shared facett component

Filed 2026-07-12 (dwarves love pass). ✅ **LANDED 2026-07-12** — the
`facett-brokkrview` crate now exists in `facett/` and `dwarves-gui` mounts it
(replacing the interim two-table `ForgeView`). The rest of this doc is the contract
that was implemented; deviations from it are noted in the "As built" section below.

## Why it is flagged referenced-but-MISSING

The constellation consolidation matrix
([workspace_nordisk/.nornir/consolidation-panels-maps-design.md](../../workspace_nordisk/.nornir/consolidation-panels-maps-design.md)
§1.2, dwarves row) marks dwarves as `🟡 PARTIAL` for the panel seam: `facett-brokkr`
exists, but **`facett-brokkrview` is referenced but MISSING**. The reference is
real and lives in this repo:

- `crates/dwarves/src/brokkr/elm.rs:8` — the module doc names `facett-brokkrview` as
  the intended facett-side egui `view` + `impl_facet_via_elm!` bridge over the pure
  Elm core defined here.
- `dwarves-gui/src/panel.rs` — an **INTERIM** native render (two plain
  `facett_table::Table`s over a fixture), explicitly labelled interim, to be swapped
  for the real component once it lands.

## The seam is already done on the dwarves side (nothing to build here)

The whole point of the Elm split is that dwarves already ships the pure, testable
core the panel renders — `facett-brokkrview` is a thin facett-side follow-up, not
new logic:

- **Model / Msg / Effect** — `crate::brokkr::elm::{BrokkrModel, BrokkrMsg,
  BrokkrEffect}` (single serializable state, one mutation path `Brokkr::update`,
  side work returned as `BrokkrEffect::RunPipeline` the host executes and feeds back
  via `BrokkrMsg::RunFinished`). Covered by `brokkr/mod.rs::elm_facet_run_flow_greens_the_state_json`.
- **Headless contract** — `Brokkr::state_json()` is the wire the robot harness and
  any renderer key on (`runs[].status` = `passed`/`failed`, `steps` = `"4/4"`).
- **Richer disposition surface** — the Phase-2 incremental taxonomy the panel can
  tint distinctly (`ran` / `cache-hit` / `unchanged` / `when-skipped` /
  `upstream-skipped` / `failed`, plus a `cached` flag) is already emitted by
  `crate::incremental::CachedRunReport::state_json()`.

## What `facett-brokkrview` must be (the contract for the facett agent)

1. **A `facett-<app>` component crate** under `facett/` (mirrors `facett-brokkr`),
   deps: `egui` + `facett-core` + a thin `dwarves-client` (gRPC) — NO dwarves *logic*
   copied, only the shared UI crate + a thin stub (native cross-host backend 2, no
   wasm — see wasm-ui-panels-design §3).
2. `impl Elm for <the view>` delegating to dwarves' `Brokkr` core, then
   `impl_facet_via_elm!` for the egui `view`. It renders `BrokkrModel` and emits
   `BrokkrMsg`; the disposition chips read the `cached`/`disposition` fields.
3. **Data path:** fat = in-process `dwarves` lib; thin = `dwarves-server` over gRPC
   (the split in `.nornir/ui-embedding-and-standalone.md`). dwarves does not yet
   daemonize, so the thin adapter is gated on a `dwarves-server` existing.
4. **Test win (required):** one `nornir-robotui` scenario + one testmatrix row that
   exercises the component in BOTH hosts (nornir's CI/CD tab + the dwarves standalone
   app). Wire-once, verified-everywhere.

## Explicitly NOT done here (and why)

- No crate created in `facett/` — facett is contended (korp + holger consumers,
  concurrent editors); creating it from this repo would collide. Per the mega-prompt
  hard constraint, this is a plan item only.
- `dwarves-gui/src/panel.rs` stays as the interim native render (additive — removing
  no working path) until the real component lands; the swap is API-compatible
  (`FacetApp` → `NativeDemo`/`WebDemo` unchanged).

## Extraction order (matrix §2)

`facett-brokkrview` is the **closest / first** in the extraction order
(dwarves → holger `facett-holger` → korp `facett-korp*` → de-INTERIM nornir-demo),
because the Elm core + headless contract + interim render already exist — only the
facett-side bridge crate is missing.

## As built (2026-07-12)

- **Crate:** `facett/facett-brokkrview` (workspace member). Deps: `egui` +
  `facett-core` + `serde`/`serde_json` only — **no `nornir` and no `dwarves` edge**
  (it mirrors the dwarves brokkr model shape so dwarves can depend on it without a
  cycle, the same self-contained trick `facett-brokkr` uses for the `brokkr.toml`
  schema).
- **Three surfaces over one model:** run **Roster** (mirrors `BrokkrModel`; Run
  button per pipeline + status chips + log), the **Tree** (`TreeFacet` hierarchy
  with the incremental disposition taxonomy tinted per step), and the **DAG**
  (`DagFacet`, nodes tinted by status, longest-path column layout).
- **Elm → Facet → Panel:** implements `facett_core::Elm`; `impl_facet_via_elm!`
  (custom_state_json form) bridges to `Facet`, and the `update_json` mutation path is
  routed into the Elm `update`, so it is a `Panel` via the blanket impl and drives
  headless through JSON (`panel::drive` → `state_json` asserts).
- **Data seam:** `BrokkrView::from_brokkr_state(&Brokkr::state_json())` adopts the
  dwarves roster wire (backend (2) native cross-host). The `dwarves-server` gRPC
  transport is **deferred** (gated on dwarves daemonizing), as this doc's §3 says.
- **Wiring:** `dwarves-gui` depends on `facett-brokkrview`; `ForgeView` is now a thin
  wrapper mounting `BrokkrView` over a dwarves fixture (build/test/bench/release/
  publish roster + the build pipeline's tree + DAG). The harness API is unchanged
  (`FacetApp` → `NativeDemo`/`WebDemo`, state hook `__dwarves_state`). The now-dead
  `facett-table` dep + the interim two-table render were removed (the swap this plan
  called for). `crates/dwarves` is untouched except this doc pointer in `brokkr/elm.rs`
  — the CLI's minimal-dependency footprint is preserved (no egui edge in the lib).
- **Tests:** 7 inline (update transitions, effect emission, run-finished folding into
  tree+DAG, `from_brokkr_state`) + 2 headless JSON-drive + 4 kittest render-smoke
  (all three surfaces + a real toggle click) + 2 in `dwarves-gui`.
- **Deferred:** a dedicated `nornir-robotui` scenario + testmatrix row exercising the
  pane in BOTH hosts (nornir + dwarves). The headless JSON drive covers the pane's
  behaviour now; the shared robotui/testmatrix row lands with `dwarves-server` (it
  needs a real host to embed into). A native frame-time bench arm for the pane is
  N/A here — the Phase-0 `facett.panel_frame` arm already measures the `Box<dyn
  Panel>` native-frame floor this pane rides.

The "Explicitly NOT done here" section above is now superseded: the crate WAS created
(a facett-owning gated agent did it, so no collision), and `dwarves-gui`'s interim
render was swapped for the shared component.