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

nordisk/monetize

uitest/src/matrix.rs

//! The situations — `UI.md`'s four screens, as rows.
//!
//! ## Written from the SPEC, asserted on the DOM
//!
//! Every row names a sentence in `UI.md` or `BASE-MODEL.md` and checks what
//! the page actually holds for it: `innerText` (what a person reads), a
//! `role="meter"` (what a screen reader is told), a `title` (where the jargon
//! is allowed to live). Never the page's own claim about itself, and never a
//! class name the implementation happens to use — a row that keys on markup
//! tests the markup, and the markup is what is landing commit by commit. A row
//! for a screen that has not landed yet is RED, which is correct.
//!
//! ## Every number is the fixture's
//!
//! `crate::fixture` is the one source: `10.2 GiB` on alice's page is
//! `ALICE_PACK.text`, proved by a host test to be what the one byte
//! formatter renders for `ALICE_PACK.raw`, which is what the mock served. A
//! row that finds the string has found the wire.

use std::path::Path;
use std::time::Instant;

use crate::cdp::{Cdp, Chrome};
use crate::fixture as fx;
use crate::http;
use crate::json::{esc, Json};
use crate::rig::Rig;

pub struct Row {
    pub name: &'static str,
    pub ok: bool,
    pub ms: u128,
    pub detail: String,
}

fn go(rows: &mut Vec<Row>, only: Option<&str>, name: &'static str, f: impl FnOnce() -> Result<String, String>) -> bool {
    if let Some(o) = only {
        if !name.contains(o) {
            return true;
        }
    }
    let t = Instant::now();
    let (ok, detail) = match f() {
        Ok(d) => (true, d),
        Err(e) => (false, e),
    };
    let ms = t.elapsed().as_millis();
    println!("{:<4} {:<58} {:>6} ms  {}", if ok { "PASS" } else { "FAIL" }, name, ms, http::cut(&detail, 140));
    rows.push(Row { name, ok, ms, detail });
    ok
}

fn want(cond: bool, msg: impl FnOnce() -> String) -> Result<(), String> {
    if cond {
        Ok(())
    } else {
        Err(msg())
    }
}

// ── the API, on the real binary ──────────────────────────────────────────

struct Api<'a> {
    r: &'a Rig,
}

impl Api<'_> {
    fn get(&self, path: &str) -> Result<(u16, Json, String), String> {
        let resp = http::req(&self.r.ui_addr(), "GET", path, &[], None)?;
        Ok((resp.status, resp.json().unwrap_or(Json::Null), resp.body))
    }
    fn post(&self, path: &str, body: &str) -> Result<(u16, Json, String), String> {
        let h = [("Content-Type", "application/json".to_owned()), ("Origin", self.r.origin())];
        let resp = http::req(&self.r.ui_addr(), "POST", path, &h, Some(body))?;
        Ok((resp.status, resp.json().unwrap_or(Json::Null), resp.body))
    }
}

pub fn run(r: &Rig, chrome_bin: Option<&str>, only: Option<&str>) -> Vec<Row> {
    let mut rows: Vec<Row> = Vec::new();
    let a = Api { r };

    go(&mut rows, only, "the-console-presents-the-bearer-and-monetize-names-four-products", || {
        // The mock REFUSES a call without the suite bearer, so a serving
        // answer with the four ids is the proof the token travelled.
        let (status, j, body) = a.get("/api/health")?;
        want(status == 200, || format!("/api/health {status}: {}", http::cut(&body, 200)))?;
        want(j.path("serving").and_then(|v| v.as_bool()) == Some(true), || format!("not serving: {}", http::cut(&body, 200)))?;
        let ids: Vec<String> = j.path("products").map(|p| p.arr().iter().map(|x| x.as_str().unwrap_or("").to_owned()).collect()).unwrap_or_default();
        want(ids == fx::PRODUCT_IDS, || format!("products {ids:?}"))?;
        Ok(format!("serving {}", ids.join(", ")))
    });

    go(&mut rows, only, "every-screen-endpoint-answers-the-open-console", || {
        let mut seen = Vec::new();
        for path in ["/api/products", "/api/products/gunnar/tenants", "/api/tenants/gunnar/alice", "/api/ledger?product=gunnar"] {
            let (status, _, body) = a.get(path)?;
            want(status == 200, || format!("{path} answered {status}: {}", http::cut(&body, 200)))?;
            seen.push(format!("{path} {}B", body.len()));
        }
        Ok(seen.join(" · "))
    });

    go(&mut rows, only, "the-sealed-refusal-reaches-the-console-whole", || {
        // The ORDER: alice to 100 GiB. Refused by the product, in its words.
        let body = format!(r#"{{"product":"gunnar","tenant":"alice","caps":{{"pack_bytes":{}}},"vendor":"mockpay"}}"#, 100u64 << 30);
        let (status, j, body) = a.post("/api/purchase", &body)?;
        want(status == 400, || format!("expected 400, got {status}: {}", http::cut(&body, 200)))?;
        let msg = j.text("message");
        want(msg == fx::SEALED_REFUSAL, || format!("the sentence was changed on the way: {}", http::cut(&msg, 300)))?;
        want(msg.ends_with("not another disk"), || "the last clause is missing".into())?;
        // And the QUOTE before it says the same, as a quote — with the price
        // of what was refused beside it, and nothing moved.
        let (status, q, body) = a.get(&format!("/api/quote?product=gunnar&tenant=alice&caps=pack_bytes%3D{}", 100u64 << 30))?;
        want(status == 200, || format!("the quote answered {status}: {}", http::cut(&body, 200)))?;
        want(q.path("can_serve").and_then(|v| v.as_bool()) == Some(false), || format!("the quote must say no: {}", http::cut(&body, 200)))?;
        want(q.text("reason") == fx::SEALED_REFUSAL, || "the quote's reason is not the sentence".into())?;
        want(q.path("price_minor").and_then(|v| v.as_f64()) == Some(fx::ALICE_QUOTE_90_GIB_MINOR as f64), || format!("the price of the 90 GiB refused: {}", http::cut(&body, 200)))?;
        Ok(format!("{} chars, ends with \"not another disk\"; quoted at {} first", msg.len(), fx::ALICE_QUOTE_90_GIB_TEXT))
    });

    if let Some(chrome_bin) = chrome_bin {
        if let Some(mut b) = Browser::start(chrome_bin, &r.work.join("chrome"), &mut rows, only) {
            screens(&mut b, &r.origin(), &mut rows, only);
        }
    }
    rows
}

pub fn live(chrome_bin: &str, origin: &str, only: Option<&str>) -> Vec<Row> {
    let mut rows = Vec::new();
    let profile = std::env::temp_dir().join(format!("monetize-uitest-live-{}", std::process::id()));
    if let Some(mut b) = Browser::start(chrome_bin, &profile, &mut rows, only) {
        screens(&mut b, origin, &mut rows, only);
    }
    let _ = std::fs::remove_dir_all(&profile);
    rows
}

// ── the browser ──────────────────────────────────────────────────────────

struct Browser {
    _chrome: Chrome,
    c: Cdp,
    sess: String,
}

impl Browser {
    fn start(chrome_bin: &str, profile: &Path, rows: &mut Vec<Row>, only: Option<&str>) -> Option<Browser> {
        let port = match std::net::TcpListener::bind("127.0.0.1:0").and_then(|l| l.local_addr()) {
            Ok(a) => a.port(),
            Err(e) => {
                go(rows, None, "chrome-starts-headless", || Err(e.to_string()));
                return None;
            }
        };
        let chrome = match Chrome::launch(chrome_bin, profile, port) {
            Ok(c) => {
                go(rows, None, "chrome-starts-headless", || Ok(format!("debugging on {port}")));
                c
            }
            Err(e) => {
                go(rows, None, "chrome-starts-headless", || Err(format!("{e} — install Google Chrome or pass --chrome <path>; a matrix that cannot open a browser is red, not skipped")));
                return None;
            }
        };
        let mut c = match Cdp::attach(port) {
            Ok(c) => c,
            Err(e) => {
                go(rows, None, "devtools-protocol-attaches", || Err(e));
                return None;
            }
        };
        let sess = match c.page() {
            Ok(s) => {
                go(rows, None, "devtools-protocol-attaches", || Ok("page attached".into()));
                s
            }
            Err(e) => {
                go(rows, None, "devtools-protocol-attaches", || Err(e));
                return None;
            }
        };
        let _ = only;
        Some(Browser { _chrome: chrome, c, sess })
    }

    fn navigate(&mut self, url: &str) -> Result<(), String> {
        self.c.navigate(&self.sess, url)
    }

    /// The page as a person reads it. Closed `<details>` are NOT in it — which
    /// is exactly what a "behind a badge" assertion needs.
    fn text(&mut self) -> Result<String, String> {
        self.c.eval_str(&self.sess, "document.body ? document.body.innerText : ''")
    }

    fn wait_text(&mut self, needle: &str) -> Result<(), String> {
        self.c.wait_for(&self.sess, &format!("document.body && document.body.innerText.includes({})", esc(needle)), 20_000).map_err(|e| {
            let held = self.text().unwrap_or_default();
            let at = self.c.eval_str(&self.sess, "location.href").unwrap_or_default();
            // What the page said while it held nothing: a wasm panic, a
            // refused script, a request that never answered.
            let errs = self.errors();
            format!("{e}; at {at} the page held: {}; page errors: {}", http::cut(&held, 300), http::cut(&errs.join(" | "), 400))
        })
    }

    /// Open every disclosure on the page: `<details>`, and anything that says
    /// it is collapsed. What was behind a badge is on the surface afterwards.
    fn disclose_all(&mut self) -> Result<u64, String> {
        let n = self.c.eval(
            &self.sess,
            "(() => { let n = 0;
                for (const d of document.querySelectorAll('details')) { if (!d.open) { d.open = true; n++; } }
                for (const b of document.querySelectorAll('[aria-expanded=\"false\"]')) { b.click(); n++; }
                return n; })()",
        )?;
        Ok(n.as_f64().unwrap_or(0.0) as u64)
    }

    fn count(&mut self, selector: &str) -> Result<u64, String> {
        Ok(self.c.eval(&self.sess, &format!("document.querySelectorAll({}).length", esc(selector)))?.as_f64().unwrap_or(0.0) as u64)
    }

    /// Every `title` attribute on the page, joined — where `UI.md` rule 3 lets
    /// the precise name live.
    fn titles(&mut self) -> Result<String, String> {
        self.c.eval_str(&self.sess, "[...document.querySelectorAll('[title]')].map(e => e.getAttribute('title')).join('\\n')")
    }

    fn errors(&mut self) -> Vec<String> {
        self.c.drain_page_errors()
    }

    /// **The row that names `needle`**, as one line: the element whose own
    /// text is exactly `needle` (a tenant id, a product id), walked up to the
    /// nearest thing that is a row — a link, a table row, a list item, an
    /// ARIA row — and its text with the whitespace collapsed. `UI.md`: a
    /// product is a row, a tenant is a row, a sale is a row; each leads
    /// somewhere or sits in a list.
    fn row_of(&mut self, needle: &str) -> Result<String, String> {
        self.row_in(None, needle, true)
    }

    /// A row that merely CONTAINS `needle` (`Sold bob …`), after the
    /// heading `after` when one is given — so a sale row for a tenant is not
    /// confused with the tenant's own row further up the page.
    fn row_in(&mut self, after: Option<&str>, needle: &str, exact: bool) -> Result<String, String> {
        let r = self.c.eval_str(
            &self.sess,
            &format!(
                "(() => {{ const N = {}; const H = {}; const EXACT = {};
                   const all = [...document.body.querySelectorAll('*')];
                   let from = null;
                   if (H) {{
                     from = all.find(h => /^(H[1-6]|SUMMARY|HEADER|CAPTION|LEGEND)$/.test(h.tagName) && h.textContent.trim().toLowerCase().startsWith(H));
                     if (!from) return '';
                   }}
                   const after = e => !from || (from.compareDocumentPosition(e) & Node.DOCUMENT_POSITION_FOLLOWING);
                   const own = e => [...e.childNodes].filter(n => n.nodeType === 3).map(n => n.textContent).join('');
                   const cands = all.filter(after);
                   let leaf = cands.find(e => own(e).trim() === N);
                   if (!leaf && !EXACT) leaf = cands.find(e => own(e).includes(N));
                   if (!leaf) return '';
                   const row = leaf.closest('a, tr, li, [role=\"row\"], [class*=\"row\"]') || leaf.parentElement;
                   return row.innerText.replace(/\\s+/g, ' ').trim(); }})()",
                esc(needle),
                after.map(|h| esc(&h.to_lowercase())).unwrap_or_else(|| "null".into()),
                if exact { "true" } else { "false" }
            ),
        )?;
        if r.is_empty() {
            return Err(format!("no row names {needle:?}{}; the page held: {}", after.map(|h| format!(" under {h:?}")).unwrap_or_default(), http::cut(&self.text()?, 300)));
        }
        Ok(r)
    }

    /// Wait until the page holds a ROW for `needle` — an element whose own
    /// text is exactly that. Not merely the word anywhere: the topbar names
    /// every product before the list has loaded.
    fn wait_row(&mut self, needle: &str) -> Result<String, String> {
        let deadline = Instant::now() + std::time::Duration::from_millis(20_000);
        loop {
            if let Ok(r) = self.row_of(needle) {
                return Ok(r);
            }
            if Instant::now() > deadline {
                return Err(format!("no row for {needle:?} appeared within 20 s; the page held: {}", http::cut(&self.text()?, 300)));
            }
            std::thread::sleep(std::time::Duration::from_millis(100));
        }
    }

    fn click_button(&mut self, text: &str) -> Result<(), String> {
        let hit = self.c.eval_str(
            &self.sess,
            &format!(
                "(() => {{ const b = [...document.querySelectorAll('button,a')].find(e => e.textContent.trim() === {}); if (!b) return 'none'; b.click(); return 'clicked'; }})()",
                esc(text)
            ),
        )?;
        want(hit == "clicked", || format!("no button reads {text:?}"))
    }
}

/// The text of one section: from the line that reads `heading` (case-insensitively)
/// up to the next line that reads any of `until`, or the end.
fn section(text: &str, heading: &str, until: &[&str]) -> Result<String, String> {
    let lines: Vec<&str> = text.lines().collect();
    let start = lines.iter().position(|l| l.trim().eq_ignore_ascii_case(heading) || l.trim().to_lowercase().starts_with(&heading.to_lowercase())).ok_or_else(|| {
        let heads: Vec<&str> = lines.iter().filter(|l| l.len() < 40 && !l.trim().is_empty()).take(30).copied().collect();
        format!("no {heading:?} heading; short lines seen: {heads:?}")
    })?;
    let end = lines[start + 1..].iter().position(|l| until.iter().any(|u| l.trim().to_lowercase().starts_with(&u.to_lowercase()))).map(|i| start + 1 + i).unwrap_or(lines.len());
    Ok(lines[start + 1..end].join("\n"))
}

/// Positions of `names` in `text`, in the order they appear, or which are missing.
fn order_of(text: &str, names: &[&str]) -> Result<Vec<String>, String> {
    let mut found: Vec<(usize, &str)> = Vec::new();
    let mut missing = Vec::new();
    for n in names {
        match text.find(n) {
            Some(i) => found.push((i, n)),
            None => missing.push(*n),
        }
    }
    if !missing.is_empty() {
        return Err(format!("missing {missing:?} in: {}", http::cut(text, 300)));
    }
    found.sort();
    Ok(found.into_iter().map(|(_, n)| n.to_owned()).collect())
}

/// **The estate every screen below is one estate's** (owner 2026-10-02: the
/// ESTATE is the top level, every page of it under `/e/<estate>/…`). The rig's
/// console administers one, named by `/api/overview`; `local` is what a console
/// with a plain `--control` calls its estate.
fn estate_base(origin: &str) -> String {
    let hostport = origin.trim_start_matches("http://");
    let name = http::req(hostport, "GET", "/api/overview", &[], None)
        .ok()
        .and_then(|r| crate::json::parse(&r.body).ok())
        .and_then(|j| j.path("estates").and_then(|e| e.arr().first().map(|x| x.text("name"))))
        .filter(|n| !n.is_empty())
        .unwrap_or_else(|| "local".to_owned());
    format!("{origin}/e/{name}")
}

fn tenant_url(base: &str, tenant: &str) -> String {
    format!("{base}/customer?product=gunnar&tenant={}", tenant.replace('/', "%2F"))
}

// ── the four screens ─────────────────────────────────────────────────────

fn screens(b: &mut Browser, origin: &str, rows: &mut Vec<Row>, only: Option<&str>) {
    let base = estate_base(origin);
    // Screen 1's product lines are the estate's MAP now; its spend lines, MONEY.
    let map = format!("{base}/map");
    let money = format!("{base}/money");
    // ── screen 1 ──
    go(rows, only, "the-console-loads-under-the-served-csp-and-runs-its-script", || {
        // The blank-page regression (serve.rs, 2026-09-04): a CSP hash that
        // does not match the served bootstrap blocks the module script, nothing
        // mounts, and every route still answers 200. So the assertion is a DOM
        // node the WASM created — index.html ships an EMPTY body — under the
        // header the server really sent.
        let hostport = origin.trim_start_matches("http://");
        let resp = http::req(hostport, "GET", "/", &[("Accept", "text/html".to_owned())], None)?;
        let csp = resp.header("content-security-policy").unwrap_or("").to_owned();
        want(csp.contains("script-src") && csp.contains("'sha256-"), || format!("the page was served without a hashed script-src: {:?}", http::cut(&csp, 200)))?;
        let _ = b.errors();
        b.navigate(origin)?;
        b.c.wait_for(&b.sess, "document.body && document.body.children.length > 0", 20_000).map_err(|e| format!("the console's script did not create anything in the empty body ({e}); the CSP was: {}", http::cut(&csp, 200)))?;
        b.wait_text("monetize")?;
        let errs = b.errors();
        want(errs.is_empty(), || format!("{} page error(s): {}", errs.len(), http::cut(&errs.join(" | "), 300)))?;
        Ok(format!("body has {} children; script-src hashed", b.count("body > *")?))
    });

    go(rows, only, "screen1-one-row-per-product-and-gunnar-is-one-of-them", || {
        b.navigate(&map)?;
        b.c.wait_for(&b.sess, "document.querySelectorAll('a.product-row').length >= 4", 20_000)?;
        b.wait_row("gunnar")?;
        // A product row LEADS to its screen (`→`); the rows are the links.
        let hrefs = b.c.eval(&b.sess, "[...new Set([...document.querySelectorAll('a.product-row')].map(a => a.getAttribute('href')))]")?;
        let hrefs: Vec<String> = hrefs.arr().iter().map(|h| h.as_str().unwrap_or("").to_owned()).collect();
        want(hrefs.len() == fx::PRODUCT_IDS.len(), || format!("{} product rows, expected {}: {hrefs:?}", hrefs.len(), fx::PRODUCT_IDS.len()))?;
        let text = b.text()?;
        for id in fx::PRODUCT_IDS {
            want(text.contains(id), || format!("no row names {id}: {}", http::cut(&text, 300)))?;
        }
        // And the "huge text mass" is gone: no meter name, no prose paragraph
        // about plugins on the estate screen.
        want(!text.contains("pack_bytes"), || "the products screen still lists raw meters".into())?;
        Ok(format!("{} rows: {}", hrefs.len(), hrefs.join(" ")))
    });

    go(rows, only, "screen1-a-down-product-shows-the-down-dot-and-not-connected", || {
        b.navigate(&map)?;
        b.c.wait_for(&b.sess, "document.querySelectorAll('a.product-row').length >= 4", 20_000)?;
        let line = b.wait_row("holger")?;
        want(line.to_lowercase().contains("not connected"), || format!("holger's row does not say `not connected`: {line:?}"))?;
        // The dot carries the state: an element on that row whose class or
        // title says `down`, and the plugin's own reason reachable as a title.
        let dot = b.c.eval(
            &b.sess,
            "(() => { const row = [...document.querySelectorAll('a.product-row')].find(a => a.textContent.includes('holger')); if (!row) return 'no row';
                const el = [...row.querySelectorAll('*')].find(e => /down|not connected/i.test((e.className||'') + ' ' + (e.getAttribute('title')||'')));
                return el ? ((el.className||'') + ' | ' + (el.getAttribute('title')||'')) : 'no dot'; })()",
        )?;
        let dot = dot.as_str().unwrap_or("").to_owned();
        want(dot != "no row" && dot != "no dot", || format!("holger's row has no element marked down: {dot}"))?;
        want(dot.contains(fx::HOLGER_DOWN_DETAIL) || b.titles()?.contains(fx::HOLGER_DOWN_DETAIL), || format!("the plugin's own reason is not reachable: {dot}"))?;
        Ok(format!("{} · {}", http::cut(&line, 60), http::cut(&dot, 80)))
    });

    go(rows, only, "screen1-the-fleet-figure-is-the-fixtures-used-and-capacity", || {
        b.navigate(&map)?;
        b.c.wait_for(&b.sess, "document.querySelectorAll('a.product-row').length >= 4", 20_000)?;
        let line = b.wait_row("gunnar")?;
        want(line.contains(fx::GUNNAR_PACK_USED_TEXT), || format!("gunnar's row does not show {} used: {line:?}", fx::GUNNAR_PACK_USED_TEXT))?;
        want(line.contains(fx::GUNNAR_CAPACITY.text), || format!("gunnar's row does not show the capacity {}: {line:?}", fx::GUNNAR_CAPACITY.text))?;
        want(line.contains("7 tenants"), || format!("gunnar's row does not count its seven tenants: {line:?}"))?;
        Ok(http::cut(&line, 120))
    });

    go(rows, only, "screen1-a-rate-is-one-phrase-and-an-unknown-rate-is-nothing", || {
        b.navigate(&map)?;
        b.c.wait_for(&b.sess, "document.querySelectorAll('a.product-row').length >= 4", 20_000)?;
        b.wait_row("idun")?;
        let gunnar = b.row_of("gunnar")?;
        want(gunnar.contains(&format!("{}/wk", fx::GUNNAR_FLEET_PER_WEEK_TEXT)), || format!("gunnar's rate is not `{}/wk`: {gunnar:?}", fx::GUNNAR_FLEET_PER_WEEK_TEXT))?;
        // idun: `per_week` is null (not enough history). NO rate, and no zero.
        let idun = b.row_of("idun")?;
        want(!idun.contains("/wk"), || format!("idun has no rate yet but the row shows one: {idun:?}"))?;
        // …and not as a zero either. The check is on the RATE: `0 B in the
        // cloud` further along the row is an honest zero (idun owns nothing).
        want(!idun.contains("B/wk") && !idun.contains("0/wk"), || format!("an unknown rate rendered as zero: {idun:?}"))?;
        // skade: known but bursty — the rate stands, marked `~`.
        let skade = b.row_of("skade")?;
        want(skade.contains(&format!("~{}/wk", fx::SKADE_FLEET_PER_WEEK_TEXT)), || format!("skade's bursty rate should carry its `~`: {skade:?}"))?;
        Ok(format!("gunnar {} · idun (none) · skade ~", fx::GUNNAR_FLEET_PER_WEEK_TEXT))
    });

    go(rows, only, "screen1-unmeasured-capacity-is-neither-a-full-bar-nor-an-empty-one", || {
        b.navigate(&map)?;
        b.c.wait_for(&b.sess, "document.querySelectorAll('a.product-row').length >= 4", 20_000)?;
        let idun = b.wait_row("idun")?;
        want(idun.contains(fx::OLA_ARCHIVE.text), || format!("idun's used figure {} is missing: {idun:?}", fx::OLA_ARCHIVE.text))?;
        want(idun.to_lowercase().contains("unmeasured"), || format!("idun's capacity was never measured and the row does not say so: {idun:?}"))?;
        // No meter role on a row with no denominator: a bar there would be a
        // number nobody measured, read out to a screen reader as a percentage.
        let meters_on_idun = b.c.eval(&b.sess, "(() => { const row = [...document.querySelectorAll('a.product-row')].find(a => a.textContent.includes('idun')); return row ? row.querySelectorAll('[role=\"meter\"]').length : -1; })()")?;
        want(meters_on_idun.as_f64() == Some(0.0), || format!("idun's row carries {} meter(s) with no denominator", meters_on_idun.dump()))?;
        Ok(http::cut(&idun, 120))
    });

    go(rows, only, "screen1-the-spend-line-shows-the-lifetime-cap", || {
        // `UI.md` screen 1: `This month €0.00 spent · €10.00 cap` — "the
        // number that stops everything". A money figure with its cap, on the
        // estate screen.
        b.navigate(&money)?;
        b.wait_text("spent")?;
        let text = b.text()?;
        let line = text.lines().find(|l| l.to_lowercase().contains("spent") && l.to_lowercase().contains("cap")).ok_or_else(|| format!("no spend line (`… spent · … cap`) on the estate screen: {}", http::cut(&text, 300)))?;
        want(line.contains(fx::SPEND_SPENT_TEXT), || format!("the spend {} is not on the line: {line:?}", fx::SPEND_SPENT_TEXT))?;
        want(line.contains(fx::SPEND_CEILING_TEXT), || format!("the cap {} is not on the line: {line:?}", fx::SPEND_CEILING_TEXT))?;
        want(line.contains("EUR"), || format!("an amount without its currency: {line:?}"))?;
        Ok(http::cut(line, 120))
    });

    // ── screen 2 ──
    let gunnar = format!("{base}/customers?product=gunnar");

    go(rows, only, "screen2-one-number-is-big-and-the-trend-is-one-line", || {
        b.navigate(&gunnar)?;
        b.wait_text(fx::GUNNAR_PACK_USED_TEXT)?;
        let text = b.text()?;
        want(text.contains(fx::GUNNAR_CAPACITY.text), || format!("the capacity {} is not on the screen", fx::GUNNAR_CAPACITY.text))?;
        let trend = text.lines().find(|l| l.contains("/wk")).ok_or_else(|| format!("no trend line: {}", http::cut(&text, 300)))?;
        want(trend.contains("Full in ~"), || format!("an approximate rate with a capacity must project `Full in ~N weeks`: {trend:?}"))?;
        want(trend.contains(&format!("{}/wk", fx::GUNNAR_FLEET_PER_WEEK_TEXT)), || format!("the rate on the line is not the fixture's: {trend:?}"))?;
        want(trend.contains("last 30 d"), || format!("the window is not stated: {trend:?}"))?;
        // ONE line: no chart to interpret.
        want(b.count("canvas, svg.chart, .chart")? == 0, || "a chart is on the screen; the trend is one line".into())?;
        Ok(http::cut(trend, 120))
    });

    go(rows, only, "screen2-tenants-are-sorted-closest-to-cap-first", || {
        b.navigate(&gunnar)?;
        b.wait_text("alice")?;
        let text = b.text()?;
        let tenants = section(&text, "tenants", &["recently bought", "all activity"])?;
        // What is visible before the fold: the first five, at-cap first.
        let visible = order_of(&tenants, &fx::GUNNAR_CLOSEST_FIRST[..5])?;
        let top_two: std::collections::BTreeSet<&str> = visible.iter().take(2).map(String::as_str).collect();
        want(top_two == ["edge", "alice"].into_iter().collect(), || format!("the two tenants AT their cap are not the first two rows: {visible:?}"))?;
        want(visible[2..] == ["team/sub", "anna", "zz-visibility"], || format!("after the at-cap rows the order must be by percentage (97, 89, 50): {visible:?}"))?;
        // Alphabetical would put anna first: say so if that is what happened.
        want(visible[0] != "anna", || "the list is alphabetical, not by attention".into())?;
        Ok(visible.join(" > "))
    });

    go(rows, only, "screen2-the-rest-are-folded-and-the-uncapped-tenant-is-last", || {
        b.navigate(&gunnar)?;
        b.wait_text("alice")?;
        let text = b.text()?;
        let tenants = section(&text, "tenants", &["recently bought", "all activity"])?;
        want(!tenants.contains("bob") && !tenants.contains("bjorn"), || format!("the sixth and seventh tenants are on the surface before the fold is opened: {}", http::cut(&tenants, 200)))?;
        want(tenants.contains("2 more"), || format!("no `+ 2 more` fold: {}", http::cut(&tenants, 200)))?;
        b.disclose_all()?;
        let text = b.text()?;
        let tenants = section(&text, "tenants", &["recently bought", "all activity"])?;
        let all = order_of(&tenants, fx::GUNNAR_CLOSEST_FIRST)?;
        want(all[5..] == ["bob", "bjorn"], || format!("opened, the fold must continue the order (bob 31 %, then the uncapped bjorn last): {all:?}"))?;
        Ok(all.join(" > "))
    });

    go(rows, only, "screen2-at-cap-is-a-word-and-the-percentages-are-numbers", || {
        b.navigate(&gunnar)?;
        let alice = b.wait_row("alice")?;
        want(alice.contains("AT CAP"), || format!("alice is over cap and the row does not say AT CAP: {alice:?}"))?;
        want(alice.contains(fx::ALICE_PACK.text) && alice.contains(fx::CAP_10.text), || format!("alice's row must read {} of {}: {alice:?}", fx::ALICE_PACK.text, fx::CAP_10.text))?;
        let anna = b.row_of("anna")?;
        want(anna.contains("89 %") || anna.contains("89%"), || format!("anna is at 89 %: {anna:?}"))?;
        want(!anna.contains("AT CAP"), || format!("anna is not at cap: {anna:?}"))?;
        Ok(format!("{} · {}", http::cut(&alice, 60), http::cut(&anna, 60)))
    });

    go(rows, only, "screen2-cannot-grow-is-a-badge-and-its-paragraph-is-behind-it", || {
        b.navigate(&gunnar)?;
        b.wait_text("cannot grow")?;
        let text = b.text()?;
        want(!text.contains("not another disk"), || "the sealed paragraph is on the page, not behind the badge (UI.md rule 2)".into())?;
        let opened = b.disclose_all()?;
        want(opened > 0, || "nothing on the page can be disclosed".into())?;
        let text = b.text()?;
        want(text.contains(fx::SEALED_DETAIL), || format!("opened, the badge must hold the product's paragraph verbatim; the page held: {}", http::cut(&text, 400)))?;
        want(text.contains("fresh install"), || "the remedy (a fresh install onto a larger set) is not on the page".into())?;
        Ok(format!("badge, {} disclosure(s), paragraph ends with \"not another disk\"", opened))
    });

    go(rows, only, "screen2-a-bursty-rate-renders-the-rate-but-no-date", || {
        b.navigate(&format!("{base}/customers?product=skade"))?;
        b.wait_text("mira")?;
        let text = b.text()?;
        let trend = text.lines().find(|l| l.contains("/wk")).ok_or_else(|| format!("no rate line for skade: {}", http::cut(&text, 300)))?;
        want(trend.contains(&format!("~{}/wk", fx::SKADE_FLEET_PER_WEEK_TEXT)), || format!("the bursty rate must stand, with its `~`: {trend:?}"))?;
        want(!text.contains("Full in"), || format!("a bursty series must not become a date: {trend:?}"))?;
        want(!text.contains("cannot grow"), || "skade is operator_only, not sealed; it must not wear the badge".into())?;
        Ok(http::cut(trend, 120))
    });

    go(rows, only, "screen2-oversold-shows-the-number", || {
        b.navigate(&format!("{base}/customers?product=skade"))?;
        b.wait_text("mira")?;
        let text = b.text()?;
        want(text.to_lowercase().contains("oversold"), || format!("two tenants were promised 16 GiB on a 12 GiB box and the screen does not say oversold: {}", http::cut(&text, 300)))?;
        let lower = text.to_lowercase();
        let line = lower.lines().find(|l| l.contains("oversold")).ok_or_else(|| format!("no line says oversold: {}", http::cut(&lower, 300)))?;
        want(line.contains(&fx::SKADE_OVERSOLD.text.to_lowercase()), || format!("the oversell number {} is not on the line: {line:?}", fx::SKADE_OVERSOLD.text))?;
        Ok(http::cut(line, 120))
    });

    go(rows, only, "screen2-unmeasured-is-its-own-state-never-fits-or-zero", || {
        b.navigate(&format!("{base}/customers?product=idun"))?;
        b.wait_text("ola")?;
        let text = b.text()?;
        want(text.to_lowercase().contains("unmeasured"), || format!("idun's box never answered and the screen does not say so: {}", http::cut(&text, 300)))?;
        want(!text.to_lowercase().contains("fits"), || "an unmeasured fleet rendered as `fits`".into())?;
        want(!text.contains("0 B of") && !text.contains(" of 0 B"), || "an unmeasured capacity rendered as zero".into())?;
        want(!text.contains("Full in"), || "no capacity, so no projection".into())?;
        want(b.count(".big [role=\"meter\"], section[aria-label] [role=\"meter\"]")? == 0 || !text.contains("0 %"), || "an invented denominator drew a bar".into())?;
        Ok(format!("{} · capacity unmeasured", fx::OLA_ARCHIVE.text))
    });

    go(rows, only, "screen2-recently-bought-reads-outcomes-not-steps", || {
        b.navigate(&gunnar)?;
        b.wait_text("bob")?;
        let text = b.text()?;
        let bought = section(&text, "recently bought", &[])?;
        want(bought.contains("bob") && bought.contains("zz-visibility"), || format!("the outcomes are not under RECENTLY BOUGHT: {}", http::cut(&bought, 200)))?;
        let bob = b.row_in(Some("recently bought"), "bob", false)?;
        want(bob.contains("220.00"), || format!("bob's sale must carry its 220.00 SEK: {bob:?}"))?;
        want(bob.contains(fx::CAP_10.text), || format!("`10 GiB for bob`: {bob:?}"))?;
        let zz = b.row_in(Some("recently bought"), "zz-visibility", false)?;
        want(zz.to_lowercase().contains("refund"), || format!("zz-visibility was refunded and the line does not say so: {zz:?}"))?;
        want(!zz.contains("Sold"), || format!("a refund must not read as a sale: {zz:?}"))?;
        want(zz.contains("1.00"), || format!("its 1.00 SEK: {zz:?}"))?;
        // Steps are NOT on this surface.
        want(!bought.contains("Reserved") && !bought.contains("Captured"), || "ledger steps are on the product screen; they belong behind a disclosure".into())?;
        Ok(format!("{} · {}", http::cut(&bob, 50), http::cut(&zz, 50)))
    });

    // ── screen 3 ──
    let alice = tenant_url(&base, "alice");

    go(rows, only, "screen3-every-meter-shows-its-own-bytes", || {
        b.navigate(&alice)?;
        b.wait_text(fx::ALICE_PACK.text)?;
        let text = b.text()?;
        for (what, s) in [("repository data", fx::ALICE_PACK), ("cache", fx::ALICE_CACHE), ("large files", fx::ALICE_LFS), ("deleted, not purged", fx::ALICE_TOMBSTONED), ("RAM", fx::ALICE_RAM)] {
            want(text.contains(s.text), || format!("{what} = {} is not on the page: {}", s.text, http::cut(&text, 300)))?;
        }
        let repos = b.row_in(None, "Repositories", false)?;
        want(repos.split(|c: char| !c.is_ascii_digit()).any(|n| n == fx::ALICE_REPOS.to_string()), || format!("the repository count {} is not on its row: {repos:?}", fx::ALICE_REPOS))?;
        want(text.contains(fx::CAP_10.text), || format!("the cap {} is not on the page", fx::CAP_10.text))?;
        Ok(format!("{} {} {} {} {} · {} repos", fx::ALICE_PACK.text, fx::ALICE_CACHE.text, fx::ALICE_LFS.text, fx::ALICE_TOMBSTONED.text, fx::ALICE_RAM.text, fx::ALICE_REPOS))
    });

    go(rows, only, "screen3-plain-words-on-the-surface-and-the-raw-name-in-the-title", || {
        b.navigate(&alice)?;
        b.wait_text(fx::ALICE_PACK.text)?;
        let text = b.text()?;
        for word in ["Repository data", "Cache", "Deleted, not purged", "Repositories"] {
            want(text.contains(word), || format!("{word:?} is not on the surface: {}", http::cut(&text, 300)))?;
        }
        for raw in fx::RAW_METER_NAMES {
            want(!text.contains(raw), || format!("{raw} is on the surface; it belongs in the detail"))?;
        }
        for word in ["wall", "not billed", "still costs"] {
            want(text.contains(word), || format!("the word {word:?} (UI.md rule 3) is not beside its meter: {}", http::cut(&text, 300)))?;
        }
        let titles = b.titles()?;
        want(titles.contains("pack_bytes"), || format!("the precise name must be reachable as a title; titles held: {}", http::cut(&titles, 200)))?;
        Ok("Repository data · Cache · Deleted, not purged · Repositories; pack_bytes in a title".into())
    });

    go(rows, only, "screen3-at-cap-appears-exactly-when-value-reaches-cap", || {
        let mut seen = Vec::new();
        for (tenant, at_cap, needle) in [("alice", true, fx::ALICE_PACK.text), ("edge", true, fx::EDGE_PACK.text), ("bob", false, fx::BOB_PACK.text), ("team/sub", false, fx::TEAM_SUB_PACK.text)] {
            b.navigate(&tenant_url(&base, tenant))?;
            b.wait_text(needle)?;
            let text = b.text()?;
            let has = text.contains("AT CAP");
            want(has == at_cap, || format!("{tenant}: AT CAP {} (value {needle}, cap {})", if has { "shown" } else { "missing" }, fx::CAP_10.text))?;
            if at_cap {
                let full = b.c.eval(&b.sess, "[...document.querySelectorAll('[role=\"meter\"]')].some(m => m.getAttribute('aria-valuenow') === '100')")?;
                want(full.as_bool() == Some(true), || format!("{tenant}: no meter announces 100 %"))?;
            }
            seen.push(format!("{tenant}:{}", if has { "AT CAP" } else { "below" }));
        }
        Ok(seen.join(" "))
    });

    go(rows, only, "screen3-uncapped-is-its-own-state-with-no-meter-role", || {
        b.navigate(&tenant_url(&base, "bjorn"))?;
        b.wait_text(fx::BJORN_PACK.text)?;
        let text = b.text()?;
        let meters = b.count("[role=\"meter\"]")?;
        want(meters == 0, || format!("bjorn names no cap and the page still announces {meters} meter(s) — an empty bar is `0 %` to a screen reader"))?;
        want(text.to_lowercase().contains("no cap") || text.to_lowercase().contains("uncapped"), || format!("the uncapped state is not said: {}", http::cut(&text, 300)))?;
        want(!text.contains("AT CAP"), || "an uncapped tenant cannot be at a cap".into())?;
        Ok(format!("{} · no cap, 0 meter roles", fx::BJORN_PACK.text))
    });

    go(rows, only, "screen3-the-trend-is-one-line-with-its-rate", || {
        b.navigate(&alice)?;
        b.wait_text(fx::ALICE_PACK.text)?;
        let text = b.text()?;
        let line = text.lines().find(|l| l.contains("/wk")).ok_or_else(|| format!("no rate line on alice's screen: {}", http::cut(&text, 300)))?;
        want(line.contains(&format!("{}/wk", fx::ALICE_PER_WEEK_TEXT)), || format!("alice's own rate is {}/wk: {line:?}", fx::ALICE_PER_WEEK_TEXT))?;
        Ok(http::cut(line, 120))
    });

    // ── the order: quoted before any money moves ──
    go(rows, only, "screen3-how-much-quotes-price-cost-and-can-serve-before-confirming", || {
        b.navigate(&alice)?;
        b.wait_text(fx::ALICE_PACK.text)?;
        b.click_button("How much?")?;
        b.c.wait_for(&b.sess, "!!document.getElementById('order-pack_bytes')", 10_000).map_err(|e| format!("the order form did not open: {e}; page: {}", http::cut(&b.text().unwrap_or_default(), 300)))?;
        let prefilled = b.c.eval_str(&b.sess, "document.getElementById('order-pack_bytes').value")?;
        want(prefilled == "10", || format!("the form must start at what the tenant holds (10 GiB), not at zero: {prefilled:?}"))?;
        // 100 GiB: the sealed refusal, priced.
        b.c.eval(
            &b.sess,
            "(() => { const el = document.getElementById('order-pack_bytes'); const proto = Object.getPrototypeOf(el); Object.getOwnPropertyDescriptor(proto, 'value').set.call(el, '100'); el.dispatchEvent(new Event('input', {bubbles:true})); return 'set'; })()",
        )?;
        b.click_button("Quote")?;
        b.wait_text(fx::ALICE_QUOTE_90_GIB_TEXT)?;
        let text = b.text()?;
        want(text.contains("90.0 GiB"), || format!("the quote must name the DELTA (90 GiB), not the hundred: {}", http::cut(&text, 300)))?;
        want(text.contains(fx::ALICE_QUOTE_90_GIB_TEXT), || format!("the price is not on the page: {}", http::cut(&text, 300)))?;
        let cost = format!("{}.{:02} EUR", fx::ALICE_QUOTE_90_GIB_COST_MINOR / 100, fx::ALICE_QUOTE_90_GIB_COST_MINOR % 100);
        want(text.contains(&cost), || format!("the provider's cost {cost} is not beside the price: {}", http::cut(&text, 300)))?;
        want(text.ends_with("not another disk") || text.contains("not another disk"), || format!("can serve: no must carry the sentence to its last clause: {}", http::cut(&text, 300)))?;
        want(!text.contains("take the money") && !text.contains("place the order"), || "a refused quote must not offer a confirm button".into())?;
        // 20 GiB: served, and the confirm button appears — nothing is pressed.
        b.c.eval(
            &b.sess,
            "(() => { const el = document.getElementById('order-pack_bytes'); const proto = Object.getPrototypeOf(el); Object.getOwnPropertyDescriptor(proto, 'value').set.call(el, '20'); el.dispatchEvent(new Event('input', {bubbles:true})); return 'set'; })()",
        )?;
        b.click_button("Quote")?;
        b.c.wait_for(&b.sess, "document.body.innerText.includes('take the money')", 10_000).map_err(|e| format!("a servable quote did not offer a confirm: {e}; page: {}", http::cut(&b.text().unwrap_or_default(), 300)))?;
        let text = b.text()?;
        let ten = format!("{}.{:02} SEK", 10 * fx::GUNNAR_DISK_GIB_MONTH_MINOR / 100, 10 * fx::GUNNAR_DISK_GIB_MONTH_MINOR % 100);
        want(text.contains(&ten), || format!("10 GiB more at 22.00 is {ten}: {}", http::cut(&text, 300)))?;
        Ok(format!("100 GiB: {} · refused whole · 20 GiB: {ten} · confirm offered, not pressed", fx::ALICE_QUOTE_90_GIB_TEXT))
    });

    go(rows, only, "screen3-backed-by-names-the-disk-under-the-quota", || {
        b.navigate(&alice)?;
        b.wait_text(fx::ALICE_PACK.text)?;
        let text = b.text()?;
        let backed = section(&text, "backed by", &["history", "activity"]).or_else(|_| section(&text, "resources", &["ledger", "history"]))?;
        want(backed.contains("10 GiB") && backed.contains("disk"), || format!("the 10 GiB disk is not named: {}", http::cut(&backed, 200)))?;
        want(backed.contains(fx::ALICE_DISK_SERVER), || format!("the server it sits on is not named: {}", http::cut(&backed, 200)))?;
        Ok(http::cut(&backed, 120))
    });

    // ── screen 4 ──
    let activity = format!("{base}/money?product=gunnar");

    go(rows, only, "screen4-open-attempts-float-to-needs-you-first", || {
        b.navigate(&activity)?;
        b.wait_text("bob")?;
        let text = b.text()?;
        want(text.to_lowercase().contains("needs you"), || format!("no NEEDS YOU on the activity screen: {}", http::cut(&text, 300)))?;
        let order = order_of(&text, &["stuck", "bob"])?;
        want(order == ["stuck", "bob"], || format!("the open attempt (stuck) must come before history (bob): {order:?}"))?;
        let stuck = b.row_in(None, "stuck", false)?;
        want(stuck.contains("never delivered") || stuck.contains(fx::STUCK_REASON), || format!("the task row must say what is wrong: {stuck:?}"))?;
        Ok(http::cut(&stuck, 120))
    });

    go(rows, only, "screen4-a-released-attempt-reads-refunded-not-sold", || {
        b.navigate(&activity)?;
        b.wait_text("zz-visibility")?;
        let zz = b.row_in(None, "zz-visibility", false)?;
        want(zz.to_lowercase().contains("refund"), || format!("{zz:?}"))?;
        want(!zz.contains("Sold"), || format!("a refund reads as a sale: {zz:?}"))?;
        let bob = b.row_in(None, "bob", false)?;
        want(bob.contains("Sold") && bob.contains("220.00"), || format!("`Sold bob 10 GiB · 220.00 SEK`: {bob:?}"))?;
        // Declined is NOT a refund: nothing was locked.
        let anna = b.row_in(None, "anna", false)?;
        want(anna.to_lowercase().contains("declined") && !anna.to_lowercase().contains("refund"), || format!("a decline must not wear the refund word: {anna:?}"))?;
        Ok(format!("{} · {}", http::cut(&zz, 50), http::cut(&bob, 50)))
    });

    go(rows, only, "screen4-the-steps-are-hidden-until-disclosed", || {
        b.navigate(&activity)?;
        b.wait_text("bob")?;
        let text = b.text()?;
        want(!text.contains("Captured") && !text.contains("EntitlementPushed") && !text.contains("charged"), || format!("ledger steps are on the surface (UI.md rule 6): {}", http::cut(&text, 300)))?;
        let opened = b.disclose_all()?;
        want(opened > 0, || "nothing on the page can be disclosed, so the steps are unreachable".into())?;
        b.c.wait_for(&b.sess, "/Captured|charged|Reserved|reserved/.test(document.body.innerText)", 10_000).map_err(|e| format!("opened, the steps still do not appear: {e}"))?;
        let text = b.text()?;
        want(text.contains("Captured") || text.contains("charged"), || format!("the disclosed steps do not include the capture: {}", http::cut(&text, 300)))?;
        Ok(format!("{opened} disclosure(s); steps visible after"))
    });

    // ── what must NEVER appear, on every screen ──
    //
    // Money words (`BASE-MODEL.md`: there is no balance) and raw meter names
    // on EVERY screen — `UI.md`'s four surfaces, and there is no fifth: the
    // Plans page went with the catalogue, and the order form names a meter in
    // plain words with the wire name in the label's title.
    go(rows, only, "no-screen-carries-money-vocabulary-or-a-raw-meter-name", || {
        let mut swept = 0;
        let e = base.trim_start_matches(origin).to_owned();
        for path in [
            "/".to_owned(),
            format!("{e}/map"),
            format!("{e}/customers?product=gunnar"),
            format!("{e}/customers?product=idun"),
            format!("{e}/customers?product=skade"),
            format!("{e}/customers?product=holger"),
            tenant_url(&e, "alice"),
            tenant_url(&e, "bjorn"),
            format!("{e}/money?product=gunnar"),
            format!("{e}/events"),
            format!("{e}/offer"),
            format!("{e}/settings"),
        ] {
            let path = path.as_str();
            b.navigate(&format!("{origin}{path}"))?;
            b.c.wait_for(&b.sess, "document.body && document.body.innerText.length > 40", 20_000)?;
            let is_screen = true;
            // Twice: once as shown, once with every disclosure open.
            for _ in 0..2 {
                let text = b.text()?;
                let lower = text.to_lowercase();
                let titles = b.titles()?.to_lowercase();
                for w in fx::FORBIDDEN_WORDS {
                    want(!lower.contains(w) && !titles.contains(w), || format!("{path}: {w:?} on the surface (BASE-MODEL.md: there is no balance) near: {}", http::cut(&excerpt(&lower, w), 120)))?;
                }
                for raw in fx::RAW_METER_NAMES.iter().filter(|_| is_screen) {
                    want(!text.contains(raw), || format!("{path}: raw meter name {raw} on the surface near: {}", http::cut(&excerpt(&text, raw), 120)))?;
                }
                b.disclose_all()?;
            }
            swept += 1;
        }
        Ok(format!("{swept} screens, both folded and unfolded: no money word anywhere, no raw meter name on any surface"))
    });
}

fn excerpt(text: &str, needle: &str) -> String {
    match text.find(needle) {
        Some(i) => text[i.saturating_sub(40)..(i + needle.len() + 40).min(text.len())].to_owned(),
        None => String::new(),
    }
}

// ── the passkey rig ──────────────────────────────────────────────────────

const SETTLED: &str = "fetch('/auth/status').then(r => r.json()).then(j => j.authenticated === true || !!document.querySelector('.failure'))";

pub fn passkey(r: &Rig, chrome_bin: &str, only: Option<&str>) -> Vec<Row> {
    let mut rows = Vec::new();
    let Some(mut b) = Browser::start(chrome_bin, &r.work.join("chrome"), &mut rows, only) else { return rows };
    let origin = r.origin();

    let auth = std::cell::RefCell::new(String::new());
    if !go(&mut rows, only, "a-virtual-authenticator-is-installed", || {
        let id = b.c.add_authenticator(&b.sess)?;
        want(!id.is_empty(), || "no authenticatorId".into())?;
        *auth.borrow_mut() = id.clone();
        Ok(format!("authenticator {id}"))
    }) {
        return rows;
    }
    let auth = auth.into_inner();

    go(&mut rows, only, "a-stranger-is-sent-to-the-login-page", || {
        b.navigate(&origin)?;
        b.c.wait_for(&b.sess, "document.body && document.body.innerText.length > 20", 20_000)?;
        let path = b.c.eval_str(&b.sess, "location.pathname")?;
        let text = b.text()?;
        want(path == "/login" || text.to_lowercase().contains("passkey"), || format!("an unauthenticated visitor landed on {path} showing {}", http::cut(&text, 150)))?;
        let s = b.c.eval(&b.sess, "fetch('/api/products').then(r => r.status)")?;
        want(s.as_f64() == Some(401.0), || format!("/api/products answered {} to a stranger", s.dump()))?;
        Ok(format!("at {path}, api closed"))
    });

    let registered = go(&mut rows, only, "the-first-admin-enrols-with-the-break-glass-token", || {
        b.navigate(&format!("{origin}/login"))?;
        b.c.wait_for(&b.sess, "!!document.getElementById('tok')", 20_000).map_err(|e| format!("no admin-token field on the login page ({e}); is bootstrap on? page: {}", http::cut(&b.text().unwrap_or_default(), 200)))?;
        b.c.eval(
            &b.sess,
            &format!(
                "(() => {{ const set = (id, v) => {{ const el = document.getElementById(id); const proto = Object.getPrototypeOf(el); Object.getOwnPropertyDescriptor(proto, 'value').set.call(el, v); el.dispatchEvent(new Event('input', {{bubbles:true}})); }};
                   set('tok', {}); set('user', 'operator'); set('label', 'virtual'); return 'set'; }})()",
                esc(&r.admin_token)
            ),
        )?;
        b.click_button("Enrol as admin")?;
        b.c.wait_for(&b.sess, "!!document.querySelector('.notice') || !!document.querySelector('.failure')", 30_000)?;
        let text = b.text()?;
        want(text.contains("enrolled") || text.contains("Enrolled"), || format!("the page said: {}", http::cut(&text, 300)))?;
        // The page's word is not the evidence. The authenticator's is.
        let creds = b.c.credentials(&b.sess, &auth)?;
        want(creds.len() == 1, || format!("the authenticator holds {} credentials", creds.len()))?;
        want(creds[0].text("rpId") == "localhost", || format!("rpId {:?}", creds[0].text("rpId")))?;
        Ok(format!("1 credential for rp {}", creds[0].text("rpId")))
    });

    if registered {
        go(&mut rows, only, "the-admin-token-retires-once-an-admin-passkey-exists", || {
            let h = [("Authorization", format!("Bearer {}", r.admin_token))];
            let resp = http::req(&r.ui_addr(), "GET", "/api/products", &h, None)?;
            want(resp.status == 401, || format!("the token still opens /api/products: {} {}", resp.status, http::cut(&resp.body, 120)))?;
            let st = http::req(&r.ui_addr(), "GET", "/auth/status", &[], None)?.json()?;
            want(st.path("bootstrap").and_then(|v| v.as_bool()) == Some(false), || format!("bootstrap still on: {}", st.dump()))?;
            Ok("401 — the break-glass token is spent".into())
        });
    }

    go(&mut rows, only, "the-passkey-logs-in-as-admin", || {
        b.navigate(&format!("{origin}/login"))?;
        b.c.wait_for(&b.sess, "[...document.querySelectorAll('button')].some(b => b.textContent.includes('passkey'))", 20_000)?;
        b.click_button("Log in with a passkey")?;
        b.c.wait_for(&b.sess, SETTLED, 30_000)?;
        let st = b.c.eval(&b.sess, "fetch('/auth/status').then(r => r.json())")?;
        want(st.path("authenticated").and_then(|v| v.as_bool()) == Some(true), || format!("not authenticated: {}", http::cut(&st.dump(), 200)))?;
        want(st.text("user") == "operator", || format!("logged in as {:?}", st.text("user")))?;
        want(st.text("role") == "admin", || format!("role {:?}", st.text("role")))?;
        Ok(format!("{} as {}", st.text("user"), st.text("role")))
    });

    // ★ **A stranger's login offers nobody's passkey** (owner 2026-10-06):
    // the operator's key is enrolled, and a request with no session asks
    // login/start. Anonymous: no credential id at all (the discoverable
    // ceremony the row above just logged in with). Named: that account's keys
    // among made-up ones, eight in all, and as many for a name nobody has.
    go(&mut rows, only, "a-strangers-login-offers-nobodys-passkey", || {
        let count = |q: &str| -> Result<usize, String> {
            let j = http::req(&r.ui_addr(), "GET", &format!("/auth/passkey/login/start{q}"), &[], None)?.json()?;
            j.path("publicKey.allowCredentials").map(|a| a.arr().len()).ok_or_else(|| format!("login/start{q} answered {}", j.dump()))
        };
        let (anonymous, named, unknown) = (count("")?, count("?user=operator")?, count("?user=nobody-here")?);
        want(anonymous == 0, || format!("an anonymous login/start offers {anonymous} credential ids"))?;
        want(named == 8 && unknown == 8, || format!("the named offers are {named} (operator) and {unknown} (nobody): not the same eight"))?;
        Ok(format!("anonymous {anonymous}; operator {named}; a name nobody has {unknown}"))
    });

    go(&mut rows, only, "the-passkey-session-renders-the-estate-under-the-served-csp", || {
        // The production posture of the blank-page regression: a logged-in
        // operator, the real header, the real bundle. The 401s the stranger
        // row deliberately collected are drained first; only THIS page's
        // errors count.
        let _ = b.errors();
        // The Overview: every estate, each answering for itself, its map as a
        // thumbnail — under the cookie session, the real header, the real bundle.
        b.navigate(&origin)?;
        b.c.wait_for(&b.sess, "document.querySelectorAll('.estate-row img').length >= 1 && /serving|down/.test(document.body.innerText)", 20_000).map_err(|e| format!("{e}; the page held: {}", http::cut(&b.text().unwrap_or_default(), 300)))?;
        let errs = b.errors();
        want(errs.is_empty(), || format!("{} page error(s): {}", errs.len(), http::cut(&errs.join(" | "), 300)))?;
        // Who is logged in is under You ▾ (owner 2026-10-02): open it.
        b.disclose_all()?;
        let text = b.text()?;
        want(text.contains("operator"), || "the shell does not name who is logged in".into())?;
        Ok("the Overview's estate rows on a cookie session".into())
    });

    rows
}


// ── a REAL estate's monetize (no mock) ───────────────────────────────────

/// **The console against a REAL monetize** — an estate's, through its
/// `monetize.<apex>` (owner 2026-09-28: every page PASS/FAIL against the
/// orders the estate's functional test made). No fixture: every assertion
/// compares what the PAGE shows with what the console's own API says monetize
/// holds, and each page's text is kept under `work/` as the evidence.
///
/// `origin` is the console, loopback only (its passkey RP is `localhost`);
/// `admin_token` is its break-glass bearer, spent on the first passkey.
pub fn estate(chrome_bin: &str, origin: &str, admin_token: &str, work: &Path, only: Option<&str>) -> Vec<Row> {
    let mut rows = Vec::new();
    let _ = std::fs::create_dir_all(work);
    let profile = work.join("chrome");
    let _ = std::fs::remove_dir_all(&profile);
    let Some(mut b) = Browser::start(chrome_bin, &profile, &mut rows, only) else { return rows };
    let keep = |name: &str, text: &str| {
        let _ = std::fs::write(work.join(format!("{name}.txt")), text);
    };
    let fetch_json = |b: &mut Browser, path: &str| -> Result<Json, String> {
        let v = b.c.eval(&b.sess, &format!("fetch({}).then(async r => ({{status: r.status, body: await r.text()}}))", esc(path)))?;
        let st = v.path("status").and_then(|s| s.as_f64()).unwrap_or(0.0) as u16;
        let body = v.text("body");
        want(st == 200, || format!("{path} answered {st}: {}", http::cut(&body, 200)))?;
        crate::json::parse(&body)
    };
    let no_failure = |b: &mut Browser| -> Result<(), String> {
        let n = b.count(".failure")?;
        want(n == 0, || format!("{n} failure panel(s): {}", http::cut(&b.text().unwrap_or_default(), 300)))
    };

    let auth = std::cell::RefCell::new(String::new());
    if !go(&mut rows, only, "a-virtual-authenticator-is-installed", || {
        let id = b.c.add_authenticator(&b.sess)?;
        *auth.borrow_mut() = id.clone();
        Ok(format!("authenticator {id}"))
    }) {
        return rows;
    }
    let auth = auth.into_inner();

    go(&mut rows, only, "login: a stranger is sent to the login page and the API is closed", || {
        b.navigate(origin)?;
        b.c.wait_for(&b.sess, "document.body && document.body.innerText.length > 20", 30_000)?;
        let path = b.c.eval_str(&b.sess, "location.pathname")?;
        let s = b.c.eval(&b.sess, "fetch('/api/products').then(r => r.status)")?;
        want(s.as_f64() == Some(401.0), || format!("/api/products answered {} to a stranger", s.dump()))?;
        Ok(format!("at {path}, /api/products 401"))
    });

    let enrolled = go(&mut rows, only, "login: the break-glass token enrols the first admin passkey", || {
        b.navigate(&format!("{origin}/login"))?;
        b.c.wait_for(&b.sess, "!!document.getElementById('tok')", 30_000).map_err(|e| format!("no admin-token field ({e}); page: {}", http::cut(&b.text().unwrap_or_default(), 200)))?;
        b.c.eval(
            &b.sess,
            &format!(
                "(() => {{ const set = (id, v) => {{ const el = document.getElementById(id); const proto = Object.getPrototypeOf(el); Object.getOwnPropertyDescriptor(proto, 'value').set.call(el, v); el.dispatchEvent(new Event('input', {{bubbles:true}})); }};
                   set('tok', {}); set('user', 'operator'); set('label', 'virtual'); return 'set'; }})()",
                esc(admin_token)
            ),
        )?;
        b.click_button("Enrol as admin")?;
        b.c.wait_for(&b.sess, "!!document.querySelector('.notice') || !!document.querySelector('.failure')", 30_000)?;
        let text = b.text()?;
        want(text.to_lowercase().contains("enrolled"), || format!("the page said: {}", http::cut(&text, 300)))?;
        let creds = b.c.credentials(&b.sess, &auth)?;
        want(creds.len() == 1, || format!("the authenticator holds {} credentials", creds.len()))?;
        Ok(format!("1 credential for rp {}", creds[0].text("rpId")))
    });
    if enrolled {
        go(&mut rows, only, "login: the break-glass token retires once an admin passkey exists", || {
            let st = fetch_json(&mut b, "/auth/status")?;
            want(st.path("bootstrap").and_then(|v| v.as_bool()) == Some(false), || format!("bootstrap still on: {}", st.dump()))?;
            Ok("bootstrap off".into())
        });
    }
    let logged_in = go(&mut rows, only, "login: the passkey logs in as admin", || {
        b.c.eval(&b.sess, "fetch('/auth/logout', {method: 'POST', headers: {'Origin': location.origin}}).then(r => r.status)")?;
        b.navigate(&format!("{origin}/login"))?;
        b.c.wait_for(&b.sess, "[...document.querySelectorAll('button')].some(b => b.textContent.includes('passkey'))", 30_000)?;
        b.click_button("Log in with a passkey")?;
        b.c.wait_for(&b.sess, SETTLED, 30_000)?;
        let st = fetch_json(&mut b, "/auth/status")?;
        want(st.path("authenticated").and_then(|v| v.as_bool()) == Some(true), || format!("not authenticated: {}", st.dump()))?;
        want(st.text("role") == "admin", || format!("role {:?}", st.text("role")))?;
        Ok(format!("{} as {}", st.text("user"), st.text("role")))
    });
    if !logged_in {
        return rows;
    }

    // ── every estate (one console administers each estate's monetize) ──
    // Owner 2026-10-02: the ESTATE is the top level. Overview is the one page
    // that spans them; every other page is one estate's, under /e/<estate>/…
    let overview = fetch_json(&mut b, "/api/overview").ok();
    let names: Vec<String> = overview.as_ref().and_then(|e| e.path("estates")).map(|e| e.arr().iter().map(|x| x.text("name")).filter(|n| !n.is_empty()).collect()).unwrap_or_default();
    let base = format!("{origin}/e/{}", names.first().cloned().unwrap_or_else(|| "local".into()));
    go(&mut rows, only, "estates: the Overview is one row per estate, each with its map or a named placeholder", || {
        b.navigate(origin)?;
        let all = format!("document.querySelectorAll('.estate-row').length === {}", names.len().max(1));
        b.c.wait_for(&b.sess, &all, 30_000).map_err(|e| format!("{} estate row(s) expected ({e}): {}", names.len(), http::cut(&b.text().unwrap_or_default(), 300)))?;
        // Every row settles: no row still asking after the console's patience.
        let _ = b.c.wait_for(&b.sess, "!document.body.innerText.includes('asking …')", 30_000);
        let text = b.text()?;
        keep("overview", &text);
        let mut said = Vec::new();
        for n in &names {
            let s = fetch_json(&mut b, &format!("/api/estates/{n}/summary"))?;
            let reached = s.path("reached").and_then(|v| v.as_bool()) == Some(true);
            let row = b.c.eval_str(&b.sess, &format!("(document.querySelector('.estate-row[data-estate=\"' + {} + '\"]') || {{innerText: ''}}).innerText", esc(n)))?;
            want(!row.is_empty(), || format!("no row for {n}"))?;
            let named = row.contains("not reached") || row.contains("not fed this console's key");
            want(named != reached, || format!("{n}: its monetize says reached={reached}, the row says {row:?}"))?;
            let thumbs = b.c.eval(&b.sess, &format!("document.querySelectorAll('.estate-row[data-estate=\"' + {} + '\"] .thumb').length", esc(n)))?;
            want(thumbs.as_f64() == Some(1.0), || format!("{n}: {} thumbnail(s); every estate has its map or a named placeholder", thumbs.dump()))?;
            said.push(format!("{n}: {}", if reached { "answered" } else { "named as not answering" }));
        }
        want(!text.to_lowercase().contains("production") && !text.to_lowercase().contains("staging"), || "an estate wears a role".into())?;
        no_failure(&mut b)?;
        Ok(said.join(" · "))
    });
    if names.len() > 1 {
        go(&mut rows, only, "estates: the picker groups every estate under its product, and choosing one opens that estate", || {
            let api = fetch_json(&mut b, "/api/estates")?;
            let serving: Vec<Json> = api.path("estates").map(|e| e.arr().iter().filter(|x| x.path("serving").and_then(|v| v.as_bool()) == Some(true)).cloned().collect()).unwrap_or_default();
            b.navigate(&format!("{base}/map"))?;
            b.c.wait_for(&b.sess, "document.querySelectorAll('select[aria-label=Estate] optgroup').length > 0", 30_000).map_err(|e| format!("no grouped estate picker ({e})"))?;
            let mut options: Vec<String> = b.c.eval_str(&b.sess, "[...document.querySelectorAll('select[aria-label=Estate] option')].map(o => o.value).filter(v => v).join(',')")?.split(',').map(str::to_owned).collect();
            options.sort();
            let mut all = names.clone();
            all.sort();
            want(options == all, || format!("the picker offers {options:?}, the console administers {all:?}"))?;
            let groups = b.c.eval_str(&b.sess, "[...document.querySelectorAll('select[aria-label=Estate] optgroup')].map(g => g.label).join(',')")?;
            let first = names[0].clone();
            let Some(other) = serving.iter().find(|e| e.text("name") != first).cloned() else { return Err("no second estate is serving: nothing to switch to".into()) };
            let target = other.text("name");
            let want_ids: Vec<String> = other.path("products").map(|p| p.arr().iter().filter_map(|x| x.as_str().map(str::to_owned)).collect()).unwrap_or_default();
            b.c.eval(&b.sess, &format!("(() => {{ const s = document.querySelector('select[aria-label=Estate]'); s.value = {}; s.dispatchEvent(new Event('change', {{bubbles: true}})); return 1; }})()", esc(&target)))?;
            b.c.wait_for(&b.sess, &format!("location.pathname.startsWith('/e/' + {} + '/')", esc(&target)), 30_000)?;
            b.c.wait_for(&b.sess, SETTLED, 30_000)?;
            for id in &want_ids {
                b.wait_row(id)?;
            }
            keep("estate-switched", &b.text()?);
            b.c.eval(&b.sess, &format!("(() => {{ const s = document.querySelector('select[aria-label=Estate]'); s.value = {}; s.dispatchEvent(new Event('change', {{bubbles: true}})); return 1; }})()", esc(&first)))?;
            b.c.wait_for(&b.sess, &format!("location.pathname.startsWith('/e/' + {} + '/')", esc(&first)), 30_000)?;
            Ok(format!("groups {groups}; {target} shows {} and back to {first}", want_ids.join(", ")))
        });
    } else {
        go(&mut rows, only, "estates: a console of one estate names it in the picker", || {
            b.navigate(&format!("{base}/map"))?;
            b.c.wait_for(&b.sess, SETTLED, 30_000)?;
            let n = names.first().cloned().unwrap_or_default();
            b.c.wait_for(&b.sess, &format!("[...document.querySelectorAll('select[aria-label=Estate] option')].some(o => o.value === {} && o.selected)", esc(&n)), 30_000)?;
            Ok(format!("{n}, selected"))
        });
    }

    // ── products ──
    let products = std::cell::RefCell::new(Vec::<String>::new());
    go(&mut rows, only, "products: every product monetize serves is a row", || {
        let api = fetch_json(&mut b, "/api/products")?;
        let ids: Vec<String> = api.path("products").map(|p| p.arr().iter().map(|x| x.text("id")).collect()).unwrap_or_default();
        want(!ids.is_empty(), || format!("monetize serves no product: {}", http::cut(&api.dump(), 200)))?;
        let _ = b.errors();
        b.navigate(&format!("{base}/map"))?;
        let mut seen = Vec::new();
        for id in &ids {
            seen.push(b.wait_row(id)?);
        }
        let text = b.text()?;
        keep("products", &text);
        let errs = b.errors();
        want(errs.is_empty(), || format!("page errors: {}", http::cut(&errs.join(" | "), 300)))?;
        no_failure(&mut b)?;
        *products.borrow_mut() = ids.clone();
        Ok(format!("{} product(s): {}", ids.len(), seen.join(" / ")))
    });
    let products = products.into_inner();

    // ── tenants, per product ──
    let tenants = std::cell::RefCell::new(Vec::<(String, String)>::new());
    go(&mut rows, only, "tenants: each product's tenants are rows", || {
        let mut said = Vec::new();
        for p in &products {
            let api = fetch_json(&mut b, &format!("/api/products/{p}/tenants"))?;
            let ids: Vec<String> = api.path("tenants").map(|t| t.arr().iter().map(|x| x.text("tenant")).filter(|t| !t.is_empty()).collect()).unwrap_or_default();
            b.navigate(&format!("{base}/customers?product={p}"))?;
            b.c.wait_for(&b.sess, "document.body && document.body.innerText.length > 40", 30_000)?;
            // The page folds the tail of a long list: unfold it first.
            std::thread::sleep(std::time::Duration::from_millis(500));
            b.disclose_all()?;
            for t in &ids {
                tenants.borrow_mut().push((p.clone(), t.clone()));
            }
            let all = format!("[{}].every(t => document.body.innerText.includes(t))", ids.iter().map(|t| esc(t)).collect::<Vec<_>>().join(","));
            let seen = b.c.wait_for(&b.sess, &all, 20_000);
            b.disclose_all()?;
            let text = b.text()?;
            keep(&format!("tenants-{p}"), &text);
            let missing: Vec<&String> = ids.iter().filter(|t| !text.contains(t.as_str())).collect();
            want(seen.is_ok() || missing.is_empty(), || format!("{p}: monetize lists {} tenant(s); the page shows none of {missing:?}", ids.len()))?;
            no_failure(&mut b)?;
            said.push(format!("{p}: {} tenant(s) {}", ids.len(), ids.iter().take(8).cloned().collect::<Vec<_>>().join(", ")));
        }
        Ok(said.join(" · "))
    });
    let tenants = tenants.into_inner();

    go(&mut rows, only, "tenant: the tenant view names its product and its tenant", || {
        let mut said = Vec::new();
        for (p, t) in tenants.iter() {
            b.navigate(&format!("{base}/customer?product={p}&tenant={}", t.replace('/', "%2F")))?;
            b.wait_text(t)?;
            // Loaded = nothing on the page still says it is loading or checking.
            let settled = b.c.wait_for(&b.sess, "document.body && document.body.innerText.length > 80 && !/Loading …|checking …|loading the/.test(document.body.innerText)", 30_000);
            let text = b.text()?;
            keep(&format!("tenant-{p}-{}", t.replace('/', "_")), &text);
            settled.map_err(|e| format!("{p}/{t} is still loading after 30 s ({e}): {}", http::cut(&text, 300)))?;
            keep(&format!("tenant-{p}-{}", t.replace('/', "_")), &text);
            want(text.contains(p.as_str()), || format!("the tenant page for {t} does not name {p}: {}", http::cut(&text, 300)))?;
            no_failure(&mut b)?;
            said.push(format!("{p}/{t}"));
        }
        want(!said.is_empty(), || "no tenant to open".into())?;
        Ok(said.join(", "))
    });

    // ── the ledger: orders, their states, amounts ──
    go(&mut rows, only, "orders: every recent order in monetize's ledger is on the ledger page, with its state", || {
        let api = fetch_json(&mut b, "/api/ledger")?;
        let mut refs: Vec<String> = Vec::new();
        let mut states: std::collections::BTreeMap<String, Vec<String>> = Default::default();
        for r in api.path("recent").map(|r| r.arr().to_vec()).unwrap_or_default() {
            let reference = r.text("reference");
            if !refs.contains(&reference) {
                refs.push(reference.clone());
            }
            states.entry(reference).or_default().push(r.text("kind"));
        }
        want(!refs.is_empty(), || format!("monetize's ledger holds no order: {}", http::cut(&api.dump(), 200)))?;
        b.navigate(&format!("{base}/money"))?;
        b.c.wait_for(&b.sess, "document.body && document.body.innerText.includes('History')", 30_000)?;
        b.disclose_all()?;
        let text = b.text()?;
        let titles = b.titles()?;
        keep("ledger", &text);
        keep("ledger-titles", &titles);
        let missing: Vec<&String> = refs.iter().filter(|r| {
            let tenant = r.split('/').nth(1).unwrap_or(r);
            !text.contains(r.as_str()) && !titles.contains(r.as_str()) && !text.contains(tenant)
        }).collect();
        want(missing.is_empty(), || format!("orders in the ledger but not on the page: {missing:?}"))?;
        no_failure(&mut b)?;
        Ok(states.iter().map(|(r, k)| format!("{r}: {}", k.join("→"))).collect::<Vec<_>>().join(" | "))
    });

    // Each panel draws NOTHING when its read is empty (by design: the absence
    // is the good state). So: what the API holds decides what must be seen.
    for (name, path, list, heading) in [
        ("signups: the paid signups panel shows every paid signup", "/api/signups", "signups", "Paid signups"),
        ("refusals: the refused vendor deliveries panel shows every refusal", "/api/callback-refusals", "refusals", "refus"),
        ("paid at vendor: the panel of payments monetize did not fulfil", "/api/paid-at-vendor", "cases", "paid at the vendor"),
    ] {
        go(&mut rows, only, name, || {
            let api = fetch_json(&mut b, path)?;
            let n = api.path(list).map(|l| l.arr().len()).unwrap_or(0);
            b.navigate(&format!("{base}/money"))?;
            b.c.wait_for(&b.sess, "document.body && document.body.innerText.includes('History')", 30_000)?;
            if n == 0 {
                std::thread::sleep(std::time::Duration::from_secs(3));
                let text = b.text()?;
                want(!text.to_lowercase().contains(&heading.to_lowercase()), || format!("{path} holds nothing and the page still shows {heading:?}"))?;
                no_failure(&mut b)?;
                return Ok(format!("{path}: 0 rows, and the panel is absent (the good state)"));
            }
            let seen = b.c.wait_for(&b.sess, &format!("document.body.innerText.toLowerCase().includes({})", esc(&heading.to_lowercase())), 20_000);
            let text = b.text()?;
            keep(&format!("ledger-{list}"), &text);
            seen.map_err(|e| format!("{path} holds {n} row(s) and the page never showed {heading:?} within 20 s ({e}): {}", http::cut(&text, 300)))?;
            no_failure(&mut b)?;
            Ok(format!("{n} row(s): {}", around(&text, heading)))
        });
    }
    go(&mut rows, only, "tax: the tax page loads", || {
        b.navigate(&format!("{base}/tax"))?;
        b.c.wait_for(&b.sess, "document.body && document.body.innerText.length > 60", 30_000)?;
        std::thread::sleep(std::time::Duration::from_secs(2));
        let text = b.text()?;
        keep("tax", &text);
        no_failure(&mut b)?;
        Ok(http::cut(&text, 160))
    });
    // Owner 2026-09-28: the estate's [tax] links its jurisdiction (DK, 25 %),
    // so the page is no longer "no jurisdiction is linked" with every box "—".
    go(&mut rows, only, "tax: a jurisdiction is linked, not the loud default", || {
        let api = fetch_json(&mut b, "/api/tax/periods?year=2026")?;
        let j = api.text("jurisdiction");
        want(!j.is_empty() && j != "—", || format!("/api/tax names no jurisdiction: {}", http::cut(&api.dump(), 300)))?;
        let text = std::fs::read_to_string(work.join("tax.txt")).unwrap_or_default();
        want(!text.contains("no jurisdiction is linked"), || format!("the page still says no jurisdiction is linked: {}", http::cut(&text, 300)))?;
        Ok(format!("jurisdiction {j}"))
    });
    // Owner 2026-09-28: the VAT per order — every sale the API lists that
    // charged anything carries its tax, and the page shows it.
    go(&mut rows, only, "ledger: every sale carries its VAT", || {
        let l = fetch_json(&mut b, "/api/ledger")?;
        let sales: Vec<Json> = l.path("sales").map(|s| s.arr().to_vec()).unwrap_or_default();
        let charged: Vec<&Json> = sales.iter().filter(|s| s.text("outcome") != "declined").collect();
        let untaxed: Vec<String> = charged.iter().filter(|s| s.path("tax_minor").and_then(|t| t.as_f64()).is_none()).map(|s| s.text("reference")).collect();
        want(untaxed.is_empty(), || format!("sales with no tax on the wire: {untaxed:?}"))?;
        if charged.is_empty() {
            return Ok("no sale charged anything yet: nothing to show".into());
        }
        b.navigate(&format!("{base}/money"))?;
        b.c.wait_for(&b.sess, "document.body && document.body.innerText.includes('VAT ')", 30_000)?;
        let text = b.text()?;
        Ok(format!("{} sale(s) with their VAT: {}", charged.len(), around(&text, "VAT ")))
    });
    // Owner 2026-09-28: a MACHINE principal is no tenant (gunnar says so at
    // the source, Namespace.owner_kind).
    go(&mut rows, only, "tenants: no machine principal is listed as a tenant", || {
        let machines = ["front-certify", "gunnar-guest", "gunnar-twin", "gunnar-ui", "installer-seed", "monetize", "twin", "dwarves-oden", "monetize-ui", "monetize-ui-bench"];
        let listed: Vec<String> = tenants.iter().map(|(_, t)| t.clone()).filter(|t| machines.contains(&t.as_str())).collect();
        want(listed.is_empty(), || format!("machine principals listed as tenants: {listed:?}"))?;
        Ok(format!("{} tenant(s), none a machine", tenants.len()))
    });

    // ── live: an order that changes state reaches an open page without a reload ──
    // The live probe needs a tenant the product can serve NOW (a bundle with
    // `can_serve`) and with no attempt already open (that one would be handed
    // back, which is no NEW order). Tenants that have paid before come first:
    // the others may be machine principals no product sells room to.
    let pick: Option<(String, String, String)> = (|| {
        let l = fetch_json(&mut b, "/api/ledger").ok()?;
        let open: Vec<String> = l.path("unfinished").map(|u| u.arr().iter().filter_map(|x| x.text("reference").split('/').nth(1).map(str::to_owned)).collect()).unwrap_or_default();
        let paid: Vec<String> = l.path("sales").map(|s| s.arr().iter().map(|x| x.text("tenant")).collect()).unwrap_or_default();
        let mut order: Vec<(String, String)> = tenants.iter().filter(|(_, t)| paid.contains(t)).cloned().collect();
        order.extend(tenants.iter().filter(|(_, t)| !paid.contains(t)).cloned());
        for (p, t) in order.into_iter().filter(|(_, t)| !open.contains(t)) {
            let bundles = fetch_json(&mut b, &format!("/api/bundles?product={p}&tenant={t}")).ok()?;
            if let Some(id) = bundles.path("bundles").and_then(|x| x.arr().iter().find(|b| b.path("can_serve").and_then(|v| v.as_bool()) == Some(true)).map(|b| b.text("id"))) {
                return Some((p, t, id));
            }
        }
        None
    })();
    if let Some((p, t, bundle)) = pick {
        go(&mut rows, only, "sse: a new order reaches the open ledger page live (no reload)", || {
            b.navigate(&format!("{base}/money"))?;
            b.c.wait_for(&b.sess, "document.body && document.body.innerText.includes('History')", 30_000)?;
            std::thread::sleep(std::time::Duration::from_secs(2));
            let rows_before = b.count(".sale-row")?;
            b.c.eval(&b.sess, "(() => { window.__feed = 0; const es = new EventSource('/api/events'); es.addEventListener('feed', () => window.__feed++); window.__es = es; return 1; })()")?;
            let started = b.c.eval(
                &b.sess,
                &format!(
                    "fetch('/api/purchase', {{method: 'POST', headers: {{'content-type': 'application/json', 'Origin': location.origin}}, body: JSON.stringify({{product: {}, tenant: {}, caps: {{}}, bundle: {}}})}}).then(async r => ({{status: r.status, body: await r.text()}}))",
                    esc(&p), esc(&t), esc(&bundle)
                ),
            )?;
            let body = started.text("body");
            want(started.path("status").and_then(|s| s.as_f64()) == Some(200.0), || format!("the order was not started: {}", http::cut(&started.dump(), 300)))?;
            let reference = crate::json::parse(&body).map(|v| v.text("reference")).unwrap_or_default();
            want(!reference.is_empty(), || format!("no reference in {}", http::cut(&body, 200)))?;
            b.c.wait_for(&b.sess, "window.__feed > 0", 30_000).map_err(|e| format!("no feed event within 30 s: {e}"))?;
            // The new attempt is a NEW ROW naming the tenant ("Needs you": money
            // reserved, not yet captured) — the page does not print the reference.
            let on_page = format!("document.querySelectorAll('.sale-row').length > {rows_before} && [...document.querySelectorAll('.sale-row')].some(e => e.innerText.includes({}))", esc(&t));
            let live = b.c.wait_for(&b.sess, &on_page, 30_000);
            let n = b.c.eval(&b.sess, "window.__feed")?.as_f64().unwrap_or(0.0);
            let held = b.text()?;
            keep("ledger-live", &held);
            // Whether a RELOAD shows it tells a dead live path from a page that never draws it.
            let after_reload = if live.is_err() {
                b.navigate(&format!("{base}/money"))?;
                let r = b.c.wait_for(&b.sess, &on_page, 20_000).is_ok();
                keep("ledger-reloaded", &b.text()?);
                Some(r)
            } else {
                None
            };
            // Leave nothing open: the reservation is given back.
            let _ = b.c.eval(&b.sess, &format!("fetch('/api/purchase/abandon', {{method: 'POST', headers: {{'content-type': 'application/json', 'Origin': location.origin}}, body: JSON.stringify({{reference: {}, reason: 'uitest: the live-feed probe gives its reservation back'}})}}).then(r => r.status)", esc(&reference)));
            match after_reload {
                None => Ok(format!("{reference} ({bundle}) on the page live; {n} feed event(s)")),
                Some(true) => Err(format!("{reference}: {n} feed event(s) reached the page but it did not redraw; a RELOAD shows the order — the live path is broken")),
                Some(false) => Err(format!("{reference}: {n} feed event(s); not on the page live NOR after a reload: {}", http::cut(&held, 300))),
            }
        });
    } else {
        go(&mut rows, only, "sse: a new order reaches the open ledger page live (no reload)", || Err("no tenant the product can serve now without an attempt already open — nothing to order".into()));
    }
    rows
}

/// The line of `text` that mentions `needle` (case-insensitively), whole.
fn around(text: &str, needle: &str) -> String {
    let n = needle.to_lowercase();
    text.lines().find(|l| l.to_lowercase().contains(&n)).map(|l| http::cut(l, 160)).unwrap_or_default()
}

// ── pictures: every page at desktop and at phone width ───────────────────

/// **The console as it LOOKS**, at 1440 px and at 390 px (owner 2026-10-02:
/// a visual check at desktop and phone width, no horizontal page scroll).
///
/// The Overview, every estate's Map, and every section of the first estate,
/// each saved as `<dir>/<desktop|phone>-<page>.png`. One row per picture,
/// and the row is RED when the page scrolls sideways — the document, or any
/// `.page` that scrolls inside it — since a phone reader would have to pan.
/// Fixture-free: it asserts layout only, so it runs against the rig, a
/// `--live` console, or a demo of several estates alike.
pub fn shots(chrome_bin: &str, origin: &str, dir: &Path, only: Option<&str>) -> Vec<Row> {
    let mut rows = Vec::new();
    let _ = std::fs::create_dir_all(dir);
    let profile = std::env::temp_dir().join(format!("monetize-uitest-shots-{}", std::process::id()));
    let Some(mut b) = Browser::start(chrome_bin, &profile, &mut rows, only) else { return rows };
    let hostport = origin.trim_start_matches("http://");
    let names: Vec<String> = http::req(hostport, "GET", "/api/overview", &[], None)
        .ok()
        .and_then(|r| crate::json::parse(&r.body).ok())
        .and_then(|j| j.path("estates").map(|e| e.arr().iter().map(|x| x.text("name")).filter(|n| !n.is_empty()).collect()))
        .unwrap_or_default();
    let mut pages: Vec<(String, String)> = vec![("overview".into(), "/".into())];
    for n in &names {
        pages.push((format!("{n}-map"), format!("/e/{n}/map")));
    }
    if let Some(n) = names.first() {
        for s in ["customers", "money", "tax", "offer", "events", "settings"] {
            pages.push((format!("{n}-{s}"), format!("/e/{n}/{s}")));
        }
        pages.push((format!("{n}-customer"), format!("/e/{n}/customer?product=gunnar&tenant=alice")));
    }
    for (name, path) in pages {
        for (w, h, tag) in [(1440u32, 1000u32, "desktop"), (390, 844, "phone")] {
            let row: &'static str = Box::leak(format!("picture-{tag}-{name}").into_boxed_str());
            go(&mut rows, only, row, || {
                b.c.viewport(&b.sess, w, h)?;
                b.navigate(&format!("{origin}{path}"))?;
                // Settled: the page has drawn its words and no row is still asking.
                let _ = b.c.wait_for(&b.sess, "document.body && document.body.innerText.length > 60 && !/asking …|Loading …|checking …/.test(document.body.innerText)", 15_000);
                std::thread::sleep(std::time::Duration::from_millis(700));
                let over = b.c.eval(
                    &b.sess,
                    "(() => { const d = document.documentElement.scrollWidth - window.innerWidth;
                       const inner = [...document.querySelectorAll('.page')].map(p => p.scrollWidth - p.clientWidth);
                       return Math.max(d, ...inner, 0); })()",
                )?;
                let px = over.as_f64().unwrap_or(0.0);
                let file = dir.join(format!("{tag}-{name}.png"));
                let bytes = b.c.screenshot(&b.sess, &file)?;
                want(px <= 1.0, || {
                    let wide = b
                        .c
                        .eval_str(
                            &b.sess,
                            "[...document.querySelectorAll('.page *')].filter(e => e.getBoundingClientRect().right > window.innerWidth + 1).slice(0, 4).map(e => e.tagName + '.' + (e.className || '')).join(' ')",
                        )
                        .unwrap_or_default();
                    format!("{path} at {w} px scrolls sideways by {px} px ({}); widest: {wide}", file.display())
                })?;
                Ok(format!("{} ({} kB)", file.display(), bytes / 1024))
            });
        }
    }
    let _ = std::fs::remove_dir_all(&profile);
    rows
}