dwarves-gui/src/coderoom.rs
//! **The Coderoom tab** — the code room of the SELECTED workspace, as nornir's server
//! answers it.
//!
//! The pane is the shared, presentational [`facett_coderoom_view::CoderoomPane`]. The
//! host seam ([`RoomCoderoom`]) asks the room through the forge (`CodeRoom.Answer`,
//! [`crate::room`]) and maps the answers onto the pane's shapes — nothing is computed
//! here:
//!
//! | what the pane draws | asked of | answer |
//! |---|---|---|
//! | the crate graph: one node per `name@version`, a join per pair with its dependency / call / type-use counts | `Viz.CrateGraph` | the room's quotient by crate |
//! | what changed: crates added / removed, symbols added / removed / changed per crate | `Coderoom.SnapshotDiff` per consumed repo | the room at its last two consumed commits |
//! | compat verdicts | `Coderoom.Compat` | `not_wired` — the world ring (crates.io versions of other crates) has no producer |
//! | what breaks | `Coderoom.WhatBreaks` | `not_wired`, for the same reason |
//!
//! ## No verdict without a producer
//! A compatibility verdict answers *may I bump this?*. Nothing produces one today, so
//! the pane draws the two `not_wired` states with nornir's own sentences, no red or
//! green edge, no "0 breaking edges" and no impact set. The joins it does draw are
//! counts, in a neutral colour.
//!
//! ## Checkable
//! Under the `via …` line the pane prints the room the graph was read from —
//! `workspace w · repo@seq (commit) · N nodes · M edges` — and the two states of each
//! diff. The same question asked of the server (`CodeRoom.Answer`, `Viz.CrateGraph`)
//! gives the same numbers.
//!
//! ## What this replaced
//! The tab used to read a DEDICATED pre-v2 "coderoom" warehouse (`symbol_facts` /
//! `semver_diffs` / `call_edges`) and compute the compat verdict, the snapshot diff
//! and a transitive impact closure in the UI. Those tables have no writer, and a UI
//! does not compute verdicts. All of it is gone; `$DWARVES_CODEROOM_WORKSPACE` is no
//! longer read.
use std::sync::Arc;
use facett_coderoom_view::{
CoderoomDataSeam, CoderoomPane, CoderoomSnapshotDiff, CompatEdge, CrateDelta, CrateLink,
CrateNode, LayerState, WhatBreaks, LAYER_DIFF, LAYER_GRAPH, LAYER_VERDICTS,
};
use facett_core::Facet;
use crate::room::{self, ForgeRoom, Pending, RoomAnswer, RoomSource};
/// The crate-graph surface.
pub const S_GRAPH: &str = "Viz.CrateGraph";
/// The snapshot-diff surface (asked once per consumed repo).
pub const S_DIFF: &str = "Coderoom.SnapshotDiff";
/// The compat-verdict surface (waits for the world ring).
pub const S_COMPAT: &str = "Coderoom.Compat";
/// The what-breaks surface (waits for the world ring).
pub const S_WHAT_BREAKS: &str = "Coderoom.WhatBreaks";
/// The pane's fourth layer name (the facett pane names the other three).
pub const LAYER_WHAT_BREAKS: &str = "what breaks";
/// Is the dependency ring folded into the crate graph? NO. The tab draws the room of
/// the SELECTED WORKSPACE: its own crates, and every crate their code depends on or
/// calls into (a dependency is then the reference stubs the workspace's code points
/// at — its `nodes` are those stubs, and the reader says so in the evidence).
///
/// Measured 2026-10-13 on znippy-zoomies (one repo, 12 local crates): ring off = 60
/// crates / 137 joins / 18 668 nodes, answered in under a second; ring on = 483 crates /
/// 2 843 joins / 725 787 nodes, 53 s for the first fold on a debug server — the whole
/// transitive closure and std, which is the world ring's picture, not this workspace's.
pub const WITH_RING: bool = false;
/// Everything one read of the room brought back for this tab.
#[derive(Debug, Clone)]
pub struct Reading {
pub graph: RoomAnswer,
pub compat: RoomAnswer,
pub what_breaks: RoomAnswer,
/// One per consumed repo of the graph answer, in its order.
pub diffs: Vec<RoomAnswer>,
}
impl Reading {
/// Ask the room. The diff is asked per repo the graph answer names; a graph that
/// did not answer names none, and the diff layer says so.
pub fn read(source: &dyn RoomSource) -> Self {
let graph = source.ask(S_GRAPH, &serde_json::json!({ "universe": WITH_RING }));
let compat = source.ask(S_COMPAT, &serde_json::json!({}));
let what_breaks = source.ask(S_WHAT_BREAKS, &serde_json::json!({}));
let diffs = graph
.rows("repos")
.iter()
.filter_map(|r| r["repo"].as_str())
.map(|repo| source.ask(S_DIFF, &serde_json::json!({ "repo": repo })))
.collect();
Self {
graph,
compat,
what_breaks,
diffs,
}
}
/// The reading a tab holds before its first read landed.
fn pending(source: &dyn RoomSource) -> Self {
let via = source.describe();
Self {
graph: RoomAnswer::pending(S_GRAPH, &via),
compat: RoomAnswer::pending(S_COMPAT, &via),
what_breaks: RoomAnswer::pending(S_WHAT_BREAKS, &via),
diffs: Vec::new(),
}
}
}
fn u(v: &serde_json::Value) -> u64 {
v.as_u64().unwrap_or(0)
}
/// The crate nodes of a graph answer — the reader's rows, field for field.
pub fn crates_of(graph: &RoomAnswer) -> Vec<CrateNode> {
graph
.rows("crates")
.iter()
.map(|c| {
CrateNode::new(
c["name"].as_str().unwrap_or("?"),
c["version"].as_str().unwrap_or(""),
u(&c["symbols"]),
)
})
.collect()
}
/// The joins of a graph answer — the reader's rows, field for field.
pub fn links_of(graph: &RoomAnswer) -> Vec<CrateLink> {
graph
.rows("edges")
.iter()
.map(|e| CrateLink {
from: e["from"].as_str().unwrap_or("?").to_string(),
to: e["to"].as_str().unwrap_or("?").to_string(),
depends_on: u(&e["depends_on"]),
calls: u(&e["calls"]),
call_sites: u(&e["call_sites"]),
uses_type: u(&e["uses_type"]),
})
.collect()
}
/// The pane's "what changed" model from the diff answers that are measurements: the
/// reader's per-crate counts, the states' names joined as the two labels. `None` when
/// no diff was measured (the mode is then not offered; the layer says why).
pub fn diff_of(diffs: &[RoomAnswer]) -> Option<CoderoomSnapshotDiff> {
let measured: Vec<&RoomAnswer> = diffs.iter().filter(|d| d.is_measured()).collect();
if measured.is_empty() {
return None;
}
let strs = |v: &serde_json::Value| -> Vec<String> {
v.as_array()
.map(|a| {
a.iter()
.filter_map(|x| x.as_str().map(str::to_string))
.collect()
})
.unwrap_or_default()
};
let label = |key: &str| {
measured
.iter()
.filter_map(|d| d.body[key].as_str())
.collect::<Vec<_>>()
.join(", ")
};
let mut out = CoderoomSnapshotDiff {
snapshot_a: label("from"),
snapshot_b: label("to"),
crates_added: Vec::new(),
crates_removed: Vec::new(),
crates_changed: Vec::new(),
};
for d in measured {
let by = &d.body["by_crate"];
out.crates_added.extend(strs(&by["crates_added"]));
out.crates_removed.extend(strs(&by["crates_removed"]));
for c in by["crates"].as_array().map(Vec::as_slice).unwrap_or(&[]) {
let (a, r, ch) = (
u(&c["symbols_added"]),
u(&c["symbols_removed"]),
u(&c["symbols_changed"]),
);
if a + r + ch > 0 {
out.crates_changed.push(CrateDelta::new(
c["crate"].as_str().unwrap_or("?"),
a,
r,
ch,
));
}
}
}
Some(out)
}
/// One line per diff: the two states, the node counts, and the state when it is not a
/// measurement.
fn diff_lines(diffs: &[RoomAnswer]) -> Vec<String> {
diffs
.iter()
.map(|d| {
if d.is_measured() {
format!(
"diff {} → {}: {} nodes added · {} removed · {} changed",
d.body["from"].as_str().unwrap_or("?"),
d.body["to"].as_str().unwrap_or("?"),
u(&d.body["added_nodes"]),
u(&d.body["removed_nodes"]),
u(&d.body["changed_nodes"]),
)
} else {
format!("diff [{}]: {}", d.state, d.evidence_text())
}
})
.collect()
}
/// The diff layer's one state: the first diff that is not a measurement decides (its
/// state and sentence), else the first measured one.
fn diff_layer(reading: &Reading) -> LayerState {
if reading.diffs.is_empty() {
let why = if reading.graph.is_measured() {
"the crate graph names no consumed repo to diff"
} else {
"the crate graph did not answer, so no repo is known to diff"
};
return LayerState::new(LAYER_DIFF, "not_asked", false, why);
}
let pick = reading
.diffs
.iter()
.find(|d| !d.is_measured())
.unwrap_or(&reading.diffs[0]);
LayerState::new(
LAYER_DIFF,
pick.state.clone(),
reading.diffs.iter().all(RoomAnswer::is_measured),
pick.evidence_text(),
)
}
/// **The host seam**: one [`Reading`] of the room, mapped onto the pane's shapes.
pub struct RoomCoderoom {
reading: Reading,
source: String,
}
impl RoomCoderoom {
pub fn new(reading: Reading, source: String) -> Self {
Self { reading, source }
}
}
impl CoderoomDataSeam for RoomCoderoom {
fn source(&self) -> &str {
&self.source
}
fn crates(&self) -> Vec<CrateNode> {
crates_of(&self.reading.graph)
}
/// None: no producer writes a verdict (`Coderoom.Compat` is `not_wired`).
fn compat_edges(&self) -> Vec<CompatEdge> {
Vec::new()
}
/// None, always: "what breaks" has no producer either.
fn what_breaks(&self, _target: &str) -> Option<WhatBreaks> {
None
}
fn snapshot_diff(&self) -> Option<CoderoomSnapshotDiff> {
diff_of(&self.reading.diffs)
}
fn links(&self) -> Vec<CrateLink> {
links_of(&self.reading.graph)
}
fn layers(&self) -> Vec<LayerState> {
let r = &self.reading;
let layer = |name: &str, a: &RoomAnswer| {
LayerState::new(name, a.state.clone(), a.is_measured(), a.evidence_text())
};
vec![
layer(LAYER_GRAPH, &r.graph),
layer(LAYER_VERDICTS, &r.compat),
layer(LAYER_WHAT_BREAKS, &r.what_breaks),
diff_layer(r),
]
}
fn identity(&self) -> Vec<String> {
let mut lines = vec![format!("room: {}", self.reading.graph.room_line())];
lines.extend(diff_lines(&self.reading.diffs));
lines
}
}
/// What the Coderoom tab shows: the connections between crates (the reused
/// [`CoderoomPane`]) or the code as a room in 3-D ([`crate::coderoom3d`]).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
Connections,
Room3D,
}
/// The Coderoom tab: the reused [`CoderoomPane`] over the selected workspace's room,
/// and — the second mode of the same tab — the code room in 3-D.
pub struct CoderoomTab {
/// The pane over the last reading whose graph was a measurement; `None` until one
/// lands (the tab then draws the states, never an empty graph).
inner: Option<Box<dyn facett_core::Panel>>,
source: Arc<dyn RoomSource>,
/// The forge endpoint + bearer a `{"set_workspace":…}` re-binds with (`None` for an
/// injected source, which is not re-bound).
forge: Option<(String, String)>,
workspace: String,
reading: Reading,
/// A read in flight. The frame polls it; `wait` blocks on it.
pending: Option<Pending<Reading>>,
mode: Mode,
/// The 3-D room, made the first time the mode is picked (it asks its own surface).
room3d: Option<crate::coderoom3d::CodeRoom3DTab>,
}
impl CoderoomTab {
/// The shipped tab: the room of `workspace`, asked through the forge at `endpoint`.
pub fn new(
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(ForgeRoom::shared(&endpoint, &token, &workspace), workspace);
me.forge = Some((endpoint, token));
me
}
/// The tab over an injected source (tests). Starts its first read.
pub fn with_source(source: Arc<dyn RoomSource>, workspace: impl Into<String>) -> Self {
let reading = Reading::pending(&*source);
let mut me = Self {
inner: None,
source,
forge: None,
workspace: workspace.into(),
reading,
pending: None,
mode: Mode::Connections,
room3d: None,
};
me.bind();
me
}
/// **(Re-)read the room** off the egui thread. A re-bind started while one is in
/// flight is not a second round trip.
pub fn bind(&mut self) {
if self.pending.is_some() {
return;
}
let source = Arc::clone(&self.source);
self.pending = Some(Pending::spawn("coderoom-read", move || {
Reading::read(&*source)
}));
}
/// Non-blocking: land the in-flight read if the worker finished. Called once/frame.
pub fn poll(&mut self) {
let Some(p) = &self.pending else { return };
if let Some(res) = p.try_take() {
self.pending = None;
self.land(res);
}
}
/// Blocking: wait for the in-flight read (a scripted caller; the window never does).
pub fn wait(&mut self) {
if let Some(p) = self.pending.take() {
let res = p.wait();
self.land(res);
}
}
pub fn is_pending(&self) -> bool {
self.pending.is_some()
}
fn land(&mut self, res: Result<Reading, ()>) {
let reading = res.unwrap_or_else(|()| {
let mut r = Reading::pending(&*self.source);
r.graph = room::worker_died(S_GRAPH, &*self.source);
r
});
// A graph that is not a measurement mounts NO pane: the tab draws the state.
self.inner = reading.graph.is_measured().then(|| {
Box::new(
CoderoomPane::new(Box::new(RoomCoderoom::new(
reading.clone(),
self.source.describe(),
)))
.with_title("Code room"),
) as Box<dyn facett_core::Panel>
});
self.reading = reading;
}
/// The workspace whose room the tab reads.
pub fn workspace(&self) -> &str {
&self.workspace
}
/// Show `mode`; the 3-D room is made (and asks the server) the first time.
pub fn set_mode(&mut self, mode: Mode) {
self.mode = mode;
if mode == Mode::Room3D && self.room3d.is_none() {
self.room3d = Some(crate::coderoom3d::CodeRoom3DTab::with_source(
Arc::clone(&self.source),
&self.workspace,
));
}
}
}
impl Facet for CoderoomTab {
fn title(&self) -> &str {
"Coderoom"
}
fn ui(&mut self, ui: &mut egui::Ui) {
let mut mode = self.mode;
ui.horizontal(|ui| {
ui.selectable_value(&mut mode, Mode::Connections, "🔗 connections");
ui.selectable_value(&mut mode, Mode::Room3D, "🏛 3D room");
});
if mode != self.mode {
self.set_mode(mode);
}
if self.mode == Mode::Room3D {
if let Some(r) = self.room3d.as_mut() {
r.ui(ui);
}
return;
}
self.poll();
if self.pending.is_some() {
ui.ctx().request_repaint();
}
ui.horizontal(|ui| {
ui.label(format!(
"Code room of: {}",
if self.workspace.is_empty() {
"(no workspace selected)"
} else {
&self.workspace
}
));
if let Some(p) = &self.pending {
ui.separator();
ui.spinner();
ui.label(egui::RichText::new(crate::room::asking_line(p.started)).small());
}
});
ui.separator();
match self.inner.as_mut() {
// the pane leads with its own `via …`, room line and layer states
Some(pane) => pane.ui(ui),
// no measured graph: the four states and their evidence — no graph, no number
None => {
room::callout(ui, LAYER_GRAPH, &self.reading.graph);
room::callout(ui, LAYER_VERDICTS, &self.reading.compat);
room::callout(ui, LAYER_WHAT_BREAKS, &self.reading.what_breaks);
for d in &self.reading.diffs {
room::callout(ui, LAYER_DIFF, d);
}
}
}
}
fn state_json(&self) -> serde_json::Value {
// The pane's own state when a graph was measured; else NO pane keys at all —
// a reader of this JSON must not find a `crate_count: 0` that was never counted.
let mut v = match &self.inner {
Some(pane) => pane.state_json(),
None => serde_json::json!({ "kind": "coderoom" }),
};
if let Some(obj) = v.as_object_mut() {
obj.insert("workspace".into(), serde_json::json!(self.workspace));
obj.insert(
"mode".into(),
serde_json::json!(if self.mode == Mode::Room3D {
"3d"
} else {
"connections"
}),
);
obj.insert(
"room3d".into(),
self.room3d
.as_ref()
.map(|r| r.state_json())
.unwrap_or(serde_json::Value::Null),
);
obj.insert("from_room".into(), serde_json::json!(self.inner.is_some()));
obj.insert("pending".into(), serde_json::json!(self.pending.is_some()));
obj.insert(
"room".into(),
serde_json::json!({
"graph": self.reading.graph.state_json(),
"compat": self.reading.compat.state_json(),
"what_breaks": self.reading.what_breaks.state_json(),
"diffs": self.reading.diffs.iter().map(RoomAnswer::state_json).collect::<Vec<_>>(),
// the reader's own totals, beside the pane's — the two must agree
"nodes": self.reading.graph.is_measured().then(|| self.reading.graph.body["room"]["nodes"].clone()),
"edges_total": self.reading.graph.is_measured().then(|| self.reading.graph.body["room"]["edges_total"].clone()),
}),
);
}
v
}
fn update_json(&mut self, msg_json: &str) {
if let Ok(v) = serde_json::from_str::<serde_json::Value>(msg_json) {
if let Some(m) = v.get("mode").and_then(|t| t.as_str()) {
self.set_mode(if m == "3d" {
Mode::Room3D
} else {
Mode::Connections
});
return;
}
// In the 3-D mode every other message but the selector is the room's.
if self.mode == Mode::Room3D && v.get("set_workspace").is_none() {
if let Some(r) = self.room3d.as_mut() {
r.update_json(msg_json);
}
return;
}
// The selector's broadcast: the tab reads the SELECTED workspace's room.
if let Some(ws) = v.get("set_workspace").and_then(|t| t.as_str()) {
// the 3-D room is made again, over the new workspace, when next shown
self.room3d = None;
if let Some((endpoint, token)) = &self.forge {
self.source = ForgeRoom::shared(endpoint, token, ws);
// an in-flight read of the previous workspace must not land as this one's
self.pending = None;
}
self.workspace = ws.to_string();
self.bind();
if self.mode == Mode::Room3D {
self.set_mode(Mode::Room3D);
}
return;
}
if v.get("reread").and_then(|t| t.as_bool()) == Some(true) {
self.bind();
self.wait();
return;
}
if v.get("wait").and_then(|t| t.as_bool()) == Some(true) {
self.wait();
return;
}
}
// Anything else (mode / refresh / target) forwards to the reused pane.
if let Some(pane) = self.inner.as_mut() {
pane.update_json(msg_json);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::room::testing::{refusal, room, CannedRoom};
use serde_json::json;
const COMPAT_NOTE: &str = "Coderoom.Compat: the world ring has no v2 producer (gap P5)";
const BREAKS_NOTE: &str =
"Coderoom.WhatBreaks: the registry versions of other crates are not ingested (gap P5)";
/// A crate graph as the reader answers one: two local crates of one repo, one
/// dependency, three joins.
fn graph() -> serde_json::Value {
json!({
"state": "complete",
"crates": [
{ "id": "app@1.0.0", "name": "app", "version": "1.0.0", "repo": "shop", "local": true, "nodes": 60, "symbols": 41, "fns": 30, "files": 4, "calls_inside": 22 },
{ "id": "core@0.3.0", "name": "core", "version": "0.3.0", "repo": "shop", "local": true, "nodes": 25, "symbols": 12, "fns": 9, "files": 2, "calls_inside": 5 },
{ "id": "serde@1.0.228", "name": "serde", "version": "1.0.228", "repo": null, "local": false, "nodes": 900, "symbols": 310, "fns": 200, "files": 0, "calls_inside": 0 },
],
"edges": [
{ "from": "app@1.0.0", "to": "core@0.3.0", "depends_on": 1, "calls": 7, "call_sites": 19, "uses_type": 2 },
{ "from": "app@1.0.0", "to": "serde@1.0.228", "depends_on": 1, "calls": 3, "call_sites": 4, "uses_type": 0 },
{ "from": "core@0.3.0", "to": "serde@1.0.228", "depends_on": 1, "calls": 0, "call_sites": 0, "uses_type": 0 },
],
"repos": [{ "repo": "shop", "seq": 2, "sha": "0123456789abcdef0123", "crates": ["app@1.0.0", "core@0.3.0"] }],
"repo_edges": [],
"evidence": { "missing": [], "notes": ["calls through closures are not recorded: a lower bound"] },
"room": room("w", "shop", 2, 985, 2310),
})
}
fn diff() -> serde_json::Value {
json!({
"state": "complete", "from": "shop@1", "to": "shop@2",
"added_nodes": 3, "removed_nodes": 1, "changed_nodes": 2,
"by_crate": {
"crates_added": ["log@0.4.29"], "crates_removed": [],
"crates": [
{ "crate": "app@1.0.0", "symbols_added": 2, "symbols_removed": 1, "symbols_changed": 2, "nodes_added": 3, "nodes_removed": 1, "nodes_changed": 2 },
{ "crate": "core@0.3.0", "symbols_added": 0, "symbols_removed": 0, "symbols_changed": 0, "nodes_added": 0, "nodes_removed": 0, "nodes_changed": 0 },
],
},
"evidence": { "missing": [], "notes": [] },
})
}
fn fed() -> Arc<CannedRoom> {
CannedRoom::new(&[
(S_GRAPH, graph()),
(S_DIFF, diff()),
(S_COMPAT, refusal("not_wired", COMPAT_NOTE)),
(S_WHAT_BREAKS, refusal("not_wired", BREAKS_NOTE)),
])
}
fn tab(src: &Arc<CannedRoom>) -> CoderoomTab {
let mut t = CoderoomTab::with_source(src.clone(), "w");
t.wait();
t
}
/// The tab draws the READER's crate graph — its crates, its joins with their counts,
/// the room it was read from — and the reader's own states for what has no producer.
#[test]
fn the_tab_draws_the_rooms_crate_graph_and_the_readers_states() {
let src = fed();
let s = tab(&src).state_json();
assert_eq!(
(s["from_room"].as_bool(), s["workspace"].as_str()),
(Some(true), Some("w"))
);
// the graph: values read back, each one the reader's
assert_eq!(
(s["crate_count"].as_u64(), s["link_count"].as_u64()),
(Some(3), Some(3))
);
assert_eq!(
(
s["depends_on_edges"].as_u64(),
s["call_edges"].as_u64(),
s["call_sites"].as_u64()
),
(Some(3), Some(10), Some(23))
);
let app = s["crates"]
.as_array()
.unwrap()
.iter()
.find(|c| c["id"] == "app@1.0.0")
.expect("app is drawn");
assert_eq!(app["symbols"], 41);
assert_eq!(
s["links"][0],
json!({ "from": "app@1.0.0", "to": "core@0.3.0", "depends_on": 1, "calls": 7, "call_sites": 19, "uses_type": 2 })
);
assert_eq!(
(s["graph_nodes"].as_u64(), s["graph_edges"].as_u64()),
(Some(3), Some(3)),
"the reused viz got the scene"
);
// where it came from, and which room
assert_eq!(s["source"], "canned room (test)");
assert_eq!(
s["identity"][0],
"room: workspace w · shop@2 (0123456789) · 985 nodes · 2310 edges"
);
assert_eq!(
s["identity"][1],
"diff shop@1 → shop@2: 3 nodes added · 1 removed · 2 changed"
);
assert_eq!(
(
s["room"]["nodes"].as_u64(),
s["room"]["edges_total"].as_u64()
),
(Some(985), Some(2310))
);
// the diff: the reader's per-crate counts; a crate with no symbol change is not listed
assert_eq!(s["diff_summary"], "1 added · 0 removed · 1 changed");
assert_eq!(
s["snapshot_diff"]["crates_changed"],
json!([{ "crate_id": "app@1.0.0", "symbols_added": 2, "symbols_removed": 1, "symbols_changed": 2 }])
);
// no verdict: the two states with nornir's sentences, and no number
assert_eq!(s["verdicts_measured"], false);
assert!(
s["breaking_edges"].is_null() && s["compatible_edges"].is_null(),
"{s}"
);
assert_eq!(
(s["edge_count"].as_u64(), s["impacted_count"].as_u64()),
(Some(0), Some(0))
);
let layers = s["layers"].as_array().unwrap();
assert_eq!(
layers
.iter()
.map(|l| (l["layer"].as_str().unwrap(), l["state"].as_str().unwrap()))
.collect::<Vec<_>>(),
vec![
("crate graph", "complete"),
("compat verdicts", "not_wired"),
("what breaks", "not_wired"),
("snapshot diff", "complete"),
]
);
assert_eq!(
(
layers[1]["evidence"].as_str(),
layers[2]["evidence"].as_str()
),
(Some(COMPAT_NOTE), Some(BREAKS_NOTE))
);
// what was asked: the graph with the ring, the diff of the repo the graph named
let asked = src.asked.lock().unwrap().clone();
assert_eq!(
asked[0],
(S_GRAPH.to_string(), json!({ "universe": WITH_RING }))
);
assert!(
asked.contains(&(S_DIFF.to_string(), json!({ "repo": "shop" }))),
"{asked:?}"
);
}
/// A room that refuses draws its state and NOTHING else: no pane keys, no zero.
#[test]
fn a_refused_graph_is_a_state_with_no_number() {
for (state, note) in [
(
"not_consumed",
"code room of `w`: no repo has a consumer offset yet",
),
("no_room", "no workspace selected"),
] {
let src = CannedRoom::new(&[
(S_GRAPH, refusal(state, note)),
(S_COMPAT, refusal("not_wired", COMPAT_NOTE)),
(S_WHAT_BREAKS, refusal("not_wired", BREAKS_NOTE)),
]);
let s = tab(&src).state_json();
assert_eq!(s["from_room"], false);
assert_eq!(
(
s["room"]["graph"]["state"].as_str(),
s["room"]["graph"]["evidence"].as_str()
),
(Some(state), Some(note))
);
for key in [
"crate_count",
"link_count",
"breaking_edges",
"crates",
"links",
"summary",
] {
assert!(
s.get(key).is_none(),
"`{key}` must not exist without a measured graph: {s}"
);
}
assert!(
s["room"]["nodes"].is_null() && s["room"]["diffs"].as_array().unwrap().is_empty()
);
// no repo was named, so no diff was asked
assert!(!src.asked.lock().unwrap().iter().any(|a| a.0 == S_DIFF));
}
}
/// One consumed commit: the reader compares with the empty state and says so; a
/// diff that refuses is a named state on its layer and the mode is not offered.
#[test]
fn the_diff_layer_carries_the_readers_state() {
let src = fed();
src.set(
S_DIFF,
refusal("not_consumed", "repo `shop` has no consumer offset"),
);
let s = tab(&src).state_json();
assert!(
s["snapshot_diff"].is_null(),
"no measured diff ⇒ no what-changed model"
);
assert_eq!(
s["layers"][3],
json!({ "layer": "snapshot diff", "state": "not_consumed", "measured": false, "evidence": "repo `shop` has no consumer offset" })
);
assert_eq!(
s["identity"][1],
"diff [not_consumed]: repo `shop` has no consumer offset"
);
assert_eq!(s["crate_count"], 3, "the graph is still the graph");
}
/// One tab, two modes: the connections (the crate graph) and the 3-D room, which
/// is made — and asks its own surface — only when it is picked.
#[test]
fn the_3d_room_is_a_mode_of_this_tab_asked_only_when_picked() {
let src = fed();
let mut t = tab(&src);
let s = t.state_json();
assert_eq!(
(s["mode"].as_str(), s["room3d"].is_null()),
(Some("connections"), true),
"{s}"
);
assert!(
!src.asked
.lock()
.unwrap()
.iter()
.any(|(surface, _)| surface == crate::coderoom3d::S_CODE_MAP),
"not asked before it is picked"
);
t.update_json(r#"{"mode":"3d"}"#);
t.update_json(r#"{"wait":true}"#);
let s = t.state_json();
assert_eq!(s["mode"], "3d");
assert_eq!(s["room3d"]["kind"], "code_map", "{s}");
assert!(
src.asked
.lock()
.unwrap()
.iter()
.any(|(surface, _)| surface == crate::coderoom3d::S_CODE_MAP),
"picked: asked"
);
t.update_json(r#"{"mode":"connections"}"#);
assert_eq!(
t.state_json()["crate_count"],
3,
"the connections are still there"
);
}
/// The selector's broadcast re-reads; an injected source keeps its source.
#[test]
fn a_reread_draws_what_the_room_says_now() {
let src = fed();
let mut t = tab(&src);
assert_eq!(t.state_json()["crate_count"], 3);
let mut g = graph();
g["crates"].as_array_mut().unwrap().pop();
g["edges"] = json!([]);
src.set(S_GRAPH, g);
t.update_json(r#"{"reread":true}"#);
let s = t.state_json();
assert_eq!(
(s["crate_count"].as_u64(), s["link_count"].as_u64()),
(Some(2), Some(0))
);
}
}