Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

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"));
}