dwarves-gui/src/coverage.rs
//! **The 📐 Coverage tab — the coverage index.**
//!
//! dwarves' answer to *"is this actually finished?"*, and the UI surface of
//! [`dwarves::coverage`]. L3 says a feature is not done without a test AND a heavy
//! bench; this pane is where you see how far from that the constellation is.
//!
//! The pane owns **no coverage logic**. Every gap, every rank, every host verdict
//! comes from the `dwarves::coverage` engine, which `dwarves coverage` drives
//! through the identical [`dwarves::coverage::build`] call — so the tab and the
//! command line can never disagree about what a gap is. This file is the read seam
//! plus the render.
//!
//! ## Three views, kept structurally apart
//! * **Repos** — per repo: functions no test can reach, hot paths with no bench.
//! * **Bench integrity** — one table per HOST. `loki · 12 cores` and `oden · 32
//! cores` are not comparable, so they are never in the same table, and a
//! `<host>-preview` row is badged unpublishable. This separation is structural,
//! not stylistic: it is the failure this constellation keeps having to retract.
//! * **Cannot-go-red** — tests that have never once failed over changing code, and,
//! above them, the plain statement of how much history that verdict rests on.
//!
//! ## What the pane must never do
//! **Render an empty index as a clean bill of health.** Every view leads with the
//! engine's `honest_headline()`, which names the MISSING FEEDS by table. A repo the
//! warehouse never scanned shows `NOT SCANNED`, never `0 gaps`. A weak-test verdict
//! with no history shows the evidence note, never an empty "all good" list.
//!
//! ## Read seam
//! Two halves, both through the forge, both off the UI thread:
//!
//! * the CODE GRAPH — which fns exist, which a test reaches, the call sites arriving,
//! what each test stands on — is ONE question to nornir's code room
//! (`Knowledge.TestReach`, [`crate::room`]). The walk is the room's; nothing here or
//! in the engine walks an edge. Its answer's state leads the pane: a room that did
//! not compute draws its state and its sentence, and no repo row.
//! * heat, bench runs / results and test outcomes are warehouse tables over the thin
//! `Warehouse.Scan` seam, as before.
//!
//! The roster (the report's fixed denominator) is the workspace's member list as the
//! server registers it — a member the room has not consumed shows NOT SCANNED.
//!
//! ## No sample
//! With no reachable server the pane says UNAVAILABLE with the reason. It draws no
//! fixture: the demo index this file used to carry lives behind the `fixtures`
//! feature, which only the tests enable.
use std::collections::{BTreeMap, BTreeSet};
use dwarves::coverage::{build, rows::Table, CoverageIndex, GraphFeed, GRAPH_SURFACE, TABLES};
use facett_core::Facet;
use crate::room::{ForgeRoom, RoomAnswer, RoomSource};
/// Row cap per table on the thin read. The server honours a non-zero limit.
const SCAN_LIMIT: u32 = 800_000;
/// Seconds between warehouse-generation probes. A populate takes minutes; probing its
/// snapshot id every few seconds is prompt without being a poll loop.
const GENERATION_PROBE_S: f64 = 4.0;
/// One read of everything the index folds.
#[derive(Debug, Clone)]
pub struct Loaded {
/// The code room's answer to [`GRAPH_SURFACE`] — by state.
pub graph: RoomAnswer,
/// The warehouse tables ([`TABLES`]).
pub tables: BTreeMap<String, Table>,
/// The report's fixed denominator: the workspace's members as the server
/// registers them.
pub roster: Vec<String>,
}
/// Where the pane's index comes from. Injected so a test drives the pane over a
/// deterministic reading with no server.
///
/// `Send + Sync` because the pane runs [`load`](Self::load) on a WORKER thread — the
/// read is a room question plus a gRPC round trip per table and must never happen on
/// the frame that is trying to draw. The `Arc` the pane holds is what the worker clones.
pub trait CoverageSource: Send + Sync {
/// Read the room's answer and the tables — or the reason NOTHING could be read
/// (the server did not answer). A room that answered with a refusal is `Ok`: its
/// state is the reading.
fn load(&self) -> Result<Loaded, String>;
/// What to show in the header.
fn describe(&self) -> String;
}
/// The shipped source: the forge — nornir's code room for the graph, its warehouse
/// for the tables, its registry for the roster.
pub struct WarehouseCoverage {
pub endpoint: String,
pub token: String,
pub workspace: String,
}
impl WarehouseCoverage {
pub fn new(
endpoint: impl Into<String>,
token: impl Into<String>,
workspace: impl Into<String>,
) -> Self {
Self {
endpoint: endpoint.into(),
token: token.into(),
workspace: workspace.into(),
}
}
/// One table over the wire.
fn scan(&self, table: &str) -> Result<Table, String> {
nornir_warehouse_embed::scan_warehouse_table(
&self.endpoint,
&self.token,
&self.workspace,
table,
SCAN_LIMIT,
)
.map(|s| Table::new(s.columns, s.rows))
.map_err(|e| e.to_string())
}
}
impl CoverageSource for WarehouseCoverage {
fn load(&self) -> Result<Loaded, String> {
// The room's answer is the one read without which there is no surface at all.
// When the question never reached a reader (server down, bad credentials) THAT
// is the failure — an unreachable server must not look like an empty index.
let graph = ForgeRoom::new(&self.endpoint, &self.token, &self.workspace)
.ask(GRAPH_SURFACE, &serde_json::json!({}));
if graph.kind == crate::room::RoomState::NotAsked {
return Err(format!("[{}] {}", graph.state, graph.evidence_text()));
}
// Every table degrades on its own: a missing feed is REPORTED as missing by
// the engine, which is a different statement from "no gaps".
let tables = TABLES
.iter()
.map(|t| ((*t).to_string(), self.scan(t).unwrap_or_default()))
.collect();
// The denominator: the members the server registers for this workspace.
let roster =
nornir_warehouse_embed::get_warehouse(&self.endpoint, &self.token, &self.workspace)
.map(|w| w.members)
.unwrap_or_default();
Ok(Loaded {
graph,
tables,
roster,
})
}
fn describe(&self) -> String {
format!(
"code room + warehouse @ {} / {}",
self.endpoint, self.workspace
)
}
}
/// **A deterministic reading for the tests** (feature `fixtures`; no shipped build
/// enables it). Its SHAPE is the real 2026-07-22 reading of the nordisk warehouse, so
/// it exercises each finding class:
///
/// * `znippy` — benched, one arm, one fn a test reaches, one it does not.
/// * `korp` — in the room's answer but **never benched**.
/// * `bifrost` — a member, **not in the room's answer** at all.
/// * an `oden-preview` row next to an `oden` one (unpublishable vs publishable), and
/// a 4-core `ci` row that makes the repo multi-host.
#[cfg(any(test, feature = "fixtures"))]
pub struct FixtureCoverage;
#[cfg(any(test, feature = "fixtures"))]
impl FixtureCoverage {
fn table(cols: &[&str], rows: &[&[&str]]) -> Table {
Table::new(
cols.iter().map(|s| s.to_string()).collect(),
rows.iter()
.map(|r| r.iter().map(|s| s.to_string()).collect())
.collect(),
)
}
/// The room's answer, in the reader's own shape.
pub fn graph_body() -> serde_json::Value {
let f = |repo: &str,
module: &str,
name: &str,
file: &str,
line: u32,
reachable: bool,
fan_in: u32| {
serde_json::json!({
"repo": repo, "crate_name": repo, "module": module, "item_name": name, "fqn": format!("{module}::{name}"),
"kind": "fn", "visibility": "pub", "file": file, "line": line,
"test_code": false, "test_reachable": reachable, "fan_in": fan_in,
})
};
serde_json::json!({
"state": "complete",
"files": [{ "repo": "znippy", "file": "src/decode.rs" }],
"fns": [
f("znippy", "znippy::decode", "decode_block", "src/decode.rs", 40, true, 1),
f("znippy", "znippy::vault", "seal", "src/vault.rs", 12, false, 0),
f("korp", "korp::clip", "clip_poly", "src/clip.rs", 88, false, 1),
f("korp", "korp::io", "load_pbf", "src/io.rs", 17, false, 0),
],
"tests": [{
"repo": "znippy", "crate_name": "znippy", "module": "znippy::tests", "test_name": "roundtrip", "fqn": "znippy::tests::roundtrip",
"file": "src/decode.rs", "line": 300, "calls_out": 1, "names": 0, "reached": 1, "files": [0],
}],
"evidence": { "missing": [], "notes": ["calls through closures are not recorded: a lower bound"] },
"room": {
"workspace": "nordisk", "universe": false, "nodes": 4210, "edges_total": 9877,
"repos": [{ "repo": "korp", "seq": 4, "sha": "aaaaaaaaaabbbbbbbbbb" }, { "repo": "znippy", "seq": 7, "sha": "ccccccccccdddddddddd" }],
},
})
}
fn tables() -> BTreeMap<String, Table> {
let mut t: BTreeMap<String, Table> = BTreeMap::new();
t.insert(
"bench_runs".into(),
Self::table(
&[
"run_id",
"repo",
"ts_micros",
"date",
"version",
"machine",
"cores",
],
&[
&[
"r1",
"znippy",
"1784000000000000",
"2026-07-22",
"0.9.6",
"oden",
"32",
],
&[
"r2",
"znippy",
"1784000000000000",
"2026-07-22",
"0.9.6",
"oden-preview",
"32",
],
&[
"r3",
"znippy",
"1782000000000000",
"2026-07-08",
"0.9.0",
"ci",
"4",
],
],
),
);
t.insert(
"bench_results".into(),
Self::table(
&["run_id", "result_name", "metric_name", "metric_value"],
&[
&["r1", "znippy.decode_mt", "mb_s", "808"],
&["r2", "znippy.decode_mt", "mb_s", "790"],
&["r3", "znippy.legacy_arm", "mb_s", "410"],
],
),
);
t.insert(
"test_outcomes".into(),
Self::table(
&["run_id", "test_name", "passed"],
&[
&["r1", "znippy.decode_mt", "true"],
&["r2", "znippy.decode_mt", "true"],
&["r3", "znippy.decode_mt", "true"],
],
),
);
// `test_results` / `coverage_fn` / `surface_coverage` / `git_heat_facts` are
// left ABSENT on purpose: the reading must exercise a MISSING FEED, which is
// the real state of the warehouse and the thing the pane must never hide.
t
}
}
#[cfg(any(test, feature = "fixtures"))]
impl CoverageSource for FixtureCoverage {
fn load(&self) -> Result<Loaded, String> {
Ok(Loaded {
graph: RoomAnswer::from_body(GRAPH_SURFACE, &self.describe(), Self::graph_body()),
tables: Self::tables(),
roster: vec!["bifrost".into(), "korp".into(), "znippy".into()],
})
}
fn describe(&self) -> String {
"fixture (tests) — a benched repo, a never-benched repo, an unscanned repo, a preview row"
.to_string()
}
}
/// Which slice of the index the pane is showing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum View {
/// Per-repo test + bench coverage.
Repos,
/// The host-partitioned bench tables.
Integrity,
/// The cannot-go-red detector.
Weak,
/// What the warehouse actually held, per table.
Feeds,
}
impl View {
fn as_str(self) -> &'static str {
match self {
View::Repos => "repos",
View::Integrity => "integrity",
View::Weak => "weak",
View::Feeds => "feeds",
}
}
fn parse(s: &str) -> Option<Self> {
match s {
"repos" => Some(View::Repos),
"integrity" => Some(View::Integrity),
"weak" => Some(View::Weak),
"feeds" => Some(View::Feeds),
_ => None,
}
}
}
/// The **📐 Coverage** pane.
pub struct CoveragePane {
title: String,
/// The read seam. `Arc` so the off-thread worker clones it — the SAME shape the
/// Dupes pane's `reader` and the Lenses pane's source have.
source: std::sync::Arc<dyn CoverageSource>,
/// A read in flight (worker receiver + when it started). The frame polls it; the
/// robot seam blocks on it. The read runs OFF the egui thread so a cold
/// `call_edges` scan (half a million rows) never freezes the window.
pending: Option<(
std::sync::mpsc::Receiver<Result<Loaded, String>>,
std::time::Instant,
)>,
/// `None` until the first scan — the pane opens on an explicit, honest
/// "not scanned yet" rather than blocking the shell on a warehouse read.
index: Option<CoverageIndex>,
/// The code room's answer behind `index` — drawn as the callout (state, evidence,
/// the room it was read from) above every view.
graph: Option<RoomAnswer>,
/// Honest failure text from the last attempt (`None` ⇒ no failure).
error: Option<String>,
view: View,
/// Show only repos that have a gap (default: show all, so a clean repo is
/// visibly clean rather than merely absent).
gaps_only: bool,
/// Expanded repo.
selected: Option<String>,
/// The GENERATION this pane last read at, and the background probe that moves it.
/// The pane reads itself when the warehouse's snapshot id changes — never on a
/// press. `gen` is `None` for a fixture/injected source, which has no warehouse to
/// probe: that pane simply does its one cold read (LAW 5 / `crate::generation`).
watch: crate::generation::Watch,
gen: Option<crate::generation::WarehouseGeneration>,
}
impl CoveragePane {
/// The shipped pane, bound to the selected workspace over the thin gRPC seam.
pub fn live(
endpoint: impl Into<String>,
token: impl Into<String>,
workspace: impl Into<String>,
) -> Self {
let (endpoint, token, workspace) = (endpoint.into(), token.into(), workspace.into());
let mut me = Self::with_source(Box::new(WarehouseCoverage::new(
endpoint.clone(),
token.clone(),
workspace.clone(),
)));
me.gen = Some(crate::generation::WarehouseGeneration::new(
endpoint,
token,
workspace,
GENERATION_PROBE_S,
));
me
}
/// The pane over an injected source (the robot-test seam). Taken as a `Box` (the
/// caller's spelling is unchanged) and held as an `Arc`, which is what the read
/// worker clones.
pub fn with_source(source: Box<dyn CoverageSource>) -> Self {
Self {
title: "Coverage".to_string(),
source: source.into(),
pending: None,
index: None,
graph: None,
error: None,
view: View::Repos,
gaps_only: false,
selected: None,
// Interval 0: the warehouse probe below is what rate-limits; this watch only
// compares.
watch: crate::generation::Watch::new(0.0),
gen: None,
}
}
/// The pane over the tests' deterministic reading (feature `fixtures`).
#[cfg(any(test, feature = "fixtures"))]
pub fn fixture() -> Self {
Self::with_source(Box::new(FixtureCoverage))
}
/// **Start the index read OFF the egui thread.** The worker owns a clone of the
/// source; the frame keeps painting and [`Self::poll`] lands the result. A scan
/// started while one is in flight is not a second round trip.
///
/// MEASURED before this was off-thread (2026-08-25, this box, `SlowSource(600 ms)`
/// standing in for the real per-table gRPC scans): the frame carrying the Scan click
/// held for **700 ms** and the window could not paint for the whole read.
pub fn scan(&mut self) {
if self.pending.is_some() {
return;
}
let source = std::sync::Arc::clone(&self.source);
let (tx, rx) = std::sync::mpsc::channel();
std::thread::Builder::new()
.name("coverage-scan".into())
.spawn(move || {
let _ = tx.send(source.load());
})
.expect("spawn coverage scan worker");
self.pending = Some((rx, std::time::Instant::now()));
}
/// Non-blocking: land the in-flight read if the worker finished. Called once/frame.
pub fn poll(&mut self) {
let Some((rx, _)) = &self.pending else { return };
match rx.try_recv() {
Ok(res) => {
self.pending = None;
self.land_scan(res);
}
Err(std::sync::mpsc::TryRecvError::Empty) => {}
Err(std::sync::mpsc::TryRecvError::Disconnected) => {
self.pending = None;
// A dead worker is a FAILED REFRESH, which by this pane's own contract
// keeps the previous reading and records the reason. Blanking a good
// index because a thread died would be the very failure the pane exists
// to prevent.
self.error = Some("the coverage read worker died before it answered".into());
}
}
}
/// Blocking: wait for the in-flight read. The ROBOT seam (`update_json`) uses this
/// so a scripted scan is complete when the call returns; the window never blocks.
pub fn wait(&mut self) {
let Some((rx, _)) = self.pending.take() else {
return;
};
let res = rx
.recv()
.unwrap_or_else(|_| Err("the coverage read worker died before it answered".into()));
self.land_scan(res);
}
/// Whether a read is in flight.
pub fn is_pending(&self) -> bool {
self.pending.is_some()
}
/// Fold a landed read into the index. On failure the pane keeps the previous result
/// (if any) and records the reason — a failed refresh never silently blanks a good
/// reading.
fn land_scan(&mut self, res: Result<Loaded, String>) {
match res {
Ok(read) => {
let borrowed: BTreeMap<&str, &Table> =
read.tables.iter().map(|(k, v)| (k.as_str(), v)).collect();
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_micros() as i64)
.unwrap_or(0);
self.index = Some(build(
self.source.describe(),
&borrowed,
&GraphFeed::from(&read.graph),
&read.roster,
&BTreeSet::new(),
now,
dwarves::coverage::DEFAULT_HOT_PATHS,
dwarves::coverage::DEFAULT_WORST,
));
self.graph = Some(read.graph);
self.error = None;
}
Err(e) => self.error = Some(e),
}
}
/// The one-line status. Never claims green without the evidence: before a scan
/// it says so, after a failure it says why, and otherwise it is the ENGINE's own
/// honest headline (which names the missing feeds).
pub fn status(&self) -> String {
if let Some(e) = &self.error {
return format!("UNAVAILABLE — {e}");
}
match &self.index {
// A read in flight is not "nothing here" and not "no gaps" — it is a third
// state, and it says so rather than leaving the operator to guess.
None if self.pending.is_some() => "reading the warehouse — not scanned yet".to_string(),
// NOT "press Scan" — there is no button, and there never should have been.
// This is the pre-first-read state: the generation probe has not answered
// yet, so the pane has not been told what to read.
None => "waiting for the warehouse generation — no reading yet".to_string(),
Some(i) => i.honest_headline(),
}
}
/// The repo rows the current filter shows.
fn visible(&self) -> Vec<&dwarves::coverage::RepoCoverage> {
let Some(i) = &self.index else {
return Vec::new();
};
i.repos
.iter()
.filter(|r| {
!self.gaps_only || r.unscanned || r.test.missing > 0 || r.bench.unbenched > 0
})
.collect()
}
}
/// **One repo row, as text — the ONE spelling of it.**
///
/// The pane paints this and the copy button hands this over, so what lands on the
/// clipboard cannot drift from what the operator was reading. Two `format!`s of the
/// same columns is exactly how a copied table ends up with different widths, a stale
/// column, or a row the screen does not have (LAW 5).
///
/// An unscanned repo says so in place of numbers, here as on screen: a row of zeros
/// would read as "measured, and it is zero", which is the opposite of the truth.
fn repo_row_text(r: &dwarves::coverage::RepoCoverage) -> String {
if r.unscanned {
return format!(
"{:<16} the code room's answer holds no fn of this repo",
r.repo
);
}
format!(
"{:<16} {:>7} {:>7} {:>5.0}% {:>7} {:>5} {:>6}",
r.repo,
r.test.surface,
r.test.covered,
r.test.covered_frac() * 100.0,
r.test.missing,
r.bench.hot_paths,
r.bench.unbenched,
)
}
/// The whole visible listing as one clipboard payload: the header the pane paints,
/// then every row it paints, in the order it paints them.
fn repos_listing(header: &str, rows: &[dwarves::coverage::RepoCoverage]) -> String {
std::iter::once(header.to_string())
.chain(rows.iter().map(repo_row_text))
.collect::<Vec<_>>()
.join("\n")
}
fn chip(ui: &mut egui::Ui, text: &str, color: egui::Color32) {
ui.label(
egui::RichText::new(format!(" {text} "))
.small()
.strong()
.color(egui::Color32::WHITE)
.background_color(color),
);
}
/// Red for a hard gap, amber for a caveat, teal for clean. Never hue-codes a
/// *magnitude* — the numbers do that.
fn gap_color(has_gap: bool) -> egui::Color32 {
if has_gap {
egui::Color32::from_rgb(0xc6, 0x28, 0x28)
} else {
egui::Color32::from_rgb(0x00, 0x69, 0x5c)
}
}
impl Facet for CoveragePane {
fn title(&self) -> &str {
&self.title
}
fn ui(&mut self, ui: &mut egui::Ui) {
// Land an in-flight read, if the worker finished, and keep asking for frames
// while one is running so the "reading" line and any spinner stay live.
self.poll();
if self.pending.is_some() {
ui.ctx().request_repaint();
}
// GENERATION, not a press. The pane reads itself the first time it is drawn
// (a cold watch is a move) and again whenever the workspace's snapshot id
// changes — i.e. when a populate has actually landed new rows. An unchanged
// warehouse costs one small RPC every `GENERATION_PROBE_S` and no read at all.
//
// NO Scan button (owner 2026-08-25: SCAN is not a user action). A control
// labelled `Scan` teaches that data is something you ask for, which is the
// lesson `lens_chain` ("lenses are jobs, results are tables") abolished.
let now_s = ui.input(|i| i.time);
let g = match self.gen.as_mut() {
Some(p) => p.poll(now_s),
// No warehouse behind this source (fixture / injected): its generation is
// constant, so the cold read is the only read.
None => Some("fixture".to_string()),
};
if let Some(g) = g {
if self.watch.saw(&g) {
self.scan();
}
}
ui.horizontal(|ui| {
ui.heading("📐 coverage index");
ui.checkbox(&mut self.gaps_only, "gaps only");
});
// The view toggle sits directly under the heading, ABOVE every line that comes
// and goes (the "reading" line, the code-graph callout, MISSING FEED): a control
// must not move under the pointer when a read lands.
ui.horizontal(|ui| {
for v in [View::Repos, View::Integrity, View::Weak, View::Feeds] {
if ui.selectable_label(self.view == v, v.as_str()).clicked() {
self.view = v;
}
}
});
// A read IN FLIGHT is named on the surface. The previous reading (if any) stays
// painted underneath — a refresh never blanks a good index.
if let Some((_, started)) = &self.pending {
ui.horizontal(|ui| {
ui.spinner();
ui.label(
egui::RichText::new(format!(
"reading the warehouse — {:.1}s (the scan runs off the UI thread; \
the reading below, if any, is the previous one)",
started.elapsed().as_secs_f32()
))
.small(),
);
});
}
ui.label(egui::RichText::new(self.status()).monospace());
ui.label(egui::RichText::new(format!("source: {}", self.source.describe())).small());
// What the code half rests on: the room's state, its evidence, the room it was
// read from — above every view, like the MISSING FEEDS callout below.
if let Some(g) = &self.graph {
crate::room::callout(ui, "code graph", g);
}
// The MISSING FEEDS callout sits ABOVE every view — an empty table must be
// impossible to miss, because "no rows" read as "no gaps" is the failure this
// whole pane exists to end.
if let Some(i) = &self.index {
let missing = i.missing_feeds();
if !missing.is_empty() {
ui.horizontal_wrapped(|ui| {
chip(ui, "MISSING FEED", egui::Color32::from_rgb(0xc6, 0x28, 0x28));
ui.label(
egui::RichText::new(format!(
"{} — no rows in the warehouse. This is a missing producer, NOT a clean bill of health.",
missing.join(", ")
))
.small(),
);
});
}
}
ui.separator();
ui.add_space(4.0);
let Some(index) = &self.index else {
ui.label(egui::RichText::new("no index yet — the pane asks nornir's code room and reads its warehouse itself, on open and whenever the workspace snapshot moves; it never re-scans source. Nothing to press.").small());
return;
};
match self.view {
View::Repos => {
let rows: Vec<_> = self.visible().into_iter().cloned().collect();
// ── WHY A BUTTON AND NOT A CopyableBlock ────────────────────────
//
// stinky `kopiera-2`: N labels in a clipped or scrolled area lose any
// selection that reaches past the fold, because egui 0.35 drops a
// cross-label selection the moment either end leaves the visible rect.
// The cure for a LOG is `CopyableBlock` — one text widget.
//
// This table is not a log. Its rows are `selectable_label`s that PICK a
// repo, and folding them into one `TextEdit` would take the clicking
// away in order to give the copying. So the rows stay widgets and the
// shared `copy_button` hands over the whole listing — header included,
// in the same column widths the pane paints, so what lands on the
// clipboard is what the operator was looking at.
let header = format!(
"{:<16} {:>7} {:>7} {:>6} {:>7} {:>5} {:>6}",
"REPO", "FNS", "COVERED", "COV%", "NO-TEST", "HOT", "UNBEN"
);
ui.horizontal(|ui| {
ui.label(egui::RichText::new(&header).small().monospace().strong());
facett_app::widgets::copy_button(ui, &repos_listing(&header, &rows));
});
egui::ScrollArea::vertical()
.max_height(320.0)
.id_salt("coverage-repos")
.show(ui, |ui| {
for r in &rows {
ui.horizontal(|ui| {
if r.unscanned {
chip(
ui,
"NOT SCANNED",
egui::Color32::from_rgb(0x6a, 0x1b, 0x9a),
);
ui.monospace(repo_row_text(r));
return;
}
if r.bench.never_benched() {
chip(
ui,
"NEVER BENCHED",
egui::Color32::from_rgb(0xc6, 0x28, 0x28),
);
}
let clicked = ui
.selectable_label(
self.selected.as_deref() == Some(r.repo.as_str()),
egui::RichText::new(repo_row_text(r)).monospace(),
)
.clicked();
if clicked {
self.selected = Some(r.repo.clone());
}
});
if self.selected.as_deref() == Some(r.repo.as_str()) && !r.unscanned {
ui.indent(&r.repo, |ui| {
ui.label(
egui::RichText::new("un-benched hot paths (hottest first)")
.small()
.strong()
.color(gap_color(r.bench.unbenched > 0)),
);
for h in r.bench.worst.iter().take(8) {
ui.label(
egui::RichText::new(format!(
" {:<40} score {:.2} fan-in {:<5} churn90d {}",
h.module, h.score, h.fan_in, h.commits_90d
))
.small()
.monospace(),
);
}
ui.label(
egui::RichText::new("most load-bearing untested fns")
.small()
.strong()
.color(gap_color(r.test.missing > 0)),
);
for u in r.test.worst.iter().take(8) {
ui.label(
egui::RichText::new(format!(
" {:<52} fan-in {:<5} {}:{}",
u.fqn, u.fan_in, u.file, u.line
))
.small()
.monospace(),
);
}
});
}
}
});
}
View::Integrity => {
ui.label(
egui::RichText::new(
"ONE TABLE PER HOST — a 12-core loki number and a 32-core oden number are \
not comparable and are never merged here.",
)
.small(),
);
egui::ScrollArea::vertical().max_height(300.0).id_salt("coverage-integrity").show(ui, |ui| {
for t in &index.integrity.tables {
ui.horizontal(|ui| {
for d in &t.defects {
chip(ui, d.tag(), egui::Color32::from_rgb(0xc6, 0x28, 0x28));
}
if t.defects.is_empty() {
chip(ui, "publishable", gap_color(false));
}
ui.monospace(format!(
"{:<14} {:<14} {:>3} cores {:>3} runs {:>3} arms {:>4}d old",
t.repo,
t.machine,
t.cores,
t.runs,
t.arms.len(),
if t.age_days == i64::MAX { -1 } else { t.age_days },
));
});
}
for (repo, hosts) in &index.integrity.multi_host_repos {
ui.label(
egui::RichText::new(format!(
"! {repo} was measured on {} different hosts ({}) — two trend lines, never one",
hosts.len(),
hosts.join(" vs ")
))
.small()
.color(egui::Color32::from_rgb(0xc6, 0x28, 0x28)),
);
}
for (arm, hosts) in &index.integrity.host_split_arms {
ui.label(
egui::RichText::new(format!(
"! arm {arm} has rows from {} — NOT COMPARABLE",
hosts.join(" and ")
))
.small()
.color(egui::Color32::from_rgb(0xc6, 0x28, 0x28)),
);
}
});
}
View::Weak => {
// The evidence note comes FIRST: a verdict is never shown without the
// statement of how much history it rests on.
ui.label(
egui::RichText::new(&index.weak.evidence_note)
.small()
.monospace(),
);
ui.add_space(4.0);
if !index.weak.has_usable_history() {
ui.label(
egui::RichText::new(
"No usable outcome history. This is a MISSING FEED, not a verdict of \
'no weak tests' — wire `nornir test` into the warehouse and this view \
fills in.",
)
.small()
.color(egui::Color32::from_rgb(0xc6, 0x28, 0x28)),
);
}
egui::ScrollArea::vertical()
.max_height(300.0)
.id_salt("coverage-weak")
.show(ui, |ui| {
for w in &index.weak.never_red {
ui.horizontal(|ui| {
chip(
ui,
"CANNOT-GO-RED",
egui::Color32::from_rgb(0xc6, 0x28, 0x28),
);
ui.monospace(format!(
"{:<14} {} ({} runs / {} versions, 0 failures)",
w.repo, w.test_name, w.runs, w.versions
));
});
}
});
}
View::Feeds => {
egui::ScrollArea::vertical()
.max_height(300.0)
.id_salt("coverage-feeds")
.show(ui, |ui| {
for f in &index.feeds {
ui.horizontal(|ui| {
if f.missing {
chip(ui, "MISSING", egui::Color32::from_rgb(0xc6, 0x28, 0x28));
} else {
chip(ui, "ok", gap_color(false));
}
ui.monospace(format!(
"{:<18} {:>9} rows {:>3} repos",
f.table, f.rows, f.repos
));
});
}
});
}
}
}
fn state_json(&self) -> serde_json::Value {
let mut obj = serde_json::Map::new();
obj.insert("kind".into(), serde_json::json!("coverage"));
obj.insert("title".into(), serde_json::json!(self.title));
obj.insert("source".into(), serde_json::json!(self.source.describe()));
obj.insert("status".into(), serde_json::json!(self.status()));
obj.insert("scanned".into(), serde_json::json!(self.index.is_some()));
// The in-flight oracle a robot reads to tell "still reading" from "read nothing".
obj.insert("pending".into(), serde_json::json!(self.pending.is_some()));
obj.insert("error".into(), serde_json::json!(self.error));
obj.insert("view".into(), serde_json::json!(self.view.as_str()));
obj.insert("gaps_only".into(), serde_json::json!(self.gaps_only));
obj.insert("selected".into(), serde_json::json!(self.selected));
obj.insert("visible".into(), serde_json::json!(self.visible().len()));
// The whole engine index, verbatim — the pane adds no numbers of its own, so
// a robot asserting on the pane is asserting on the engine's RESULT.
obj.insert(
"index".into(),
self.index
.as_ref()
.map(CoverageIndex::state_json)
.unwrap_or(serde_json::Value::Null),
);
// The room's answer behind the index: its state, evidence, the room it names.
obj.insert(
"room".into(),
self.graph
.as_ref()
.map(RoomAnswer::state_json)
.unwrap_or(serde_json::Value::Null),
);
serde_json::Value::Object(obj)
}
fn update_json(&mut self, msg_json: &str) {
// The remote seam: `"scan"` / `{"scan":true}` runs the index,
// `{"view":"integrity"}` switches view, `{"select":"korp"}` expands a repo,
// `{"gaps_only":true}` filters. The SAME messages the buttons above drive.
if msg_json.trim() == "\"scan\"" {
self.scan();
self.wait();
return;
}
let Ok(v) = serde_json::from_str::<serde_json::Value>(msg_json) else {
return;
};
if v.get("scan").and_then(|b| b.as_bool()).unwrap_or(false) {
self.scan();
self.wait();
}
if let Some(t) = v.get("view").and_then(|t| t.as_str()) {
if let Some(f) = View::parse(t) {
self.view = f;
}
}
if let Some(s) = v.get("select").and_then(|s| s.as_str()) {
self.selected = (!s.is_empty()).then(|| s.to_string());
}
if let Some(b) = v.get("gaps_only").and_then(|b| b.as_bool()) {
self.gaps_only = b;
}
}
}
/// The shipped Coverage tab, bound to the SELECTED workspace. The roster is the
/// server's member list for it (read with the index), so a member the room has not
/// consumed shows as NOT SCANNED rather than vanishing from the denominator.
pub fn coverage_pane(endpoint: &str, token: &str, workspace: &str) -> CoveragePane {
CoveragePane::live(endpoint, token, workspace)
}
#[cfg(test)]
mod tests {
use super::*;
/// **A source that answers correctly, just not instantly.**
///
/// The stand-in for what the SHIPPED source really does: `WarehouseCoverage::load`
/// runs one `nornir_warehouse_embed::scan_warehouse_table` per table in
/// [`TABLES`] — a gRPC round trip each in the thin build, an embedded skade read in
/// the fat one. Neither is instant, and a cold `call_edges` (half a million rows for
/// the constellation) is seconds.
///
/// Sleeping is the honest model of that: what the timing test measures is WHERE the
/// wait happens, not whether the read works.
struct SlowSource(std::time::Duration);
impl CoverageSource for SlowSource {
fn load(&self) -> Result<Loaded, String> {
std::thread::sleep(self.0);
FixtureCoverage.load()
}
fn describe(&self) -> String {
"slow warehouse (test) — answers, but not this frame".to_string()
}
}
/// How long [`SlowSource`] holds its answer. Comfortably longer than a 60 fps
/// frame budget (16 ms), so "the frame waited for the read" is not a timing
/// coincidence on a loaded box.
const SLOW: std::time::Duration = std::time::Duration::from_millis(600);
/// **RED-when-broken: the pane READS ITSELF on open, and the opening frame comes
/// back before the warehouse answers.**
///
/// There is no Scan button any more (owner 2026-08-25: SCAN is not a user action),
/// so the read is started by the GENERATION drive on the first frame the pane is
/// drawn. That makes this assertion strictly stronger than the click version it
/// replaces: a synchronous read would now hang the window on OPEN, with nobody
/// having pressed anything.
///
/// Driven through the REAL widget tree of the shipped pane (egui_kittest), against a
/// source that holds its answer for [`SLOW`]. What is asserted is the wall-clock of
/// the frame that starts the read, plus the state the pane publishes to a robot
/// (`pending`) — not the presence of a channel.
///
/// MEASURED before the conversion: `Coverage::scan` called `self.source.load()`
/// straight from `Facet::ui`, so the frame held for the whole read and the window
/// did not paint — read by the operator as "it hangs".
#[test]
fn the_pane_reads_itself_on_open_without_holding_the_frame() {
use egui_kittest::kittest::Queryable;
use egui_kittest::Harness;
let pane = std::cell::RefCell::new(CoveragePane::with_source(Box::new(SlowSource(SLOW))));
let mut h = Harness::builder()
.with_size(egui::vec2(1100.0, 700.0))
.build_ui(|ui| {
pane.borrow_mut().ui(ui);
});
// ── THE OPENING FRAME, timed. Nobody pressed anything: the generation watch is
// cold, which counts as a move, so this frame STARTS the read.
let t0 = std::time::Instant::now();
h.step();
let held = t0.elapsed();
assert!(
held < SLOW,
"the opening frame held for {held:?} — the source needs {SLOW:?} to answer, \
so the warehouse read ran ON the egui thread and the window could not paint \
while it ran. With no button to press, that is a hang on OPEN."
);
assert_eq!(
pane.borrow().state_json()["pending"],
serde_json::json!(true),
"the opening frame must START the read and hand the frame back — state {}",
pane.borrow().state_json()
);
// And it really was self-started: no PRESSABLE widget in the tree could have
// started it. Role-filtered, because the pane's own prose legitimately contains
// the word "scanned" — it is the control that must be gone, not the vocabulary.
assert!(
h.query_all(
egui_kittest::kittest::By::new()
.role(accesskit::Role::Button)
.label_contains("Scan")
)
.next()
.is_none(),
"the pane must carry no Scan control — the read is the generation's job"
);
assert_eq!(
pane.borrow().state_json()["scanned"],
serde_json::json!(false),
"not scanned yet — the source is still holding its answer"
);
// ── NEVER BLANK WHILE IT LOADS. The pane must say what it is doing on the
// surface, not go quiet for the duration of the read.
assert!(
h.query_all_by_label_contains("reading the warehouse")
.next()
.is_some(),
"a pane with a read in flight must SAY SO on the rendered surface"
);
// ── keep painting; the worker lands the result. `step` in a pump, never
// `run()`, which panics on a frame that keeps requesting repaints.
let mut landed = false;
for _ in 0..600 {
h.step();
if pane.borrow().state_json()["pending"] == serde_json::json!(false) {
landed = true;
break;
}
std::thread::sleep(std::time::Duration::from_millis(10));
}
assert!(landed, "the read must land within 6 s");
let js = pane.borrow().state_json();
assert_eq!(
js["scanned"],
serde_json::json!(true),
"…and the index is the worker's"
);
assert_eq!(js["error"], serde_json::Value::Null);
assert_eq!(
js["index"]["repos_total"], 3,
"the SAME index the inline read built"
);
}
/// **The never-blank promise survives the conversion.** A refresh started over a
/// slow source must not clear the reading already on screen: a good index stays
/// visible, and stays visible for every frame the new read is in flight.
#[test]
fn a_refresh_in_flight_never_blanks_the_reading_already_on_screen() {
let mut p = CoveragePane::fixture();
p.update_json("\"scan\"");
let good = p.state_json()["index"]["repos_scanned"].clone();
assert_ne!(good, serde_json::Value::Null, "a reading to protect");
p.source = std::sync::Arc::new(SlowSource(SLOW));
p.scan();
assert!(p.is_pending(), "the refresh is off-thread");
// Every frame between the start and the landing keeps the old reading.
for _ in 0..10 {
p.poll();
assert_eq!(
p.state_json()["index"]["repos_scanned"],
good,
"a refresh in flight must never blank the previous reading"
);
std::thread::sleep(std::time::Duration::from_millis(10));
}
p.wait();
assert_eq!(p.state_json()["index"]["repos_scanned"], good);
assert_eq!(p.state_json()["pending"], serde_json::json!(false));
}
/// A source that fails, so the honest-unavailable path is drivable.
struct DeadSource;
impl CoverageSource for DeadSource {
fn load(&self) -> Result<Loaded, String> {
Err("[unreachable] the server did not answer: connect to http://127.0.0.1:0".into())
}
fn describe(&self) -> String {
"code room + warehouse @ http://127.0.0.1:0".into()
}
}
/// A source whose ROOM refuses (nothing consumed) while its warehouse answers.
struct NotConsumed;
impl CoverageSource for NotConsumed {
fn load(&self) -> Result<Loaded, String> {
let mut read = FixtureCoverage.load()?;
// a refusal body that (wrongly) still carries rows: they must not be folded
let mut body = FixtureCoverage::graph_body();
body["state"] = serde_json::json!("not_consumed");
body["refused"] = serde_json::json!(true);
body["note"] =
serde_json::json!("code room of `nordisk`: no repo has a consumer offset yet");
read.graph = RoomAnswer::from_body(GRAPH_SURFACE, "test", body);
Ok(read)
}
fn describe(&self) -> String {
"a workspace nothing was consumed into (test)".into()
}
}
/// **RED-when-broken.** A room that refuses draws its STATE: no repo is scanned,
/// no fn count exists, the status leads with the room's own sentence — while the
/// bench half, which is warehouse rows, is still reported.
#[test]
fn a_room_that_refuses_is_a_state_and_no_repo_number() {
let mut p = CoveragePane::with_source(Box::new(NotConsumed));
p.update_json("\"scan\"");
let js = p.state_json();
assert_eq!(js["room"]["state"], "not_consumed");
assert_eq!(js["room"]["measured"], false);
assert_eq!(
js["room"]["evidence"],
"code room of `nordisk`: no repo has a consumer offset yet"
);
assert!(
js["index"]["graph"]["fns"].is_null(),
"rows of a refusal are not folded, and not counted"
);
assert!(
js["index"]["repos_scanned"].is_null() && js["index"]["test"].is_null(),
"no number for a graph nobody read: {}",
js["index"]
);
assert!(
js["status"]
.as_str()
.unwrap()
.starts_with("CODE GRAPH NOT READ [not_consumed]: code room of `nordisk`"),
"{}",
js["status"]
);
assert_eq!(
js["index"]["repos_benched"], 1,
"the bench runs are warehouse rows and are still read"
);
}
/// **ROBOT SCENARIO — the whole pane driven over the remote seam, asserting a
/// RESULT.**
///
/// `state_json` / `update_json` only (no egui): Scan → korp is named as NEVER
/// BENCHED, bifrost as NOT SCANNED, the preview row is unpublishable, and the
/// missing feeds are listed. Every assertion is on a number the engine computed,
/// never on a literal `true`.
#[test]
fn the_pane_scans_and_reports_the_real_gap_shapes() {
let mut p = CoveragePane::fixture();
// Before the scan the pane is HONESTLY empty, not green.
let before = p.state_json();
assert_eq!(before["scanned"], false);
assert_eq!(before["index"], serde_json::Value::Null);
assert!(
before["status"]
.as_str()
.unwrap()
.contains("no reading yet"),
"the pane opens honestly un-read — and says so WITHOUT telling anyone to press \
a button that no longer exists: {}",
before["status"]
);
p.update_json("\"scan\"");
let js = p.state_json();
assert_eq!(js["scanned"], true);
assert_eq!(js["error"], serde_json::Value::Null);
let idx = &js["index"];
assert_eq!(idx["repos_total"], 3, "the roster is the denominator");
assert_eq!(
idx["repos_scanned"], 2,
"bifrost is in the roster with no rows"
);
assert_eq!(idx["repos_benched"], 1, "only znippy has a bench run");
// korp is scanned but NEVER benched — named, not merely absent.
let never: Vec<String> =
serde_json::from_value(idx["bench"]["never_benched_repos"].clone()).unwrap();
assert!(never.contains(&"korp".to_string()), "{never:?}");
assert!(never.contains(&"bifrost".to_string()));
// The empty tables are MISSING FEEDS and the headline says so.
let missing: Vec<String> = serde_json::from_value(idx["missing_feeds"].clone()).unwrap();
assert!(missing.contains(&"test_results".to_string()));
assert!(missing.contains(&"coverage_fn".to_string()));
assert!(js["status"].as_str().unwrap().contains("MISSING FEEDS"));
// Hosts are partitioned: oden / oden-preview / ci are three tables, not one.
assert_eq!(idx["integrity"]["host_tables"], 3);
assert!(idx["integrity"]["defective_tables"].as_u64().unwrap() >= 2);
let multi: Vec<String> =
serde_json::from_value(idx["integrity"]["multi_host_repos"].clone()).unwrap();
assert!(
multi.contains(&"znippy".to_string()),
"ci + oden ⇒ two trend lines"
);
// A real test-coverage result, not a tautology: the room says a test reaches
// znippy::decode::decode_block and not znippy::vault::seal or either korp fn.
assert_eq!(idx["test"]["covered"], 1);
assert_eq!(idx["test"]["missing"], 3);
// …and the pane says which room said so.
assert_eq!(js["room"]["state"], "complete");
assert_eq!(js["room"]["room"], "workspace nordisk · korp@4 (aaaaaaaaaa), znippy@7 (cccccccccc) · 4210 nodes · 9877 edges");
assert_eq!(
(idx["graph"]["fns"].as_u64(), idx["graph"]["tests"].as_u64()),
(Some(4), Some(1))
);
}
/// Within-pane interaction, asserted on the pane's own oracles: the view switch,
/// the gaps-only filter and the repo expansion all move `state_json`.
#[test]
fn the_pane_controls_move_its_own_state_oracle() {
let mut p = CoveragePane::fixture();
p.update_json("\"scan\"");
assert_eq!(p.state_json()["view"], "repos");
assert_eq!(
p.state_json()["visible"],
3,
"all three repos show by default"
);
p.update_json(r#"{"gaps_only":true}"#);
// Every fixture repo HAS a gap, so the filter must not change the count — the
// discriminating case is asserted below by construction of the fixture.
assert_eq!(p.state_json()["gaps_only"], true);
p.update_json(r#"{"view":"integrity"}"#);
assert_eq!(p.state_json()["view"], "integrity");
p.update_json(r#"{"view":"weak"}"#);
assert_eq!(p.state_json()["view"], "weak");
p.update_json(r#"{"select":"korp"}"#);
assert_eq!(p.state_json()["selected"], "korp");
p.update_json(r#"{"select":""}"#);
assert_eq!(p.state_json()["selected"], serde_json::Value::Null);
}
/// **RED-when-broken.** An unreachable server must render the honest UNAVAILABLE
/// state with the reason — never an empty index that reads as "no gaps".
#[test]
fn a_dead_server_says_unavailable_and_never_shows_a_clean_index() {
let mut p = CoveragePane::with_source(Box::new(DeadSource));
p.update_json("\"scan\"");
let js = p.state_json();
assert_eq!(
js["scanned"], false,
"a failed read must NOT produce an index"
);
assert_eq!(js["index"], serde_json::Value::Null);
assert!(js["status"].as_str().unwrap().starts_with("UNAVAILABLE"));
assert!(js["error"].as_str().unwrap().contains("unreachable"));
}
/// A failed REFRESH keeps the last good reading rather than blanking it — and
/// still reports the failure.
#[test]
fn a_failed_refresh_keeps_the_previous_reading() {
let mut p = CoveragePane::fixture();
p.update_json("\"scan\"");
let good = p.state_json()["index"]["repos_scanned"].clone();
p.source = std::sync::Arc::new(DeadSource);
p.update_json("\"scan\"");
let js = p.state_json();
assert_eq!(
js["index"]["repos_scanned"], good,
"the good reading survives"
);
assert!(js["status"].as_str().unwrap().starts_with("UNAVAILABLE"));
}
}