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

nordisk/dwarves

dwarves-gui/src/warehouse_select.rs

//! **WarehouseSelector** — dwarves' warehouse/workspace selector, a faithful mirror
//! of nornir's viz workspace picker (`nornir/src/viz/app.rs`).
//!
//! The model (nornir parity): MANY warehouses may be registered, but exactly ONE is
//! **selected/active** at a time (`""` = none — a legitimate resting state, not an
//! error). Every warehouse-bound function binds to the selection: the host routes the
//! chosen name to each tab's `set_workspace` (the funnel re-fetches THAT warehouse),
//! and release/warehouse-writes target it ([`release_target`](WarehouseSelector::release_target)).
//! This is the "active warehouse" that sits beside the "active zone".
//!
//! nornir's shape reproduced:
//! * `warehouses: Vec<String>` — the registered options offered in the picker.
//! * `selected: String` — the ONE active name (`""` = none).
//! * `state_json.workspace_picker = { options, selected, count }` → here surfaced as
//!   the top-level `selected_warehouse` + a `warehouse_select` slice.
//! * the egui `ComboBox` with a `⚠ none — pick one` label when nothing is selected.
//!
//! It is NOT a `Facet`/tab — it is a header widget the [`TabbedView`](crate::tabbed)
//! owns and renders above the tab bar; its `ui` returns the newly-chosen name so the
//! host can broadcast the re-bind.

use nornir_warehouse_embed::{AddWarehouse, PopulateProgress};

/// How often the picker asks the server whether a populate is happening.
///
/// The poll is TWO cheap RPCs (`Workspaces.Get` + the compact `PopulateStatus`
/// roll-up), never the unpaginated `Viz.CloneEvents`. It runs whenever a warehouse is
/// selected and an endpoint is configured — NOT only after a button here started
/// something — because nornir's own poll loop, another client and `nornir workspace
/// populate` all move the same warehouse this shell is bound to.
const PROGRESS_POLL: std::time::Duration = std::time::Duration::from_millis(1500);

/// Every Nth progress poll also counts crates (a `dep_graph_edges` scan). The repo
/// denominator is exact and free; the crate count is the REFINING estimate and costs a
/// table scan, so it arrives later and improves. `det är ok` that it moves.
const CRATE_REFINE_EVERY: u32 = 4;

/// Row cap on the crate-count scan. Bounded so a poll can never pull an unbounded
/// table across the wire.
const CRATE_SCAN_LIMIT: u32 = 200_000;

/// **The variable this client reads its host bearer from.** Named once, by the crate
/// that owns the resolution chain, so the code that reads it and every message that
/// names it cannot drift apart.
pub const TOKEN_ENV: &str = dwarves_embed::TOKEN_ENV;

/// **The credential this pane's messages describe** — the dwarves client chain
/// (`$DWARVES_SERVER_TOKEN`, then `~/.dwarves/token`), walked by
/// [`dwarves_embed::ClientCredential`].
///
/// The pane holds only a bearer STRING (`AddWarehouse::token`, a
/// nornir-warehouse-embed field), and a message that has to say *no credential
/// configured* versus *token rejected* needs to know which rung produced it. So the
/// chain is re-walked and matched against the string: if they agree — which is the
/// normal case, because `build_native_app` resolves the pane's bearer from this very
/// chain — the rung is a FACT from the walk. If they disagree, the host handed this
/// pane a bearer directly and the message says that instead of guessing a rung.
fn credential_for(token: &str) -> dwarves_embed::ClientCredential {
    let chain = dwarves_embed::ClientCredential::resolve();
    if chain.token.trim() == token.trim() {
        return chain;
    }
    dwarves_embed::ClientCredential {
        token: token.to_string(),
        whence: if token.trim().is_empty() {
            dwarves_embed::TokenSource::Nothing
        } else {
            dwarves_embed::TokenSource::Flag
        },
    }
}

/// The SHORT spellings that look right and are not. Naming them is the whole value of
/// the message: see [`credential_refusal`].
pub const TOKEN_ENV_LOOKALIKES: [&str; 2] = ["DWARVES_TOKEN", "NORNIR_TOKEN"];

/// The lookalike names, spelled as a message fragment.
fn misnamed(env: &dyn Fn(&str) -> Option<String>) -> Vec<&'static str> {
    TOKEN_ENV_LOOKALIKES
        .iter()
        .copied()
        .filter(|k| env(k).is_some_and(|v| !v.trim().is_empty()))
        .collect()
}

/// **Refuse BEFORE the RPC — but only when the misconfiguration is unambiguous.**
///
/// MEASURED live 2026-08-22: the Add-warehouse form answered
/// `Workspaces.Register RPC: 'The request does not have valid authentication
/// credentials'`. The token was not wrong — there was none. The unit had been launched
/// with `DWARVES_TOKEN` / `NORNIR_TOKEN`, while this client reads [`TOKEN_ENV`]
/// (`DWARVES_SERVER_TOKEN`) and `unwrap_or_default()`s it, so it sent an EMPTY bearer
/// and the server answered with its generic refusal.
///
/// ## Why this does NOT simply refuse every empty token
///
/// Because an empty bearer is legitimate here: `lib.rs` documents "Empty ⇒ open (the
/// host's dev default)", and the pane's own gRPC tests drive a mock that wants no
/// credential. A blanket refusal would turn a working dev shell into a dead one, which
/// is a worse defect than the one being fixed.
///
/// So the split is by EVIDENCE. A short lookalike name carrying a value while the real
/// one is empty has no innocent reading — somebody set a token, meaning it for this
/// client, and spelled it wrong. That is refused here, by name, before a request
/// leaves the process, because sending an empty bearer in that state can only produce
/// the useless generic answer. Both-unset is ambiguous (it is exactly what an open
/// server looks like), so it flies, and [`explain_auth_failure`] names it afterwards
/// if the server does refuse.
///
/// ## The FILE rung, which this message used to deny
///
/// It said *"There is no `~/.nornir/token` fallback — this shell holds no nornir
/// address, so it holds no nornir credential"*, and the reasoning was sound about
/// NORNIR and wrong about the conclusion. The client's endpoint is a DWARVES endpoint,
/// so the credential resolves from the DWARVES chain — `$DWARVES_SERVER_TOKEN`, then
/// `~/.dwarves/token`, the same 0600 file `dwarves-server` reads when it resolves the
/// secret it will demand, and the same ladder the `dwarves` CLI has always used
/// (`nornir_service::resolve_token`). The owner, 2026-08-27: *"the token chain is wrong
/// EVERY time server and client are started."*
///
/// `~/.nornir/token` is still NOT a rung, and on oden the two files are byte-identical
/// (`cmp` says so), which is exactly why reading the wrong one would go unnoticed here
/// and break on the first box where they differ. What changed is that the message now
/// names the rung the operator can actually USE.
///
/// `env` is injected so the ladder is testable without mutating process environment —
/// `std::env::set_var` is unsound across test threads and would make the guard flaky.
pub fn credential_refusal(token: &str, env: &dyn Fn(&str) -> Option<String>) -> Option<String> {
    if !token.trim().is_empty() {
        return None;
    }
    let wrong = misnamed(env);
    if wrong.is_empty() {
        return None;
    }
    let file = dwarves_embed::token_file()
        .map(|p| p.display().to_string())
        .unwrap_or_else(|| "<no $HOME, so no token file>".into());
    Some(format!(
        "error: NO CREDENTIAL SENT — nothing was requested. This client walks \
         ${TOKEN_ENV}, then {file}, and neither held a secret — but ${} IS set: that is \
         the wrong name. Relaunch with ${TOKEN_ENV}, or write the host's token into \
         {file} (0600), which is a supported rung and the same file dwarves-server \
         reads. This is NOT the server refusing a token; no request was made.",
        wrong.join(" and $")
    ))
}

/// **Say which KIND of auth failure this was**, once the server has answered.
///
/// The server's refusal — `The request does not have valid authentication
/// credentials` — reads identically for four different situations: no token, a wrong
/// token, a revoked token, and a token sent to a server that wanted a different one.
/// The client knows something the server does not, namely what it sent, and it must
/// say it. That is the whole silent-shape disease: a state the UI CAN distinguish,
/// reported as a state it cannot.
///
/// Returns `None` for anything that is not an auth refusal — a clone failure, a
/// missing member, a dead endpoint all keep their own words untouched.
pub fn explain_auth_failure(
    status: &str,
    token: &str,
    env: &dyn Fn(&str) -> Option<String>,
) -> Option<String> {
    // The classification — is this an auth refusal at all, and is it *no credential*
    // or *credential rejected* — belongs to the crate that owns the chain, so the two
    // sentences and the chain they describe cannot drift apart (LAW 5). This function
    // adds the ONE thing that is local to this pane: the misspelled-variable hint,
    // which came out of a measured live incident and has no home in the resolver.
    explain_auth_failure_with(status, &credential_for(token), env)
}

/// [`explain_auth_failure`] with the credential handed in — the PURE half.
///
/// Split out so the guard below asserts the four sentences without reading this box's
/// `$HOME` or its environment. [`credential_for`] walks the real chain, which is right
/// for the pane and wrong for a test: on oden `~/.dwarves/token` exists and holds a
/// secret, so a test that went through the wrapper would be measuring this machine's
/// configuration instead of the classification.
pub fn explain_auth_failure_with(
    status: &str,
    cred: &dwarves_embed::ClientCredential,
    env: &dyn Fn(&str) -> Option<String>,
) -> Option<String> {
    let base = cred.explain_auth_failure(status)?;
    if cred.is_configured() {
        return Some(base);
    }
    let wrong = misnamed(env);
    if wrong.is_empty() {
        return Some(base);
    }
    Some(format!(
        "{base}\n\nAND: ${} IS set — that is the wrong name for this client. Relaunch \
         with ${TOKEN_ENV}.",
        wrong.join(" and $")
    ))
}

/// A warehouse RPC in flight on the background worker.
///
/// `Workspaces.Register` clones the SSH members and builds the warehouse inside
/// nornir, and `Workspaces.Fetch` re-clones and rebuilds it — **minutes**, not
/// milliseconds. Run on the egui thread they freeze the whole shell mid-click, so
/// they run on ONE named worker (`dwarves-warehouse`, blocking I/O — not a CPU
/// fan-out) and their outcome lands here on a later frame.
#[derive(Debug)]
struct WarehouseJob {
    /// What the worker is doing, shown live under the picker.
    what: String,
    /// Which verb is running — its landing decides what the picker does next.
    verb: WarehouseVerb,
    /// For [`WarehouseVerb::Remove`], the name being cleared (it must leave the picker
    /// when the job lands, and the form's own record cannot say which name it was).
    target: String,
    /// The worker's own copy of the form, sent back with `status`/`registered`/
    /// `last_fetch` filled in by the very same [`AddWarehouse`] methods the
    /// synchronous path used — the status strings are not twinned here.
    rx: std::sync::mpsc::Receiver<AddWarehouse>,
}

/// The two WHOLE actions the picker offers, mirroring nornir's own workspace
/// vocabulary. Each one either happens completely or reports that it did not.
///
/// `Resync` was the third, and it is GONE with its button: a re-sync is a populate, and
/// populate is what nornir's poll sweep does by itself when a member's git moves.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum WarehouseVerb {
    /// **Add** — register AND populate, as one act. There is no follow-up press that
    /// finishes an add; if the populate did not happen, the add FAILED.
    Add,
    /// **Remove** — clear the registry row AND the on-disk warehouse together. Never
    /// touches ssh keys or tokens.
    Remove,
    /// **Repopulate** — THE ONE REFRESH (`Workspaces.Repopulate { purge: true }`):
    /// "make the code room of the SELECTED warehouse current, now" — drop the
    /// orphaned v1 tables still in it and start a consume for every member behind
    /// its offset.
    ///
    /// Since Mímir v2 there is no derived cache to discard (a lens is computed on
    /// request), so what this adds over nornir's own sweep is the WHEN and the
    /// report: which state is served, which members are behind, what the consumer
    /// is on, or nornir's named refusal when nothing can consume them.
    ///
    /// It leaves the picker exactly as it found it: unlike Add and Remove, a Refresh
    /// changes no registry row, so nothing folds in or drops out when it lands.
    Repopulate,
}

/// The warehouse picker: a registry of registered warehouses + the ONE selected.
///
/// **Three verbs, all WHOLE actions** — nornir's own workspace vocabulary, mirrored:
///
/// * `➕ Add…` — registers **and** populates, as one act. There is no separate populate
///   step, and no "added but not yet populated" state to report green: the success
///   condition is a warehouse snapshot, so a failed member clone fails the add and the
///   warehouse does not fold into the picker.
/// * `🧹 Repopulate` — **THE ONE REFRESH** (`Workspaces.Repopulate { purge: true }`):
///   makes the selected warehouse's code graph current — drops the orphaned v1
///   tables and starts a consume for every member behind its offset — through the
///   server's open handle. One button, in the header, for the whole warehouse.
/// * `🗑 Remove…` — clears the registry entry **and** the on-disk warehouse together
///   (`Workspaces.Remove { purge: true }`). Two-step, because it destroys data. It never
///   touches ssh keys or tokens.
///
/// There is deliberately **no re-sync control**. A re-sync is `Workspaces.Fetch`, i.e. a
/// populate, and nornir's own poll sweep already fetches every monitored workspace and
/// republishes when a member's SHA moved — the same trigger, without an operator having
/// to know to press anything. SCAN is not a user action (owner, 2026-08-25).
///
/// **Why it is not called "Refresh".** It was, for about an hour, and
/// `no_button_literal_in_the_crate_is_scan_shaped` failed on it — correctly. That guard
/// bans the words `Scan` / `Rescan` / `Refresh` / `Reload` / `Re-sync` / `⟳` / `🔄` on
/// any pressable in this crate, and the ban is about the WORD, not the wiring: an
/// operator reading `🔄 Refresh` on a warehouse picker cannot tell it from the re-sync
/// button that was deleted on 2026-08-25, so the vocabulary alone re-teaches the thing
/// the deletion was meant to unteach — that a warehouse is something you poke by hand.
/// The verb is nornir's own (`Workspaces.Repopulate`), so the label is nornir's own
/// word, and the glyph is the purge rather than a cycle-arrow.
///
/// **`🧹 Repopulate` is not that button coming back, and the distinction is the point.**
/// Re-sync asked for a populate, which the sweep already does. Refresh asks for the
/// one thing auto-population *cannot* do by itself: throw away the derived answers so
/// the folds recompute instead of short-circuiting on their own cache
/// (`lens_runs` at the current generation). It re-clones nothing and re-scans nothing.
/// And it stays ONE button here rather than N per pane — the Mímir/warehouse panes are
/// READERS (see `mimir_lens.rs`), and thirteen lenses do not want thirteen buttons.
///
/// **When an action returns nothing to explain itself, the pane says THAT and points at
/// the RPC that can.** A populate that yields neither a snapshot nor a member error is
/// unexplainable *from this action's reply*; the per-member outcome lives in
/// `clone_events`, read by `Viz.CloneEvents` behind `nornir workspace events <name>`.
/// The pane names it rather than inventing a cause or drawing the silence as success.
///
/// ⚠ 2026-08-27: this doc used to say that RPC "answers `Unimplemented` server-side".
/// MEASURED false — nornir-server answers it with real rows; the dwarves forge's `Viz`
/// PROXY did not carry it, which made it unreachable for precisely this pane when the
/// UI runs thin against the forge. Now forwarded
/// (`dwarves::warehouse_proxy`, `clone_events`).
///
/// ⚠ 2026-09-03: **and this doc used to end "the pane still only NAMES it".** That was
/// true when the proxy fix landed and stopped being true when the fetch did — the pane
/// reads the rows on a named worker thread (`dwarves-clone-events`), paints them in
/// `warehouse_clone_events_grid`, publishes `clone_events_reads` /
/// `clone_events_fetching` in `state_json` so a headless run can see the READ and not
/// just a flag, and has two tests over it
/// (`the_pane_fetches_clone_events_and_paints_the_per_member_outcome`,
/// `a_refused_clone_events_read_is_named_not_drawn_as_an_empty_ledger`).
/// The stale sentence cost a real hour: a sweep on 2026-09-03 read it, grepped
/// `crates/**` — where `dwarves-gui/` is NOT, it is a top-level directory — found zero
/// hits, and filed the row as open. A doc that describes a finished thing as pending
/// is the same defect as a green that was never red, pointing the other way.
#[derive(Debug, Default)]
pub struct WarehouseSelector {
    /// Every registered warehouse name (the picker options), stable order.
    warehouses: Vec<String>,
    /// The active selection (`""` = none selected — the onboarding resting state).
    selected: String,
    /// The "Add warehouse (SSH)" form + the endpoint/token Populate also uses (present
    /// iff an add-source endpoint is configured). Add drives nornir's
    /// `Workspaces.Register` (eager) and Populate its `Workspaces.Fetch` over gRPC —
    /// the warehouse-WRITE happens in nornir.
    add: Option<AddWarehouse>,
    /// Whether the Add form is currently expanded in the header.
    adding: bool,
    /// The warehouse a `🗑 Remove…` click is asking to confirm (`None` = not asking).
    /// Remove destroys data, so it is a two-step act: the first click ARMS it and says
    /// exactly what will be cleared, the second one does it.
    confirm_remove: Option<String>,
    /// The ONE register/populate in flight (`None` = idle). At most one at a time: a
    /// second would race the first's warehouse build.
    job: Option<WarehouseJob>,
    /// The live populate progress of the SELECTED warehouse, refreshed on a poll that
    /// runs whether or not anything here started the populate. `None` before the first
    /// poll answers.
    progress: Option<PopulateProgress>,
    /// The progress poll in flight (at most one), and when the last one started.
    progress_rx: Option<std::sync::mpsc::Receiver<Option<PopulateProgress>>>,
    last_progress_poll: Option<std::time::Instant>,
    /// Poll counter — every [`CRATE_REFINE_EVERY`]th poll also refines the crate count.
    progress_ticks: u32,
    /// The last [`step_board::OrderFault`] message, if two producers raced on one run.
    /// Held rather than logged: it names a bug in the producers, and the person who can
    /// act on it is the one looking at the pane.
    step_fault: Option<String>,
    /// Was the picker's ComboBox popup open on the PREVIOUS frame? The edge detector
    /// [`Self::ui`] uses to fire exactly ONE registry refresh per open — not one per
    /// frame the popup happens to be on screen.
    picker_open: bool,
    /// The registry refresh in flight (at most one), carrying the served names.
    registry_rx: Option<std::sync::mpsc::Receiver<Vec<String>>>,
    /// How many registry refreshes have LANDED. Published in [`Self::state_json`]
    /// because a number that moves is the only way a headless drive can see that
    /// opening the picker actually went to the server — "the list looks the same" is
    /// indistinguishable from "nothing was asked".
    registry_refreshes: u32,
    /// **The per-member populate outcomes (`clone_events`) for the SELECTED
    /// warehouse** — `Ok(rows)` newest-first, or `Err(why)` when the read itself
    /// failed. `None` = never asked.
    ///
    /// A failed READ is kept as a failure and painted as one. Collapsing it to an
    /// empty list would draw "the server would not answer" identically to "every
    /// member populated cleanly", which is the exact silence this pane exists to
    /// break.
    clone_events: Option<Result<Vec<serde_json::Value>, String>>,
    /// The warehouse `clone_events` belongs to, so switching the selection
    /// invalidates them instead of showing one warehouse's failures under another's
    /// name.
    clone_events_ws: String,
    /// The `Viz.CloneEvents` read in flight (at most one).
    clone_events_rx: Option<std::sync::mpsc::Receiver<Result<Vec<serde_json::Value>, String>>>,
    /// How many `Viz.CloneEvents` reads have LANDED — published in
    /// [`Self::state_json`] so a headless drive can tell "the rows were fetched"
    /// from "the pane merely names the RPC", which is the whole defect here.
    clone_events_reads: u32,
}

/// One string field of a `clone_events` row, or `""`. The rows arrive as the
/// server's JSON verbatim (the `nornir-warehouse-embed` leg deliberately carries no
/// `nornir-wire` dependency), so the pane reads the wire contract's field names
/// rather than restating `CloneEventRow` as a second struct that would have to be
/// kept in agreement with it (LAW 5).
fn row_str(row: &serde_json::Value, field: &str) -> String {
    row.get(field)
        .and_then(|v| v.as_str())
        .unwrap_or_default()
        .to_string()
}

impl WarehouseSelector {
    /// Build a selector over `warehouses`, selecting `selected` iff it is a registered
    /// name (else nothing is selected — the `""` resting state). An unknown initial
    /// selection is never silently invented.
    pub fn new(warehouses: Vec<String>, selected: impl Into<String>) -> Self {
        let mut s = Self {
            warehouses,
            selected: String::new(),
            add: None,
            adding: false,
            confirm_remove: None,
            job: None,
            progress: None,
            progress_rx: None,
            last_progress_poll: None,
            progress_ticks: 0,
            step_fault: None,
            picker_open: false,
            registry_rx: None,
            registry_refreshes: 0,
            clone_events: None,
            clone_events_ws: String::new(),
            clone_events_rx: None,
            clone_events_reads: 0,
        };
        let want = selected.into();
        if !want.is_empty() {
            s.select(&want);
        }
        s
    }

    /// **Enable the "Add warehouse (SSH)" affordance** by pointing it at a
    /// `nornir-server` endpoint (+ bearer token). Without this the selector is a pure
    /// picker; with it the header grows an `➕ Add…` control that registers + populates
    /// a warehouse from an SSH git remote via nornir's `Workspaces` RPCs.
    pub fn with_add_source(
        mut self,
        endpoint: impl Into<String>,
        token: impl Into<String>,
    ) -> Self {
        self.add = Some(AddWarehouse::new(endpoint, token));
        self
    }

    /// Mutable access to the Add form (for the host / robot test to drive it).
    pub fn add_form_mut(&mut self) -> Option<&mut AddWarehouse> {
        self.add.as_mut()
    }

    /// After a successful Add/Populate, fold the newly-registered warehouse into the
    /// picker and select it (so every tab re-binds to the freshly-populated warehouse).
    /// Returns the name iff it was newly registered+selected this call.
    ///
    /// **A warehouse that was not POPULATED never folds in.** A register whose member
    /// clone failed produces a record with no snapshot; showing it in the picker as a
    /// selectable warehouse is drawing a failure as a success, which is exactly what
    /// `✓ populated now (eager register)` over an empty `git/` did.
    pub fn commit_added(&mut self) -> Option<String> {
        let form = self.add.as_ref()?;
        let name = form.registered.as_ref()?.name.clone();
        let populated = form
            .last_add
            .as_ref()
            .map(|a| a.populated)
            .unwrap_or_else(|| form.registered.as_ref().is_some_and(|r| r.is_populated()));
        if name.is_empty() || !populated {
            return None;
        }
        let newly = self.register(name.clone());
        // Select it whether or not it was newly added (re-populate keeps it selected).
        self.select(&name);
        if newly || self.selected == name {
            Some(name)
        } else {
            None
        }
    }

    /// The active warehouse name (`""` = none selected).
    pub fn selected(&self) -> &str {
        &self.selected
    }

    /// True iff a warehouse is currently selected.
    pub fn has_selection(&self) -> bool {
        !self.selected.is_empty()
    }

    /// The registered warehouse names (the picker options).
    pub fn warehouses(&self) -> &[String] {
        &self.warehouses
    }

    /// **The warehouse release/warehouse-writes bind to** — the SELECTED one. dwarves'
    /// release ops and warehouse submits target this (never a hard-coded / env-only
    /// default), so a release always writes to the active warehouse. `""` when none is
    /// selected (a release must refuse rather than write to an unnamed warehouse).
    pub fn release_target(&self) -> &str {
        &self.selected
    }

    /// **Register a warehouse** (populate the registry). Idempotent: a duplicate name
    /// is ignored. Returns `true` iff it was newly added.
    pub fn register(&mut self, name: impl Into<String>) -> bool {
        let name = name.into();
        if name.is_empty() || self.warehouses.iter().any(|w| w == &name) {
            return false;
        }
        self.warehouses.push(name);
        true
    }

    /// **Select the active warehouse** by name. An empty string deselects (back to the
    /// `""` none state). A non-empty name is honoured only if it is registered (an
    /// unknown name is a no-op, never an invented selection). Returns `true` iff the
    /// selection changed.
    pub fn select(&mut self, name: impl AsRef<str>) -> bool {
        let name = name.as_ref();
        if name.is_empty() {
            let changed = !self.selected.is_empty();
            self.selected.clear();
            return changed;
        }
        if self.warehouses.iter().any(|w| w == name) && self.selected != name {
            self.selected = name.to_string();
            return true;
        }
        false
    }

    /// **Re-read the registry from the server** — `Workspaces.List` through the dwarves
    /// host, on the SAME thin `nornir-warehouse-embed` client the Add/Populate form uses.
    ///
    /// Called by [`Self::ui`] on the frame the picker OPENS (see the `picker_open` edge
    /// detector there), which is the honest moment: the picker IS the affordance that
    /// shows the registry, so it is the one place a list frozen at process start is
    /// indefensible. Until this existed, `list_warehouses` had exactly ONE call site in
    /// the tree — `lib.rs::registered_warehouses`, run once inside `build_native_app` —
    /// so a warehouse registered afterwards by the nornir CLI, a second dwarves window
    /// or another operator was invisible until the app was restarted.
    ///
    /// Off the UI thread (a gRPC round trip on it would stall the frame the click
    /// opened), at most one in flight, and it never blocks the popup: the names land on
    /// a later frame, exactly as the populate-progress poll does.
    pub fn refresh_registry(&mut self, ctx: &egui::Context) {
        if self.registry_rx.is_some() {
            return; // one refresh at a time
        }
        let Some(form) = self.add.as_ref() else {
            return;
        };
        let (endpoint, token) = (form.endpoint.clone(), form.token.clone());
        let (tx, rx) = std::sync::mpsc::channel();
        let ctx2 = ctx.clone();
        // ONE named worker doing blocking I/O off the UI thread — not a CPU fan-out.
        let spawned = std::thread::Builder::new()
            .name("dwarves-warehouse-registry".to_string())
            .spawn(move || {
                let names = nornir_warehouse_embed::list_warehouses(&endpoint, &token)
                    .map(|served| {
                        served
                            .into_iter()
                            .map(|w| w.name)
                            .filter(|n| !n.is_empty())
                            .collect()
                    })
                    .unwrap_or_default();
                let _ = tx.send(names);
                ctx2.request_repaint();
            });
        if spawned.is_ok() {
            self.registry_rx = Some(rx);
        }
    }

    /// Land a finished registry refresh, if any.
    ///
    /// **It UNIONS; it never replaces.** A name the shell was seeded with — from
    /// `$DWARVES_WAREHOUSES`, or from a server that answered at startup and has since
    /// gone quiet — survives a refresh, and so does the current selection. Replacing
    /// the list would mean one failed or empty `Workspaces.List` silently takes the
    /// user's warehouse away mid-session, which is a worse lie than a slightly long
    /// list: `list_warehouses` returns `Ok(vec![])` for "unreachable" as readily as for
    /// "genuinely none". Removal stays what it already is — the explicit 🗑 Remove verb.
    fn land_registry(&mut self) {
        let Some(rx) = self.registry_rx.as_ref() else {
            return;
        };
        match rx.try_recv() {
            Ok(names) => {
                for n in names {
                    self.register(n);
                }
                self.registry_refreshes = self.registry_refreshes.wrapping_add(1);
                self.registry_rx = None;
            }
            Err(std::sync::mpsc::TryRecvError::Empty) => {}
            Err(std::sync::mpsc::TryRecvError::Disconnected) => self.registry_rx = None,
        }
    }

    /// True iff a register/populate is running on the worker right now.
    pub fn is_busy(&self) -> bool {
        self.job.is_some()
    }

    /// True iff `🔄 Populate` is offered *and enabled* this frame: an endpoint is
    /// configured, a warehouse is selected, and no other warehouse RPC is in flight.
    /// This is a function of the SELECTION — nothing about it changes when the add form
    /// opens or closes, which is exactly why the control survives a successful Add.
    pub fn can_populate(&self) -> bool {
        self.add.is_some() && self.has_selection() && self.job.is_none()
    }

    /// Start a warehouse RPC on the named worker. The worker mutates its own clone of
    /// the form via the SAME [`AddWarehouse`] methods the UI would have called inline,
    /// then ships it back — so status/outcome formatting lives in exactly one place.
    fn spawn_job(
        &mut self,
        ctx: &egui::Context,
        what: impl Into<String>,
        verb: WarehouseVerb,
        target: impl Into<String>,
        op: impl FnOnce(&mut AddWarehouse) + Send + 'static,
    ) {
        if self.job.is_some() {
            return; // one warehouse build at a time
        }
        let Some(form) = self.add.as_ref() else {
            return;
        };
        // ── THE CREDENTIAL PREFLIGHT ────────────────────────────────────────────
        //
        // Every warehouse write — Add, Refresh, Remove — is spawned from here, so
        // this is the ONE place that can refuse before a request leaves the process.
        // See [`credential_refusal`] for the incident it exists because of.
        if let Some(why) = credential_refusal(&form.token, &|k| std::env::var(k).ok()) {
            if let Some(f) = self.add.as_mut() {
                f.status = why;
            }
            return;
        }
        let mut work = form.clone();
        let (tx, rx) = std::sync::mpsc::channel();
        let ctx = ctx.clone();
        let spawned = std::thread::Builder::new()
            .name("dwarves-warehouse".to_string())
            .spawn(move || {
                op(&mut work);
                let _ = tx.send(work);
                // Wake the shell so the outcome lands on the very next frame.
                ctx.request_repaint();
            });
        match spawned {
            Ok(_) => {
                self.job = Some(WarehouseJob {
                    what: what.into(),
                    verb,
                    target: target.into(),
                    rx,
                })
            }
            Err(e) => {
                if let Some(f) = self.add.as_mut() {
                    f.status = format!("error: could not start the warehouse worker: {e}");
                }
            }
        }
    }

    /// Land a finished job, if any. Returns `Some((verb, target))` on the frame the
    /// outcome arrives. The user's `name`/`ssh_url` are NOT overwritten — they may have
    /// kept typing while the RPC ran.
    fn poll_job(&mut self) -> Option<(WarehouseVerb, String)> {
        let job = self.job.as_ref()?;
        let verb = job.verb;
        let target = job.target.clone();
        match job.rx.try_recv() {
            Ok(done) => {
                if let Some(f) = self.add.as_mut() {
                    // Every warehouse outcome lands here, so this is the one place that
                    // can tell "the server refused a token I sent" from "the server
                    // refused because I sent none". See `explain_auth_failure`.
                    f.status =
                        explain_auth_failure(&done.status, &f.token, &|k| std::env::var(k).ok())
                            .unwrap_or(done.status);
                    f.registered = done.registered;
                    f.last_fetch = done.last_fetch;
                    f.last_add = done.last_add;
                    f.last_removed = done.last_removed;
                    // The worker mutates a CLONE of the form, so every outcome the pane
                    // renders has to be carried back across here explicitly. `🧹 Repopulate`
                    // is the whole reason that matters: its ENTIRE visible result is this
                    // field — the queued jera job ids, or nornir's named refusal — because
                    // a Repopulate touches no registry row and so changes nothing else on
                    // screen. Omit this line and the button reports `null` forever while
                    // looking like it worked.
                    f.last_repopulate = done.last_repopulate;
                }
                self.job = None;
                Some((verb, target))
            }
            Err(std::sync::mpsc::TryRecvError::Empty) => None,
            Err(std::sync::mpsc::TryRecvError::Disconnected) => {
                // The worker died without reporting — say so, never hang on a spinner.
                if let Some(f) = self.add.as_mut() {
                    f.status = "error: the warehouse worker died before reporting".to_string();
                }
                self.job = None;
                Some((verb, target))
            }
        }
    }

    /// **Did the last `Add` genuinely produce a warehouse?** `false` when no add has
    /// been run, and — crucially — `false` when the register succeeded over a
    /// warehouse with no snapshot. A registered-but-empty warehouse is not an added
    /// warehouse, so it never folds into the picker as if it were.
    fn last_add_succeeded(&self) -> bool {
        self.add
            .as_ref()
            .and_then(|a| a.last_add.as_ref())
            .map(|o| o.populated)
            .unwrap_or(false)
    }

    /// True iff `🗑 Remove…` is offered and enabled this frame.
    pub fn can_remove(&self) -> bool {
        self.add.is_some() && self.has_selection() && self.job.is_none()
    }

    /// True iff `🧹 Repopulate` is offered and enabled this frame — the same three
    /// conditions Remove needs (an endpoint, a selection, no job in flight), because
    /// it is the same kind of thing: a WRITE to the selected warehouse that must not
    /// race another one.
    pub fn can_repopulate(&self) -> bool {
        self.add.is_some() && self.has_selection() && self.job.is_none()
    }

    /// The live populate progress of the selected warehouse, if a poll has answered.
    pub fn progress(&self) -> Option<&PopulateProgress> {
        self.progress.as_ref()
    }

    /// **Drive the populate-progress poll.** Called every frame; it starts at most one
    /// RPC per [`PROGRESS_POLL`] and lands the result on a later frame.
    ///
    /// It runs whenever a warehouse is selected and an endpoint is configured — NOT
    /// only while a job of ours is in flight. A populate started by nornir's poll loop,
    /// by another client, or from the command line moves the same warehouse this shell
    /// is bound to, and the person looking at the screen is entitled to see it.
    fn drive_progress(&mut self, ctx: &egui::Context) {
        // Land a finished poll first.
        if let Some(rx) = &self.progress_rx {
            match rx.try_recv() {
                Ok(p) => {
                    if let Some(p) = p {
                        // **Publish the transition.** The poll is the producer: it is the
                        // only thing here that sees a phase change, and it sees one
                        // whether or not a button in this process started the populate.
                        //
                        // The OrderFault is NOT swallowed. It means a second producer
                        // announced a phase over this one — the poll worker owns the
                        // clone while a Fetch from the button, from nornir's poll loop or
                        // from the CLI owns the build — and a person watching a bar go red
                        // is entitled to the sentence that says which two steps raced.
                        // The board has already marked the step Failed by the time this
                        // returns, so the bar is red either way; this only carries the
                        // words to the status line.
                        if let Err(fault) = crate::populate_steps::observe(
                            &p.workspace,
                            &p,
                            "a populate is under way on the server",
                        ) {
                            self.step_fault = Some(fault.message());
                        }
                        self.progress = Some(p);
                    }
                    self.progress_rx = None;
                }
                Err(std::sync::mpsc::TryRecvError::Empty) => {}
                Err(std::sync::mpsc::TryRecvError::Disconnected) => self.progress_rx = None,
            }
        }
        let (Some(form), false) = (self.add.as_ref(), self.selected.is_empty()) else {
            self.progress = None;
            return;
        };
        if self.progress_rx.is_some() {
            return; // one poll at a time
        }
        let due = self
            .last_progress_poll
            .is_none_or(|t| t.elapsed() >= PROGRESS_POLL);
        if !due {
            // Keep repainting while a populate is on screen so the bar actually moves.
            if self.progress.as_ref().is_some_and(|p| p.running) {
                ctx.request_repaint_after(PROGRESS_POLL);
            }
            return;
        }
        self.last_progress_poll = Some(std::time::Instant::now());
        self.progress_ticks = self.progress_ticks.wrapping_add(1);
        // The crate count is the REFINING estimate: it costs a table scan, so it is
        // taken on every Nth tick rather than every one.
        let limit = if self.progress_ticks % CRATE_REFINE_EVERY == 1 {
            CRATE_SCAN_LIMIT
        } else {
            0
        };
        let (endpoint, token, ws) = (
            form.endpoint.clone(),
            form.token.clone(),
            self.selected.clone(),
        );
        let (tx, rx) = std::sync::mpsc::channel();
        let ctx2 = ctx.clone();
        // ONE named worker doing blocking I/O off the UI thread — not a CPU fan-out.
        let spawned = std::thread::Builder::new()
            .name("dwarves-populate-progress".to_string())
            .spawn(move || {
                let p =
                    nornir_warehouse_embed::poll_populate_progress(&endpoint, &token, &ws, limit)
                        .ok();
                let _ = tx.send(p);
                ctx2.request_repaint();
            });
        if spawned.is_ok() {
            self.progress_rx = Some(rx);
        }
    }

    /// **The populate progress bar.** Drawn whenever a populate is happening OR one has
    /// just finished with something to say — never only when a button here started it.
    ///
    /// Indeterminate (a spinner) while there is no denominator; a proportion the moment
    /// one exists. The counters are drawn beside it, and a counter that has not been
    /// measured shows `…` while one this seam cannot measure shows `n/a` with the
    /// reason on hover — neither is ever drawn as `0`.
    fn progress_ui(&self, ui: &mut egui::Ui) {
        // ── THE STEP BAR ────────────────────────────────────────────────────────
        // Drawn from the BOARD, not from `self.progress`: the board is the producer's
        // published claim and it survives between polls, so the bar is on screen on the
        // frame after the run starts rather than on whichever frame an RPC happens to
        // land. It is also what a robot posture seeds, which is how this gets a
        // screenshot without a live populate to photograph.
        // One segment per phase, drawn by the shared `facett_jobview` widget from the
        // producer's own StepRun — so the sentence under it is the string
        // `populate_steps` built, not a second one composed here that could disagree
        // with it.
        //
        // This replaces a single `repos_done/repos_total` bar. That bar was not wrong
        // about the clone; it was wrong about the other two thirds of the work, which it
        // drew as 100%.
        if let Some(run) =
            crate::populate_steps::board().get(&crate::populate_steps::job_id(&self.selected))
        {
            let meter = run.to_meter();
            facett_jobview::step_meter_ui(ui, &run.id, &meter);
        }

        // A producer race is a bug in dwarves, not a status of the populate — so it is
        // said in words, in facett's warm red, next to the red segment. Before the early
        // return below, because a fault is exactly the case where the poll may have
        // stopped landing.
        if let Some(fault) = &self.step_fault {
            ui.colored_label(facett_core::look::RED, fault);
        }

        let Some(p) = &self.progress else { return };
        // Nothing running and nothing populated ⇒ nothing more to say.
        if !p.running && p.repos_total == 0 {
            return;
        }

        ui.horizontal(|ui| {
            // The CLONE phase's own fraction still gets a number beside the bar, because
            // "12/19" is the thing a person actually wants off a clone. It is the same
            // measured pair the step segment fills from — one source, two renderings of
            // it is exactly what this pane used to get wrong.
            if let Some(f) = p.fraction() {
                ui.label(format!("clone {:.0}%", f * 100.0));
            }
            ui.label(p.counters_line())
                .on_hover_text(nornir_warehouse_embed::BYTES_UNAVAILABLE_WHY);
        });

        if p.running {
            ui.weak(format!(
                "{} member(s) fetching · this bar follows the SERVER, so it shows a populate \
                 started anywhere — nornir's poll loop, another client, or the CLI",
                p.repos_fetching
            ));
        }
        if p.repos_failed > 0 {
            ui.colored_label(
                egui::Color32::from_rgb(0xd9, 0x53, 0x4f),
                format!(
                    "{} member(s) failed{}{}",
                    p.repos_failed,
                    if p.failing_member.is_empty() {
                        String::new()
                    } else {
                        format!(" — first: {}", p.failing_member)
                    },
                    if p.last_error.is_empty() {
                        String::new()
                    } else {
                        format!(": {}", p.last_error)
                    },
                ),
            );
        }
    }

    /// Drop a warehouse from the picker (after a landed Remove) and deselect if it was
    /// the selection. Returns `true` iff it had been registered here.
    fn forget(&mut self, name: &str) -> bool {
        let Some(i) = self.warehouses.iter().position(|w| w == name) else {
            return false;
        };
        self.warehouses.remove(i);
        if self.selected == name {
            self.selected.clear();
        }
        true
    }

    /// Render the picker (a `ComboBox`, nornir-style). Returns `Some(new_selection)`
    /// **iff the selection changed this frame** (so the host re-binds every tab to it);
    /// `None` when unchanged.
    pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<String> {
        let ctx = ui.ctx().clone();

        // ── land any finished background RPC BEFORE drawing ─────────────────────
        // A landed REGISTER folds the new warehouse into the picker and selects it, so
        // this frame already draws it as an option AND as the Populate target. (Doing
        // this after the draw is what made the old form flicker: the state changed
        // behind widgets that had already been laid out.)
        // …including a finished REGISTRY refresh, so a warehouse somebody else
        // registered is already an option on the frame that draws the open popup.
        self.land_registry();
        let mut committed: Option<String> = None;
        let mut removed: Option<String> = None;
        match self.poll_job() {
            // An ADD folds in ONLY if it actually produced a warehouse. A register
            // whose member clone failed must not appear in the picker as a warehouse —
            // that is drawing a failure as a success, and it is the exact shape that
            // printed `✓ populated now (eager register)` over an empty `git/`.
            Some((WarehouseVerb::Add, _)) => {
                if self.last_add_succeeded() {
                    if let Some(name) = self.commit_added() {
                        // The add is WHOLE — registered and populated — so there is no
                        // next step and the form can close.
                        self.adding = false;
                        committed = Some(name);
                    }
                } else {
                    // Keep the form open with the failure on screen: the user has
                    // something to fix (a deploy key, a URL), not a button to press.
                    self.adding = true;
                }
            }
            Some((WarehouseVerb::Remove, name)) => {
                // Only drop it from the picker if the RPC said Ok — a failed Remove
                // leaves the warehouse where it is.
                let ok = self
                    .add
                    .as_ref()
                    .and_then(|a| a.last_removed.clone())
                    .is_some_and(|n| n == name);
                if ok && self.forget(&name) {
                    removed = Some(name);
                }
            }
            // A landed REFRESH changes nothing here, and that is the correct
            // behaviour rather than a gap: `Workspaces.Repopulate` touches no
            // registry row, so no warehouse folds in and none drops out. Its whole
            // outcome — the queued job ids, or nornir's named refusal — is the
            // status line `poll_job` already wrote from the worker's form.
            Some((WarehouseVerb::Repopulate, _)) => {}
            _ => {}
        }
        if self.job.is_some() {
            ctx.request_repaint();
        }

        let before = self.selected.clone();
        let mut chosen = self.selected.clone();
        let label = if chosen.is_empty() {
            "⚠ none — pick one".to_string()
        } else {
            chosen.clone()
        };
        ui.label("Warehouse:");
        let combo = egui::ComboBox::from_id_salt("dwarves_warehouse_picker")
            .selected_text(label)
            .show_ui(ui, |ui| {
                // A leading "none" option so the user can deselect.
                ui.selectable_value(&mut chosen, String::new(), "⚠ none");
                for w in &self.warehouses {
                    ui.selectable_value(&mut chosen, w.clone(), w);
                }
            });

        // ── REFRESH ON OPEN ─────────────────────────────────────────────────────
        // `show_ui` yields `Some` only while the popup is up, so this is a real
        // closed→open EDGE, not a per-frame poll: opening the picker asks the server
        // what is registered, once. It is the only interaction whose whole purpose is
        // to show the registry, so it is where the list stops being a photograph taken
        // at process start. The answer lands on a later frame (`land_registry`), which
        // is why the popup never waits on a socket.
        let popup_open = combo.inner.is_some();
        if popup_open && !self.picker_open {
            self.refresh_registry(&ctx);
        }
        self.picker_open = popup_open;

        if self.add.is_some() {
            // ── NO Re-sync button (owner 2026-08-25: SCAN is not a user action) ──
            //
            // Re-sync was `Workspaces.Fetch { force: false }` — a populate. populate is
            // precisely the thing the whole architecture says happens BY ITSELF: nornir's
            // own poll sweep (`spawn_server_poll_loop` → `republish_served`) fetches each
            // monitored workspace's git members and republishes on change, which is what
            // moves the generation and submits the feed + lens chains. A button here only
            // let an operator ask for work the server already does on the same trigger,
            // and it taught that a warehouse is something you refresh by hand.
            //
            // What remains reachable, deliberately: `➕ Add…` still registers AND
            // populates as ONE whole action (a new warehouse has no poll history to ride
            // on), and `🗑 Remove…` still destroys. Both are INTENTS, not data requests.

            // ── Add warehouse (SSH): the WHOLE action ──────────────────────────
            let toggle = if self.adding {
                "✖ cancel"
            } else {
                "➕ Add…"
            };
            if ui.button(toggle).clicked() {
                self.adding = !self.adding;
            }

            // ── 🧹 Repopulate: THE ONE REFRESH ────────────────────────────────────
            //
            // This is the button that the deleted per-pane Scan buttons were NOT.
            // Scan asked, N times over, for work nornir already schedules by itself
            // — a landed snapshot submits one jera chain job per lens, deduped by key
            // and superseded by generation. What no button could ask for was the one
            // thing auto-population cannot do on its own: *throw the derived answers
            // away and compute them again*. `Workspaces.Repopulate { purge: true }`
            // is that verb, and this is its ONE door. It does not come back as a Scan
            // button per pane, and it never will: a pane is a READER.
            //
            // It shows what happened by NAME — the jera job ids the chain queued, so
            // the operator can watch them in the Jobs panel; or nornir's own refusal
            // sentence, which names the in-flight job id when the populate gate is
            // held or when the chain asked for is already the chain running.
            let can_repopulate = self.can_repopulate();
            if ui
                .add_enabled(can_repopulate, egui::Button::new("🧹 Repopulate"))
                .on_hover_text(
                    "Repopulate the SELECTED warehouse: make its code graph current — drop the \
                     orphaned v1 tables still in it (the code room's lake is never touched) and \
                     start a consume for every member behind its offset. Shows which members, \
                     or nornir's named refusal when nothing can consume them.",
                )
                .on_disabled_hover_text("pick a warehouse first")
                .clicked()
            {
                let name = self.selected.clone();
                let what = format!("repopulating `{name}` (Workspaces.Repopulate --purge)…");
                let target = name.clone();
                self.spawn_job(
                    &ctx,
                    what,
                    WarehouseVerb::Repopulate,
                    &target,
                    move |form| {
                        // PURGE is the point of the button: a Repopulate that only
                        // re-enqueued would be answered by the chain's own short-circuit
                        // ("already computed at this generation") and change nothing an
                        // operator can see. The refusal/status the RPC returns is written
                        // into `form.status` by `repopulate_named` either way.
                        let _ = form.repopulate_named(&name, true);
                    },
                );
            }

            // ── Remove: the OTHER whole action ─────────────────────────────────
            // Two-step, because it destroys data: the first click arms it and states
            // exactly what goes and what does not.
            let can_remove = self.can_remove();
            if ui
                .add_enabled(can_remove, egui::Button::new("🗑 Remove…"))
                .on_hover_text("clear the SELECTED warehouse: its registry entry AND its on-disk warehouse (warehouse + catalog.redb + jobs.redb). ssh keys and tokens are never touched.")
                .on_disabled_hover_text("pick a warehouse first")
                .clicked()
            {
                self.confirm_remove = Some(self.selected.clone());
            }
            if self.job.is_some() || !self.has_selection() {
                self.confirm_remove = None;
            }

            if self.adding {
                self.add_form_ui(ui, &ctx);
            }
            if let Some(victim) = self.confirm_remove.clone() {
                self.remove_confirm_ui(ui, &ctx, &victim);
            }

            // ── the populate progress bar ───────────────────────────────────────
            // Driven every frame and polled on its own cadence, so it is on screen for
            // ANY populate — not only one this process started.
            self.drive_progress(&ctx);
            self.progress_ui(ui);

            // ── the status line belongs to the PICKER, not the form ─────────────
            // It outlives the form's close, so the add's verdict is still on screen
            // afterwards.
            // Set by the unexplainable-add branch below: an add that cannot say why
            // it failed AUTO-asks the RPC that can, instead of leaving the operator to
            // go and run a CLI verb the pane just told them about.
            let mut want_events = false;
            if let Some(job) = &self.job {
                ui.horizontal(|ui| {
                    ui.spinner();
                    ui.label(&job.what);
                });
            } else if let Some(f) = &self.add {
                if !f.status.is_empty() {
                    // A failed action is drawn as a failure, in the failure colour.
                    if f.status.starts_with("error:") {
                        ui.colored_label(egui::Color32::from_rgb(0xd9, 0x53, 0x4f), &f.status);
                    } else {
                        ui.label(&f.status);
                    }
                }
                // WHEN THIS ACTION'S REPLY CANNOT SAY WHY, SAY THAT — and point at the
                // RPC that can, instead of leaving an unexplained failure. Until
                // 2026-08-27 this text asserted the RPC "answers Unimplemented", which
                // was measured FALSE: nornir served it and the forge's Viz proxy did not
                // forward it. The proxy now does; the pane does not yet fetch the rows.
                if f.last_add.as_ref().is_some_and(|a| a.cause_unknown) {
                    ui.colored_label(
                        egui::Color32::from_rgb(0xe0, 0xa8, 0x00),
                        format!(
                            "no cause in this reply: the populate returned neither a snapshot nor \
                             an error. The per-member outcome is in `clone_events` — `{}` \
                             (the RPC behind `nornir workspace events <name>`) answers it, and \
                             the rows are below.",
                            nornir_warehouse_embed::MISSING_DIAGNOSTIC_RPC,
                        ),
                    );
                    want_events = true;
                }
            }

            // ── the per-member populate outcomes ────────────────────────────────
            // Naming the RPC is not answering the question. This FETCHES it.
            self.clone_events_ui(ui, &ctx, want_events);
        }

        if let Some(name) = removed {
            // The removed warehouse was (possibly) the selection; the host must re-bind
            // every tab to whatever the selection is NOW, which may be none.
            let _ = name;
            return Some(self.selected.clone());
        }

        if chosen != before {
            // Route through `select` so an unknown name can never be forced in.
            if chosen.is_empty() {
                self.select("");
            } else {
                self.select(&chosen);
            }
            Some(self.selected.clone())
        } else {
            // A freshly registered warehouse becomes the new selection.
            committed
        }
    }

    /// **The per-member populate outcomes — fetched, not merely named.**
    ///
    /// `clone_events` is the ONE answer to "why did member X fail to populate":
    /// per member, `ok`/`error`, the error chain, and when. Until now this pane
    /// only printed the RPC's NAME, which is an answer nobody on the `:7900` road
    /// could act on — dwarves-ui on t14s can reach the forge and not nornir, so
    /// "go and ask `Viz.CloneEvents`" was a dead end for exactly the operator who
    /// needed it. The forge's `Viz` proxy now forwards the verb
    /// (`dwarves::warehouse_proxy`) and [`nornir_warehouse_embed::viz::clone_events`]
    /// speaks it, so the rows come here.
    ///
    /// `auto` = the caller has a reason to want them now (an add that could not
    /// explain itself). Otherwise there is an explicit control, because reading a
    /// populate ledger on every frame is not free.
    ///
    /// **A failed READ is painted as a failure.** An unreachable server, an
    /// `Unauthenticated`, a non-array body — each is named on screen. Drawing any of
    /// them as "no rows" would be the same silence in a new place.
    fn clone_events_ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context, auto: bool) {
        // Land a finished read first.
        if let Some(rx) = &self.clone_events_rx {
            match rx.try_recv() {
                Ok(res) => {
                    self.clone_events = Some(res);
                    self.clone_events_reads = self.clone_events_reads.wrapping_add(1);
                    self.clone_events_rx = None;
                }
                Err(std::sync::mpsc::TryRecvError::Empty) => {}
                Err(std::sync::mpsc::TryRecvError::Disconnected) => self.clone_events_rx = None,
            }
        }
        // A selection change invalidates rows that belong to another warehouse.
        if self.clone_events_ws != self.selected {
            self.clone_events = None;
            self.clone_events_ws = self.selected.clone();
        }
        let (Some(form), false) = (self.add.as_ref(), self.selected.is_empty()) else {
            return;
        };
        let (endpoint, token, ws) = (
            form.endpoint.clone(),
            form.token.clone(),
            self.selected.clone(),
        );

        let mut go = auto && self.clone_events.is_none();
        ui.horizontal(|ui| {
            let busy = self.clone_events_rx.is_some();
            if ui
                .add_enabled(!busy, egui::Button::new("why did a member fail?"))
                .on_hover_text(format!(
                    "read `{}` — the per-member populate outcome (ok / error + the error \
                     chain + when) for `{ws}`",
                    nornir_warehouse_embed::MISSING_DIAGNOSTIC_RPC,
                ))
                .clicked()
            {
                go = true;
            }
            if busy {
                ui.spinner();
            }
        });

        if go && self.clone_events_rx.is_none() {
            let (tx, rx) = std::sync::mpsc::channel();
            let ctx2 = ctx.clone();
            // ONE named worker doing blocking gRPC off the UI thread.
            let spawned = std::thread::Builder::new()
                .name("dwarves-clone-events".to_string())
                .spawn(move || {
                    let res = nornir_warehouse_embed::viz::clone_events(&endpoint, &token, &ws)
                        .map_err(|e| e.to_string());
                    let _ = tx.send(res);
                    ctx2.request_repaint();
                });
            match spawned {
                Ok(_) => self.clone_events_rx = Some(rx),
                // A thread that could not be spawned is a NAMED failure, not silence.
                Err(e) => {
                    self.clone_events =
                        Some(Err(format!("could not start the clone_events read: {e}")));
                    self.clone_events_reads = self.clone_events_reads.wrapping_add(1);
                }
            }
        }

        match &self.clone_events {
            None => {}
            Some(Err(why)) => {
                ui.colored_label(
                    egui::Color32::from_rgb(0xd9, 0x53, 0x4f),
                    format!(
                        "{} failed: {why}",
                        nornir_warehouse_embed::MISSING_DIAGNOSTIC_RPC
                    ),
                );
            }
            Some(Ok(rows)) if rows.is_empty() => {
                ui.label(format!(
                    "{} answered with no rows for `{}` — nothing has been cloned or fetched \
                     under this warehouse yet.",
                    nornir_warehouse_embed::MISSING_DIAGNOSTIC_RPC,
                    self.clone_events_ws,
                ));
            }
            Some(Ok(rows)) => {
                ui.label(format!(
                    "{} · {} row(s) for `{}` (newest first)",
                    nornir_warehouse_embed::MISSING_DIAGNOSTIC_RPC,
                    rows.len(),
                    self.clone_events_ws,
                ));
                egui::ScrollArea::vertical()
                    .id_salt("warehouse_clone_events")
                    .max_height(180.0)
                    .show(ui, |ui| {
                        egui::Grid::new("warehouse_clone_events_grid")
                            .num_columns(4)
                            .striped(true)
                            .spacing([12.0, 2.0])
                            .show(ui, |ui| {
                                for r in rows.iter().take(200) {
                                    let member = row_str(r, "member");
                                    let status = row_str(r, "status");
                                    let detail = row_str(r, "detail");
                                    let ms =
                                        r.get("elapsed_ms").and_then(|v| v.as_i64()).unwrap_or(0);
                                    // The verdict keeps its colour: a red row must
                                    // read as red, not as one more grey line.
                                    if status == "error" {
                                        ui.colored_label(
                                            egui::Color32::from_rgb(0xd9, 0x53, 0x4f),
                                            format!("✗ {member}"),
                                        );
                                    } else {
                                        ui.label(format!("✓ {member}"));
                                    }
                                    ui.label(&status);
                                    ui.label(format!("{ms} ms"));
                                    // The error chain is the payload — never elided to
                                    // a status word.
                                    ui.label(detail);
                                    ui.end_row();
                                }
                            });
                    });
            }
        }
    }

    /// The inline Add-warehouse form (name + SSH remote → Add).
    ///
    /// **Add is a WHOLE action**: it registers *and* populates. There is no second
    /// button that finishes it, and no state in which the warehouse is "added but not
    /// yet populated" — if the populate did not happen, the add failed and says so.
    fn add_form_ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
        let busy = self.job.is_some();
        let Some(form) = self.add.as_mut() else {
            return;
        };
        let mut go = false;
        ui.horizontal(|ui| {
            // Add leftmost, then the ssh url (the field you actually type), then the
            // name — pre-filled from the url (`workspace_nordisk` → `nordisk`) via the
            // form's `set_ssh_url`, so it usually needs no typing at all.
            let can = form.can_add() && !busy;
            go = ui
                .add_enabled(can, egui::Button::new("Add"))
                .on_hover_text("register AND populate this SSH remote as a warehouse, as one action — nornir clones the members and builds the warehouse. If a member clone fails, the add fails.")
                .clicked();
            ui.label("ssh:");
            let mut ssh = form.ssh_url.clone();
            if ui
                .add(
                    egui::TextEdit::singleline(&mut ssh)
                        .hint_text("git@host:owner/repo.git")
                        .desired_width(320.0),
                )
                .changed()
            {
                form.set_ssh_url(&ssh);
            }
            ui.label("name:");
            let mut name = form.name.clone();
            if ui.add(egui::TextEdit::singleline(&mut name).desired_width(110.0)).changed() {
                form.set_name(&name);
            }
        });
        ui.weak("Add registers and populates in one action — there is no separate populate step.");
        if go {
            let name = form.name.trim().to_string();
            let what = format!("adding `{name}` — registering + populating …");
            self.spawn_job(ctx, what, WarehouseVerb::Add, &name, move |f| {
                // Best-effort: on failure the status line shows the error; no panic.
                // `add()` returns Err when there is no snapshot at the end, and it has
                // already written the honest verdict into `status`.
                let _ = f.add();
            });
        }
    }

    /// The Remove confirmation — armed by `🗑 Remove…`, and explicit about the blast
    /// radius in BOTH directions: what it clears, and what it provably does not.
    fn remove_confirm_ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context, victim: &str) {
        let busy = self.job.is_some();
        let mut go = false;
        let mut cancel = false;
        ui.group(|ui| {
            ui.colored_label(
                egui::Color32::from_rgb(0xd9, 0x53, 0x4f),
                format!("Remove `{victim}` — this clears data."),
            );
            ui.label(
                "Goes: the registry entry AND the workspace's on-disk warehouse \
                 (warehouse + catalog.redb + jobs.redb).",
            );
            ui.label(
                "Stays: your ssh keys and your tokens. This is a workspace-data RPC on \
                 the server; it has no field that names a credential, and the dwarves \
                 key and vault live on this machine, which it never touches.",
            );
            ui.horizontal(|ui| {
                go = ui
                    .add_enabled(
                        !busy,
                        egui::Button::new(format!("🗑 Remove `{victim}` for real")),
                    )
                    .clicked();
                cancel = ui.button("✖ keep it").clicked();
            });
        });
        if cancel {
            self.confirm_remove = None;
        }
        if go {
            let name = victim.to_string();
            let target = name.clone();
            self.confirm_remove = None;
            self.spawn_job(
                ctx,
                format!("removing `{name}` — clearing its warehouse …"),
                WarehouseVerb::Remove,
                &target,
                move |f| {
                    let _ = f.remove_named(&name);
                },
            );
        }
    }

    /// The picker AS DATA (the robot oracle): mirrors nornir's `workspace_picker`
    /// slice, plus the `release_target` the release/write binding reads.
    pub fn state_json(&self) -> serde_json::Value {
        serde_json::json!({
            "kind": "warehouse-select",
            "selected_warehouse": self.selected,
            "has_selection": self.has_selection(),
            "warehouses": self.warehouses,
            "count": self.warehouses.len(),
            "release_target": self.release_target(),
            "adding": self.adding,
            "can_add_warehouses": self.add.is_some(),
            // Populate binds to the SELECTION, not to "an Add just happened" — so this
            // stays true across frames for as long as a warehouse is picked.
            "can_populate": self.can_populate(),
            "populate_target": if self.add.is_some() { self.selected.clone() } else { String::new() },
            // Remove is the OTHER whole action; `confirming_remove` names the warehouse
            // a click has ARMED but not yet performed (it destroys data, so it is two
            // steps), and is `null` when nothing is armed.
            "can_remove": self.can_remove(),
            "confirming_remove": self.confirm_remove,
            // THE ONE REFRESH, made observable headlessly. `last_repopulate` is the
            // whole outcome — the job ids the chain queued and the tables the purge
            // dropped, or nornir's NAMED refusal. A drive that could only see
            // "a button exists" could not tell a Refresh that queued thirteen jobs
            // from one that was refused, which is the only thing worth asserting.
            "can_repopulate": self.can_repopulate(),
            "last_repopulate": self
                .add
                .as_ref()
                .and_then(|a| a.last_repopulate.as_ref())
                .map(|r| serde_json::json!({
                    "workspace": r.workspace,
                    "purged": r.purged,
                    "dropped": r.dropped,
                    "generation": r.generation,
                    "queued": r.queued,
                    "in_flight": r.in_flight,
                    "refusal": r.refusal,
                    "is_refusal": r.is_refusal(),
                    "line": r.describe(),
                })),
            "busy": self.job.is_some(),
            "busy_what": self.job.as_ref().map(|j| j.what.clone()).unwrap_or_default(),
            // Opening the picker re-reads the registry (`Workspaces.List`). This counter
            // is what makes that observable headlessly: "the list looks the same" and
            // "nothing was asked" are otherwise the same picture.
            "registry_refreshes": self.registry_refreshes,
            "registry_refreshing": self.registry_rx.is_some(),
            // The `clone_events` READ, made observable headlessly. `reads` moving is
            // the only way a drive can tell "the pane fetched the rows" from "the pane
            // named the RPC" — which were the same picture until 2026-08-27, and the
            // reason a green test could sit on top of a pane that answered nothing.
            "clone_events_reads": self.clone_events_reads,
            "clone_events_fetching": self.clone_events_rx.is_some(),
            "clone_events": match &self.clone_events {
                None => serde_json::Value::Null,
                Some(Err(why)) => serde_json::json!({ "error": why }),
                Some(Ok(rows)) => serde_json::json!({
                    "workspace": self.clone_events_ws,
                    "rows": rows.len(),
                    "failed": rows.iter().filter(|r| row_str(r, "status") == "error").count(),
                    "members": rows.iter().map(|r| row_str(r, "member")).collect::<Vec<_>>(),
                }),
            },
            "add": self.add.as_ref().map(|a| a.state_json()),
            // The live populate progress of the SELECTED warehouse — present for a
            // populate started ANYWHERE, not only by this process. `null` before the
            // first poll answers. `fraction` is null while the total is unknown, which
            // is what makes the bar indeterminate rather than parked at 0%.
            "progress": self.progress.as_ref().map(|p| serde_json::json!({
                "workspace": p.workspace,
                "running": p.running,
                "repos_total": p.repos_total,
                "repos_done": p.repos_done,
                "repos_fetching": p.repos_fetching,
                "repos_failed": p.repos_failed,
                "fraction": p.fraction(),
                "crates": p.crates,
                "bytes": p.bytes,
                "counters": p.counters_line(),
                "verdict": p.verdict,
                "last_synced": p.last_synced,
                "failing_member": p.failing_member,
                "last_error": p.last_error,
            })),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Emit this pane's coverage-board cell (reuse edda's testmatrix — never a local
    /// harness). The warehouse SELECTOR is the shell's header affordance every
    /// warehouse-bound tab re-binds off, so it owes the board a row.
    fn cell(name: &str, ok: bool, detail: &str) {
        #[cfg(feature = "testmatrix")]
        nornir_testmatrix::functional_status("dwarves-gui", name, ok, detail);
        let _ = (name, ok, detail);
    }

    /// The exact sentence the live server sent the owner on 2026-08-22.
    const SERVER_REFUSAL: &str = "error: Workspaces.Register RPC: status: Unauthenticated, \
         message: \"The request does not have valid authentication credentials\"";

    /// A stand-in environment. Injected rather than `set_var`, which is unsound across
    /// test threads and would make this guard flaky — a flaky guard is a guard nobody
    /// believes, which is the same as not having one.
    fn env_of<'a>(pairs: &'a [(&'a str, &'a str)]) -> impl Fn(&str) -> Option<String> + 'a {
        move |k: &str| {
            pairs
                .iter()
                .find(|(n, _)| *n == k)
                .map(|(_, v)| v.to_string())
        }
    }

    /// **The four auth situations must not read alike.**
    ///
    /// MEASURED live 2026-08-22: the Add-warehouse form reported the server's generic
    /// `The request does not have valid authentication credentials` when the real
    /// cause was that the unit had been launched with `DWARVES_TOKEN` instead of
    /// `DWARVES_SERVER_TOKEN`, so the client sent NO credential at all. The server's
    /// sentence is identical for a missing token, a wrong token and a revoked one, so
    /// the operator hunts a bad secret while the truth is that none was ever supplied.
    ///
    /// Each case asserts on the WORDS the operator would read — the short name by
    /// name, the correct name by name, the count of characters actually sent. A test
    /// that only asserted `status.contains("error")` would pass on every one of these
    /// and on the broken shape too.
    #[test]
    fn a_missing_token_and_a_rejected_token_do_not_report_alike() {
        // ── 1. NO token, and a lookalike short name IS set: the live incident. ──
        let e = env_of(&[("DWARVES_TOKEN", "115580c8")]);
        let pre =
            credential_refusal("", &e).expect("the misspelling must be refused BEFORE the RPC");
        assert!(
            pre.contains("DWARVES_TOKEN IS set"),
            "it must name the wrong variable: {pre}"
        );
        assert!(
            pre.contains("DWARVES_SERVER_TOKEN"),
            "…and the right one: {pre}"
        );
        assert!(
            pre.contains("no request was made"),
            "…and say nothing was sent: {pre}"
        );

        // The classification goes through the PURE half, with the credential handed in:
        // `credential_for` walks the real chain, and on oden `~/.dwarves/token` exists
        // and holds a secret, so the wrapper would measure this box's configuration
        // instead of the four sentences.
        let none = dwarves_embed::ClientCredential {
            token: String::new(),
            whence: dwarves_embed::TokenSource::Nothing,
        };
        let after = explain_auth_failure_with(SERVER_REFUSAL, &none, &e)
            .expect("an auth refusal is classified");
        assert!(after.contains("NO CREDENTIAL"), "{after}");
        assert!(
            after.contains("EMPTY bearer"),
            "the cause is the empty bearer, not a bad token: {after}"
        );
        assert!(after.contains("DWARVES_TOKEN IS set"), "{after}");
        assert!(
            after.contains("server said:"),
            "the server's own words are kept, not replaced: {after}"
        );

        // ── 2. NO token and NOTHING set: ambiguous, so it must NOT block. ───────
        // An empty bearer is legitimate — `lib.rs` documents "Empty ⇒ open (the host's
        // dev default)" and this pane's own gRPC tests drive a mock that wants none.
        // Refusing here would kill a working dev shell to fix a typo.
        let bare = env_of(&[]);
        assert_eq!(
            credential_refusal("", &bare),
            None,
            "a blanket refusal of every empty token would break the open-server default"
        );
        let after = explain_auth_failure_with(SERVER_REFUSAL, &none, &bare)
            .expect("still classified once refused");
        assert!(after.contains("NO CREDENTIAL"), "{after}");
        // ★ THE FIX: the message names the WHOLE chain, and the file rung is offered as
        // something the operator can USE. It used to say "No ~/.nornir/token fallback
        // exists here", which was the wrong rule stated as a fact — a dwarves endpoint
        // resolves a dwarves credential, and `~/.dwarves/token` is a rung.
        assert!(after.contains("$DWARVES_SERVER_TOKEN"), "{after}");
        assert!(
            after.contains(".dwarves/token"),
            "the FILE rung must be named: {after}"
        );
        assert!(
            after.contains("supported rung") && after.contains("dwarves-server reads"),
            "…and named as usable, not denied: {after}"
        );
        assert!(
            !after.contains(".nornir/token"),
            "and nornir's credential is still not this client's: {after}"
        );

        // ── 3. A token WAS sent and the server refused it: a different verdict. ─
        let from_file = dwarves_embed::ClientCredential {
            token: "deadbeefdeadbeef".to_string(),
            whence: dwarves_embed::TokenSource::File("/home/x/.dwarves/token".into()),
        };
        let rejected = explain_auth_failure_with(SERVER_REFUSAL, &from_file, &bare)
            .expect("a rejected token is still an auth failure");
        assert!(rejected.contains("TOKEN REJECTED"), "{rejected}");
        assert!(
            rejected.contains("16 chars"),
            "it must say what it sent: {rejected}"
        );
        // …and WHICH rung produced the rejected one: a stale token file and a stale
        // environment are different mistakes, and the old message said "$VAR" for both.
        assert!(
            rejected.contains("/home/x/.dwarves/token"),
            "the rung that produced the rejected credential must be named: {rejected}"
        );
        let from_env = dwarves_embed::ClientCredential {
            token: "deadbeefdeadbeef".to_string(),
            whence: dwarves_embed::TokenSource::Env(TOKEN_ENV.to_string()),
        };
        let rejected_env = explain_auth_failure_with(SERVER_REFUSAL, &from_env, &bare).unwrap();
        assert!(
            rejected_env.contains(&format!("${TOKEN_ENV}")),
            "{rejected_env}"
        );
        assert_ne!(
            rejected, rejected_env,
            "the same rejected secret from two different rungs must not read alike"
        );
        assert!(
            !rejected.contains("NO CREDENTIAL"),
            "a rejected token must NOT be reported as a missing one — that is the whole \
             distinction this guard exists for: {rejected}"
        );
        assert_eq!(
            credential_refusal("deadbeefdeadbeef", &e),
            None,
            "a client that HAS a token must never be blocked before the RPC"
        );

        // ── 4. A NON-auth failure keeps its own words, untouched. ───────────────
        let clone_failed =
            "error: member clone failed: git@forge.example:nordisk/nope.git — repository not found";
        assert_eq!(
            explain_auth_failure_with(clone_failed, &none, &e),
            None,
            "a clone failure must not be relabelled an auth problem — over-matching here \
             would hide every other cause behind a token story"
        );

        cell(
            "warehouse_auth_named",
            true,
            "a missing host token is named BY VARIABLE before/at the RPC (and the wrong \
             short spelling is named too) instead of surfacing the server's generic \
             'does not have valid authentication credentials', which reads identically \
             for a missing, wrong and revoked token; a token that WAS sent and refused \
             reports the rejection with the length sent; a non-auth failure is untouched",
        );
    }

    #[test]
    fn register_select_and_release_target_bind_to_one_selection() {
        let mut s = WarehouseSelector::new(vec!["nordisk".into()], "");
        // Nothing selected initially: the onboarding resting state, no invented default.
        assert!(!s.has_selection());
        assert_eq!(s.selected(), "");
        assert_eq!(
            s.release_target(),
            "",
            "no selection ⇒ release has no target"
        );

        // Register more warehouses (populate the registry).
        assert!(s.register("skade"));
        assert!(s.register("knut"));
        assert!(!s.register("skade"), "duplicate register is idempotent");
        assert_eq!(s.warehouses().len(), 3);

        // Select one → release_target follows the SELECTED warehouse (the bind).
        assert!(s.select("skade"));
        assert_eq!(s.selected(), "skade");
        assert_eq!(
            s.release_target(),
            "skade",
            "release binds to the selected warehouse"
        );

        // Selecting an UNKNOWN name is a no-op (never invents a selection).
        assert!(!s.select("does-not-exist"));
        assert_eq!(s.selected(), "skade");

        // Re-select a different one → both selection AND release_target move together.
        assert!(s.select("nordisk"));
        assert_eq!(s.release_target(), "nordisk");

        // Deselect → back to none.
        assert!(s.select(""));
        assert!(!s.has_selection());

        let st = s.state_json();
        let ok = st["selected_warehouse"] == "" && st["count"] == 3 && st["release_target"] == "";
        assert_eq!(st["selected_warehouse"], "");
        assert_eq!(st["count"], 3);
        assert_eq!(st["release_target"], "");

        cell(
            "warehouse_selector_registry_and_one_selection",
            ok,
            "the warehouse selector holds MANY registered warehouses but exactly ONE selection (nornir parity): register is idempotent, selecting an unknown name is a no-op, the release target follows the selection, and deselecting returns the honest empty onboarding state",
        );
    }

    /// **MODEL level only** — the fold-in arithmetic, with the register HAND-INJECTED.
    /// SSH validation gates `can_add`, and a `registered` record folds into the picker
    /// and becomes the selection. RED-when-broken: an HTTPS remote must never be
    /// addable (L6), and a registered warehouse must land in the picker + be selected
    /// (so tabs re-bind to it).
    ///
    /// It renders NOTHING and clicks NOTHING, so it can say nothing about whether the
    /// buttons exist or are hittable — that is
    /// [`robot_add_is_whole_and_resync_stays_hittable`]'s job, and it is
    /// what this test's prose used to claim while never touching a widget.
    #[test]
    fn add_warehouse_validates_ssh_and_folds_into_the_picker() {
        use nornir_warehouse_embed::RegisteredWorkspace;
        let mut s = WarehouseSelector::new(vec!["skade".into()], "skade")
            .with_add_source("http://127.0.0.1:0", "");
        assert_eq!(s.state_json()["can_add_warehouses"], true);

        // Drive the form: an HTTPS remote is rejected, an SSH one is accepted.
        let form = s.add_form_mut().expect("add source configured");
        form.update_json(
            r#"{"name":"nordisk","ssh_url":"https://forge.example/nordisk/dwarves.git"}"#,
        );
        assert_eq!(form.state_json()["ssh_valid"], false);
        assert!(!form.can_add(), "HTTPS ⇒ Add disabled (L6)");
        form.update_json(r#"{"ssh_url":"git@forge.example:nordisk/dwarves.git"}"#);
        assert!(form.can_add(), "valid SSH + name ⇒ Add enabled");

        // Nothing registered yet ⇒ commit is a no-op.
        assert!(s.commit_added().is_none());

        // Simulate a successful eager register (the server populated it).
        s.add_form_mut().unwrap().registered = Some(RegisteredWorkspace {
            name: "nordisk".into(),
            mode: "monitored".into(),
            current_snapshot: "snap-1".into(),
            members: vec!["dwarves".into()],
        });
        let committed = s
            .commit_added()
            .expect("newly registered warehouse folds in");
        assert_eq!(committed, "nordisk");
        assert!(
            s.warehouses().contains(&"nordisk".to_string()),
            "the added warehouse joins the picker"
        );
        assert_eq!(s.selected(), "nordisk", "and becomes the active selection");
        assert_eq!(
            s.release_target(),
            "nordisk",
            "release now targets the freshly-populated warehouse"
        );
        assert_eq!(s.state_json()["add"]["populated"], true);

        cell(
            "warehouse_selector_add_ssh_validates_and_folds_in",
            s.warehouses().contains(&"nordisk".to_string())
                && s.selected() == "nordisk"
                && s.state_json()["add"]["populated"] == true,
            "MODEL ONLY (no render, no click): SSH validation gates can_add (an HTTPS remote is rejected, L6) and a hand-injected registered record folds into the picker, becomes the active selection and re-targets release — button reachability is asserted separately by the kittest scenario",
        );
    }

    /// **RED-when-broken: an IDLE picker never writes to a warehouse by itself.**
    ///
    /// `Populate` drives `Workspaces.Fetch{force}` — nornir re-clones the members and
    /// rebuilds the warehouse. That is a WRITE against whichever server the shell was
    /// pointed at, so it must happen if and ONLY if a human clicks it. A self-firing
    /// button here would re-sync a live production warehouse the instant the UI opened.
    ///
    /// This renders the real widget for many frames with a warehouse already selected
    /// and **no input whatsoever**, then asserts the mock server was never asked to do
    /// anything: no `Fetch`, no `Register`, no job ever started.
    #[test]
    fn an_idle_picker_never_fires_a_warehouse_write_by_itself() {
        let srv = nornir_warehouse_embed::testkit::spawn_mock_workspaces();
        let sel = std::cell::RefCell::new(
            WarehouseSelector::new(vec!["nordisk".into()], "nordisk")
                .with_add_source(&srv.endpoint, ""),
        );
        let mut h = egui_kittest::Harness::builder()
            .with_size(egui::vec2(900.0, 420.0))
            .build_ui(|ui| {
                sel.borrow_mut().ui(ui);
            });

        // Many frames, zero events. Populate is enabled the whole time (a warehouse IS
        // selected) — being enabled must not make it fire.
        let mut ever_busy = false;
        for _ in 0..120 {
            h.step();
            ever_busy |= sel.borrow().is_busy();
        }

        let s = sel.borrow();
        assert_eq!(
            s.state_json()["can_populate"],
            true,
            "Re-sync is offered (a warehouse is selected)"
        );
        assert_eq!(s.state_json()["can_remove"], true, "Remove is offered too");
        assert!(!ever_busy, "…but NO job ever started without a click");
        assert!(
            srv.log.fetched().is_empty(),
            "no Workspaces.Fetch was sent: {:?}",
            srv.log.fetched()
        );
        assert!(
            srv.log.registered().is_empty(),
            "no Workspaces.Register was sent: {:?}",
            srv.log.registered()
        );
        // REMOVE is the destructive one — an idle picker must not arm it, let alone fire
        // it. `confirming_remove` stays null and nothing reaches the wire.
        assert!(
            srv.log.removed().is_empty(),
            "no Workspaces.Remove was sent: {:?}",
            srv.log.removed()
        );
        assert!(
            s.state_json()["confirming_remove"].is_null(),
            "Remove is not even ARMED without a click"
        );
        assert_eq!(
            s.state_json()["add"]["status"],
            "",
            "no status ⇒ no RPC was even attempted"
        );

        let ok = !ever_busy
            && srv.log.fetched().is_empty()
            && srv.log.registered().is_empty()
            && srv.log.removed().is_empty();
        cell(
            "warehouse_selector_idle_never_writes",
            ok,
            "an idle warehouse picker with a selection renders 120 real egui frames with no input and sends NOTHING to nornir — no Workspaces.Fetch, no Register, no Remove, no background job, and the destructive Remove is not even armed: these are warehouse WRITES and may only ever fire from a human click",
        );
    }

    /// **ROBOT SCENARIO (kittest render + real clicks + a real gRPC seam): `Add` is a
    /// WHOLE action, and `🔄 Re-sync` is a separate verb that stays hittable.**
    ///
    /// Two things are asserted here and they are different claims:
    ///
    /// 1. **Add registers AND populates.** The mock's `Register` deliberately returns
    ///    an EMPTY snapshot — the "recorded, not populated" state — so if `Add` were
    ///    only a register, the warehouse would end unpopulated and the user would have
    ///    to press something else to finish. It does not: one click drives a
    ///    `Workspaces.Register` *and* the `Workspaces.Fetch` that mints the snapshot,
    ///    and the picker reports `populated`.
    /// 2. **Re-sync is still there afterwards, and still hittable.** It is not a step
    ///    of Add (Add is finished); it is the operation on an EXISTING warehouse — one
    ///    added in an earlier session, or one whose remotes have moved. It used to live
    ///    inside the add form gated on `registered.is_some()`, painted for exactly one
    ///    frame under a pointer still on `Add`, so no user could click it.
    ///
    /// kittest routes a click through `accesskit::Action::Click`, which egui honours
    /// **only for enabled widgets**, so a missing, disabled or one-frame-flickering
    /// button produces no RPC and reds this.
    #[test]
    fn robot_add_is_whole_and_no_resync_control_remains() {
        use egui_kittest::kittest::Queryable;
        use egui_kittest::Harness;

        // A REAL nornir `Workspaces` server (mock): Register records the workspace with
        // NO snapshot, Fetch is what mints one — so "did the add populate?" is
        // answerable on the wire rather than by trusting the register's return value.
        let srv = nornir_warehouse_embed::testkit::spawn_mock_workspaces();

        let sel = std::cell::RefCell::new(
            WarehouseSelector::new(vec![], "").with_add_source(&srv.endpoint, ""),
        );
        let mut h = Harness::builder()
            .with_size(egui::vec2(900.0, 620.0))
            .build_ui(|ui| {
                sel.borrow_mut().ui(ui);
            });

        // Pump frames until `done` or we give up — never `run()`, which panics when a
        // frame keeps requesting repaints (the busy spinner does exactly that).
        let pump = |h: &mut Harness<'_>, done: &dyn Fn() -> bool| {
            for _ in 0..600 {
                h.step();
                if done() {
                    return true;
                }
                std::thread::sleep(std::time::Duration::from_millis(5));
            }
            false
        };

        // ── 1. open the add form by clicking the REAL toggle ────────────────────
        h.step();
        h.get_by_label("➕ Add…").click();
        h.step();
        assert!(
            sel.borrow().state_json()["adding"] == true,
            "the real ➕ Add… toggle opened the form"
        );

        // Type the name + SSH remote over the form's documented control seam (text
        // entry is not what is under test here; button reachability is).
        sel.borrow_mut()
            .add_form_mut()
            .expect("add source configured")
            .update_json(r#"{"name":"nordisk","ssh_url":"git@forge.example:nordisk/dwarves.git"}"#);
        h.step();

        // ── 2. ONE click on the REAL Add button drives the WHOLE action ─────────
        h.get_by_label("Add").click();
        h.step();
        let landed = pump(&mut h, &|| {
            !srv.log.fetched().is_empty() && !sel.borrow().is_busy()
        });
        assert!(
            landed,
            "the real Add button drove register + populate over gRPC to completion"
        );
        assert_eq!(
            srv.log.registered(),
            vec!["nordisk".to_string()],
            "Workspaces.Register hit the wire"
        );
        assert_eq!(
            srv.log.fetched(),
            vec!["nordisk".to_string()],
            "…and so did the populate, from the SAME click — Add is not half an action"
        );

        // The add folded in: the warehouse is a picker option AND the selection, it is
        // POPULATED, and the form is closed because nothing remains to be done.
        let add_is_whole = {
            let s = sel.borrow();
            let st = s.state_json();
            assert_eq!(
                st["selected_warehouse"], "nordisk",
                "the added warehouse became the selection"
            );
            assert_eq!(
                st["release_target"], "nordisk",
                "…and release re-targets it"
            );
            assert_eq!(
                st["adding"], false,
                "the add form closed — the add is finished"
            );
            assert_eq!(
                st["add"]["populated"], true,
                "ADDED means REGISTERED AND POPULATED"
            );
            assert_eq!(st["add"]["last_add"]["populated"], true);
            let status = st["add"]["status"].as_str().unwrap_or_default().to_string();
            assert!(
                status.starts_with("ok: added `nordisk`"),
                "the verdict is the whole action's: {status}"
            );
            assert!(
                status.contains("populated"),
                "…and it names the populate: {status}"
            );
            st["add"]["populated"] == true
        };

        // ── 3. There is NO re-sync control, and Add's populate is untouched ─────
        //
        // `🔄 Re-sync` was removed on 2026-08-25 (the owner: SCAN is not a user action).
        // A re-sync is `Workspaces.Fetch`, i.e. a populate, and nornir's own poll sweep
        // already fetches every monitored workspace and republishes when a member's SHA
        // moved — the same trigger, without an operator having to know to press
        // anything. What this leg proves is that removing it did NOT quietly take Add's
        // populate with it: exactly ONE fetch reached the wire, and it came from Add.
        h.step();
        h.step();
        assert!(
            h.query_by_label("🔄 Re-sync").is_none(),
            "there is no Re-sync control — populate is not a button any more"
        );
        assert!(
            h.query_by_label("🔄 Populate").is_none(),
            "…and there is no Populate button either — Add already populated"
        );
        assert!(
            h.query_all(
                egui_kittest::kittest::By::new()
                    .role(accesskit::Role::Button)
                    .label_contains("sync")
            )
            .next()
            .is_none(),
            "no pressable control offers a re-sync under any spelling"
        );

        // The WIRE is the proof, not the widget count: one fetch, from the Add.
        assert_eq!(
            srv.log.fetched(),
            vec!["nordisk".to_string()],
            "exactly ONE Workspaces.Fetch reached the wire — Add's. Removing the button \
             must not have removed the populate that makes Add whole."
        );

        let s = sel.borrow();
        let st = s.state_json();
        let status = st["add"]["status"].as_str().unwrap_or_default().to_string();
        assert!(
            status.starts_with("ok: added `nordisk`"),
            "the ADD outcome is reported on the picker, where it outlives the form: {status}"
        );
        assert_eq!(
            st["add"]["last_add"]["populated"], true,
            "the add really populated"
        );

        let ok = add_is_whole && srv.log.fetched().len() == 1;
        cell(
            "warehouse_selector_add_is_whole_and_no_resync_control",
            ok,
            "a kittest frame clicks the REAL ➕ Add… and Add buttons against a real Workspaces gRPC mock whose Register returns NO snapshot: that ONE click drives both Workspaces.Register and the Workspaces.Fetch that populates, and the picker reports populated=true — so adding a warehouse needs no follow-up press. The 🔄 Re-sync button is GONE (SCAN is not a user action; nornir's poll sweep owns re-populating), and the wire proves its removal did not take Add's populate with it: exactly one Fetch, from the Add",
        );
    }

    /// **RED-when-broken: a failed member clone FAILS the add.**
    ///
    /// The bug, verbatim from the session log: `workspace add` printed
    /// `✓ populated now (eager register)` over an empty `git/`. The descriptor fetch
    /// succeeded and the member clone failed, and the command reported green.
    ///
    /// Here the mock's `Fetch` reports a member clone failure and NO snapshot. The add
    /// must therefore: report `error:`, name the member failure, NOT report populated,
    /// and — the part that matters most — **not fold the warehouse into the picker**,
    /// because a warehouse that was not built is not a warehouse you can select.
    #[test]
    fn a_failed_member_clone_fails_the_add() {
        use nornir_warehouse_embed::testkit::{spawn_mock_workspaces_with, FetchBehaviour};
        let srv = spawn_mock_workspaces_with(FetchBehaviour::MemberCloneFails);

        let mut s = WarehouseSelector::new(vec![], "").with_add_source(&srv.endpoint, "");
        let form = s.add_form_mut().expect("add source configured");
        form.update_json(
            r#"{"name":"njord","ssh_url":"git@github.com:vetra-automation/Njord.git"}"#,
        );
        // Drive the SAME method the button drives (synchronously — the worker is not
        // what is under test).
        let outcome = form.add();

        assert!(
            outcome.is_err(),
            "a failed member clone must fail the action, not report green"
        );
        let st = s.state_json();
        let status = st["add"]["status"].as_str().unwrap_or_default().to_string();
        assert!(
            status.starts_with("error:"),
            "the status is an ERROR, not a ✓: {status}"
        );
        assert!(
            status.contains("NOT populated"),
            "…and says the warehouse was not built: {status}"
        );
        assert!(
            status.contains("Permission denied"),
            "…quoting the member's real failure: {status}"
        );
        assert_eq!(st["add"]["populated"], false, "registered ≠ populated");
        assert_eq!(
            st["add"]["cause_unknown"], false,
            "the cause WAS available — the fetch reported it"
        );

        // The register did happen on the wire (that leg succeeded) …
        assert_eq!(srv.log.registered(), vec!["njord".to_string()]);
        // … but the picker must not show it as an added warehouse.
        assert!(
            s.commit_added().is_none(),
            "a warehouse that was not populated must never fold into the picker"
        );
        assert!(
            !s.warehouses().contains(&"njord".to_string()),
            "…so it is not an option"
        );
        assert!(
            !s.has_selection(),
            "…and it certainly did not become the active selection"
        );

        cell(
            "warehouse_add_fails_when_a_member_clone_fails",
            outcome.is_err() && st["add"]["populated"] == false,
            "against a Workspaces mock whose Fetch reports a member clone failure and no snapshot, the whole-action Add returns Err and its status reads `error: ... registered but NOT populated - member: clone failed: Permission denied (publickey)` instead of the `✓ populated now (eager register)` the CLI printed over an empty git/ — the success condition is a warehouse snapshot, not an Ok from Register",
        );
    }

    /// **RED-when-broken: when this action's reply cannot say WHY, the pane says THAT —
    /// and names the RPC that can.**
    ///
    /// `nornir workspace events <name>` / `Viz.CloneEvents` is the way to see why a
    /// member did not populate. There is a real failure shape — populate returns neither
    /// a snapshot nor an error — where the ADD REPLY alone cannot explain itself. The
    /// pane must not invent a cause and must not draw the silence as success; it names
    /// the RPC that holds the per-member outcome.
    ///
    /// ⚠ 2026-08-27: this test used to also assert the word `Unimplemented`, because the
    /// pane claimed the RPC was unimplemented server-side. MEASURED false (nornir served
    /// it; the forge's `Viz` proxy did not forward it — now it does), so the assertion
    /// went with the claim. The load-bearing half is unchanged and still red-when-broken:
    /// the pane must NAME the RPC, in the model AND on screen.
    #[test]
    fn an_unexplainable_add_names_the_rpc_that_would_have_explained_it() {
        use egui_kittest::kittest::Queryable;
        use nornir_warehouse_embed::testkit::{spawn_mock_workspaces_with, FetchBehaviour};
        let srv = spawn_mock_workspaces_with(FetchBehaviour::SilentlyDoesNothing);

        let sel = std::cell::RefCell::new(
            WarehouseSelector::new(vec![], "").with_add_source(&srv.endpoint, ""),
        );
        {
            let mut s = sel.borrow_mut();
            let form = s.add_form_mut().expect("add source configured");
            form.update_json(r#"{"name":"njord","ssh_url":"git@github.com:nordisk/njord.git"}"#);
            assert!(form.add().is_err(), "no snapshot ⇒ the add failed");
        }

        let st = sel.borrow().state_json();
        assert_eq!(st["add"]["populated"], false);
        assert_eq!(
            st["add"]["cause_unknown"], true,
            "no error and no snapshot ⇒ the cause is unknown"
        );
        let status = st["add"]["status"].as_str().unwrap_or_default().to_string();
        assert!(
            status.contains("Viz.CloneEvents"),
            "the status names the diagnostic RPC: {status}"
        );
        // …and it must NOT claim the RPC is unimplemented: that was measured false on
        // 2026-08-27, and a stale attribution painted on screen is the defect this line
        // now guards against rather than the one it used to assert.
        assert!(
            !status.contains("Unimplemented"),
            "the status must not blame the server for a gap that was in the forge proxy: {status}"
        );

        // And it is PAINTED, not merely in the model.
        let mut h = egui_kittest::Harness::builder()
            .with_size(egui::vec2(1100.0, 700.0))
            .build_ui(|ui| {
                sel.borrow_mut().ui(ui);
            });
        h.step();
        h.step();
        let named_on_screen = h
            .query_all_by_label_contains("Viz.CloneEvents")
            .next()
            .is_some();
        assert!(
            named_on_screen,
            "the pane names the missing diagnostic RPC on screen"
        );

        cell(
            "warehouse_add_names_the_missing_diagnostic_rpc",
            named_on_screen,
            "when a populate returns neither a snapshot nor an error, the add REPLY cannot say why - the per-member outcome lives in clone_events, read by Viz.CloneEvents behind `nornir workspace events <name>`. The pane paints that, naming the RPC, instead of showing an unexplained failure or drawing the silence as a success. It also no longer claims the RPC is Unimplemented: measured 2026-08-27, nornir served it and the dwarves Viz proxy did not forward it, so the gap was ours and the on-screen sentence blamed the wrong product",
        );
    }

    /// **RED-when-broken: the pane FETCHES `Viz.CloneEvents` and paints the rows.**
    ///
    /// Naming an RPC is not answering the question it answers. Until 2026-08-27 this
    /// pane printed `Viz.CloneEvents` and stopped there, which is a dead end for the
    /// operator who needs it most: dwarves-ui on t14s reaches the forge (`:7900`) and
    /// not nornir, so "go ask that RPC" was advice they could not take.
    ///
    /// Drives the REAL pane against a mock `Viz` server, presses the real control, and
    /// asserts on the APPLIED output — the member names and the error chain painted in
    /// the grid — not that a flag flipped. `clone_events_reads` moving is what
    /// separates "the rows were fetched" from "the RPC was named", the two states that
    /// used to render identically.
    #[test]
    fn the_pane_fetches_clone_events_and_paints_the_per_member_outcome() {
        use egui_kittest::kittest::Queryable;
        use nornir_warehouse_embed::testkit::{spawn_mock_viz, MockViz};

        // Two members: one that populated, one that did not — with the error chain
        // that is the whole reason to read this table.
        let rows = r#"[
          {"ts_micros":1756312986000000,"workspace":"nordisk","member":"skade","remote":"ssh://git@forge.example/nordisk/skade.git","op":"clone-fetch","status":"error","detail":"gix::init /home/dwarves/.nornir/workspaces/nordisk/git/skade: File exists (os error 17)","elapsed_ms":13},
          {"ts_micros":1756312983000000,"workspace":"nordisk","member":"znippy","remote":"ssh://git@forge.example/nordisk/znippy.git","op":"clone-fetch","status":"ok","detail":"4dce050a","elapsed_ms":3021}
        ]"#;
        let mock = MockViz {
            clone_events: rows.to_string(),
            ..Default::default()
        };
        let seen_ws = mock.seen_workspaces.clone();
        let srv = spawn_mock_viz(mock);

        let sel = std::cell::RefCell::new(
            WarehouseSelector::new(vec!["nordisk".to_string()], "nordisk")
                .with_add_source(&srv.endpoint, ""),
        );
        assert_eq!(
            sel.borrow().state_json()["clone_events"],
            serde_json::Value::Null,
            "nothing is fetched before it is asked for",
        );

        let mut h = egui_kittest::Harness::builder()
            .with_size(egui::vec2(1200.0, 800.0))
            .build_ui(|ui| {
                sel.borrow_mut().ui(ui);
            });
        h.step();
        // Press the REAL control, by its real label.
        h.get_by_label_contains("why did a member fail?").click();
        h.step();

        // The read is on a worker thread; drive frames until it lands (or give up).
        let t0 = std::time::Instant::now();
        while sel.borrow().state_json()["clone_events_reads"]
            .as_u64()
            .unwrap_or(0)
            == 0
        {
            assert!(
                t0.elapsed() < std::time::Duration::from_secs(10),
                "the read never landed"
            );
            std::thread::sleep(std::time::Duration::from_millis(20));
            h.step();
        }
        h.step();

        let st = sel.borrow().state_json();
        assert_eq!(st["clone_events"]["rows"], 2, "both rows must arrive: {st}");
        assert_eq!(
            st["clone_events"]["failed"], 1,
            "the failed member must be countable: {st}"
        );
        assert_eq!(st["clone_events"]["workspace"], "nordisk");
        assert_eq!(
            st["clone_events"]["members"],
            serde_json::json!(["skade", "znippy"]),
            "the rows are the SERVER's, in the server's order",
        );

        // ── the APPLIED output: it is on the screen, not only in the model ──
        let painted_member = h.query_all_by_label_contains("skade").next().is_some();
        let painted_error = h
            .query_all_by_label_contains("File exists (os error 17)")
            .next()
            .is_some();
        assert!(painted_member, "the failing member is not on screen");
        assert!(
            painted_error,
            "the ERROR CHAIN is not on screen — a status word alone answers nothing"
        );

        // The client stamped the selected workspace rather than leaning on a default.
        let seen = seen_ws.lock().unwrap().clone();
        assert!(
            seen.iter().any(|w| w == "nordisk"),
            "the read must carry the selected workspace in the header: {seen:?}"
        );

        cell(
            "warehouse_pane_fetches_clone_events",
            painted_member && painted_error,
            "the pane no longer merely NAMES Viz.CloneEvents - it calls it and paints the per-member populate outcome, error chain included. That is the only answer to `why did member X fail to populate`, and before this it was unreachable for every client that can see :7900 but not nornir (dwarves-ui on t14s)",
        );
    }

    /// **RED-when-broken: a failed READ is painted as a failure, never as "no rows".**
    ///
    /// An unauthenticated call, an unreachable server and an empty ledger must be three
    /// distinguishable pictures. Collapsing the first two into the third is the same
    /// silence in a new place — and it is the failure mode this pane was built to break.
    #[test]
    fn a_refused_clone_events_read_is_named_not_drawn_as_an_empty_ledger() {
        use egui_kittest::kittest::Queryable;
        use nornir_warehouse_embed::testkit::{spawn_mock_viz, Code, MockViz};

        let srv = spawn_mock_viz(MockViz {
            refuse: Some((Code::Unauthenticated, "missing bearer token".into())),
            ..Default::default()
        });
        let sel = std::cell::RefCell::new(
            WarehouseSelector::new(vec!["nordisk".to_string()], "nordisk")
                .with_add_source(&srv.endpoint, ""),
        );
        let mut h = egui_kittest::Harness::builder()
            .with_size(egui::vec2(1200.0, 800.0))
            .build_ui(|ui| {
                sel.borrow_mut().ui(ui);
            });
        h.step();
        h.get_by_label_contains("why did a member fail?").click();
        h.step();
        let t0 = std::time::Instant::now();
        while sel.borrow().state_json()["clone_events_reads"]
            .as_u64()
            .unwrap_or(0)
            == 0
        {
            assert!(
                t0.elapsed() < std::time::Duration::from_secs(10),
                "the read never landed"
            );
            std::thread::sleep(std::time::Duration::from_millis(20));
            h.step();
        }
        h.step();

        let st = sel.borrow().state_json();
        assert!(
            st["clone_events"]["error"].is_string(),
            "a refused read must stay a failure in the model: {st}"
        );
        assert!(
            st["clone_events"]["rows"].is_null(),
            "a refused read must NOT present a row count: {st}"
        );
        let named = h.query_all_by_label_contains("failed:").next().is_some();
        assert!(named, "the failure is not painted");
    }

    /// **RED-when-broken: Remove is a WHOLE action — it clears the warehouse — and it
    /// never touches the identity.**
    ///
    /// Drives the real `🗑 Remove…` → confirm → `Workspaces.Remove` path and asserts on
    /// the wire that `purge = true` (the on-disk warehouse goes with the registry row,
    /// not just the row). The identity half is a structural claim, asserted as such:
    /// the whole request message this UI can send carries a name and a purge flag and
    /// nothing else, so there is no field through which a key or token could be
    /// destroyed.
    #[test]
    fn remove_clears_the_warehouse_and_can_never_touch_the_identity() {
        use egui_kittest::kittest::Queryable;
        let srv = nornir_warehouse_embed::testkit::spawn_mock_workspaces();
        // Seed the registry server-side so Remove has something to remove.
        nornir_warehouse_embed::add_warehouse_by_ssh(
            &srv.endpoint,
            "",
            "nordisk",
            "git@forge.example:nordisk/dwarves.git",
        )
        .expect("mock register");

        let sel = std::cell::RefCell::new(
            WarehouseSelector::new(vec!["nordisk".into()], "nordisk")
                .with_add_source(&srv.endpoint, ""),
        );
        let mut h = egui_kittest::Harness::builder()
            .with_size(egui::vec2(1100.0, 700.0))
            .build_ui(|ui| {
                sel.borrow_mut().ui(ui);
            });
        h.step();

        // ── one click ARMS it (it destroys data — it is not a one-click act) ────
        h.get_by_label("🗑 Remove…").click();
        h.step();
        assert_eq!(
            sel.borrow().state_json()["confirming_remove"],
            "nordisk",
            "the first click arms the confirmation, it does not remove"
        );
        assert!(
            srv.log.removed().is_empty(),
            "…and nothing has reached the wire yet"
        );
        // The confirmation states the blast radius in both directions.
        let says_what_stays = h
            .query_all_by_label_contains("ssh keys and your tokens")
            .next()
            .is_some();
        assert!(
            says_what_stays,
            "the confirmation says what is NOT destroyed, not only what is"
        );

        // ── the second click does it ───────────────────────────────────────────
        h.get_by_label("🗑 Remove `nordisk` for real").click();
        h.step();
        let mut done = false;
        for _ in 0..600 {
            h.step();
            if !srv.log.removed().is_empty() && !sel.borrow().is_busy() {
                done = true;
                break;
            }
            std::thread::sleep(std::time::Duration::from_millis(5));
        }
        assert!(
            done,
            "the confirmed Remove drove a real Workspaces.Remove to completion"
        );
        assert_eq!(
            srv.log.removed(),
            vec![("nordisk".to_string(), true)],
            "Remove is sent with purge=true — the on-disk warehouse goes with the registry row"
        );

        let s = sel.borrow();
        let st = s.state_json();
        assert!(
            !s.warehouses().contains(&"nordisk".to_string()),
            "…and it leaves the picker"
        );
        assert_eq!(
            st["selected_warehouse"], "",
            "the selection it held is released, not left dangling"
        );
        let status = st["add"]["status"].as_str().unwrap_or_default().to_string();
        assert!(status.starts_with("ok: removed `nordisk`"), "{status}");
        assert!(status.contains("ssh keys and tokens untouched"), "{status}");

        // THE IDENTITY CLAIM, structurally: the entire message this UI can send is
        // {name, purge}. There is no credential-shaped field on it to abuse.
        let req = nornir_warehouse_embed::pb::WorkspaceName {
            name: "nordisk".into(),
            purge: true,
        };
        let _exhaustive = match req {
            nornir_warehouse_embed::pb::WorkspaceName { name: _, purge: _ } => (),
        };

        cell(
            "warehouse_remove_is_whole_and_keeps_the_identity",
            srv.log.removed() == vec![("nordisk".to_string(), true)],
            "a kittest frame clicks the REAL 🗑 Remove... (which only ARMS a confirmation - nothing reaches the wire), reads the confirmation stating what is destroyed AND what is kept, then clicks the confirm and proves a Workspaces.Remove{purge:true} reached the server: the registry row and the on-disk warehouse (warehouse + catalog.redb + jobs.redb) go together, the warehouse leaves the picker and releases the selection. The identity half is structural - the whole request message is {name, purge}, exhaustively matched here, so no credential can ride it",
        );
    }

    /// **RED-when-broken: THE ONE REFRESH reaches the wire with `purge = true`, and a
    /// NAMED refusal is drawn as a refusal — never as a success with a zero in it.**
    ///
    /// The per-pane Scan buttons are gone and are not coming back; what replaced them
    /// is one button, here, driving `Workspaces.Repopulate`. So the assertions are the
    /// two things that make it that button rather than a re-sync in a new hat:
    ///
    /// 1. it sends `purge = true` — a Refresh that only re-enqueued would be answered
    ///    by the chain's own "already computed at this generation" short-circuit and
    ///    change nothing an operator can see;
    /// 2. when the server refuses BY NAME (the dedup path: the chain asked for is the
    ///    chain already running), the pane shows nornir's sentence WITH the in-flight
    ///    job id in it, and does not report the run as ok.
    ///
    /// The RAGNARÖK inverse is in the same test: with the mock answering normally the
    /// same click is a success carrying job ids — so a green here cannot be "the pane
    /// always says refused".
    #[test]
    fn the_one_refresh_purges_on_the_wire_and_draws_a_named_refusal_as_a_refusal() {
        use egui_kittest::kittest::Queryable;
        let srv = nornir_warehouse_embed::testkit::spawn_mock_workspaces();
        nornir_warehouse_embed::add_warehouse_by_ssh(
            &srv.endpoint,
            "",
            "nordisk",
            "git@forge.example:nordisk/dwarves.git",
        )
        .expect("mock register");

        let sel = std::cell::RefCell::new(
            WarehouseSelector::new(vec!["nordisk".into()], "nordisk")
                .with_add_source(&srv.endpoint, ""),
        );
        let mut h = egui_kittest::Harness::builder()
            .with_size(egui::vec2(1100.0, 700.0))
            .build_ui(|ui| {
                sel.borrow_mut().ui(ui);
            });
        h.step();

        assert_eq!(
            sel.borrow().state_json()["can_repopulate"],
            true,
            "a selected warehouse and an endpoint is all Refresh needs"
        );
        assert!(
            srv.log.repopulated().is_empty(),
            "…and nothing has reached the wire before a click: {:?}",
            srv.log.repopulated()
        );

        // ── ONE click, and it is one click: a Refresh destroys no source data, so
        //    unlike Remove it needs no confirmation step. ──
        h.get_by_label("🧹 Repopulate").click();
        let mut done = false;
        for _ in 0..600 {
            h.step();
            if !srv.log.repopulated().is_empty() && !sel.borrow().is_busy() {
                done = true;
                break;
            }
            std::thread::sleep(std::time::Duration::from_millis(5));
        }
        assert!(
            done,
            "the Refresh click drove a real Workspaces.Repopulate to completion"
        );
        assert_eq!(
            srv.log.repopulated(),
            vec![("nordisk".to_string(), true)],
            "Refresh is sent with purge=true — the orphaned v1 tables are dropped"
        );

        let st = sel.borrow().state_json();
        assert_eq!(
            st["last_repopulate"]["is_refusal"], false,
            "a queued run is not a refusal"
        );
        let queued = st["last_repopulate"]["queued"]
            .as_array()
            .expect("the job ids are reported")
            .len();
        assert!(queued > 0, "the run must name the jobs it queued: {st}");
        let status = st["add"]["status"].as_str().unwrap_or_default().to_string();
        assert!(status.starts_with("ok: `nordisk` serves "), "{status}");
        assert!(
            status.contains("2 member(s) behind, consume started")
                && status.contains("edda behind"),
            "the status names WHICH members a consume was started for, not just that \
             something happened: {status}"
        );
        assert!(
            status.contains("3 orphaned v1 table(s) dropped"),
            "a purge says what kind of table it dropped: {status}"
        );

        // ── RED: the SAME click against a server that cannot consume. ──
        srv.log.refuse_repopulate_as_unconsumable();
        let before = srv.log.repopulated().len();
        h.get_by_label("🧹 Repopulate").click();
        let mut refused = false;
        for _ in 0..600 {
            h.step();
            if srv.log.repopulated().len() > before && !sel.borrow().is_busy() {
                refused = true;
                break;
            }
            std::thread::sleep(std::time::Duration::from_millis(5));
        }
        assert!(refused, "the second Refresh reached the server too");

        let st = sel.borrow().state_json();
        assert_eq!(
            st["last_repopulate"]["is_refusal"], true,
            "nothing queued + nornir's named reason IS a refusal: {st}"
        );
        assert!(
            st["last_repopulate"]["queued"]
                .as_array()
                .is_some_and(|a| a.is_empty()),
            "a refusal queued nothing, and must not claim otherwise: {st}"
        );
        let status = st["add"]["status"].as_str().unwrap_or_default().to_string();
        assert!(
            !status.starts_with("ok:"),
            "a refusal is not an ok: {status}"
        );
        assert!(
            status.contains("[driver_not_available]") && status.contains("cargo-mimir-dump"),
            "the pane must show nornir's own refusal — the STATE and what is missing on the \
             box — verbatim: {status}"
        );

        cell(
            "warehouse_one_refresh_repopulate",
            srv.log.repopulated() == vec![("nordisk".into(), true), ("nordisk".into(), true)]
                && status.contains("[driver_not_available]"),
            "a kittest frame clicks the REAL 🧹 Repopulate - the ONE door onto Workspaces.Repopulate that replaced the deleted per-pane Scan buttons - and proves it reaches the wire with purge=true (drop the orphaned v1 tables), reports which members are behind and had a consume started, and when the server refuses BY NAME because nothing can consume them, paints nornir's own sentence WITH its state instead of an ok with a zero in it",
        );
    }

    /// **RED-when-broken: the progress bar paints for a populate NOBODY HERE STARTED,
    /// and its estimate refines instead of jumping.**
    ///
    /// The picker is built over an already-registered warehouse and **nothing in this
    /// test clicks Add, Re-sync or anything else**. The server's own per-member
    /// `sync_state` is moved between frames — exactly what nornir's monitor writes
    /// while it clones — and the bar has to follow it. That is the difference between
    /// "a progress bar for our button" and "a progress bar for population".
    #[test]
    fn a_populate_started_elsewhere_still_draws_a_bar() {
        use egui_kittest::kittest::Queryable;
        let srv = nornir_warehouse_embed::testkit::spawn_mock_workspaces();
        nornir_warehouse_embed::register_warehouse_by_ssh(
            &srv.endpoint,
            "",
            "nordisk",
            "git@forge.example:nordisk/dwarves.git",
        )
        .expect("mock register");
        // A populate is under way on the SERVER. This process did not start it.
        srv.log.set_member_states(&[
            ("dwarves", "fetching"),
            ("nornir", ""),
            ("holger", ""),
            ("skade", ""),
        ]);

        let sel = std::cell::RefCell::new(
            WarehouseSelector::new(vec!["nordisk".into()], "nordisk")
                .with_add_source(&srv.endpoint, ""),
        );
        let mut h = egui_kittest::Harness::builder()
            .with_size(egui::vec2(1100.0, 700.0))
            .build_ui(|ui| {
                sel.borrow_mut().ui(ui);
            });

        // Pump until the first poll lands (it runs on the named worker).
        let pump = |h: &mut egui_kittest::Harness<'_>, done: &dyn Fn() -> bool| {
            for _ in 0..600 {
                h.step();
                if done() {
                    return true;
                }
                std::thread::sleep(std::time::Duration::from_millis(5));
            }
            false
        };
        let landed = pump(&mut h, &|| sel.borrow().progress().is_some());
        assert!(
            landed,
            "the progress poll runs without any button being pressed"
        );

        {
            let s = sel.borrow();
            let st = s.state_json();
            assert_eq!(
                st["progress"]["running"], true,
                "a populate is running on the server"
            );
            assert_eq!(
                st["busy"], false,
                "…and no job of OURS is in flight — nothing here started it"
            );
            assert_eq!(
                st["progress"]["repos_total"], 4,
                "the denominator is known on the first poll"
            );
            assert_eq!(st["progress"]["repos_done"], 0);
            assert_eq!(st["progress"]["fraction"], 0.0);
            // The refining counter has not been measured yet: `…`, never `0`.
            assert_eq!(st["progress"]["crates"]["state"], "pending");
            // Bytes are not measurable over this seam and say so rather than read 0.
            assert_eq!(st["progress"]["bytes"]["state"], "unavailable");
            assert!(
                st["progress"]["counters"]
                    .as_str()
                    .unwrap_or_default()
                    .contains("n/a bytes"),
                "{}",
                st["progress"]["counters"]
            );
        }
        // …and the bar is really painted, with its counters.
        h.step();
        assert!(
            h.query_all_by_label_contains("0/4 repo(s)")
                .next()
                .is_some(),
            "the counters are on screen beside the bar"
        );
        assert!(
            h.query_all_by_label_contains("shows a populate started anywhere")
                .next()
                .is_some(),
            "…and the pane says the bar follows the server, not our button"
        );

        // ── the server advances; the bar must follow ───────────────────────────
        srv.log.set_member_states(&[
            ("dwarves", "ok"),
            ("nornir", "ok"),
            ("holger", "fetching"),
            ("skade", ""),
        ]);
        let moved = pump(&mut h, &|| {
            sel.borrow().progress().is_some_and(|p| p.repos_done == 2)
        });
        assert!(
            moved,
            "the bar follows the server's own progress with no input here"
        );
        let advanced = sel.borrow().state_json()["progress"]["fraction"] == 0.5;
        assert!(
            advanced,
            "the proportion moved: {}",
            sel.borrow().state_json()["progress"]
        );

        cell(
            "warehouse_progress_bar_follows_any_populate",
            advanced,
            "with a warehouse selected and NO button ever clicked, the picker polls the server and paints a populate progress bar: it reads the per-member sync_state nornir's monitor writes, so it shows a populate started by nornir's poll loop, another client or the CLI. The repo denominator is known on the first poll (a proportion, not a bar parked at 0%), the bar follows the server as members land, the crate count reports `pending` until it is measured, and bytes report `unavailable` with the reason instead of a fabricated 0",
        );
    }

    /// RED-when-broken: the release target MUST equal the selection. If `release_target`
    /// ever decoupled from `selected` (e.g. returned a fixed default), this fails.
    #[test]
    fn release_target_equals_selection_always() {
        let mut s = WarehouseSelector::new(vec!["a".into(), "b".into()], "a");
        let start_ok = s.release_target() == s.selected();
        assert_eq!(s.release_target(), s.selected());
        s.select("b");
        assert_eq!(s.release_target(), "b");
        assert_eq!(s.release_target(), s.selected());

        cell(
            "warehouse_selector_release_target_equals_selection",
            start_ok && s.release_target() == "b" && s.release_target() == s.selected(),
            "the release target is never decoupled from the selection: it equals the selected warehouse before and after every re-selection (a fixed default would red this)",
        );
    }

    /// **OPENING THE PICKER REFRESHES THE REGISTRY** — the design doc's Deferred item
    /// (`.nornir/add-warehouse-ssh-populate-design.md`: *"Live `list_warehouses`
    /// refresh on open. The client fn exists; wiring the header to pre-fill the picker
    /// from `Workspaces.List` on startup is a follow-up."*).
    ///
    /// Before this cell, `list_warehouses` had exactly ONE call site in the whole tree
    /// — `lib.rs::registered_warehouses`, called once inside `build_native_app`. The
    /// picker was therefore a photograph of the registry taken at process start. A
    /// warehouse registered afterwards by ANYTHING else — the nornir CLI, a second
    /// dwarves window, another operator — was invisible until the user restarted the
    /// app, and the affordance that looks like it would show you the current list (the
    /// picker itself) was the one thing that could not.
    ///
    /// RED-when-broken, and it does not call the refresh seam directly: it CLICKS the
    /// real ComboBox button through kittest (AccessKit `Action::Click`, which egui
    /// honours only for enabled widgets), so what is proven is the WIRING in `ui()` and
    /// not merely that a method exists. Remove the open-edge call and `registry_refreshes`
    /// never moves and `late-ws` never appears.
    #[test]
    fn robot_opening_the_picker_refreshes_the_registry_from_the_server() {
        use egui_kittest::kittest::Queryable;
        use egui_kittest::Harness;

        // A REAL nornir `Workspaces` server (mock) — the same one the Add/Populate cell
        // drives, reached over the same thin client (L5, never a second mock).
        let srv = nornir_warehouse_embed::testkit::spawn_mock_workspaces();

        // The shell as `build_native_app` left it: the picker holds the ONE name the
        // startup query knew about.
        let sel = std::cell::RefCell::new(
            WarehouseSelector::new(vec!["seed-ws".to_string()], "seed-ws")
                .with_add_source(&srv.endpoint, ""),
        );

        // …and then SOMEBODY ELSE registers a warehouse. This is the whole scenario:
        // the registry moved and this process did not do it.
        nornir_warehouse_embed::register_warehouse_by_ssh(
            &srv.endpoint,
            "",
            "late-ws",
            "git@forge.example:nordisk/late-ws.git",
        )
        .expect("another client registered a warehouse against the same server");

        let mut h = Harness::builder()
            .with_size(egui::vec2(900.0, 620.0))
            .build_ui(|ui| {
                sel.borrow_mut().ui(ui);
            });

        // Pump frames until `done` — never `run()`, which panics when a frame keeps
        // requesting repaints.
        let pump = |h: &mut Harness<'_>, done: &dyn Fn() -> bool| {
            for _ in 0..600 {
                h.step();
                if done() {
                    return true;
                }
                std::thread::sleep(std::time::Duration::from_millis(5));
            }
            false
        };

        h.step();
        // The premise: nothing has refreshed, so the picker is still the startup photo.
        let before = sel.borrow().state_json();
        assert_eq!(
            before["count"], 1,
            "the picker starts on its one seeded name"
        );
        // Tolerant of a MISSING key on purpose: the red this test exists to catch is
        // "opening the picker asked the server nothing", not "the oracle field is
        // absent". Absent reads as zero here so the failure lands on the assertion
        // below, which is the one with something to say.
        assert_eq!(
            before["registry_refreshes"].as_u64().unwrap_or(0),
            0,
            "and nothing has refreshed it yet"
        );

        // ── OPEN THE PICKER, for real. ──
        // BY ROLE, not by label: egui gives the combo button `Role::ComboBox` and hangs
        // its selected text on a child `Label`/`TextRun`, so `get_by_label("seed-ws")`
        // finds nothing (MEASURED — kittest dumped the tree). The role is also the
        // stabler handle: it does not move when the selection changes.
        h.get_by_role(egui::accesskit::Role::ComboBox).click();
        h.step();

        let refreshed = pump(&mut h, &|| {
            sel.borrow().state_json()["registry_refreshes"]
                .as_u64()
                .unwrap_or(0)
                >= 1
        });
        assert!(
            refreshed,
            "opening the picker must fire a Workspaces.List refresh — the picker is the \
             affordance that shows the registry, so it is the one place a stale list is \
             indefensible"
        );

        let st = sel.borrow().state_json();
        let names: Vec<String> = st["warehouses"]
            .as_array()
            .expect("warehouses is an array")
            .iter()
            .map(|w| w.as_str().unwrap_or_default().to_string())
            .collect();
        let sees_late = names.iter().any(|n| n == "late-ws");
        assert!(
            sees_late,
            "the warehouse registered by the other client is now offered: {names:?}"
        );
        // A refresh UNIONS — it never empties the picker. A name the shell was seeded
        // with (`$DWARVES_WAREHOUSES`, or a server that answered once and has since gone
        // quiet) must survive a refresh, or a transient RPC failure would silently take
        // the user's selection away.
        let keeps_seed = names.iter().any(|n| n == "seed-ws");
        assert!(keeps_seed, "a refresh adds, it does not replace: {names:?}");
        assert_eq!(
            st["selected_warehouse"], "seed-ws",
            "…and the selection is untouched"
        );

        let ok = sees_late && keeps_seed;
        cell(
            "warehouse_picker_refreshes_the_registry_on_open",
            ok,
            "a kittest frame CLICKS the real warehouse ComboBox against a live Workspaces \
             gRPC mock: opening the picker fires Workspaces.List through the host and folds \
             in a warehouse registered by ANOTHER client after this shell started, without \
             dropping the startup-seeded names or moving the selection. Closes the design \
             doc's Deferred `live list_warehouses refresh on open` — before this the RPC's \
             only call site was the once-at-startup seed in build_native_app, so the picker \
             was a photograph of the registry taken at process start",
        );
    }
}