<!-- 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.