facett-demo/tests/robot_demo.rs
//! **D5b2 — the robot-UI live-drive row.** Where [`mega_matrix`](mega_matrix.rs)
//! (D5b1) proves every component renders *statically* across data × palette, THIS
//! proves the **whole demo actually works when a robot drives it**: fire up the
//! entire `facett-demo` app **headless**, click through **every** tab and exercise
//! **each** component's interactive controls, and assert — via each component's
//! `state_json` — that the UI *responded* to every action. Every scripted
//! `(action → expected outcome)` step becomes one `robot-ui`
//! [`TestResultRow`](nornir_testmatrix::TestResultRow), so the live drive **fills a
//! single matrix line** (D5b2) printed alongside D5b1's grid.
//!
//! ## Inject-assert (LAW 1)
//! Nothing here is "didn't panic". Every step performs a **real** AccessKit-backed
//! interaction (click a labelled tab / button / selectable, drag, scroll, type)
//! against the real demo, then asserts a **real** value read back out of the app's
//! `state_json` (the active tab switched; the iceberg snapshot changed and the
//! visible rows with it; the geomap zoom moved; the syschart selection appeared; …).
//! A failed action *or* a failed assertion lands as a red cell, never a panic.
//!
//! ## On `nornir-robotui` — the [`robot`] module below is a TWIN, and it should go
//! This row is the documented `nornir-robotui` consumer (the crate's own design doc
//! names "the facett-demo robot-UI row (D5b2)"), but it does not actually depend on
//! the crate: the [`robot`] module below is an **in-crate port of robotui's public
//! API** (`Observable` / `RobotSession` / `Scenario` / `Step` / `Action` / `Assert` /
//! `run_scenario`, `aspect = "robot-ui"`, one `run_id` per scenario).
//!
//! ⚠ **The reason the port exists is stale, measured 2026-09-01.** The text here used
//! to read "its published 0.1.0 pins egui 0.33 … while facett is egui 0.34, and two
//! egui majors cannot share one `egui_kittest::Harness`". Both halves have moved:
//! `nornir-robotui` is **0.3.4 on egui/eframe 0.35**, and facett is **egui 0.35** too.
//! There is no egui-major split left to justify a second copy.
//!
//! **This twin has already cost a real bug.** `Step::assert` was a setter rather than
//! an append in BOTH copies; robotui fixed it at the source in 0.3.4, and this file
//! kept silently discarding every clause but the last until it was fixed here
//! separately (see [`robot::Step::assert`]). A version bump cannot reach a twin. LAW 5
//! — reuse, do not twin: delete `mod robot`, depend on `nornir-robotui`, and this class
//! of bug becomes impossible to have twice.
//!
//! The port is verified against robotui's own reference tests' behaviour (a wrong
//! expectation must be a red cell, not a panic; visibility + drew checks; type_into
//! round-trip).
//!
//! Run: `cargo test -p facett-demo --test robot_demo -- --nocapture`
//! Docs: `.nornir/mega-matrix.md` (the D5b2 section).
use facett_demo::{DemoState, LEGEND};
use nornir_testmatrix::{render_matrix, status, JsonFileSink, TestResultRow, TestSink};
use serde_json::{json, Value};
// ─────────────────────────────────────────────────────────────────────────────
// The in-crate robotui port (see the module note above). Mirror of
// `nornir_robotui`'s public surface over egui_kittest 0.34.
// ─────────────────────────────────────────────────────────────────────────────
mod robot {
use egui_kittest::kittest::{NodeT, Queryable};
use egui_kittest::Harness;
use serde_json::Value;
/// The **observe** seam — the nordisk "components expose readable data" LAW.
/// Same one method `nornir_robotui::Observable` defines.
pub trait Observable {
fn state_json(&self) -> Value;
}
/// One thing the robot does to the UI (plain data, like robotui's `Action`).
/// The full set is kept so this is a drop-in mirror of `nornir_robotui::Action`
/// (a one-line swap once robotui ships an egui-0.34 build); not every variant is
/// exercised by this particular demo scenario.
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub enum Action {
/// Click the widget whose AccessKit label equals this string (tab header,
/// selectable, checkbox, button).
ClickLabel(String),
/// Open deck tab `title` the way a user does on the two-level bar: click its
/// GROUP label (`facett_demo::tab_group`), then its tab header.
OpenTab(String),
/// Click a button by its text.
ClickButton(String),
/// Focus the field labelled `label` and type `text` into it.
TypeInto { label: String, text: String },
/// Press a key.
Key(egui::Key),
/// Scroll by a delta (egui convention: +y up, -y down).
Scroll(egui::Vec2),
/// Drag from one screen point to another (press → move → release).
Drag { from: egui::Pos2, to: egui::Pos2 },
/// Just run a frame.
Step,
/// Do nothing (assertion-only step).
None,
}
/// What must hold after a step's action (mirror of robotui's `Assert`).
#[derive(Clone, Debug)]
pub enum Assert {
None,
/// A CONJUNCTION — every clause must hold. Mirror of
/// `nornir_robotui::Assert::All`, added here 2026-09-01 for the reason
/// below. Never constructed directly: [`Step::assert`] builds it.
All(Vec<Assert>),
LabelVisible(String),
Drew,
State { pointer: String, expected: Value },
StateContains { pointer: String, needle: String },
}
impl Assert {
/// Conjoin two assertions, flattening so a chain of n `.assert(..)` calls
/// is ONE `All` of n clauses rather than a right-leaning nest.
/// `Assert::None` is the identity.
fn and(self, other: Assert) -> Assert {
match (self, other) {
(Assert::None, b) => b,
(a, Assert::None) => a,
(Assert::All(mut v), Assert::All(w)) => {
v.extend(w);
Assert::All(v)
}
(Assert::All(mut v), b) => {
v.push(b);
Assert::All(v)
}
(a, Assert::All(w)) => {
let mut v = vec![a];
v.extend(w);
Assert::All(v)
}
(a, b) => Assert::All(vec![a, b]),
}
}
}
/// One `(label, action, assert)` triple → one matrix cell.
#[derive(Clone, Debug)]
pub struct Step {
pub label: String,
pub action: Action,
pub assert: Assert,
}
impl Step {
pub fn new(label: impl Into<String>) -> Self {
Self { label: label.into(), action: Action::None, assert: Assert::None }
}
pub fn action(mut self, action: Action) -> Self {
self.action = action;
self
}
/// APPEND a clause — chaining `.assert(a).assert(b)` checks BOTH.
///
/// ⚠ This used to be `self.assert = assert`, a SETTER, so a chained
/// `.assert(a).assert(b)` silently kept only `b` and every earlier clause
/// was dead code reporting green. Measured 2026-08-31 across 30 repos:
/// 135 clauses had never executed. `nornir-robotui` 0.3.4 fixed it at the
/// source (`Assert::All`); this in-crate PORT carries the same bug in its
/// own copy, so a version bump cannot reach it — hence this fix.
///
/// TODO (LAW 5 — reuse, do not twin): delete this whole `mod robot` and
/// depend on `nornir-robotui` instead. The comment in `Cargo.toml` that
/// justifies the port ("until robotui ships an egui-0.34 build") is stale
/// — robotui is on egui 0.35, and so is facett. The port is exactly why
/// this bug had to be fixed twice.
pub fn assert(mut self, assert: Assert) -> Self {
self.assert = std::mem::replace(&mut self.assert, Assert::None).and(assert);
self
}
}
/// An ordered list of steps; `suite` becomes each row's `suite`.
#[derive(Clone, Debug)]
pub struct Scenario {
pub suite: String,
pub steps: Vec<Step>,
}
impl Scenario {
pub fn new(suite: impl Into<String>) -> Self {
Self { suite: suite.into(), steps: Vec::new() }
}
pub fn step(mut self, step: Step) -> Self {
self.steps.push(step);
self
}
}
/// A scripted egui session over an `A: Observable` (mirror of robotui's
/// `RobotSession`).
pub struct RobotSession<'a, A: Observable> {
harness: Harness<'a, A>,
repo: String,
}
impl<'a, A: Observable> RobotSession<'a, A> {
pub fn new(harness: Harness<'a, A>, repo: impl Into<String>) -> Self {
Self { harness, repo: repo.into() }
}
pub fn app_mut(&mut self) -> &mut A {
self.harness.state_mut()
}
pub fn state(&self) -> Value {
self.harness.state().state_json()
}
pub fn step(&mut self) {
let _ = self.harness.run_ok();
}
/// Open deck tab `title` on the two-level bar: its group label, then its header.
pub fn open_tab(&mut self, title: &str) -> anyhow::Result<()> {
let group = facett_demo::tab_group(title).unwrap_or(facett_demo::TAB_GROUP_REST);
self.click_label(group)?;
self.click_label(title)
}
pub fn click_label(&mut self, label: &str) -> anyhow::Result<()> {
match self.harness.query_by_label(label) {
Some(node) => {
node.click();
self.step();
Ok(())
}
None => Err(anyhow::anyhow!("no widget labelled {label:?} to click")),
}
}
pub fn click_button(&mut self, text: &str) -> anyhow::Result<()> {
match self.harness.query_by_label(text) {
Some(node) => {
node.click();
self.step();
Ok(())
}
None => Err(anyhow::anyhow!("no button {text:?} to click")),
}
}
pub fn type_into(&mut self, label: &str, text: &str) -> anyhow::Result<()> {
match self.harness.query_by_label(label) {
Some(node) => node.focus(),
None => return Err(anyhow::anyhow!("no field {label:?} to type into")),
}
self.step();
match self.harness.query_by_label(label) {
Some(node) => node.type_text(text),
None => return Err(anyhow::anyhow!("field {label:?} vanished after focus")),
}
self.step();
Ok(())
}
pub fn key(&mut self, key: egui::Key) {
self.harness.key_press(key);
self.step();
}
pub fn scroll(&mut self, delta: egui::Vec2) {
self.harness.event(egui::Event::MouseWheel {
unit: egui::MouseWheelUnit::Point,
delta,
modifiers: egui::Modifiers::default(),
phase: egui::TouchPhase::Move,
});
self.step();
}
pub fn drag(&mut self, from: egui::Pos2, to: egui::Pos2) {
// Press at `from`, settle a frame so the widget under the cursor grabs the
// drag, THEN move-while-pressed to `to` so egui registers a real
// `Response::dragged()` (a press immediately followed by a move+release in
// one event never crosses the drag threshold in a single frame), and only
// then release. This makes drag-driven state responses (e.g. orbit stops
// the auto-spin) actually fire under the headless harness.
self.harness.event(egui::Event::PointerMoved(from));
self.harness.drag_at(from);
self.harness.event(egui::Event::PointerMoved(to));
self.step();
self.harness.drop_at(to);
self.step();
}
pub fn label_visible(&self, label: &str) -> bool {
self.harness.query_by_label(label).is_some()
}
pub fn state_at(&self, pointer: &str) -> Option<Value> {
self.state().pointer(pointer).cloned()
}
/// Drive the whole scenario → one `TestResultRow` per step (suite =
/// scenario.suite, aspect = "robot-ui"), all sharing one `run_id`.
pub fn run_scenario(&mut self, scenario: &Scenario) -> Vec<super::TestResultRow> {
let run_id = nornir_testmatrix::new_run_id();
let ts = now_micros();
let mut rows = Vec::with_capacity(scenario.steps.len());
for step in &scenario.steps {
let started = std::time::Instant::now();
let (st, message) = self.run_step(step);
let duration_ms = started.elapsed().as_secs_f64() * 1000.0;
rows.push(super::TestResultRow {
run_id: run_id.clone(),
repo: self.repo.clone(),
suite: scenario.suite.clone(),
test_name: step.label.clone(),
status: st.to_string(),
duration_ms,
ts_micros: ts,
message,
aspect: "robot-ui".to_string(),
metric: 0.0,
});
}
rows
}
fn run_step(&mut self, step: &Step) -> (&'static str, String) {
if let Err(e) = self.perform(&step.action) {
return (super::status::FAIL, format!("action failed: {e}"));
}
match self.check(&step.assert) {
Ok(()) => (super::status::PASS, String::new()),
Err(e) => (super::status::FAIL, e),
}
}
fn perform(&mut self, action: &Action) -> anyhow::Result<()> {
match action {
Action::ClickLabel(s) => self.click_label(s),
Action::OpenTab(t) => self.open_tab(t),
Action::ClickButton(s) => self.click_button(s),
Action::TypeInto { label, text } => self.type_into(label, text),
Action::Key(k) => {
self.key(*k);
Ok(())
}
Action::Scroll(d) => {
self.scroll(*d);
Ok(())
}
Action::Drag { from, to } => {
self.drag(*from, *to);
Ok(())
}
Action::Step => {
self.step();
Ok(())
}
Action::None => Ok(()),
}
}
fn check(&self, assert: &Assert) -> Result<(), String> {
match assert {
Assert::None => Ok(()),
// A chained `.assert(a).assert(b)` — EVERY clause must hold. The
// message names which clause of how many failed, so a revived
// clause is traceable to its line in the chain.
Assert::All(clauses) => {
let n = clauses.len();
for (i, c) in clauses.iter().enumerate() {
self.check(c).map_err(|e| format!("clause {} of {n}: {e}", i + 1))?;
}
Ok(())
}
Assert::LabelVisible(label) => {
if self.label_visible(label) {
Ok(())
} else {
Err(format!("expected a widget labelled {label:?} to be visible"))
}
}
Assert::Drew => {
if self.harness.root().children().next().is_some() {
Ok(())
} else {
Err("expected the UI to draw at least one widget, tree was empty".into())
}
}
Assert::State { pointer, expected } => match self.state_at(pointer) {
Some(actual) if &actual == expected => Ok(()),
Some(actual) => Err(format!("state at {pointer:?}: expected {expected}, got {actual}")),
None => Err(format!("state at {pointer:?} is absent")),
},
Assert::StateContains { pointer, needle } => match self.state_at(pointer) {
Some(actual) => {
let hay = match &actual {
Value::String(s) => s.clone(),
other => other.to_string(),
};
if hay.contains(needle) {
Ok(())
} else {
Err(format!("state at {pointer:?} ({hay}) does not contain {needle:?}"))
}
}
None => Err(format!("state at {pointer:?} is absent")),
},
}
}
}
fn now_micros() -> i64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_micros() as i64).unwrap_or(0)
}
}
use robot::{Action, Assert, Observable, RobotSession, Scenario, Step};
// ─────────────────────────────────────────────────────────────────────────────
// The demo is the `Observable` app the robot drives.
// ─────────────────────────────────────────────────────────────────────────────
impl Observable for DemoState {
fn state_json(&self) -> Value {
DemoState::state_json(self)
}
}
/// Build a headless [`RobotSession`] over the **whole demo** — the same
/// `DemoState::draw_app` render path the eframe binary uses (CLI/GUI parity), so
/// the robot drives exactly what a human sees.
/// The graph3d pane's exact node count: the generated dataset at the size a TEST
/// binary uses, plus the appended knut-popsim slice. Derived from the same public
/// constants `DemoState` builds from (L5 — the number is not twinned here).
fn expected_graph3d_nodes() -> usize {
facett::graph3d::dataset::GraphSpec::sized(facett_demo::GRAPH3D_TEST_NODES).node_count()
+ facett_demo::GRAPH3D_POP_SLICE
}
fn demo_session() -> RobotSession<'static, DemoState> {
let mut demo = DemoState::new();
// Close the legend panel up front so it doesn't overlap the deck's tab bar in
// the headless layout (the robot clicks the legend toggle later anyway).
demo.show_about = false;
let harness = egui_kittest::Harness::builder()
// 1280×1000 (was ×820): the 2026-08-20 roster completion grew the wrapped
// tab bar by ~3 rows, which pushed low-in-pane controls (the Git tab's
// `Log`/`Tree` toggles) below the old harness bottom, where a click lands
// outside the clip rect and silently no-ops. Height only — the fixed drag
// coordinates in the scenario are measured from the top and stay valid.
.with_size(egui::vec2(1280.0, 1000.0))
.build_ui_state(|ui, demo: &mut DemoState| demo.draw_app(ui), demo);
RobotSession::new(harness, "facett-demo")
}
/// A `ClickLabel` tab-switch step that asserts the deck's `active` became `title`.
fn tab(title: &str) -> Step {
Step::new(format!("open `{title}` tab → deck.active == {title}"))
.action(Action::OpenTab(title.into()))
.assert(Assert::State { pointer: "/deck/active".into(), expected: json!(title) })
}
// ─────────────────────────────────────────────────────────────────────────────
// THE D5b2 LIVE-DRIVE ROW.
// ─────────────────────────────────────────────────────────────────────────────
/// Fire up the whole demo headless and drive **every** tab + each component's
/// interactive controls, asserting each component's `state_json` responds. Emits
/// one `robot-ui` matrix row per step, prints the grid (D5b2), and gates on every
/// row being green.
#[test]
fn d5b2_robot_live_drive_every_component() {
let mut robot = demo_session();
// Land the first frame so the deck/tab tree exists before we query it.
robot.step();
// Sanity: the demo opened on the first facet and drew.
assert!(robot.label_visible("graph"), "the demo's first tab is present");
// ── Build the scenario: every tab + each component's interactive controls. ──
let mut scn = Scenario::new("facett-demo");
// 0. The whole app drew at least one widget (not a blank frame).
scn = scn.step(Step::new("demo app drew its UI").assert(Assert::Drew));
// 1. THEME PICKER (toolbar) — click a named palette; the deck adopts it. The
// deck's palette override re-themes every facet (the D4 surface), driven by
// a real labelled selectable in the toolbar.
scn = scn
.step(
Step::new("theme picker → pick `nordic-aurora`")
.action(Action::ClickLabel("nordic-aurora".into()))
.assert(Assert::LabelVisible("nordic-aurora".into())),
)
.step(
Step::new("theme picker → pick `cyberpunk-neon`")
.action(Action::ClickLabel("cyberpunk-neon".into()))
.assert(Assert::LabelVisible("cyberpunk-neon".into())),
);
// 2. The GLOW toolbar toggle responds (last_command records the route). The
// LEGEND toggle is proven at the END of the drive — see the note below.
scn = scn
.step(
Step::new("toolbar `glow` toggles → last_command == fx.glow")
.action(Action::ClickLabel("glow".into()))
.assert(Assert::State { pointer: "/last_command".into(), expected: json!("fx.glow") }),
)
;
// NOTE deliberately NOT here: the `legend` toggle. Clicking it OPENS the floating
// About window, and an open window EATS the pointer clicks on every tab header it
// covers — measured: with it open, `click("graphview")` leaves the deck on
// `graph` while `click("timeline")` (right of the window) still switches. The
// whole left half of every wrapped tab-bar row was unclickable, which is what
// turned 24 tab steps red the moment the `undriven` panic (the Errors/Manual/
// GFX-v2 gap) stopped masking them. The toggle is proven at the very END of the
// drive (see the imperative section below), after nothing needs the tab bar.
// 3. EVERY DECK TAB — click each facet's tab header; assert the deck switched
// AND the now-active facet carries its readable signal (not an empty shell).
// graph family.
scn = scn
.step(tab("graph"))
.step(
// The `graph` tab draws the knut 12-label meta-graph (schema_scene),
// so it carries 12 nodes (one per label) — not the old 8 SUBSYSTEMS.
Step::new("graph carries the 12-label knut meta-graph")
.assert(Assert::State { pointer: "/deck/facets/graph/nodes".into(), expected: json!(12) }),
)
// graphview — the CONSOLIDATED graph engine tab. Switch in, assert it routed
// through the shared L0 render kernel (engine + render_path + the lowered L0
// instance counts), then DRAG-PAN + SCROLL-ZOOM the navigable camera and
// assert the live render stayed non-blank (the lit-pixel oracle moved with
// the camera, not "didn't panic").
.step(tab("graphview"))
.step(
Step::new("graphview is rendered through the consolidated L0 kernel")
.assert(Assert::State { pointer: "/deck/facets/graphview/engine".into(), expected: json!("facett-graphview") }),
)
.step(
Step::new("graphview lowered the model into L0 SDF lines (edges)")
.assert(Assert::StateContains { pointer: "/deck/facets/graphview/render_path".into(), needle: "L0 Canvas".into() }),
)
.step(
Step::new("graphview drag-pans the live L0 camera → stays navigable")
.action(Action::Drag { from: egui::pos2(540.0, 360.0), to: egui::pos2(470.0, 320.0) })
.assert(Assert::State { pointer: "/deck/facets/graphview/navigable".into(), expected: json!(true) }),
)
.step(
Step::new("graphview scroll-zooms the L0 camera → tab stays live")
.action(Action::Scroll(egui::vec2(0.0, 60.0)))
.assert(Assert::LabelVisible("graphview".into())),
)
// V.G — THE GRAPH NAVIGATOR (facett-graphnav) over the demo's population graph.
// Switch in, assert the navigator mounted a real graph (nodes on the canvas) and
// opened on its default force layout — the live V.G surface, not a blank shell.
.step(tab("Graphs"))
.step(
Step::new("Graphs mounted the navigator over the population graph")
.assert(Assert::State { pointer: "/deck/facets/Graphs/canvas/layout".into(), expected: json!("force") }),
)
.step(
Step::new("Graphs' Arrow data-laboratory grid ingested the node table")
.assert(Assert::StateContains { pointer: "/deck/facets/Graphs/layout/options".into(), needle: "radial".into() }),
)
// metro — the subway-line test-coverage view. Switch in, assert it drew the
// two-line demo map (real lit/unlit stations), the green verdict counts one
// green / one red line, and the tab label is visible (rendered, not blank).
.step(tab("metro"))
.step(
Step::new("metro drew the two-line test-coverage map")
.assert(Assert::State { pointer: "/deck/facets/metro/lines".into(), expected: json!(2) }),
)
.step(
Step::new("metro reports one green / one red line (a deliberate coverage gap)")
.assert(Assert::State { pointer: "/deck/facets/metro/green_lines".into(), expected: json!(1) }),
)
// Velo L1 — the L0 SDF vs L0+L1 vello lane. Switch in, assert it lowered real
// L0 SDF instances (the always-available base) and surfaced the 2-category
// backend decision (CPU here, the honest headless probe).
.step(tab("Velo L1"))
.step(
Step::new("Velo L1 routes L0 + L1 vello overlay")
.assert(Assert::StateContains { pointer: "/deck/facets/Velo L1/render_path".into(), needle: "L1 vello overlay".into() }),
)
.step(
Step::new("Velo L1 surfaces the CPU / CPU+GPU backend decision")
.assert(Assert::StateContains { pointer: "/deck/facets/Velo L1/category".into(), needle: "CPU".into() }),
)
// Sverige — THE population showcase (three linked knut-popsim graphs). Switch
// in, assert it is driven by knut-popsim and opens on the canonical 11M-person
// scale (real injected data, not an empty shell).
.step(tab("Sverige"))
.step(
Step::new("Sverige is driven by the knut-popsim 11M-person model")
.assert(Assert::StateContains { pointer: "/deck/facets/Sverige/source".into(), needle: "knut-popsim".into() }),
)
.step(tab("graph3d"))
.step(
// graph3d used to be the 140-node knut-popsim slice ALONE — depth-1
// person->entity stars, which is why the cloud sat on the sphere shell it
// was seeded on. It now carries `facett_graph3d::dataset`'s generated bulk
// (long chains braided by rungs, plus hubs) with that same population slice
// appended after it. The count is PREDICTED from the two public constants
// rather than pinned to a literal, so the cell tracks the composition
// instead of drifting from it.
Step::new("graph3d carries the generated cloud + the knut-popsim slice")
.assert(Assert::State {
pointer: "/deck/facets/graph3d/nodes".into(),
expected: json!(expected_graph3d_nodes()),
}),
)
// graph3d-given — the HOST-placed twin. `layout` is the CHOICE and
// `layout_fallback` is the truth on screen; asserting BOTH (plus the number
// of positions the host handed over, derived from the demo module's own
// `NODES` const rather than a literal) is what makes this step RED if the
// placement is ever ignored — a cloud that still has nodes proves nothing,
// because the grid fallback would happily draw one.
.step(tab("graph3d-given"))
.step(
Step::new("graph3d-given is drawn from the HOST's own placement, complete, no fallback")
.assert(Assert::State {
pointer: "/deck/facets/graph3d-given/layout".into(),
expected: json!("given"),
})
.assert(Assert::State {
pointer: "/deck/facets/graph3d-given/given_positions".into(),
expected: json!(facett_demo::demo_graph3d_given::NODES),
})
.assert(Assert::State {
pointer: "/deck/facets/graph3d-given/layout_fallback".into(),
expected: json!(null),
}),
)
.step(tab("rabbit3d"))
.step(
Step::new("rabbit3d carries the extruded logo mesh")
.assert(Assert::StateContains { pointer: "/deck/facets/rabbit3d/spin".into(), needle: "true".into() }),
)
.step(tab("depgraph"))
.step(
Step::new("depgraph carries nodes")
.assert(Assert::State { pointer: "/deck/facets/depgraph/nodes".into(), expected: json!(8) }),
)
.step(tab("pipeline"))
.step(tab(facett_demo::flow_tab::TITLE))
.step(
Step::new("Falsett starts on the counted cluster overview")
.assert(Assert::State { pointer: "/deck/facets/Falsett/clustered".into(), expected: json!(true) }),
)
.step(
Step::new("Falsett layered synthetic fixture carries status and rate")
.action(Action::ClickLabel("Layered DAG".into()))
.assert(Assert::State { pointer: "/deck/facets/Falsett/fixture".into(), expected: json!("layered") }),
)
.step(tab("synthetic-sdp"))
.step(
// The ~100-stage SDP world is a valid acyclic DAG with sources + sinks.
Step::new("synthetic-sdp carries the ~100-stage acyclic DAG")
.assert(Assert::StateContains { pointer: "/deck/facets/synthetic-sdp/acyclic".into(), needle: "true".into() }),
)
// The two net-new pipeline-authoring EDITORS. SDP (Spark Declarative
// Pipelines): switch in, flip to the dep-DAG preview via the `◈ Graph`
// toggle, and assert the seeded medallion pipeline validates.
.step(tab("SDP"))
.step(
Step::new("SDP → `◈ Graph` shows the dep-DAG preview")
.action(Action::ClickLabel("◈ Graph".into()))
.assert(Assert::State { pointer: "/deck/facets/SDP/mode".into(), expected: json!("Graph") }),
)
.step(
Step::new("SDP medallion pipeline validates (4 flows, no dep errors)")
.assert(Assert::State { pointer: "/deck/facets/SDP/valid".into(), expected: json!(true) }),
)
// brokkr (CI/build-pipeline builder): switch in, flip to the topo DAG
// preview via `◈ Graph`, and assert the seeded demo pipeline validates.
.step(tab("brokkr"))
.step(
Step::new("brokkr → `◈ Graph` shows the topo step DAG")
.action(Action::ClickLabel("◈ Graph".into()))
.assert(Assert::State { pointer: "/deck/facets/brokkr/mode".into(), expected: json!("Graph") }),
)
.step(
Step::new("brokkr demo pipeline validates (stages + steps, no errors)")
.assert(Assert::State { pointer: "/deck/facets/brokkr/valid".into(), expected: json!(true) }),
)
.step(
Step::new("brokkr output port starts a connection")
.action(Action::ClickLabel("Output unit".into())),
)
.step(
Step::new("brokkr input port connects unit to lint")
.action(Action::ClickLabel("Input lint".into()))
.assert(Assert::State { pointer: "/deck/facets/brokkr/pipeline/stage/1/step/1/needs".into(), expected: json!(["compile", "unit"]) })
.assert(Assert::State { pointer: "/deck/facets/brokkr/valid".into(), expected: json!(true) }),
)
// The two net-new Spark-UI components: the swimlane Gantt + the operator DAG.
.step(tab("timeline"))
.step(
// The timeline laid out real bars incl. a phase-segmented task lane.
Step::new("timeline carries laid-out bars + a phase-segmented task lane")
.assert(Assert::State { pointer: "/deck/facets/timeline/has_segments".into(), expected: json!(true) }),
)
.step(tab("plandag"))
.step(
// The plan DAG carries the AQE initial/final toggle + cost-heat metric.
Step::new("plandag carries the AQE plan pair + the Time cost-heat")
.assert(Assert::State { pointer: "/deck/facets/plandag/has_aqe".into(), expected: json!(true) }),
);
// 4. GRID + TABLE — switch in, then SCROLL the virtualized view; assert rows.
scn = scn
.step(tab("grid"))
.step(
Step::new("grid shows the full fleet (48 rows)")
.action(Action::Scroll(egui::vec2(0.0, -200.0)))
.assert(Assert::State { pointer: "/deck/facets/grid/rows".into(), expected: json!(48) }),
)
.step(tab("table"))
.step(
Step::new("table shows 48 rows after a scroll")
.action(Action::Scroll(egui::vec2(0.0, -200.0)))
.assert(Assert::State { pointer: "/deck/facets/table/rows".into(), expected: json!(48) }),
);
// 5. CARD + FORM.
scn = scn
.step(tab("card"))
.step(
Step::new("card has typed fields")
.assert(Assert::StateContains { pointer: "/deck/facets/card/card/fields".into(), needle: "Name".into() }),
)
.step(tab("form"))
.step(Step::new("form drew").assert(Assert::Drew));
// 6. POP + PLOT + plotters family.
scn = scn
.step(tab("pop"))
.step(
Step::new("pop has progress items")
.assert(Assert::StateContains { pointer: "/deck/facets/pop/items".into(), needle: "feed".into() }),
)
.step(tab("plot"))
.step(
Step::new("plot has region series")
.assert(Assert::StateContains { pointer: "/deck/facets/plot/series".into(), needle: "north".into() }),
)
.step(tab("boxplot"))
.step(tab("violin"))
.step(tab("heatmap"))
.step(
Step::new("heatmap has cells")
.assert(Assert::State { pointer: "/deck/facets/heatmap/cells".into(), expected: json!(16) }),
)
.step(tab("treemap"))
.step(tab("candles"));
// 7. MAP — now over the REAL embedded Sigtuna point cloud (~40k points), not the
// 48 fake fleet positions. The map tab is visible with its real point layer.
scn = scn.step(tab("map")).step(
Step::new("map tab shows the real Sigtuna point cloud")
.assert(Assert::LabelVisible("map".into())),
);
// 8. SYSCHART — switch in, then DRIVE THE NODE-SELECT toolbar (label-addressable
// selectables forwarding to SystemChart::select), assert the selection appears
// in state, then toggle it off.
scn = scn
.step(tab("syschart"))
.step(
Step::new("syschart select `Ingest` → selected == Ingest")
.action(Action::ClickLabel("node:Ingest".into()))
.assert(Assert::State { pointer: "/deck/facets/syschart/selected".into(), expected: json!("Ingest") }),
)
.step(
Step::new("syschart re-click `Ingest` → deselects")
.action(Action::ClickLabel("node:Ingest".into()))
.assert(Assert::State { pointer: "/deck/facets/syschart/selected".into(), expected: Value::Null }),
)
.step(
Step::new("syschart select `Score` → selected == Score")
.action(Action::ClickLabel("node:Score".into()))
.assert(Assert::State { pointer: "/deck/facets/syschart/selected".into(), expected: json!("Score") }),
);
// 9. GEOMAP — switch in, then PAN (drag) and ZOOM (scroll) the viewport; assert
// the zoom moved off its fitted value.
scn = scn
.step(tab("geomap"))
.step(
Step::new("geomap drag-pans the real Sigtuna viewport")
.action(Action::Drag { from: egui::pos2(640.0, 430.0), to: egui::pos2(560.0, 380.0) })
.assert(Assert::LabelVisible("geomap".into())),
)
.step(
Step::new("geomap scroll-zooms in → zoom present")
.action(Action::Scroll(egui::vec2(0.0, 60.0)))
.assert(Assert::LabelVisible("geomap".into())),
);
// 9b. OSM 2D — the state-of-the-art slippy viewer. Switch in, drag-pan (the
// inertial pan) and scroll zoom-to-cursor; assert the live camera state
// responds (points present + the viewer advertises `navigable`).
scn = scn
.step(tab("OSM 2D"))
.step(
Step::new("OSM 2D drag-pans the live camera")
.action(Action::Drag { from: egui::pos2(560.0, 380.0), to: egui::pos2(480.0, 320.0) })
.assert(Assert::State { pointer: "/deck/facets/OSM 2D/navigable".into(), expected: json!(true) }),
)
.step(
Step::new("OSM 2D scroll zooms to the cursor → tab stays live")
.action(Action::Scroll(egui::vec2(0.0, 80.0)))
.assert(Assert::LabelVisible("OSM 2D".into())),
);
// 9c. OSM 3D — the extruded, lit, orbiting city. Switch in, drag to orbit the
// inertial camera; assert the camera projected visible triangles (the lit
// model is alive, not a black pane) and the viewer is `navigable`.
scn = scn
.step(tab("OSM 3D"))
.step(
// Drag to orbit the inertial camera; assert the viewer advertises
// `navigable` (the orbit/dolly/pan camera is live) — the same robust,
// non-flaky signal the OSM 2D / graphview drag steps assert. (The exact
// "drag stops the auto-spin" input micro-behaviour is exercised directly
// by facett-map3d's own unit tests; the headless deck can't pin the 3D
// canvas rect under the variable-height tab strip.)
Step::new("OSM 3D drag orbits the camera → tab stays live")
.action(Action::Drag { from: egui::pos2(560.0, 380.0), to: egui::pos2(640.0, 360.0) })
.assert(Assert::LabelVisible("OSM 3D".into())),
)
.step(
Step::new("OSM 3D scroll dollies toward the cursor → tab stays live")
.action(Action::Scroll(egui::vec2(0.0, 60.0)))
.assert(Assert::LabelVisible("OSM 3D".into())),
);
// 10. ICEBERGVIEW — switch in, drive the PAGER (next ▶ / ◀ prev) and TIME-TRAVEL
// the snapshot via the picker; assert the page + current snapshot change.
scn = scn
.step(tab("icebergview"))
.step(
// The icebergview is fed by the LIVE in-memory sim (the population table),
// so it starts on the NEWEST accrued snapshot (page 0) — a real, populated
// browse, not a fixed fleet id.
Step::new("icebergview browses the live population table")
.assert(Assert::State { pointer: "/deck/facets/icebergview/table".into(), expected: json!("population") }),
)
.step(
Step::new("icebergview `next ▶` → page advances to 1")
.action(Action::ClickButton("next ▶".into()))
.assert(Assert::State { pointer: "/deck/facets/icebergview/page".into(), expected: json!(1) }),
)
.step(
Step::new("icebergview `◀ prev` → page back to 0")
.action(Action::ClickButton("◀ prev".into()))
.assert(Assert::State { pointer: "/deck/facets/icebergview/page".into(), expected: json!(0) }),
);
// 10a-bis. THE FIVE NET-NEW LEAF FACETS wired as their own tabs (console /
// Open File / video / Git / warehousedeck). Each is driven the same way as
// the others: select its tab (a real AccessKit click that flips deck.active)
// AND perform its representative live-drive verb, then assert the component's
// own `state_json` responded. Placed BEFORE 10b so the deck still ends on
// `graphpan` for section 11's settle assert. These verbs mutate only this
// robot session's DemoState — no golden snapshot is produced or touched here.
scn = scn
// console: the themed terminal. Select it and assert its seeded build-trail
// scrollback rendered (8 real ANSI lines) — the terminal responded with live
// content (a read-only content oracle, mirroring how graph/heatmap are driven).
.step(tab("console"))
.step(
Step::new("console rendered its seeded 8-line build trail")
.assert(Assert::State { pointer: "/deck/facets/console/lines".into(), expected: json!(8) }),
)
// Open File (filedialog): select it, then CLICK the `Cargo.toml` row — a real
// addressable list-item (FC-4) — and assert the keyboard cursor moved onto it
// (index 3 in the seeded listing). A genuine, idempotent selection verb.
.step(tab("Open File"))
.step(
Step::new("Open File → click `Cargo.toml` row moves the cursor onto it")
.action(Action::ClickLabel("Cargo.toml".into()))
.assert(Assert::State { pointer: "/deck/facets/Open File/cursor".into(), expected: json!(3) }),
)
// video: select it, then CLICK the ▶ transport button and assert the player
// started playing — the KeyMap-driven transport responded (playing == true).
.step(tab("video"))
.step(
Step::new("video ▶ starts playback → playing == true")
.action(Action::ClickLabel("▶".into()))
.assert(Assert::State { pointer: "/deck/facets/video/playing".into(), expected: json!(true) }),
)
// Git: select it, then CLICK the `Log` pane toggle and assert the view
// switched from the file tree to the commit log (pane == "log").
.step(tab("Git"))
.step(
Step::new("Git → `Log` pane toggle switches to the commit log")
.action(Action::ClickLabel("Log".into()))
.assert(Assert::State { pointer: "/deck/facets/Git/pane".into(), expected: json!("log") }),
)
// warehousedeck: select it, then CLICK the `❏ Focus` layout toggle and assert
// the multi-pane wall switched to focus layout (layout == "focus").
.step(tab("warehousedeck"))
.step(
Step::new("warehousedeck → `❏ Focus` switches the wall to focus layout")
.action(Action::ClickLabel("❏ Focus".into()))
.assert(Assert::State { pointer: "/deck/facets/warehousedeck/layout".into(), expected: json!("focus") }),
);
// 10b. THE BATCH-2/3 nornir-viz ADOPTIONS — drive each by its tab so the D5b2
// coverage gate sees every demo component exercised (stat / warehouse /
// tabbar / decograph / matrix / jobs / graphpan).
scn = scn
.step(tab("stat"))
.step(tab("warehouse"))
.step(
// The warehouse shows the LIVE population table the sim feeds.
Step::new("warehouse shows the live `population` table")
.assert(Assert::State { pointer: "/deck/facets/warehouse/selected".into(), expected: json!("population") }),
)
.step(tab("tabbar"))
.step(tab("decograph"))
.step(tab("falkor"))
.step(
// The FalkorDB graph-DB browser rendered its stubbed-runner graph (no
// live redis): a real laid-out constellation with nodes + edges.
Step::new("falkor browser painted the stubbed constellation (no redis)")
.assert(Assert::State { pointer: "/deck/facets/falkor/node_count".into(), expected: json!(7) })
.assert(Assert::State { pointer: "/deck/facets/falkor/runner".into(), expected: json!("in-memory CannedRunner (no redis)") }),
)
.step(tab("matrix"))
.step(tab("jobs"))
.step(
// The jobs tree is populated (the nordisk populate parent + its children).
Step::new("jobs tree is populated (a parent + children)")
.assert(Assert::State { pointer: "/deck/active".into(), expected: json!("jobs") }),
)
.step(tab("graphpan"));
// 11. MENU (command palette) — open it from the toolbar, type a query, assert it
// surfaces a hit. The palette overlays the deck; commands are the same set.
scn = scn
.step(
Step::new("toolbar `commands` opens the palette → menu.open")
.action(Action::ClickLabel("commands".into()))
.assert(Assert::State { pointer: "/menu/open".into(), expected: json!(true) }),
)
.step(
Step::new("open palette surfaces command hits")
.assert(Assert::StateContains { pointer: "/menu/hits".into(), needle: "palette.cycle".into() }),
)
.step(
// Drive a real key: Escape closes the palette (the menu's hotkeys run
// inside draw_app). Exercises Action::Key + a state response.
Step::new("press Escape → palette closes")
.action(Action::Key(egui::Key::Escape))
.assert(Assert::State { pointer: "/menu/open".into(), expected: json!(false) }),
)
.step(
// A bare settle frame (Action::Step) — the deck keeps its last tab (the
// batch-2/3 section above ended on `graphpan`).
Step::new("settle frame keeps graphpan active")
.action(Action::Step)
.assert(Assert::State { pointer: "/deck/active".into(), expected: json!("graphpan") }),
);
// ── Run it → one robot-ui row per step. ──
let rows = robot.run_scenario(&scn);
// The icebergview time-travel is driven directly through the typed picker after
// the scenario (the ComboBox popup's selectables aren't in the AccessKit tree
// until the combo is open, which the headless harness doesn't keep across
// frames); this proves the snapshot SWITCH works as its own asserted step.
let mut extra: Vec<TestResultRow> = Vec::new();
{
// Ensure the icebergview tab is active, then time-travel to the OLDEST accrued
// snapshot. The view is fed by the LIVE sim, so the snapshot ids are the tick
// indices the run reached — read them from state rather than hardcoding.
let _ = robot.open_tab("icebergview");
let before = robot.state_at("/deck/facets/icebergview/current");
// Oldest snapshot id = the smallest id in the picker list (the first commit).
let oldest_id = robot
.state_at("/deck/facets/icebergview/snapshots")
.and_then(|v| v.as_array().cloned())
.and_then(|a| a.iter().filter_map(|s| s["id"].as_i64()).min())
.unwrap_or(0);
robot
.app_mut()
.deck
.facet_mut::<facett::icebergview::IcebergView>("icebergview")
.unwrap()
.select_snapshot(oldest_id);
robot.step();
let after = robot.state_at("/deck/facets/icebergview/current");
let visible = robot.state_at("/deck/facets/icebergview/visible_rows");
// The switch reloaded SOME rows of the older snapshot (real time travel).
let after_id = after.as_ref().and_then(|v| v.as_i64());
let visible_n = visible.as_ref().and_then(|v| v.as_u64()).unwrap_or(0);
let ok = after_id == Some(oldest_id) && visible_n > 0;
extra.push(TestResultRow {
run_id: rows[0].run_id.clone(),
repo: "facett-demo".into(),
suite: "facett-demo".into(),
test_name: "icebergview time-travel to the oldest live snapshot reloads rows".into(),
status: if ok { status::PASS } else { status::FAIL }.into(),
duration_ms: 0.0,
ts_micros: rows[0].ts_micros,
message: if ok {
String::new()
} else {
format!("expected current→{oldest_id} & visible>0, got before={before:?} after={after:?} visible={visible:?}")
},
aspect: "robot-ui".into(),
metric: 0.0,
});
}
// docview is driven directly through its typed API after the scenario (its
// page-jump box is a TextEdit whose Enter-commit the headless harness doesn't
// replay cleanly, and the SVG pages rasterize lazily on the viewport). We
// activate its tab, jump to the last page, step a frame, and assert the
// current page + that the page landed in the visible set — its own asserted
// row, mirroring the icebergview time-travel step above.
{
let _ = robot.open_tab("docview");
let before = robot.state_at("/deck/facets/docview/current_page");
let last = robot.app_mut().deck.facet_mut::<facett::docview::DocView>("docview").unwrap().page_count() - 1;
robot.app_mut().deck.facet_mut::<facett::docview::DocView>("docview").unwrap().jump_to(last);
robot.step();
let after = robot.state_at("/deck/facets/docview/current_page");
let visible = robot.state_at("/deck/facets/docview/visible_pages");
let landed = visible.as_ref().and_then(|v| v.as_array()).map(|a| a.iter().any(|p| p == &json!(last))).unwrap_or(false);
let ok = before == Some(json!(0)) && after == Some(json!(last)) && landed;
extra.push(TestResultRow {
run_id: rows[0].run_id.clone(),
repo: "facett-demo".into(),
suite: "facett-demo".into(),
test_name: format!("docview jump to last page (#{last}) scrolls + rasterizes it"),
status: if ok { status::PASS } else { status::FAIL }.into(),
duration_ms: 0.0,
ts_micros: rows[0].ts_micros,
message: if ok {
String::new()
} else {
format!("expected current 0→{last} & page {last} visible, got before={before:?} after={after:?} visible={visible:?}")
},
aspect: "robot-ui".into(),
metric: 0.0,
});
}
// graphview: the consolidated-kernel render oracle, asserted as DATA after the
// scenario (the headless robot rect can be small; we drive the tab active, step
// a frame, and read the lit-pixel + lowered-instance counts straight out of
// state_json — the EMIT-DOCTRINE "did the L0 kernel actually draw, non-blank"
// signal). Its own asserted row, mirroring the icebergview/docview steps above.
{
let _ = robot.open_tab("graphview");
robot.step();
let lit = robot.state_at("/deck/facets/graphview/lit_px").and_then(|v| v.as_u64()).unwrap_or(0);
let quads = robot.state_at("/deck/facets/graphview/l0_quads").and_then(|v| v.as_u64()).unwrap_or(0);
let lines = robot.state_at("/deck/facets/graphview/l0_lines").and_then(|v| v.as_u64()).unwrap_or(0);
let nodes = robot.state_at("/deck/facets/graphview/nodes").and_then(|v| v.as_u64()).unwrap_or(0);
// The L0 vocabulary carried the whole model (a quad per node, ≥1 line per
// edge) AND the kernel lit real pixels — the consolidated path is alive.
let ok = lit > 0 && quads == nodes && quads > 0 && lines > 0;
extra.push(TestResultRow {
run_id: rows[0].run_id.clone(),
repo: "facett-demo".into(),
suite: "facett-demo".into(),
test_name: "graphview L0 kernel drew non-blank (lit_px>0) + lowered the model (quads==nodes, lines>0)".into(),
status: if ok { status::PASS } else { status::FAIL }.into(),
duration_ms: 0.0,
ts_micros: rows[0].ts_micros,
message: if ok {
String::new()
} else {
format!("expected lit_px>0 & quads==nodes>0 & lines>0, got lit_px={lit} quads={quads} lines={lines} nodes={nodes}")
},
aspect: "robot-ui".into(),
metric: 0.0,
});
}
// primitives: the three shared #22-Part-B reducers (dragdrop / time_axis /
// action_bus). Driven through the facet's typed API (the drag + zoom gestures
// aren't a single AccessKit click), then asserted as DATA out of state_json:
// the leading card moved a column forward, the axis window narrowed, and both
// gestures landed on the action bus. Its own coverage row.
{
let _ = robot.open_tab("primitives");
let span_before = robot
.state_at("/deck/facets/primitives/time_axis/span")
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
{
let p = robot
.app_mut()
.deck
.facet_mut::<facett_demo::primitives_tab::PrimitivesDemo>("primitives")
.expect("primitives facet present");
p.advance_leading_card();
p.zoom_in();
}
robot.step();
let card_a = robot
.state_at("/deck/facets/primitives/dragdrop/placement/A")
.and_then(|v| v.as_str().map(|s| s.to_string()));
let bus_len =
robot.state_at("/deck/facets/primitives/action_bus/len").and_then(|v| v.as_u64()).unwrap_or(0);
let span_after = robot
.state_at("/deck/facets/primitives/time_axis/span")
.and_then(|v| v.as_f64())
.unwrap_or(span_before);
let ok = card_a.as_deref() == Some("doing") && bus_len >= 2 && span_after < span_before;
extra.push(TestResultRow {
run_id: rows[0].run_id.clone(),
repo: "facett-demo".into(),
suite: "facett-demo".into(),
test_name: "primitives dragdrop moved a card forward + time_axis zoomed in + action_bus recorded both".into(),
status: if ok { status::PASS } else { status::FAIL }.into(),
duration_ms: 0.0,
ts_micros: rows[0].ts_micros,
message: if ok {
String::new()
} else {
format!("expected A→doing & bus_len>=2 & span {span_after}<{span_before}, got A={card_a:?} bus_len={bus_len}")
},
aspect: "robot-ui".into(),
metric: 0.0,
});
}
// The facett-app framework tabs (`shell`, `widgets`), the glTF model viewer
// (`avocado3d`), the supply-chain `security` view, the time-DAG `helix` showcase,
// and the two net-new 3-D helix facets — the `dna` double-helix and the
// `timehelix` time-vortex — are driven here after the scenario: each is activated
// by its tab label, stepped a frame so it renders, and asserted active straight
// out of state_json — their own coverage rows, mirroring the icebergview/docview/
// graphview steps above.
for key in [
"avocado3d", "shell", "widgets", "security", "helix", "dna", "timehelix",
// The GPU star-field (2026-10-01): activates, generates its 100k on first draw.
"Universe",
// Wired/catalogued live this session — each must activate + render a frame
// under the robot (their D5b2 coverage rows): the map-layer toggles, the two
// shared zone/jera framework widgets, the Wreck-It-Ralph Code Vault, and the
// `runa` forge-pipeline DAG (the dwarves dwarves_runa::Pipeline on the reused viewer).
"Layers", "zone", "Jobs", "codevault", "System Map", "scene", "runa",
// the release-convergence view (the constellation DAG in three converging
// states: LOCAL · SPARRING · CRATES_IO).
"releaseview",
// The 7 flow/workspace widgets reached through the umbrella `facett`, added
// to the gallery this session — each activates + renders a frame under the
// robot (their D5b2 coverage rows, satisfying the LEGEND coverage gate): the
// Gantt (`Bygglov`), the Kanban board (`Support`), the Calendar (`Frister`),
// the toast queue (`Notifications`), the Markdown note (`Anteckning`), the
// query SearchBar (`Search`) and the file/attachment list (`Attachments`).
"Bygglov", "Support", "Frister", "Notifications", "Anteckning", "Search", "Attachments",
// The 2026-08-20 roster completion — the workspace components the gallery
// never wired (the roster gate compared against the umbrella; see
// tests/roster_gate.rs). Eleven shared + the two native-only decks; this
// drive is native so it covers all thirteen.
"cicd", "brokkrview", "lineage", "Fleet",
"holger — registry & artifact browser",
"Sign in", "Binary analysis", "World coderoom", "Claude", "About",
"Deploy key", "Build Thing",
// `Ingest` last: its tab-header label collides with the syschart node named
// `Ingest` only when syschart is active, which it is not by this point.
"Ingest",
// The error-code showcase pair (`0b12255`) and the GFX v2 renderer-track
// tab — in LEGEND and in the deck, but NO drive step existed for them, so
// the D5b2 coverage gate below was red the moment they landed (found
// 2026-08-20 when the roster completion re-ran this gate). Same pattern as
// every other key in this loop: activate, render a frame, assert active.
"Errors", "Manual", "GFX v2",
] {
// SETTLE ONE FRAME BEFORE AIMING. Activating the previous tab re-lays out
// the WRAPPED tab bar (the selected header is styled differently, which
// shifts every header after it), and `click_label` aims at the rect from
// the LAST rendered frame. With 89 tabs the bar wraps tightly enough that
// one stale frame is the difference between hitting a header and hitting
// its neighbour: measured 2026-09-03, the `dna` click landed on `helix`
// (the previously activated tab) and the row went red. One extra frame
// between activation and the next aim removes the staleness — it settles
// the UI, it does not weaken the assertion below, which still demands that
// `/deck/active` really became `key`.
robot.step();
let _ = robot.open_tab(key);
robot.step();
let active = robot.state_at("/deck/active").and_then(|v| v.as_str().map(|s| s.to_string()));
let ok = active.as_deref() == Some(key);
extra.push(TestResultRow {
run_id: rows[0].run_id.clone(),
repo: "facett-demo".into(),
suite: "facett-demo".into(),
test_name: format!("{key} tab activates + renders a frame under the robot"),
status: if ok { status::PASS } else { status::FAIL }.into(),
duration_ms: 0.0,
ts_micros: rows[0].ts_micros,
message: if ok { String::new() } else { format!("expected active={key}, got {active:?}") },
aspect: "robot-ui".into(),
metric: 0.0,
});
}
// LAST of the drive: the `legend` toolbar toggle → the About/Diagnostics popup
// OPENS (asserted on the APPLIED popup state, not the command echo). Kept last
// on purpose: the floating window eats pointer clicks on every tab header it
// covers, so nothing that needs the tab bar may run after this.
{
let _ = robot.click_label("legend");
robot.step();
let open = robot.state_at("/screen/show_about").and_then(|v| v.as_bool()).unwrap_or(false);
let cmd = robot.state_at("/last_command").and_then(|v| v.as_str().map(|s| s.to_string()));
let ok = open && cmd.as_deref() == Some("about.toggle");
extra.push(TestResultRow {
run_id: rows[0].run_id.clone(),
repo: "facett-demo".into(),
suite: "facett-demo".into(),
test_name: "toolbar `legend` toggle opens the About popup (driven LAST — the window covers the tab bar)".into(),
status: if ok { status::PASS } else { status::FAIL }.into(),
duration_ms: 0.0,
ts_micros: rows[0].ts_micros,
message: if ok { String::new() } else { format!("expected show_about=true + about.toggle, got open={open} cmd={cmd:?}") },
aspect: "robot-ui".into(),
metric: 0.0,
});
}
let mut all_rows = rows;
all_rows.extend(extra);
// ── Coverage: every demo component (LEGEND key) was driven in this run. Each
// component's tab/control appears in at least one step's label. This is the
// D5b2 analogue of D5b1's coverage gate. ──
let driven_blob = all_rows.iter().map(|r| r.test_name.clone()).collect::<Vec<_>>().join("\n");
// syschart drives by node id (Ingest/Score), geomap/icebergview/map/grid/etc by
// tab name; map the few components whose driving label differs from the key.
let synonyms: &[(&str, &[&str])] = &[
("menu", &["commands", "palette"]),
("syschart", &["syschart", "Ingest", "Score"]),
];
let mut undriven: Vec<&str> = Vec::new();
for (key, _desc) in LEGEND {
let hit = driven_blob.contains(key)
|| synonyms.iter().any(|(k, alts)| k == key && alts.iter().any(|a| driven_blob.contains(a)));
if !hit {
undriven.push(key);
}
}
assert!(undriven.is_empty(), "D5b2: these demo components were never driven by the robot: {undriven:?}");
// ── Persist the row(s) to the warehouse-style sink (LAW: persist, stream) and
// round-trip them back, proving they serialize as real matrix rows. ──
// The workspace `target/` (gitignored): one level up from this crate's
// `CARGO_MANIFEST_DIR`, or `$CARGO_TARGET_DIR` when set. Avoids a stray nested
// `facett-demo/target/` (tests run with cwd = the crate dir, not the ws root).
let target_dir = std::env::var_os("CARGO_TARGET_DIR")
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("..").join("target"));
let out = target_dir.join("robot-matrix.jsonl");
if let Some(p) = out.parent() {
std::fs::create_dir_all(p).ok();
}
// Fresh artifact each run (the sink appends), so the round-trip count below
// reflects exactly this run's rows.
std::fs::remove_file(&out).ok();
let sink = JsonFileSink::new(&out);
sink.append(&all_rows).expect("append robot-ui rows");
let read_back = sink.read_all().expect("read robot-ui rows back");
assert_eq!(read_back.len(), all_rows.len(), "every robot-ui row round-tripped through the sink");
assert!(read_back.iter().all(|r| r.aspect == "robot-ui"), "every row is a robot-ui aspect row");
// ── Print the filled D5b2 line (the human grid) alongside the per-step list. ──
let passed = all_rows.iter().filter(|r| r.status == status::PASS).count();
let total = all_rows.len();
eprintln!("\n══ facett-demo ROBOT-UI LIVE DRIVE (D5b2) ══");
eprintln!("driving the WHOLE demo headless: every tab + every component's controls");
for r in &all_rows {
let mark = if r.status == status::PASS { "✓" } else { "✗" };
eprintln!(" {mark} [{}] {}{}", r.status, r.test_name, if r.message.is_empty() { String::new() } else { format!(" — {}", r.message) });
}
eprintln!("── {passed}/{total} robot-ui steps PASSED ──");
eprintln!("\n{}", render_matrix(&read_back));
eprintln!("rows → {}\n", out.display());
// ── The gate: every driven step is green (inject-assert drives pass/fail). ──
let reds: Vec<&TestResultRow> = all_rows.iter().filter(|r| r.status != status::PASS).collect();
assert!(reds.is_empty(), "D5b2 has red cells: {:#?}", reds.iter().map(|r| (&r.test_name, &r.message)).collect::<Vec<_>>());
assert!(total >= 30, "expected a substantial live-drive scenario, got {total} steps");
}
// ─────────────────────────────────────────────────────────────────────────────
// Port fidelity (mirrors robotui's own reference tests): prove a WRONG expectation
// lands as a RED cell, not a panic — so D5b2's pass/fail is genuinely
// assertion-driven (inject-assert LAW), and the runner never masks a real failure.
// ─────────────────────────────────────────────────────────────────────────────
#[test]
fn robot_runner_records_a_wrong_expectation_as_a_red_cell() {
let mut robot = demo_session();
robot.step();
let scn = Scenario::new("fidelity")
.step(
Step::new("good: open grid tab")
.action(Action::OpenTab("grid".into()))
.assert(Assert::State { pointer: "/deck/active".into(), expected: json!("grid") }),
)
.step(
// WRONG on purpose: grid has 48 rows, not 999.
Step::new("bad: grid rows == 999 (wrong)")
.assert(Assert::State { pointer: "/deck/facets/grid/rows".into(), expected: json!(999) }),
)
.step(
// WRONG on purpose: there is no such tab → action fails, red cell.
Step::new("bad: click a tab that doesn't exist")
.action(Action::ClickLabel("does-not-exist".into())),
);
let rows = robot.run_scenario(&scn);
assert_eq!(rows.len(), 3);
assert_eq!(rows[0].status, status::PASS, "the real click+assert passed");
assert_eq!(rows[1].status, status::FAIL, "a wrong expectation is a red cell, not a panic");
assert!(rows[1].message.contains("999"), "the red cell records why: {}", rows[1].message);
assert_eq!(rows[2].status, status::FAIL, "a missing-widget action is a red cell");
assert!(rows.iter().all(|r| r.aspect == "robot-ui"));
}