dwarves — standalone service, cross-embedding UI, and the extraction from nornir
Decided 2026-07-11. Answers: how does dwarves run standalone, how do nornir and dwarves each show the other's panels without duplicating code (even thin code), and should the UI be WASM?
1. End state: pure-nornir + pure-dwarves, cross-embedding over thin gRPC
- nornir = code's time-space dimensional warehouse (Iceberg + introspection).
- dwarves = the CI/CD forge (Brokkr assembles/signs/packs, Sindri shapes pipelines).
They will each want to see the other: nornir wants a CI/CD panel, dwarves wants warehouse panels. The rule is no duplicated code, even thin. Solution = wasm-component panels (see §2, the decided approach), with the facett component crates below as the enabling Elm seam + the interim native path:
- Every panel is one
impl Elm/impl Facetcrate (facett-coreelm.rs). - Both native egui hosts depend on that one crate and drop the component into a tab — the same crate renders in both apps. Zero duplication.
- The data under a cross-host panel rides the thin/fat gRPC split: dwarves' CI/CD panel embedded in nornir talks to
dwarves-serverover gRPC (thin); fat mode = in-process. No dwarves logic is copied into nornir — only the shared UI crate + a thin gRPC stub. - Test win: a shared component gets ONE
nornir-robotuiscenario + ONE testmatrix row that covers it in BOTH hosts. Wire-once, verified-everywhere — see [[wiring-coverage-fixlist]].
Crates:
facett-cicd(new) — the Elm component(s) for the CI/CD / release panels. Deps: egui + facett-core + a thindwarves-client(gRPC). Lives here in dwarves (or facett).- warehouse/release panes currently in
nornir/src/viz— reworked into shareable Elm component crates + a thinnornir-client, so dwarves can embed them.
2. THE decision: UI panels are WASM COMPONENTS loaded by every host (revised 2026-07-11)
Constellation-wide design + phased plan: this section is the seed. The full design — trait-first: the
Paneltrait is primary, with four backends (native in-process; native cross-host via a thin adapter, which is the primary app↔app embedding path and uses NO wasm; a wasm Component only when the browser is the target; headless for tests) — plus the WITpanelworld +basehost imports for the browser backend, the whole-constellation pane inventory, and the phased rollout — lives in workspace_nordisk/.nornir/wasm-ui-panels-design.md. Cross-embedding (dwarves⇄nornir⇄holger) is native via that thin adapter + the thin/fat gRPC split in §1 below; wasm is the browser escape hatch, not the cross-embedding mechanism.
Rickard's call, and the right one for a polyrepo constellation: a UI panel is built ONCE as a wasm component, and EVERY host loads that same artifact — the native egui fat client (via a wasm runtime, e.g. wasmtime) AND a web page (browser wasm engine). The panel's code stays in its home repo (nornir's panels stay in nornir); dwarves loads nornir's panel wasm at runtime WITHOUT source-depending on nornir. Precedent: znippy already runs wasm that calls back into a native "base" (multithread support via host imports) — same boundary shape.
Why this beats build-time crate-linking here: nordisk is polyrepo with independent release cadences. Crate-linking (§1) forces dwarves to pin + rebuild on nornir's UI-crate version. A wasm ARTIFACT decouples it — deploy the panel, hosts load it, no cross-repo rebuild. One artifact, three hosts (native-nornir, native-dwarves, web), code in one place.
Enabling seam = facett-Elm. A panel wasm component:
- exports
init(),update(msg_bytes),view() -> display_list_bytes(serialized eguiShapes / a light retained scene),state() -> json— andstate()+view()are the SAME serializationnornir-testmatrix+nornir-robotuialready consume, so headless test/drive falls out for free. - imports its "base" capabilities from the host (like znippy's threads): data access (
fetch(req)->resp→ native: in-proc/gRPC; web: gRPC-web), and heavy compute / threads (native wasmtime threads; web SharedArrayBuffer + COOP/COEP). Host feeds egui input events in, callsupdate, paints the returned display list into its egui frame (native) or canvas (web).
Honest costs (decided eyes-open):
- egui is immediate-mode → the boundary serializes a paint list. Fine for normal panels; for HEAVY scenes (helix ztube, big graphs) measure it — use damage/retained updates or keep those panels native until proven.
- native host needs a wasm runtime + a font/texture-atlas handshake across the boundary.
- define the ABI with the WASM Component Model / WIT (versioned, language-agnostic) — fits Brokkr's criteria.
Rollout: (1) define the WIT panel world (exports+imports above); (2) build ONE pilot panel as a wasm component + a native wasmtime host loader and PROVE the paint-list perf; (3) migrate panels; (4) crate-linking stays the interim for our own native-only sharing until the wasm ABI is proven. HTML/JS is rejected — panels stay Rust→wasm (JS only as a binding).
3. Standalone dwarves service — reuse, do not copy nornir's server
dwarves must run standalone (systemd service + gRPC server + thin client + agent), the way holger (first) and nornir (more tested) already do. Get it by REUSE:
- Skidbladnir = the service/systemd + KVM/container mechanism. dwarves registers its service through Skidbladnir like korp/nornir/holger — no duplicated systemd glue.
- Factor nornir's gRPC-server + thin-client + agent scaffold into a shared edda crate (
nornir-serviceorskidbladnir-adjacent), consumed by nornir, holger, and dwarves. dwarves stands up its server by depending on it, not by copying nornir'ssrc/bin/nornir-server.rs.
4. Extraction plan (Brokkr/Sindri + CI/CD panes → dwarves; remove from nornir)
- Move the
brokkrandsindricrates out of nornir into the dwarves workspace (they were already published from nornir — this is a repo move + repoint, additive per polyrepo rules). - Rip the CI/CD / release UI panes out of
nornir/src/vizinto afacett-cicdElm component crate. nornir keeps embedding it (thin → dwarves-server) so the release tab still shows in nornir, but the code lives in dwarves. - Standalone service for dwarves via Skidbladnir + the extracted edda service scaffold (§3).
- For now, remove dwarves/forge-specific build code from nornir. End state: pure-nornir (warehouse) + pure-dwarves (CI/CD), each embedding the other's panel as a shared facett component over thin gRPC.
- Both hosts compile their panels to WASM for web embedding (§2).
Sequencing note: do this AFTER the current fat release frees the repos; the brokkr/sindri move is a repo move + version-repoint, so it must not race an in-flight publish.