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

nordisk/monetize

crates/estate-svg/src/lib.rs

//! **The estate, drawn**: one board, one chip per machine, the numbers a buyer
//! never sees and an operator always needs.
//!
//! # Why this exists
//!
//! monetize's model is deliberately blind to machines. An order names a METER
//! and an AMOUNT — `pack_bytes = 10 GiB` — and `Change::Ensure` decides
//! what iron that becomes and where. That abstraction is right, and it is the
//! reason a buyer's receipt says "10 GiB" and never "on the twin".
//!
//! What it left behind is an operator with no picture. `[clouds.server]` is one
//! anonymous `pool_gib`; the machines are named in three unrelated places
//! (`monetize_cloud::ServerId` on every `Resource`, the twin's provider ids in
//! the retired `[products.<id>.twin_grow]`, and nowhere at all for the front), and none of
//! them is a list. So: this module takes what the config already knows, plus
//! whatever the live boxes answered, and draws it.
//!
//! **It is a rendering, not an authority.** Nothing here measures anything, and
//! nothing here decides placement. Every number arrives in [`Estate`] from
//! somebody who measured it, and a number nobody measured is drawn as `—`
//! rather than guessed — the same rule `Figure::Unmeasured` applies on the
//! console's own screens, and for the same reason: a plan's declared vCPU count
//! is the provider's claim about a box, not a reading off it.
//!
//! # Fairly static, with one moving part
//!
//! The topology does not change much: a front, an appliance, a twin. What
//! changes is whether the twin is THERE — and that is exactly the thing an
//! operator opens this picture to find out. A twin that has never been seen is
//! drawn, dimmed, with its own sentence, rather than left out: a missing box on
//! a diagram reads as "there is no twin in this design", and there is one, it
//! is just dark.
//!
//! # ONE renderer, two doors (extracted 2026-10-04)
//!
//! * **monetize** — `EstateAdmin.Draw` (`monetize-server`'s
//!   `services::estate`) puts the config's half and the product plugin's half
//!   in front of [`estate_svg`] and hands the document to monetize's console
//!   BFF, which serves it as `image/svg+xml` behind an operator's session.
//! * **gunnar** — the console's Machine card: the BFF asks gunnar's
//!   `Admin.Topology` for the boxes gunnar has evidence of (itself, its twins
//!   and standbys, the consoles that call it, the monetize it registers with),
//!   maps every row onto a [`Machine`] AS IT CAME — gunnar decided each state,
//!   the console decides none — and draws it here with [`Estate::disk_only`]
//!   on, because a topology carries no plan and no growth verdict.
//!
//! Nothing re-draws the board on the far side of either wire: a second renderer
//! would be free to disagree about which number was read and which was merely
//! claimed, which is what this crate exists to prevent.
//!
//! # Read left to right (owner, 2026-10-05)
//!
//! The top row is always front | main | twin — the front on the left, the
//! server it talks to in the middle, the twin on the right — whatever order the
//! rows arrive in; the links run front → main → twin. Every other box (a
//! console, monetize, a standby, a second twin) is on a row below, hanging from
//! the main box, and never sits between those three.
//!
//! # The style is skade's, the colours are the page's
//!
//! A dark PCB — board gradient, chips on rails, cyan slots, mono for anything
//! an operator might paste into a command. The palette and the geometry are
//! taken from `skade/.nornir/assets/skade_flow.svg` so the suite's diagrams look
//! like one suite. Nothing is loaded from anywhere: no fonts, no images, no
//! script.
//!
//! Every colour is a page token with skade's colour behind it
//! (`var(--estate-ink, #eaf6ff)`, the names in [`TOKENS`]): a console that puts
//! the board INLINE maps the tokens onto its own theme and the board follows
//! that theme live (owner, 2026-10-05: "make it follow the chosen theme of the
//! web site"); anywhere nothing maps them — an `<img>`, a README, a PDF — the
//! output is skade's dark board.

use std::fmt::Write as _;

/// What a machine is for. The role decides where it sits on the board and what
/// is said about it, never what it is allowed to do.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Role {
    /// The public face: Caddy, the console, the money services.
    Front,
    /// The appliance itself — gunnar, PID 1, no shell. gunnar's own word for
    /// it is `primary`.
    Appliance,
    /// The follower. No customers, and therefore the one box allowed a restart.
    Twin,
    /// A replication standby gunnar pushes to (gunnar's `standby`).
    Standby,
    /// A console gunnar saw call it from a box no pairing names.
    Console,
    /// The monetize gunnar registers its plugin with, on a box of its own.
    Monetize,
    /// **Where the main box's bytes come FROM** (holger, 2026-10-23): a
    /// registry it proxies (crates.io, PyPI, npm), or an upstream holger it
    /// mirrors. Drawn on a row of its own ABOVE front | main | twin, each with
    /// a link down into the main box carrying the upstream's state.
    Upstream,
}

impl Role {
    /// The published 0.1.0 name of [`Role::from_word`], kept for its callers until their next release.
    pub fn from_gunnar(word: &str) -> Option<Role> {
        Self::from_word(word)
    }

    /// The role's own word, drawn when a machine carries no title.
    pub fn title(self) -> &'static str {
        match self {
            Role::Front => "front",
            Role::Appliance => "appliance",
            Role::Twin => "twin",
            Role::Standby => "standby",
            Role::Console => "console",
            Role::Monetize => "monetize",
            Role::Upstream => "upstream",
        }
    }

    /// **gunnar's role word** (`Admin.Topology`'s `TopologyBox.role`), read as
    /// it came. A word this build does not know is drawn as a console-sized
    /// box with its own word as the title by the caller — never refused here:
    /// this is a renderer, and a picture that panics is worse than a box in the
    /// wrong column.
    pub fn from_word(word: &str) -> Option<Role> {
        match word {
            "primary" => Some(Role::Appliance),
            "twin" => Some(Role::Twin),
            "front" => Some(Role::Front),
            "standby" => Some(Role::Standby),
            "console" => Some(Role::Console),
            "monetize" => Some(Role::Monetize),
            "upstream" => Some(Role::Upstream),
            _ => None,
        }
    }
}

/// Whether the box answered, the last time anybody asked.
///
/// `Unknown` is not a synonym for `Down`. A box nobody has asked about and a box
/// that was asked and said nothing are different facts, and an operator acts
/// differently on each.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Liveness {
    /// It answered.
    Up,
    /// It answers, and does not keep up — a twin whose lag grows. Lit, with an
    /// amber lamp: it is THERE, which is a different drawing from dark.
    Behind,
    /// It was asked and did not answer.
    Silent,
    /// Nobody asked.
    Unknown,
    /// **It does not exist yet**: a machine on a QUOTE — the private-gunnar
    /// buy page draws what the buyer would get. Lit (it is the offer, not a
    /// dead box), with an open lamp and the word `planned`.
    Planned,
}

impl Liveness {
    /// The published 0.1.0 name of [`Liveness::from_word`], kept for its callers until their next release.
    pub fn from_gunnar(word: &str) -> Liveness {
        Self::from_word(word)
    }

    /// **gunnar's state word** (`TopologyBox.state`: `up` · `behind` ·
    /// `silent` · `unknown`), read as it came. A word this build does not know
    /// is `Unknown` — never the safest-looking of the words it does know.
    pub fn from_word(word: &str) -> Liveness {
        match word {
            "up" => Liveness::Up,
            "behind" => Liveness::Behind,
            "silent" => Liveness::Silent,
            _ => Liveness::Unknown,
        }
    }

    /// The word a live board names the state by (`data-state`).
    pub fn word(self) -> &'static str {
        match self {
            Liveness::Up => "up",
            Liveness::Behind => "behind",
            Liveness::Silent => "silent",
            Liveness::Unknown => "unknown",
            Liveness::Planned => "planned",
        }
    }

    /// Present and not answering, or never asked: drawn dark.
    fn dark(self) -> bool {
        matches!(self, Liveness::Silent | Liveness::Unknown)
    }
}

/// A measured pair of bytes. There is no constructor from a declaration: if a
/// number was declared rather than read, it does not belong in here.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Disk {
    pub used_bytes: u64,
    pub total_bytes: u64,
    /// Whether THIS box can gain capacity while it runs. The twin can (stop →
    /// resize → start, it has no customers); the appliance grows in place and
    /// only where the boot stack carries the engine.
    pub growable: bool,
}

/// One machine on the board.
#[derive(Clone, Debug)]
pub struct Machine {
    /// The short name an operator uses, and the id everything else references.
    pub id: String,
    pub role: Role,
    /// The estate's own one-line description of this box, when it has one.
    /// Drawn instead of the bare role word, because "the forge, no shell" tells
    /// a reader more than "appliance" does.
    pub title: String,
    /// The `[clouds.<name>]` this box belongs to.
    pub cloud: String,
    /// The provider's own id, for the operator who is about to open a support
    /// ticket. Drawn in mono because it gets pasted.
    pub server_id: String,
    /// The provider's plan name, if the config names one.
    pub plan: Option<String>,
    /// **Declared by the provider, not measured.** Drawn with the word `plan`
    /// beside it so nobody reads it as a reading.
    pub declared_vcpu: Option<u32>,
    /// The same, for memory.
    pub declared_ram_mb: Option<u64>,
    /// **MEASURED cpu, in millicores**, when the product read it off the box.
    /// gunnar's `Admin.Capacity` reports `cpu_millicores` as one of its
    /// ceilings; a plugin that cannot reach its box reports nothing, and a
    /// meter ABSENT there was not measured — never 0. When this is present the
    /// drawing shows it INSTEAD of the plan, because a reading beats a claim.
    pub measured_cpu_millicores: Option<u64>,
    /// The same, for memory: `ram_bytes`.
    pub measured_ram_bytes: Option<u64>,
    /// Measured, or `None` — never a declaration dressed up as one.
    pub disk: Option<Disk>,
    pub liveness: Liveness,
    /// One short sentence when there is something to say: why it is silent, why
    /// it cannot grow. Rendered verbatim.
    pub note: String,
    /// **The state in a few words, from whoever judged it** — gunnar's
    /// `silent for 4 min`, `behind 120 entries`, `following`. Drawn on a
    /// [`Estate::live`] board under the title, in the lamp's colour; empty draws
    /// nothing. Never composed here.
    pub headline: String,
}

/// A cloud, as the board's footer lists it.
#[derive(Clone, Debug)]
pub struct CloudRow {
    pub name: String,
    /// False for the box's own iron.
    pub spends: bool,
    /// True for the one a purchase actually goes to.
    pub is_default: bool,
}

/// Everything the picture is drawn from.
#[derive(Clone, Debug, Default)]
pub struct Estate {
    /// The product this estate serves, for the title (`<product> estate`).
    pub product: String,
    pub machines: Vec<Machine>,
    /// The clouds along the bottom. Empty = no footer at all: a board whose
    /// caller knows no cloud does not draw an empty "iron comes from" row.
    pub clouds: Vec<CloudRow>,
    /// How many tenants the product holds, when the caller knows.
    pub tenants: Option<u64>,
    /// **The line under the title, when the caller has its own.** `None` is
    /// monetize's line — a buyer names a meter and an amount, never a machine
    /// — which is monetize's rule and not every console's to state (gunnar's
    /// open console says nothing about buying at all).
    pub subtitle: Option<String>,
    /// **Draw only what a topology knows**: the name, the role, the lamp, the
    /// address, the measured disk and the sentence — no cpu/memory row and no
    /// growth line. gunnar's `Admin.Topology` carries neither a plan nor a
    /// growth verdict per box, and a row of dashes captioned "no plan" on every
    /// chip would be a claim about a thing nobody was asked.
    pub disk_only: bool,
    /// **A board that is the estate NOW** (owner, 2026-10-04: "the picture
    /// needs to be a bit dynamic, if twin dies etc.") — drawn to be swapped in
    /// place on an open page: each chip a group naming its box and state word
    /// (`data-box`, `data-state`); a silent chip's edge red, a behind one's
    /// amber; [`Machine::headline`] under the title in the lamp's colour; and
    /// the link INTO each box (the rail on its left, or the drop above a box
    /// on the row below) carrying that box's state — a pulse for `up`, a
    /// slower amber pulse for `behind`, a still red line for `silent`, a dark
    /// dashed one for `unknown`. Every one of those comes
    /// from the box's [`Liveness`]; nothing here judges a box. Off, the board
    /// is the one monetize has always drawn: no groups, no pulses, no
    /// headlines.
    pub live: bool,
}

// ── the palettes: the page's tokens, a theme's colours behind them ──────────
//
// Owner, 2026-10-05: "make it follow the chosen theme of the web site". Every
// colour on the board is a CSS custom property of the page that shows it,
// `--estate-<name>`, with the THEME's colour as the fallback:
// `fill="var(--estate-ink, #eaf6ff)"`. Drawn INLINE (gunnar's Machine card),
// the page's stylesheet maps each name onto its own theme tokens and the board
// repaints with the page when its theme switches — no fetch, no second
// drawing. Drawn as an `<img>` (monetize's console), in a README or a PDF,
// nothing reaches it and it is the theme's own board: skade's dark one, or
// the white buy page's black and white ([`Theme::MONOCHROME`]).

/// How a machine's state is told apart besides its word.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Lamps {
    /// By HUE: green, amber, red, grey dots (skade's board).
    Hue,
    /// By LINE AND FILL, never by hue: up is a solid dot, behind a hollow
    /// ring, silent a dashed ring (and the chip's own outline dashed), not
    /// asked a dotted grey ring. For a page printed or read in black and white.
    Shape,
}

/// **The colours a board is drawn in** — each one the FALLBACK of its page
/// token (`var(--estate-<name>, <colour>)`, the names in [`Theme::tokens`]).
/// [`Theme::SKADE`] is the console's dark board; [`Theme::MONOCHROME`] is
/// black and white for the white buy page (owner, 2026-10-04 18:15).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Theme {
    pub ink: &'static str,
    pub ink_dim: &'static str,
    pub ink_body: &'static str,
    pub ink_faint: &'static str,
    pub slot_ink: &'static str,
    /// The accent: slot outlines, pins, the default cloud, the disk bar.
    pub accent: &'static str,
    pub rail_edge: &'static str,
    pub chip_edge: &'static str,
    pub rule: &'static str,
    pub via: &'static str,
    /// A dark box keeps its outline and loses its light — it is present and
    /// not answering, which is a different drawing from absent.
    pub dark_edge: &'static str,
    pub dark_ink: &'static str,
    pub pin: &'static str,
    pub bar_track: &'static str,
    /// A disk 80 % full.
    pub bar_high: &'static str,
    /// Gradient stops `(top, bottom)`: board, chip, the appliance's chip
    /// (`rail`), an inset slot, and an unlit chip.
    pub board: (&'static str, &'static str),
    pub chip: (&'static str, &'static str),
    pub rail: (&'static str, &'static str),
    pub slot: (&'static str, &'static str),
    pub dark: (&'static str, &'static str),
    /// The four state colours, one per [`Liveness`] word (`up`, `behind`,
    /// `silent`, `unknown`). A page maps them onto its own pass / warn / bad;
    /// each state also keeps its line and fill (a pulse, a slower pulse, a
    /// still line, a dashed one; an unlit chip), so no theme has to carry the
    /// meaning by hue alone.
    pub up: &'static str,
    pub behind: &'static str,
    pub silent: &'static str,
    pub unknown: &'static str,
    pub lamps: Lamps,
}

impl Theme {
    /// skade's dark board — what monetize's `EstateAdmin.Draw` and gunnar-ui's
    /// Machine card draw, and every token's fallback in [`TOKENS`]. Pinned
    /// byte for byte by `tests/golden.rs`.
    pub const SKADE: Theme = Theme {
        ink: "#eaf6ff",
        ink_dim: "#9fc9e0",
        ink_body: "#86b3cc",
        ink_faint: "#7fa8c4",
        slot_ink: "#bfe6ff",
        accent: "#6fd3f5",
        rail_edge: "#7fc8ea",
        chip_edge: "#5aa6cf",
        rule: "#143a52",
        via: "#16415c",
        dark_edge: "#3d5f74",
        dark_ink: "#6a8ba0",
        pin: "#bfecff",
        bar_track: "#0a2032",
        bar_high: "#e0a34f",
        board: ("#0b1c2e", "#06111d"),
        chip: ("#14384f", "#0e2a3e"),
        rail: ("#173d57", "#0c2438"),
        slot: ("#123448", "#0a2032"),
        dark: ("#0d1f2e", "#081521"),
        up: "#5fd39a",
        behind: "#e0a34f",
        silent: "#e0574f",
        unknown: "#7fa8c4",
        lamps: Lamps::Hue,
    };

    /// **Black and white on white**: black strokes and text, grey for the
    /// secondary lines; state by line style, fill and word, never by hue.
    pub const MONOCHROME: Theme = Theme {
        ink: "#000000",
        ink_dim: "#555555",
        ink_body: "#555555",
        ink_faint: "#777777",
        slot_ink: "#000000",
        accent: "#000000",
        rail_edge: "#000000",
        chip_edge: "#000000",
        rule: "#cccccc",
        via: "#aaaaaa",
        dark_edge: "#000000",
        dark_ink: "#555555",
        pin: "#000000",
        bar_track: "#e6e6e6",
        bar_high: "#000000",
        board: ("#ffffff", "#ffffff"),
        chip: ("#ffffff", "#ffffff"),
        rail: ("#ffffff", "#ffffff"),
        slot: ("#ffffff", "#ffffff"),
        dark: ("#ffffff", "#ffffff"),
        up: "#000000",
        behind: "#000000",
        silent: "#000000",
        unknown: "#777777",
        lamps: Lamps::Shape,
    };

    /// **The page tokens this theme fills**: every custom property the drawing
    /// reads, `(name, this theme's colour)`, in one fixed order.
    pub const fn tokens(&self) -> [(&'static str, &'static str); 29] {
        [
            ("--estate-board-top", self.board.0),
            ("--estate-board-bottom", self.board.1),
            ("--estate-chip-top", self.chip.0),
            ("--estate-chip-bottom", self.chip.1),
            ("--estate-rail-top", self.rail.0),
            ("--estate-rail-bottom", self.rail.1),
            ("--estate-slot-top", self.slot.0),
            ("--estate-slot-bottom", self.slot.1),
            ("--estate-dark-top", self.dark.0),
            ("--estate-dark-bottom", self.dark.1),
            ("--estate-ink", self.ink),
            ("--estate-ink-dim", self.ink_dim),
            ("--estate-ink-body", self.ink_body),
            ("--estate-ink-faint", self.ink_faint),
            ("--estate-slot-ink", self.slot_ink),
            ("--estate-accent", self.accent),
            ("--estate-rail-edge", self.rail_edge),
            ("--estate-chip-edge", self.chip_edge),
            ("--estate-rule", self.rule),
            ("--estate-via", self.via),
            ("--estate-dark-edge", self.dark_edge),
            ("--estate-dark-ink", self.dark_ink),
            ("--estate-pin", self.pin),
            ("--estate-bar-track", self.bar_track),
            ("--estate-bar-high", self.bar_high),
            ("--estate-up", self.up),
            ("--estate-behind", self.behind),
            ("--estate-silent", self.silent),
            ("--estate-unknown", self.unknown),
        ]
    }
}

const SKADE_TOKENS: [(&str, &str); 29] = Theme::SKADE.tokens();

/// **The page tokens the board is drawn with**: every custom property the
/// drawing reads, `(name, skade's fallback)`. A console that shows the board
/// inline maps each onto its own theme (gunnar-ui: `.estate-picture` in
/// `styles/console.css`, which a test holds to this list); a name it does not
/// map falls back to the theme's colour.
pub const TOKENS: &[(&str, &str)] = &SKADE_TOKENS;

/// A theme as the drawing writes it: every colour its page token with the
/// theme's colour behind it. The names are the drawing's own.
#[allow(non_snake_case)]
struct Pal {
    BOARD_TOP: String,
    BOARD_BOTTOM: String,
    CHIP_TOP: String,
    CHIP_BOTTOM: String,
    RAIL_TOP: String,
    RAIL_BOTTOM: String,
    SLOT_TOP: String,
    SLOT_BOTTOM: String,
    DARK_TOP: String,
    DARK_BOTTOM: String,
    INK: String,
    INK_DIM: String,
    INK_BODY: String,
    INK_FAINT: String,
    SLOT_INK: String,
    CYAN: String,
    RAIL_EDGE: String,
    CHIP_EDGE: String,
    RULE: String,
    VIA: String,
    DARK_EDGE: String,
    DARK_INK: String,
    PIN: String,
    BAR_TRACK: String,
    BAR_HIGH: String,
    UP: String,
    AMBER: String,
    RED: String,
    NOT_ASKED: String,
    lamps: Lamps,
}

impl Pal {
    #[allow(non_snake_case)]
    fn of(t: &Theme) -> Pal {
        let [BOARD_TOP, BOARD_BOTTOM, CHIP_TOP, CHIP_BOTTOM, RAIL_TOP, RAIL_BOTTOM, SLOT_TOP, SLOT_BOTTOM, DARK_TOP, DARK_BOTTOM, INK, INK_DIM, INK_BODY, INK_FAINT, SLOT_INK, CYAN, RAIL_EDGE, CHIP_EDGE, RULE, VIA, DARK_EDGE, DARK_INK, PIN, BAR_TRACK, BAR_HIGH, UP, AMBER, RED, NOT_ASKED] =
            t.tokens()
                .map(|(name, colour)| format!("var({name}, {colour})"));
        Pal {
            BOARD_TOP,
            BOARD_BOTTOM,
            CHIP_TOP,
            CHIP_BOTTOM,
            RAIL_TOP,
            RAIL_BOTTOM,
            SLOT_TOP,
            SLOT_BOTTOM,
            DARK_TOP,
            DARK_BOTTOM,
            INK,
            INK_DIM,
            INK_BODY,
            INK_FAINT,
            SLOT_INK,
            CYAN,
            RAIL_EDGE,
            CHIP_EDGE,
            RULE,
            VIA,
            DARK_EDGE,
            DARK_INK,
            PIN,
            BAR_TRACK,
            BAR_HIGH,
            UP,
            AMBER,
            RED,
            NOT_ASKED,
            lamps: t.lamps,
        }
    }
}

const MONO: &str = "'DejaVu Sans Mono', monospace";

/// The board's width for front | main | twin. A wider row widens the board
/// rather than running off its edge.
const W: u32 = 1180;
/// The board's height for one row of chips; a second row adds a chip and a
/// [`ROW_GAP`].
const H: u32 = 560;
/// Chip geometry. The top row is front | main | twin, in reading order; every
/// other box is on the row below ([`layout`]).
const CHIP_W: u32 = 300;
const CHIP_H: u32 = 268;
const CHIP_Y: u32 = 132;
const GAP: u32 = 40;
/// Between the top row and the row below: the bus the boxes below hang from.
const ROW_GAP: u32 = 72;

/// **Reading order** (owner, 2026-10-05: "the front must be on the left, going
/// to the server in the middle, and the twin on the right, in reading order"):
/// the top row is the first front, the first appliance and the first twin, in
/// that order, whichever of them the estate has — whatever order the rows came
/// in (gunnar sends THIS box first). Every other box — a console, monetize, a
/// standby, a second twin — is on the row below, by role and then as it came,
/// so none of them ever sits between front, main and twin. An estate with none
/// of the three draws its boxes on the top row, as they came.
struct Layout {
    /// Indices into [`Estate::machines`], left to right: the upstreams' row
    /// (empty on a board that names none), front | main | twin, the row below.
    above: Vec<usize>,
    top: Vec<usize>,
    below: Vec<usize>,
}

fn layout(machines: &[Machine]) -> Layout {
    let first = |role: Role| machines.iter().position(|m| m.role == role);
    let above: Vec<usize> = (0..machines.len())
        .filter(|&i| machines[i].role == Role::Upstream)
        .collect();
    let top: Vec<usize> = [Role::Front, Role::Appliance, Role::Twin]
        .into_iter()
        .filter_map(first)
        .collect();
    let mut below: Vec<usize> = (0..machines.len())
        .filter(|i| !top.contains(i) && !above.contains(i))
        .collect();
    if top.is_empty() {
        return Layout {
            above,
            top: below,
            below: Vec::new(),
        };
    }
    // Stable: boxes of one role keep the order they came in.
    below.sort_by_key(|&i| rank(machines[i].role));
    Layout { above, top, below }
}

/// The order of the row below — the box that keeps a copy before the boxes
/// that call.
fn rank(role: Role) -> u8 {
    match role {
        Role::Front => 0,
        Role::Appliance => 1,
        Role::Twin => 2,
        Role::Standby => 3,
        Role::Monetize => 4,
        Role::Console => 5,
        Role::Upstream => 6,
    }
}

/// The width a row of `n` chips takes (at least one, so an empty board is a
/// board).
fn span(n: usize) -> u32 {
    let n = n.max(1) as u32;
    n * CHIP_W + (n - 1) * GAP
}

/// **Draw the estate.** Deterministic: the same [`Estate`] always yields the
/// same bytes, so the output can be committed and a diff means something
/// changed on the estate rather than in a layout engine.
pub fn estate_svg(estate: &Estate) -> String {
    estate_svg_themed(estate, &Theme::SKADE)
}

/// **Draw the estate in `theme`**: the same layout, the same words; only the
/// colours — every one a page token with the theme's colour behind it — and,
/// for [`Lamps::Shape`], the lamps' and a dark chip's line differ.
#[allow(non_snake_case)]
pub fn estate_svg_themed(estate: &Estate, theme: &Theme) -> String {
    let p = &Pal::of(theme);
    let Pal {
        BOARD_TOP,
        BOARD_BOTTOM,
        CHIP_TOP,
        CHIP_BOTTOM,
        RAIL_TOP,
        RAIL_BOTTOM,
        SLOT_TOP,
        SLOT_BOTTOM,
        DARK_TOP,
        DARK_BOTTOM,
        INK,
        INK_DIM,
        INK_BODY,
        SLOT_INK,
        CYAN,
        RULE,
        VIA,
        DARK_EDGE,
        PIN,
        ..
    } = p;
    // Laid out first, so the board is as wide and as deep as its rows need.
    let lay = layout(&estate.machines);
    let row = |r: &[usize]| if r.is_empty() { 0 } else { span(r.len()) + 80 };
    let w = W
        .max(span(lay.top.len()) + 80)
        .max(row(&lay.below))
        .max(row(&lay.above));
    // The upstreams' row, when there is one, pushes every other row down by one.
    let lift = if lay.above.is_empty() {
        0
    } else {
        CHIP_H + ROW_GAP
    };
    let top_y = CHIP_Y + lift;
    let h = if lay.below.is_empty() {
        H
    } else {
        H + CHIP_H + ROW_GAP
    } + lift;
    let mut s = String::with_capacity(8 * 1024);
    let _ = write!(
        s,
        r##"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}" font-family="'DejaVu Sans', 'Segoe UI', sans-serif">
  <defs>
    <linearGradient id="board" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0" stop-color="{BOARD_TOP}"/><stop offset="1" stop-color="{BOARD_BOTTOM}"/>
    </linearGradient>
    <linearGradient id="chip" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0" stop-color="{CHIP_TOP}"/><stop offset="1" stop-color="{CHIP_BOTTOM}"/>
    </linearGradient>
    <linearGradient id="rail" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0" stop-color="{RAIL_TOP}"/><stop offset="1" stop-color="{RAIL_BOTTOM}"/>
    </linearGradient>
    <linearGradient id="slot" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0" stop-color="{SLOT_TOP}"/><stop offset="1" stop-color="{SLOT_BOTTOM}"/>
    </linearGradient>
    <linearGradient id="dark" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0" stop-color="{DARK_TOP}"/><stop offset="1" stop-color="{DARK_BOTTOM}"/>
    </linearGradient>
  </defs>

  <rect x="0" y="0" width="{w}" height="{h}" fill="url(#board)"/>
  <g stroke="{RULE}" stroke-width="1">
    <line x1="0" y1="76" x2="{w}" y2="76"/>
    <line x1="0" y1="{rule_low}" x2="{w}" y2="{rule_low}"/>
  </g>
  <g fill="{VIA}">
    <circle cx="40" cy="40" r="2"/><circle cx="{via_r}" cy="40" r="2"/>
    <circle cx="40" cy="{via_b}" r="2"/><circle cx="{via_r}" cy="{via_b}" r="2"/>
  </g>
"##,
        rule_low = h - 76,
        via_r = w - 40,
        via_b = h - 28,
    );

    // ── the title band ──────────────────────────────────────────────────────
    let title = if estate.product.is_empty() {
        "estate".to_owned()
    } else {
        format!("{} estate", estate.product)
    };
    let _ = writeln!(
        s,
        r##"  <text x="40" y="46" fill="{INK}" font-size="17" font-weight="700">{}</text>"##,
        esc(&title)
    );
    let subtitle = match (&estate.subtitle, estate.tenants) {
        (Some(own), _) => own.clone(),
        (None, tenants) => match tenants {
            Some(n) => format!(
                "{n} tenant{} · a buyer names a meter and an amount, never a machine",
                if n == 1 { "" } else { "s" }
            ),
            None => "a buyer names a meter and an amount, never a machine".to_owned(),
        },
    };
    let _ = writeln!(
        s,
        r##"  <text x="40" y="64" fill="{INK_BODY}" font-size="10.5">{}</text>"##,
        esc(&subtitle)
    );

    // ── the machines: front | main | twin, then the row below ──────────────
    //
    // Each row centred, so a two-machine estate is not glued to the left edge
    // of a board sized for three.
    let x0 = |n: usize| (w - span(n)) / 2;
    let one = |s: &mut String, x: u32, y: u32, m: &Machine| {
        if estate.live {
            let _ = writeln!(
                s,
                r#"  <g data-box="{}" data-state="{}">"#,
                esc(&m.id),
                m.liveness.word()
            );
        }
        chip(s, x, y, m, estate.disk_only, estate.live, p);
        if estate.live {
            s.push_str("  </g>\n");
        }
    };

    let top_x = |i: usize| x0(lay.top.len()) + i as u32 * (CHIP_W + GAP);
    let hub = lay
        .top
        .iter()
        .position(|&i| estate.machines[i].role == Role::Appliance)
        .unwrap_or(0);
    let hub_x = top_x(hub) + CHIP_W / 2;

    // The upstreams' row: each upstream drops onto a bus, and the bus runs down
    // into the main box — on a live board each drop carries that upstream's
    // state, so a dead registry is a red line into the box it fed.
    if !lay.above.is_empty() {
        let bus_y = CHIP_Y + CHIP_H + ROW_GAP / 2;
        let above_x = |j: usize| x0(lay.above.len()) + j as u32 * (CHIP_W + GAP);
        let (first, last) = (
            above_x(0) + CHIP_W / 2,
            above_x(lay.above.len() - 1) + CHIP_W / 2,
        );
        let _ = write!(
            s,
            r##"  <g stroke="{RULE}" stroke-width="1.5">
    <line x1="{}" y1="{bus_y}" x2="{}" y2="{bus_y}"/>
    <line x1="{hub_x}" y1="{bus_y}" x2="{hub_x}" y2="{top_y}"/>
  </g>
"##,
            first.min(hub_x),
            last.max(hub_x),
        );
        for (j, &at) in lay.above.iter().enumerate() {
            let x = above_x(j);
            let m = &estate.machines[at];
            one(&mut s, x, CHIP_Y, m);
            drop_link(
                &mut s,
                x + CHIP_W / 2,
                CHIP_Y + CHIP_H,
                bus_y,
                m,
                estate.live,
                p,
            );
        }
    }

    for (i, &at) in lay.top.iter().enumerate() {
        let x = top_x(i);
        one(&mut s, x, top_y, &estate.machines[at]);
        let Some(&next) = lay.top.get(i + 1) else {
            continue;
        };
        // On a live board the link INTO the next box carries that box's state:
        // front → main, main → twin.
        if estate.live {
            live_link(&mut s, x, top_y, &estate.machines[next], p);
            continue;
        }
        // The rail between neighbours: a pin out, a pin in, a line across. It
        // says "these are on one board", not "bytes flow this way" — the twin's
        // log does flow, and the front's gRPC does, but a single line cannot
        // mean both and a diagram that implies the wrong one is worse than a
        // diagram that implies neither.
        let y = top_y + CHIP_H / 2;
        let _ = write!(
            s,
            r##"  <rect x="{}" y="{}" width="6" height="8" fill="{CYAN}"/>
  <line x1="{}" y1="{y}" x2="{}" y2="{y}" stroke="{RULE}" stroke-width="1.5"/>
  <circle cx="{}" cy="{y}" r="4" fill="{PIN}"/>
"##,
            x + CHIP_W,
            y - 4,
            x + CHIP_W + 6,
            x + CHIP_W + GAP,
            x + CHIP_W + GAP,
        );
    }

    // The row below hangs from the main box (else the first on top): a line
    // down from it to a bus, and from the bus a drop into each box — on a live
    // board the drop carries that box's state, as the links on top do.
    if !lay.below.is_empty() {
        let below_y = top_y + CHIP_H + ROW_GAP;
        let bus_y = top_y + CHIP_H + ROW_GAP / 2;
        let below_x = |j: usize| x0(lay.below.len()) + j as u32 * (CHIP_W + GAP);
        let (first, last) = (
            below_x(0) + CHIP_W / 2,
            below_x(lay.below.len() - 1) + CHIP_W / 2,
        );
        let _ = write!(
            s,
            r##"  <g stroke="{RULE}" stroke-width="1.5">
    <line x1="{hub_x}" y1="{}" x2="{hub_x}" y2="{bus_y}"/>
    <line x1="{}" y1="{bus_y}" x2="{}" y2="{bus_y}"/>
  </g>
"##,
            top_y + CHIP_H,
            first.min(hub_x),
            last.max(hub_x),
        );
        for (j, &at) in lay.below.iter().enumerate() {
            let x = below_x(j);
            let m = &estate.machines[at];
            one(&mut s, x, below_y, m);
            drop_link(&mut s, x + CHIP_W / 2, bus_y, below_y, m, estate.live, p);
        }
    }

    // ── the clouds, along the bottom ────────────────────────────────────────
    //
    // Only when the caller knows a cloud: an empty "iron comes from" row says
    // there is none, which nobody measured either.
    if estate.clouds.is_empty() {
        s.push_str("</svg>\n");
        return s;
    }
    let y = h - 48;
    let _ = writeln!(
        s,
        r##"  <text x="40" y="{y}" fill="{INK_DIM}" font-size="10.5" font-weight="700">iron comes from</text>"##
    );
    let mut cx = 176;
    for c in &estate.clouds {
        let label = if c.is_default {
            format!("{} · purchases go here", c.name)
        } else if !c.spends {
            format!("{} · this box · cannot spend", c.name)
        } else {
            c.name.clone()
        };
        let w = 14 + (label.chars().count() as u32) * 6;
        let _ = write!(
            s,
            r##"  <rect x="{cx}" y="{}" width="{w}" height="22" rx="5" fill="url(#slot)" stroke="{}" stroke-width="1.4"/>
  <text x="{}" y="{}" fill="{SLOT_INK}" font-size="10" text-anchor="middle" font-family="{MONO}">{}</text>
"##,
            y - 15,
            if c.is_default { CYAN } else { DARK_EDGE },
            cx + w / 2,
            y,
            esc(&label)
        );
        cx += w + 12;
    }

    s.push_str("</svg>\n");
    s
}

/// **The link into `next`, on a live board**: the rail between it and the chip
/// on its left, carrying `next`'s state — a pulse for `up`, a slower amber
/// pulse for `behind`, a still red line for `silent`, a dark dashed one for
/// `unknown`.
fn live_link(s: &mut String, x: u32, top: u32, next: &Machine, p: &Pal) {
    let y = top + CHIP_H / 2;
    let (x1, x2) = (x + CHIP_W + 6, x + CHIP_W + GAP);
    let (stroke, extra, pulse) = link_style(next, p);
    let _ = write!(
        s,
        r##"  <g data-link="{}" data-state="{}">
  <rect x="{}" y="{}" width="6" height="8" fill="{stroke}"/>
  <line x1="{x1}" y1="{y}" x2="{x2}" y2="{y}" stroke="{stroke}" stroke-width="2.5"{extra}>"##,
        esc(&next.id),
        next.liveness.word(),
        x + CHIP_W,
        y - 4,
    );
    if let Some(dur) = pulse {
        let _ = write!(
            s,
            r#"<animate attributeName="stroke-opacity" values="1;0.25;1" dur="{dur}" repeatCount="indefinite"/>"#
        );
    }
    let _ = write!(
        s,
        r##"</line>
  <circle cx="{x2}" cy="{y}" r="4" fill="{stroke}"/>
  </g>
"##
    );
}

/// How the link into `m` is drawn on a live board: its colour, its line, and
/// its pulse — from `m`'s [`Liveness`], never judged here.
fn link_style<'p>(m: &Machine, p: &'p Pal) -> (&'p str, &'static str, Option<&'static str>) {
    match m.liveness {
        Liveness::Up => (&p.UP, "", Some("1.6s")),
        Liveness::Behind => (&p.AMBER, "", Some("3.2s")),
        Liveness::Silent => (&p.RED, "", None),
        // A planned box is not on a live board; drawn as unknown if it is.
        Liveness::Unknown | Liveness::Planned => (&p.DARK_EDGE, r#" stroke-dasharray="4 4""#, None),
    }
}

/// **The drop into a box on the row below**, from the bus at `bus_y` to the
/// chip's top at `top`, at `cx`. On a live board it carries the box's state as
/// the links on the top row do; off, it is a rail like theirs.
#[allow(non_snake_case)]
fn drop_link(s: &mut String, cx: u32, bus_y: u32, top: u32, m: &Machine, live: bool, p: &Pal) {
    let Pal {
        CYAN, RULE, PIN, ..
    } = p;
    let y1 = bus_y + 6;
    if !live {
        let _ = write!(
            s,
            r##"  <rect x="{}" y="{bus_y}" width="8" height="6" fill="{CYAN}"/>
  <line x1="{cx}" y1="{y1}" x2="{cx}" y2="{top}" stroke="{RULE}" stroke-width="1.5"/>
  <circle cx="{cx}" cy="{top}" r="4" fill="{PIN}"/>
"##,
            cx - 4,
        );
        return;
    }
    let (stroke, extra, pulse) = link_style(m, p);
    let _ = write!(
        s,
        r##"  <g data-link="{}" data-state="{}">
  <rect x="{}" y="{bus_y}" width="8" height="6" fill="{stroke}"/>
  <line x1="{cx}" y1="{y1}" x2="{cx}" y2="{top}" stroke="{stroke}" stroke-width="2.5"{extra}>"##,
        esc(&m.id),
        m.liveness.word(),
        cx - 4,
    );
    if let Some(dur) = pulse {
        let _ = write!(
            s,
            r#"<animate attributeName="stroke-opacity" values="1;0.25;1" dur="{dur}" repeatCount="indefinite"/>"#
        );
    }
    let _ = write!(
        s,
        r##"</line>
  <circle cx="{cx}" cy="{top}" r="4" fill="{stroke}"/>
  </g>
"##
    );
}

/// One machine's chip, its top-left corner at (`x`, `top`).
#[allow(non_snake_case)]
fn chip(s: &mut String, x: u32, top: u32, m: &Machine, disk_only: bool, live: bool, p: &Pal) {
    let Pal {
        INK,
        INK_DIM,
        INK_FAINT,
        CYAN,
        RAIL_EDGE,
        CHIP_EDGE,
        DARK_EDGE,
        DARK_INK,
        UP,
        AMBER,
        RED,
        NOT_ASKED,
        CHIP_TOP,
        BAR_TRACK,
        BAR_HIGH,
        ..
    } = p;
    let dark = m.liveness.dark();
    // In black and white a dark chip says so by its outline: dashed.
    let dash = if dark && p.lamps == Lamps::Shape {
        r#" stroke-dasharray="6 4""#
    } else {
        ""
    };
    let (fill, edge, ink, dim) = if dark {
        // On a live board a SILENT box is red-edged: present, and not
        // answering, which is the thing the picture is opened to see.
        (
            "url(#dark)",
            if live && m.liveness == Liveness::Silent {
                RED
            } else {
                DARK_EDGE
            },
            DARK_INK,
            DARK_INK,
        )
    } else if live && m.liveness == Liveness::Behind {
        ("url(#chip)", AMBER, INK, INK_DIM)
    } else {
        (
            if m.role == Role::Appliance {
                "url(#rail)"
            } else {
                "url(#chip)"
            },
            if m.role == Role::Appliance {
                RAIL_EDGE
            } else {
                CHIP_EDGE
            },
            INK,
            INK_DIM,
        )
    };
    let mid = x + CHIP_W / 2;
    let _ = write!(
        s,
        r##"  <rect x="{x}" y="{top}" width="{CHIP_W}" height="{CHIP_H}" rx="12" fill="{fill}" stroke="{edge}" stroke-width="2.2"{dash}/>
  <text x="{mid}" y="{}" fill="{ink}" font-size="14.5" font-weight="700" text-anchor="middle">{}</text>
  <text x="{mid}" y="{}" fill="{dim}" font-size="10.5" text-anchor="middle">{}</text>
"##,
        top + 28,
        esc(&m.id),
        top + 46,
        esc(if m.title.is_empty() {
            m.role.title()
        } else {
            &m.title
        }),
    );

    // The liveness lamp, top-right of the chip. A dot and a word: the colour is
    // the glance and the word is the answer, because a colour alone cannot be
    // read by everyone and this one decides whether there is a second copy of
    // the data.
    // `lamp` is the dot's attributes; `colour` the judge's words beside it.
    let (lamp, word, colour): (String, &str, &str) = match (p.lamps, m.liveness) {
        (Lamps::Hue, Liveness::Up) => (format!(r#"fill="{UP}""#), "up", UP),
        (Lamps::Hue, Liveness::Behind) => (format!(r#"fill="{AMBER}""#), "behind", AMBER),
        (Lamps::Hue, Liveness::Silent) => (format!(r#"fill="{RED}""#), "silent", RED),
        (Lamps::Hue, Liveness::Unknown) => {
            (format!(r#"fill="{NOT_ASKED}""#), "not asked", NOT_ASKED)
        }
        (Lamps::Hue, Liveness::Planned) => (
            format!(r#"fill="none" stroke="{NOT_ASKED}" stroke-width="1.5""#),
            "planned",
            NOT_ASKED,
        ),
        // By line and fill: solid, hollow, dashed, dotted grey — the hollow
        // ones filled with the chip's own ground.
        (Lamps::Shape, Liveness::Up) => (format!(r#"fill="{UP}""#), "up", UP),
        (Lamps::Shape, Liveness::Behind) => (
            format!(r#"fill="{CHIP_TOP}" stroke="{AMBER}" stroke-width="1.5""#),
            "behind",
            AMBER,
        ),
        (Lamps::Shape, Liveness::Silent) => (
            format!(
                r#"fill="{CHIP_TOP}" stroke="{RED}" stroke-width="1.5" stroke-dasharray="2 1.5""#
            ),
            "silent",
            RED,
        ),
        (Lamps::Shape, Liveness::Unknown) => (
            format!(
                r#"fill="{CHIP_TOP}" stroke="{NOT_ASKED}" stroke-width="1" stroke-dasharray="1 1.5""#
            ),
            "not asked",
            NOT_ASKED,
        ),
        (Lamps::Shape, Liveness::Planned) => (
            format!(r#"fill="{CHIP_TOP}" stroke="{INK_DIM}" stroke-width="1.5""#),
            "planned",
            INK_DIM,
        ),
    };
    // The judge's own words, under the title, in the lamp's colour.
    if live && !m.headline.is_empty() {
        let _ = writeln!(
            s,
            r##"  <text x="{}" y="{}" fill="{}" font-size="10.5" font-weight="700" text-anchor="middle">{}</text>"##,
            x + CHIP_W / 2,
            top + 62,
            if m.liveness == Liveness::Unknown {
                INK_DIM.as_str()
            } else {
                colour
            },
            esc(&m.headline)
        );
    }
    let _ = write!(
        s,
        r##"  <circle cx="{}" cy="{}" r="4.5" {lamp}/>
  <text x="{}" y="{}" fill="{dim}" font-size="9.5" text-anchor="end">{word}</text>
"##,
        x + 20,
        top + 22,
        x + CHIP_W - 16,
        top + 26,
    );

    // The provider id, in mono, because it is what gets pasted into a ticket.
    let mut y = top + 72;
    if !m.server_id.is_empty() {
        let label = if m.cloud.is_empty() {
            m.server_id.clone()
        } else {
            format!("{} · {}", m.cloud, m.server_id)
        };
        slot(s, x, y, &label, dark, p);
        y += 38;
    }

    // The three numbers. Disk is MEASURED and says so by carrying a bar; vCPU
    // and memory are the provider's plan and carry the word `plan`, because
    // nothing on this estate reads them off the box.
    match m.disk {
        Some(d) if d.total_bytes > 0 => {
            let pct = (d.used_bytes.saturating_mul(100) / d.total_bytes).min(100);
            slot(
                s,
                x,
                y,
                &format!("disk {} of {}", bytes(d.used_bytes), bytes(d.total_bytes)),
                dark,
                p,
            );
            // The fill bar, BELOW the slot and on its width. Drawn across the
            // slot's lower border at first, which clipped the rounded corner
            // and read as a rendering fault rather than a reading.
            let bw = CHIP_W - 44;
            let fw = (bw as u64 * pct / 100) as u32;
            let _ = write!(
                s,
                r##"  <rect x="{}" y="{}" width="{bw}" height="4" rx="2" fill="{BAR_TRACK}"/>
  <rect x="{}" y="{}" width="{fw}" height="4" rx="2" fill="{}"/>
"##,
                x + 22,
                y + 34,
                x + 22,
                y + 34,
                if dark {
                    DARK_EDGE
                } else if pct >= 80 {
                    BAR_HIGH
                } else {
                    CYAN
                },
            );
            y += 50;
        }
        _ => {
            slot(s, x, y, "disk — · not measured", dark, p);
            y += 50;
        }
    }

    // A topology knows no plan and no growth verdict: the sentence follows the
    // disk directly, and nothing is drawn for what nobody was asked.
    if disk_only {
        note(s, x, y - 6, m, p);
        return;
    }

    // **A reading beats a claim.** Where the product measured the box's own
    // ceilings, those are what is drawn and the caption says `measured`; where
    // it did not, the provider's plan is drawn and the caption says `plan says`.
    // The two must never be mixed on one chip without the caption changing with
    // them, or a picture becomes evidence for something nobody read.
    let measured = m.measured_cpu_millicores.is_some() || m.measured_ram_bytes.is_some();
    let cpu = match (m.measured_cpu_millicores, m.declared_vcpu) {
        // Millicores to cores, one decimal: 2000 is 2.0, and 1500 is a box
        // somebody capped at one and a half rather than a rounding error.
        (Some(mc), _) => format!("{:.1} cores", mc as f64 / 1000.0),
        (None, Some(c)) => format!("{c} vCPU"),
        (None, None) => "— vCPU".to_owned(),
    };
    let ram = match (m.measured_ram_bytes, m.declared_ram_mb) {
        (Some(b), _) => bytes(b),
        (None, Some(mb)) if mb >= 1024 => format!("{} GB", mb / 1024),
        (None, Some(mb)) => format!("{mb} MB"),
        (None, None) => "—".to_owned(),
    };
    slot(s, x, y, &format!("{cpu} · {ram}"), dark, p);
    let _ = writeln!(
        s,
        r##"  <text x="{}" y="{}" fill="{INK_FAINT}" font-size="9" text-anchor="middle">{}</text>"##,
        x + CHIP_W / 2,
        y + 44,
        if measured {
            match &m.plan {
                Some(p) => esc(&format!("measured · plan {p}")),
                None => "measured".to_owned(),
            }
        } else if m.plan.is_some() || m.declared_vcpu.is_some() {
            match &m.plan {
                Some(p) => esc(&format!("plan says · {p}")),
                None => "plan says · not measured".to_owned(),
            }
        } else {
            "no plan declared".to_owned()
        }
    );
    y += 62;

    // Growth, and then whatever sentence the caller had.
    let grow = match m.disk {
        Some(d) if d.growable => "can grow",
        Some(_) => "cannot grow",
        None => "growth unknown",
    };
    let _ = writeln!(
        s,
        r##"  <text x="{}" y="{y}" fill="{dim}" font-size="9.5" text-anchor="middle">{grow}</text>"##,
        x + CHIP_W / 2,
    );
    note(s, x, y, m, p);
}

/// The machine's own sentence, at most two wrapped lines under `y`.
#[allow(non_snake_case)]
fn note(s: &mut String, x: u32, y: u32, m: &Machine, p: &Pal) {
    let Pal { INK_FAINT, .. } = p;
    if !m.note.is_empty() {
        for (i, line) in wrap(&m.note, 42).into_iter().enumerate().take(2) {
            let _ = writeln!(
                s,
                r##"  <text x="{}" y="{}" fill="{INK_FAINT}" font-size="9" text-anchor="middle">{}</text>"##,
                x + CHIP_W / 2,
                y + 18 + (i as u32) * 12,
                esc(&line)
            );
        }
    }
}

/// One inset slot with a mono label — skade's inner-rectangle idiom.
#[allow(non_snake_case)]
fn slot(s: &mut String, x: u32, y: u32, label: &str, dark: bool, p: &Pal) {
    let Pal {
        SLOT_INK,
        CYAN,
        DARK_EDGE,
        DARK_INK,
        ..
    } = p;
    let _ = write!(
        s,
        r##"  <rect x="{}" y="{y}" width="{}" height="28" rx="6" fill="url(#slot)" stroke="{}" stroke-width="1.5"/>
  <text x="{}" y="{}" fill="{}" font-size="10" text-anchor="middle" font-family="{MONO}">{}</text>
"##,
        x + 22,
        CHIP_W - 44,
        if dark { DARK_EDGE } else { CYAN },
        x + CHIP_W / 2,
        y + 18,
        if dark { DARK_INK } else { SLOT_INK },
        esc(label)
    );
}

/// XML-escape. Five entities, because a provider id or an operator's note is
/// somebody else's string and one `&` in it is a document that will not parse.
fn esc(raw: &str) -> String {
    let mut out = String::with_capacity(raw.len());
    for c in raw.chars() {
        match c {
            '&' => out.push_str("&amp;"),
            '<' => out.push_str("&lt;"),
            '>' => out.push_str("&gt;"),
            '"' => out.push_str("&quot;"),
            '\'' => out.push_str("&apos;"),
            _ => out.push(c),
        }
    }
    out
}

/// Greedy wrap on spaces. SVG does not reflow text, so a sentence that must fit
/// a chip is broken here or it runs off the board.
fn wrap(text: &str, cols: usize) -> Vec<String> {
    let mut lines = Vec::new();
    let mut line = String::new();
    for word in text.split_whitespace() {
        if !line.is_empty() && line.chars().count() + 1 + word.chars().count() > cols {
            lines.push(std::mem::take(&mut line));
        }
        if !line.is_empty() {
            line.push(' ');
        }
        line.push_str(word);
    }
    if !line.is_empty() {
        lines.push(line);
    }
    lines
}

/// Bytes in the units an operator reads. Binary, like every other byte figure
/// in this system.
fn bytes(n: u64) -> String {
    const K: u64 = 1024;
    let (v, unit) = match n {
        n if n >= K * K * K * K => (n as f64 / (K * K * K * K) as f64, "TiB"),
        n if n >= K * K * K => (n as f64 / (K * K * K) as f64, "GiB"),
        n if n >= K * K => (n as f64 / (K * K) as f64, "MiB"),
        n if n >= K => (n as f64 / K as f64, "KiB"),
        n => return format!("{n} B"),
    };
    format!("{v:.1} {unit}")
}

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

    /// skade's state colours as the drawing writes them.
    const RED: &str = "var(--estate-silent, #e0574f)";
    const AMBER: &str = "var(--estate-behind, #e0a34f)";

    fn front() -> Machine {
        Machine {
            id: "gunnar-front".into(),
            role: Role::Front,
            title: String::new(),
            cloud: "upcloud".into(),
            server_id: "00f9e1a2".into(),
            plan: Some("2xCPU-4GB".into()),
            declared_vcpu: Some(2),
            declared_ram_mb: Some(4096),
            disk: Some(Disk {
                used_bytes: 9 << 30,
                total_bytes: 50 << 30,
                growable: false,
            }),
            liveness: Liveness::Up,
            measured_cpu_millicores: None,
            measured_ram_bytes: None,
            note: String::new(),
            headline: String::new(),
        }
    }

    fn appliance() -> Machine {
        Machine {
            id: "gunnar-appliance".into(),
            role: Role::Appliance,
            title: String::new(),
            cloud: "upcloud".into(),
            server_id: "0057bb10".into(),
            plan: Some("2xCPU-4GB".into()),
            declared_vcpu: Some(2),
            declared_ram_mb: Some(4096),
            disk: Some(Disk {
                used_bytes: 43_714_560,
                total_bytes: 40_588_369_920,
                growable: false,
            }),
            liveness: Liveness::Up,
            measured_cpu_millicores: Some(2000),
            measured_ram_bytes: Some(4 << 30),
            note: "grows in place; this boot stack carries no engine".into(),
            headline: String::new(),
        }
    }

    fn twin_dark() -> Machine {
        Machine {
            id: "gunnar-twin".into(),
            role: Role::Twin,
            title: String::new(),
            cloud: "upcloud".into(),
            server_id: "01a2ff03".into(),
            plan: None,
            declared_vcpu: None,
            declared_ram_mb: None,
            disk: None,
            liveness: Liveness::Silent,
            measured_cpu_millicores: None,
            measured_ram_bytes: None,
            note: "has not polled since this server started".into(),
            headline: String::new(),
        }
    }

    fn estate(machines: Vec<Machine>) -> Estate {
        Estate {
            product: "gunnar".into(),
            machines,
            clouds: vec![
                CloudRow {
                    name: "upcloud".into(),
                    spends: true,
                    is_default: true,
                },
                CloudRow {
                    name: "server".into(),
                    spends: false,
                    is_default: false,
                },
            ],
            tenants: Some(11),
            ..Estate::default()
        }
    }

    /// ★ **The question this picture is opened to answer.** A twin that has
    /// never been seen is DRAWN — dimmed, lamp red, with its own sentence —
    /// because leaving it out reads as "this design has no twin", and it has
    /// one. It is dark.
    #[test]
    fn a_dark_twin_is_drawn_and_says_so() {
        let svg = estate_svg(&estate(vec![front(), appliance(), twin_dark()]));
        assert!(svg.contains("gunnar-twin"), "the twin is not on the board");
        assert!(svg.contains("silent"), "the twin's state is not named");
        assert!(
            svg.contains("has not polled"),
            "the twin's reason is not carried"
        );
        assert!(
            svg.contains("url(#dark)"),
            "a silent box is drawn as a live one"
        );
    }

    /// A twin that answered is an ordinary chip: no dark fill, and the lamp
    /// says so in a word as well as a colour.
    #[test]
    fn a_live_twin_is_an_ordinary_chip() {
        let mut up = twin_dark();
        up.liveness = Liveness::Up;
        up.disk = Some(Disk {
            used_bytes: 26_175_638,
            total_bytes: 21_474_836_480,
            growable: true,
        });
        up.note = String::new();
        let svg = estate_svg(&estate(vec![front(), appliance(), up]));
        assert!(!svg.contains("url(#dark)"), "a live estate is drawn dimmed");
        assert!(svg.contains(">up<"), "the lamp does not say up");
        assert!(svg.contains("can grow"), "the twin's growth is not stated");
    }

    /// **A declaration is never drawn as a measurement.** vCPU and memory come
    /// off the provider's plan, and the picture says `plan says` beside them;
    /// the disk is read off the box and carries a bar instead. Getting this
    /// backwards is how a diagram becomes evidence for something nobody
    /// measured.
    #[test]
    fn declared_numbers_are_labelled_and_missing_ones_are_dashes() {
        let svg = estate_svg(&estate(vec![front(), appliance(), twin_dark()]));
        assert!(svg.contains("plan says"), "a declared plan is not labelled");
        assert!(svg.contains("— vCPU"), "an unknown vCPU count was invented");
        assert!(
            svg.contains("not measured"),
            "an unmeasured disk is not admitted"
        );
    }

    /// The buyer's rule, on the picture, because the picture is where somebody
    /// would otherwise get the idea that a customer picks a box.
    #[test]
    fn the_board_states_that_a_buyer_names_no_machine() {
        let svg = estate_svg(&estate(vec![front()]));
        assert!(
            svg.contains("never a machine"),
            "the placement rule is not on the board"
        );
    }

    /// Deterministic, so the output can be committed and a diff means the
    /// ESTATE changed.
    #[test]
    fn the_same_estate_draws_the_same_bytes() {
        let e = estate(vec![front(), appliance(), twin_dark()]);
        assert_eq!(estate_svg(&e), estate_svg(&e));
    }

    /// Somebody else's string reaches this document — a provider id, an
    /// operator's note — and one `&` in it is an SVG that will not parse.
    #[test]
    fn a_hostile_note_cannot_break_the_document() {
        let mut m = front();
        m.note = "a & b <script>alert('x')</script>".into();
        m.server_id = "id\"with&quotes".into();
        let svg = estate_svg(&estate(vec![m]));
        assert!(
            !svg.contains("<script>"),
            "markup from a note reached the document"
        );
        assert!(svg.contains("&amp;"), "an ampersand was not escaped");
    }

    /// ★ **A reading beats a claim, and the caption moves with it.** The
    /// appliance's cpu and memory are MEASURED (`Admin.Capacity`'s ceilings),
    /// so its chip says `measured` and shows cores; the front was not measured,
    /// so its chip keeps the plan and says `plan says`. Mixing the two under
    /// one caption is how a picture becomes evidence for a number nobody read.
    #[test]
    fn a_measured_box_says_measured_and_an_unmeasured_one_says_plan() {
        let svg = estate_svg(&estate(vec![front(), appliance(), twin_dark()]));
        assert!(
            svg.contains("2.0 cores"),
            "the measured cpu is not drawn: {svg}"
        );
        assert!(
            svg.contains("measured · plan 2xCPU-4GB"),
            "the measured caption is missing"
        );
        assert!(
            svg.contains("plan says · 2xCPU-4GB"),
            "the unmeasured box lost its plan caption"
        );
    }

    /// An empty estate is a board with nothing on it, not a panic and not a
    /// zero-width viewBox.
    #[test]
    fn an_empty_estate_still_draws_a_board() {
        let svg = estate_svg(&Estate::default());
        assert!(svg.starts_with("<svg"), "{svg}");
        assert!(svg.ends_with("</svg>\n"));
    }

    // ── the gunnar door (2026-10-04): a topology drawn, disk-only ──────────

    fn gunnar_box(id: &str, role: &str, state: &str, address: &str) -> Machine {
        Machine {
            id: id.into(),
            role: Role::from_word(role).unwrap_or(Role::Console),
            title: String::new(),
            cloud: String::new(),
            server_id: address.into(),
            plan: None,
            declared_vcpu: None,
            declared_ram_mb: None,
            measured_cpu_millicores: None,
            measured_ram_bytes: None,
            disk: None,
            liveness: Liveness::from_word(state),
            note: String::new(),
            headline: String::new(),
        }
    }

    fn topology(machines: Vec<Machine>) -> Estate {
        Estate {
            product: "gunnar".into(),
            machines,
            subtitle: Some("the boxes this gunnar has evidence of".into()),
            disk_only: true,
            ..Estate::default()
        }
    }

    /// ★ **A topology chip says only what a topology knows**: no cpu/memory
    /// row, no "plan" caption, no growth line — and no cloud footer and no
    /// buyer's line, which are monetize's to state and not a gunnar console's.
    #[test]
    fn a_disk_only_board_draws_no_plan_no_growth_and_no_shop() {
        let mut primary = gunnar_box("vm2", "primary", "up", "10.13.8.121");
        primary.disk = Some(Disk {
            used_bytes: 1 << 30,
            total_bytes: 40 << 30,
            growable: false,
        });
        let svg = estate_svg(&topology(vec![
            primary,
            gunnar_box("gunnar-ui", "console", "up", "127.0.0.1:41000"),
        ]));
        for gone in [
            "vCPU",
            "plan",
            "grow",
            "iron comes from",
            "buyer",
            "never a machine",
        ] {
            assert!(!svg.contains(gone), "`{gone}` on a topology board: {svg}");
        }
        assert!(
            svg.contains("the boxes this gunnar has evidence of"),
            "the caller's own subtitle"
        );
        assert!(
            svg.contains("disk 1.0 GiB of 40.0 GiB"),
            "the measured disk is drawn"
        );
        assert!(
            svg.contains(">10.13.8.121<"),
            "an address with no cloud is drawn bare, not ` · addr`"
        );
        assert!(
            svg.contains(">console<"),
            "a console with no title draws its role word"
        );
    }

    /// `behind` is LIT with an amber lamp — the twin is there and answering —
    /// and an unknown word is `unknown`, never `up`.
    #[test]
    fn behind_is_lit_amber_and_an_unknown_word_is_not_up() {
        let svg = estate_svg(&topology(vec![gunnar_box("vm3", "twin", "behind", "")]));
        assert!(svg.contains(">behind<") && svg.contains("#e0a34f"), "{svg}");
        assert!(
            !svg.contains("url(#dark)"),
            "a twin that answers is not drawn dark"
        );
        assert_eq!(Liveness::from_word("weird"), Liveness::Unknown);
        assert_eq!(Role::from_word("weird"), None);
        assert_eq!(Role::from_word("primary"), Some(Role::Appliance));
    }

    /// front | main | twin keep the 1180 × 560 board; a box beyond them goes
    /// to a second row (the board grows down, not off its edge), and a row of
    /// four widens it: 4 × 300 + 3 × 40 + 2 × 40 = 1400.
    #[test]
    fn more_than_three_machines_widen_the_board() {
        let spine = estate_svg(&topology(vec![
            gunnar_box("f", "front", "up", ""),
            gunnar_box("m", "primary", "up", ""),
            gunnar_box("t", "twin", "up", ""),
        ]));
        assert!(spine.contains(r#"viewBox="0 0 1180 560""#), "{spine}");
        let three = estate_svg(&topology(
            (0..3)
                .map(|i| gunnar_box(&format!("t{i}"), "twin", "up", ""))
                .collect(),
        ));
        assert!(
            three.contains(r#"viewBox="0 0 1180 900""#),
            "one twin on the spine, two below: {three}"
        );
        let five = estate_svg(&topology(
            (0..5)
                .map(|i| gunnar_box(&format!("t{i}"), "twin", "up", ""))
                .collect(),
        ));
        assert!(
            five.contains(r#"viewBox="0 0 1400 900""#),
            "four below: 4 × 300 + 3 × 40 + 2 × 40 = 1400: {five}"
        );
    }

    // ── the live board (2026-10-04): the state, drawn to be swapped in place ──

    fn live_estate() -> Estate {
        let mut main = gunnar_box("vm2", "primary", "up", "10.13.8.121");
        main.headline = "this box".into();
        let mut twin = gunnar_box("vm3", "twin", "silent", "10.13.8.122");
        twin.headline = "silent for 4 min".into();
        let mut front = gunnar_box("front", "front", "up", "10.13.8.120");
        front.headline = "calling".into();
        let mut e = topology(vec![main, twin, front]);
        e.live = true;
        e
    }

    /// ★ **A dead twin is obvious** (owner, 2026-10-04): its chip names its box
    /// and its state, its edge is red, gunnar's own words ("silent for 4 min")
    /// sit under the title in red, and the link into it is a STILL red line —
    /// while the link into a box that answers pulses. The links run in
    /// reading order (owner, 2026-10-05): front → main, main → twin, whatever
    /// order gunnar sent the rows in.
    #[test]
    fn a_live_board_draws_a_silent_twin_red_and_a_live_link_pulsing() {
        let svg = estate_svg(&live_estate());
        let (red, red_stroke) = (format!(r#"fill="{RED}""#), format!(r#"stroke="{RED}""#));
        assert!(
            svg.contains(r#"data-box="vm3" data-state="silent""#),
            "the silent twin is not a named group: {svg}"
        );
        assert!(svg.contains(r#"data-box="front" data-state="up""#));
        let twin = &svg[svg.find(r#"data-box="vm3""#).unwrap()..];
        let twin = &twin[..twin.find("</g>").unwrap()];
        assert!(
            twin.contains(&red_stroke),
            "the silent chip's edge is not red: {twin}"
        );
        assert!(
            twin.contains(">silent for 4 min<") && twin.contains(&red),
            "gunnar's words are not drawn red: {twin}"
        );
        let links: Vec<&str> = svg
            .match_indices("<g data-link=")
            .map(|(i, _)| &svg[i..i + svg[i..].find("</g>").unwrap()])
            .collect();
        assert_eq!(links.len(), 2, "front → main and main → twin: {svg}");
        assert!(
            links[0].contains(r#"data-link="vm2" data-state="up""#)
                && links[0].contains("<animate"),
            "the link front → main does not pulse into a live main: {}",
            links[0]
        );
        assert!(
            links[1].contains(r#"data-link="vm3" data-state="silent""#)
                && links[1].contains(&red_stroke)
                && !links[1].contains("<animate"),
            "the link main → twin is not a still red line: {}",
            links[1]
        );
    }

    /// `behind` is amber on the edge, the words and the link — and still lit.
    #[test]
    fn a_live_board_draws_behind_amber() {
        let mut e = live_estate();
        e.machines[1].liveness = Liveness::Behind;
        e.machines[1].headline = "behind 120 entries".into();
        let svg = estate_svg(&e);
        let twin = &svg[svg
            .find(r#"data-box="vm3" data-state="behind""#)
            .expect("behind group")..];
        let twin = &twin[..twin.find("</g>").unwrap()];
        assert!(
            twin.contains(&format!(r#"stroke="{AMBER}""#)) && twin.contains(">behind 120 entries<"),
            "{twin}"
        );
        assert!(!twin.contains("url(#dark)"));
    }

    /// Off, nothing of it: monetize's board carries no groups, no animation,
    /// no headline, and a silent chip keeps its dark edge.
    #[test]
    fn a_board_that_is_not_live_is_unchanged() {
        let mut e = live_estate();
        e.live = false;
        let svg = estate_svg(&e);
        for gone in ["data-box", "data-link", "<animate", "silent for 4 min"] {
            assert!(!svg.contains(gone), "`{gone}` on a board that is not live");
        }
        assert!(svg.contains("url(#dark)") && !svg.contains(&format!(r#"stroke="{RED}""#)));
    }

    // ── reading order (owner, 2026-10-05): front | main | twin, always ──────

    /// Where a chip is: the (x, y) its name is drawn at (the 14.5 title).
    fn at(svg: &str, id: &str) -> (u32, u32) {
        let end = svg
            .find(&format!(
                r#"font-size="14.5" font-weight="700" text-anchor="middle">{}</text>"#,
                esc(id)
            ))
            .unwrap_or_else(|| panic!("no chip named {id}: {svg}"));
        let start = svg[..end].rfind("<text ").unwrap();
        let num = |attr: &str| -> u32 {
            let tag = &svg[start..end];
            let v = &tag[tag.find(attr).unwrap() + attr.len()..];
            v[..v.find('"').unwrap()].parse().unwrap()
        };
        (num(r#" x=""#), num(r#" y=""#))
    }

    /// Every chip's name, row by row, left to right — what a reader reads.
    fn reading(svg: &str, ids: &[&str]) -> Vec<Vec<String>> {
        let mut placed: Vec<(u32, u32, &str)> = ids
            .iter()
            .map(|id| {
                let (x, y) = at(svg, id);
                (y, x, *id)
            })
            .collect();
        placed.sort();
        let mut rows: Vec<Vec<String>> = Vec::new();
        let mut last = None;
        for (y, _, id) in placed {
            if last != Some(y) {
                rows.push(Vec::new());
                last = Some(y);
            }
            rows.last_mut().unwrap().push(id.to_owned());
        }
        rows
    }

    fn shaped(rows: &[(&str, &str)], live: bool) -> String {
        let mut e = topology(
            rows.iter()
                .map(|(id, role)| gunnar_box(id, role, "up", ""))
                .collect(),
        );
        e.live = live;
        estate_svg(&e)
    }

    /// ★ **front on the left, the server in the middle, the twin on the
    /// right** (owner, 2026-10-05: "in reading order") — for every estate
    /// shape, and whatever order the rows arrive in (gunnar sends THIS box
    /// first; monetize sorts by role): solo, front + server, server + twin
    /// with no front, and all six orders of the three.
    #[test]
    fn the_board_reads_front_main_twin_left_to_right_for_every_shape() {
        for live in [false, true] {
            assert_eq!(
                reading(&shaped(&[("main", "primary")], live), &["main"]),
                [["main"]],
                "solo"
            );
            for rows in [
                [("main", "primary"), ("front", "front")],
                [("front", "front"), ("main", "primary")],
            ] {
                let svg = shaped(&rows, live);
                assert_eq!(
                    reading(&svg, &["front", "main"]),
                    [["front", "main"]],
                    "front + server from {rows:?}"
                );
                assert!(at(&svg, "front").0 < at(&svg, "main").0);
            }
            for rows in [
                [("twin", "twin"), ("main", "primary")],
                [("main", "primary"), ("twin", "twin")],
            ] {
                let svg = shaped(&rows, live);
                assert_eq!(
                    reading(&svg, &["main", "twin"]),
                    [["main", "twin"]],
                    "server + twin, no front, from {rows:?}"
                );
            }
            for rows in [
                [("front", "front"), ("main", "primary"), ("twin", "twin")],
                [("front", "front"), ("twin", "twin"), ("main", "primary")],
                [("main", "primary"), ("front", "front"), ("twin", "twin")],
                [("main", "primary"), ("twin", "twin"), ("front", "front")],
                [("twin", "twin"), ("front", "front"), ("main", "primary")],
                [("twin", "twin"), ("main", "primary"), ("front", "front")],
            ] {
                let svg = shaped(&rows, live);
                let (f, m, t) = (at(&svg, "front"), at(&svg, "main"), at(&svg, "twin"));
                assert!(
                    f.0 < m.0 && m.0 < t.0 && f.1 == m.1 && m.1 == t.1,
                    "front {f:?} main {m:?} twin {t:?} from {rows:?}"
                );
            }
        }
    }

    /// ★ **Extra boxes never break front | main | twin**: consoles, monetize,
    /// standbys and a second twin go on a row BELOW, and the top row is
    /// exactly the three, in reading order.
    #[test]
    fn extra_boxes_go_on_a_row_below_and_never_between() {
        let rows = [
            ("vm2", "primary"),
            ("gunnar-ui", "console"),
            ("vm3", "twin"),
            ("mon", "monetize"),
            ("front", "front"),
            ("sb", "standby"),
            ("vm4", "twin"),
        ];
        let ids: Vec<&str> = rows.iter().map(|r| r.0).collect();
        for live in [false, true] {
            let svg = shaped(&rows, live);
            let read = reading(&svg, &ids);
            assert_eq!(read.len(), 2, "two rows: {read:?}");
            assert_eq!(
                read[0],
                ["front", "vm2", "vm3"],
                "the top row is front | main | twin"
            );
            let mut below = read[1].clone();
            below.sort();
            assert_eq!(
                below,
                ["gunnar-ui", "mon", "sb", "vm4"],
                "every extra box is on the row below"
            );
            assert!(
                svg.contains(r#"viewBox="0 0 1400 900""#),
                "four below widen the board, the second row deepens it: {svg}"
            );
        }
    }

    /// The links follow the reading order: on a live board the link INTO main
    /// lies between front and main, the link into the twin between main and
    /// twin — and every box below still carries its state on a link of its own.
    #[test]
    fn the_links_run_front_to_main_and_main_to_twin() {
        let svg = shaped(
            &[
                ("main", "primary"),
                ("twin", "twin"),
                ("front", "front"),
                ("ui", "console"),
            ],
            true,
        );
        let line_x = |id: &str| -> (u32, u32) {
            let g = &svg[svg
                .find(&format!(r#"<g data-link="{id}""#))
                .unwrap_or_else(|| panic!("no link into {id}: {svg}"))..];
            let g = &g[..g.find("</g>").unwrap()];
            let n = |attr: &str| -> u32 {
                let v = &g[g.find(attr).unwrap() + attr.len()..];
                v[..v.find('"').unwrap()].parse().unwrap()
            };
            (n(r#" x1=""#), n(r#" x2=""#))
        };
        let (f, m, t) = (at(&svg, "front").0, at(&svg, "main").0, at(&svg, "twin").0);
        let into_main = line_x("main");
        let into_twin = line_x("twin");
        assert!(
            f < into_main.0 && into_main.1 < m,
            "front → main lies between them: {into_main:?}, front {f}, main {m}"
        );
        assert!(
            m < into_twin.0 && into_twin.1 < t,
            "main → twin lies between them: {into_twin:?}, main {m}, twin {t}"
        );
        assert!(
            !svg.contains(r#"<g data-link="front""#),
            "nothing on the board's left links into the front"
        );
        assert!(
            svg.contains(r#"<g data-link="ui" data-state="up""#),
            "the console below carries its state on its own link"
        );
    }

    // ── the page's theme (owner, 2026-10-05): one drawing, both themes ──────

    /// Every colour the board is drawn in, both kinds of board, every state.
    fn rich() -> Vec<String> {
        let mut full = front();
        full.disk = Some(Disk {
            used_bytes: 45 << 30,
            total_bytes: 50 << 30,
            growable: true,
        });
        let mut behind = twin_dark();
        behind.liveness = Liveness::Behind;
        behind.id = "behind".into();
        let mut unknown = twin_dark();
        unknown.liveness = Liveness::Unknown;
        unknown.id = "unknown".into();
        unknown.headline = "never asked".into();
        let mut console = gunnar_box("ui", "console", "up", "127.0.0.1");
        console.headline = "calling".into();
        let machines = vec![full, appliance(), twin_dark(), behind, unknown, console];
        let mut e = estate(machines);
        let flat = estate_svg(&e);
        e.live = true;
        e.machines[2].headline = "silent for 4 min".into();
        vec![flat, estate_svg(&e)]
    }

    /// The value of every `name="…"` attribute in the document.
    fn values<'a>(svg: &'a str, name: &str) -> Vec<&'a str> {
        let key = format!(r#" {name}=""#);
        svg.match_indices(&key)
            .map(|(i, _)| {
                let v = &svg[i + key.len()..];
                &v[..v.find('"').unwrap()]
            })
            .collect()
    }

    /// ★ **No colour is hard-coded** (owner, 2026-10-05: "make it follow the
    /// chosen theme of the web site"): every fill, stroke and gradient stop is
    /// a page token with skade's colour as its fallback —
    /// `var(--estate-<name>, #rrggbb)` — or a gradient reference or `none`.
    /// Drawn INLINE, the page's tokens reach it and a theme switch repaints
    /// it with no fetch; drawn as an `<img>`, in a README or a PDF, nothing
    /// reaches it and it is skade's board.
    #[test]
    fn every_colour_is_a_page_token_with_a_fallback() {
        let token = |v: &str| -> bool {
            let Some(inner) = v
                .strip_prefix("var(--estate-")
                .and_then(|r| r.strip_suffix(')'))
            else {
                return false;
            };
            let Some((name, hex)) = inner.split_once(", #") else {
                return false;
            };
            !name.is_empty()
                && name.chars().all(|c| c.is_ascii_lowercase() || c == '-')
                && hex.len() == 6
                && hex.chars().all(|c| c.is_ascii_hexdigit())
        };
        for svg in rich() {
            let mut seen = 0;
            for attr in ["fill", "stroke", "stop-color", "color"] {
                for v in values(&svg, attr) {
                    seen += 1;
                    assert!(
                        token(v) || v == "none" || (v.starts_with("url(#") && v.ends_with(')')),
                        "{attr}=\"{v}\" is not a page token with a fallback"
                    );
                }
            }
            assert!(seen > 50, "the board draws in colour at all: {seen}");
            // And no colour hides anywhere else: every `#` is a fallback or a
            // gradient reference.
            let hashes = svg.matches('#').count();
            let fallbacks = svg.matches(", #").count();
            let refs = svg.matches("url(#").count();
            assert_eq!(
                hashes,
                fallbacks + refs,
                "a colour outside a token's fallback: {svg}"
            );
        }
    }

    /// The fallbacks are skade's palette exactly, so a board nobody themes —
    /// monetize's `<img>`, a README, a PDF — looks as it always has.
    #[test]
    fn the_fallbacks_are_skades_board() {
        let svg = estate_svg(&live_estate());
        for (token, hex) in [
            ("board-top", "#0b1c2e"),
            ("board-bottom", "#06111d"),
            ("chip-top", "#14384f"),
            ("ink", "#eaf6ff"),
            ("chip-edge", "#5aa6cf"),
            ("rail-edge", "#7fc8ea"),
            ("up", "#5fd39a"),
            ("silent", "#e0574f"),
            ("dark-edge", "#3d5f74"),
        ] {
            assert!(
                svg.contains(&format!("var(--estate-{token}, {hex})")),
                "--estate-{token} does not fall back to skade's {hex}: {svg}"
            );
        }
        let mut e = live_estate();
        e.machines[1].liveness = Liveness::Behind;
        assert!(
            estate_svg(&e).contains("var(--estate-behind, #e0a34f)"),
            "behind is skade's amber"
        );
    }

    /// ★ **The state stays legible in every theme**: up, behind and silent are
    /// three tokens a page maps to its own pass / warn / bad — and besides the
    /// hue each keeps its line and fill: up pulses, behind pulses slower,
    /// silent stands still, unknown is dashed; a silent or unasked chip is
    /// drawn unlit.
    #[test]
    fn the_states_are_tokens_and_keep_their_line_and_fill() {
        let mut e = live_estate();
        e.machines.push(gunnar_box("vm4", "twin", "behind", ""));
        e.machines.push(gunnar_box("vm5", "standby", "unknown", ""));
        let svg = estate_svg(&e);
        let link = |id: &str| -> String {
            let g = &svg[svg
                .find(&format!(r#"<g data-link="{id}""#))
                .unwrap_or_else(|| panic!("no link into {id}: {svg}"))..];
            g[..g.find("</g>").unwrap()].to_owned()
        };
        let chip = |id: &str| -> String {
            let g = &svg[svg.find(&format!(r#"<g data-box="{id}""#)).unwrap()..];
            g[..g.find("</g>").unwrap()].to_owned()
        };
        let (up, behind, silent, unknown) = (link("vm2"), link("vm4"), link("vm3"), link("vm5"));
        assert!(
            up.contains("var(--estate-up, ") && up.contains(r#"dur="1.6s""#),
            "{up}"
        );
        assert!(
            behind.contains("var(--estate-behind, ") && behind.contains(r#"dur="3.2s""#),
            "{behind}"
        );
        assert!(
            silent.contains("var(--estate-silent, ")
                && !silent.contains("<animate")
                && !silent.contains("dasharray"),
            "{silent}"
        );
        assert!(
            unknown.contains("stroke-dasharray") && !unknown.contains("<animate"),
            "{unknown}"
        );
        assert!(
            chip("vm3").contains("url(#dark)") && chip("vm5").contains("url(#dark)"),
            "silent and unasked chips are unlit"
        );
        assert!(
            !chip("vm4").contains("url(#dark)") && !chip("vm2").contains("url(#dark)"),
            "behind and up are lit"
        );
    }

    /// ★ **An upstream is drawn ABOVE the main box** (holger A13, 2026-10-23):
    /// where the bytes come from sits over the box that takes them, the
    /// downstreams hang below, and the link from each upstream into the main
    /// box carries the upstream's state. A board without one is unchanged.
    #[test]
    fn an_upstream_sits_on_a_row_above_the_main_box() {
        let up = |id: &str, live: Liveness| Machine {
            id: id.into(),
            role: Role::Upstream,
            title: "upstream · proxy".into(),
            server_id: "https://index.crates.io".into(),
            liveness: live,
            headline: "answering".into(),
            ..appliance()
        };
        let mut down = appliance();
        down.id = "onprem-1".into();
        down.role = Role::Standby;
        let base = Estate {
            product: "holger".into(),
            machines: vec![front(), appliance(), down.clone()],
            clouds: vec![],
            tenants: None,
            subtitle: None,
            disk_only: true,
            live: true,
        };
        let plain = estate_svg(&base);
        let mut with = base.clone();
        with.machines.insert(0, up("crates-io", Liveness::Up));
        with.machines.push(up("pypi", Liveness::Silent));
        let drawn = estate_svg(&with);
        let height = |svg: &str| {
            svg.split("viewBox=\"0 0 ")
                .nth(1)
                .unwrap()
                .split('"')
                .next()
                .unwrap()
                .split(' ')
                .nth(1)
                .unwrap()
                .parse::<u32>()
                .unwrap()
        };
        assert_eq!(
            height(&drawn),
            height(&plain) + CHIP_H + ROW_GAP,
            "one more row"
        );
        let at = |svg: &str, id: &str| {
            svg.find(&format!(r#"<g data-box="{id}""#))
                .unwrap_or_else(|| panic!("{id} not drawn"))
        };
        assert!(
            at(&drawn, "crates-io") < at(&drawn, "gunnar-appliance")
                && at(&drawn, "pypi") < at(&drawn, "gunnar-appliance"),
            "the upstreams come first, on the row above"
        );
        assert!(at(&drawn, "gunnar-appliance") < at(&drawn, "onprem-1"));
        let link = |id: &str| {
            drawn
                .split(&format!(r#"<g data-link="{id}""#))
                .nth(1)
                .map(|r| r.split("</g>").next().unwrap().to_string())
        };
        assert!(
            link("crates-io").unwrap().contains(r#"data-state="up""#)
                && link("crates-io").unwrap().contains("<animate")
        );
        assert!(
            link("pypi").unwrap().contains(r#"data-state="silent""#)
                && link("pypi").unwrap().contains(RED)
        );
        // The main box's chip is pushed down by exactly one row.
        let chip_y = |svg: &str, id: &str| {
            svg[at(svg, id)..]
                .split("<rect x=\"")
                .nth(1)
                .unwrap()
                .split("y=\"")
                .nth(1)
                .unwrap()
                .split('"')
                .next()
                .unwrap()
                .parse::<u32>()
                .unwrap()
        };
        assert_eq!(
            chip_y(&drawn, "gunnar-appliance"),
            chip_y(&plain, "gunnar-appliance") + CHIP_H + ROW_GAP
        );
        assert_eq!(
            chip_y(&drawn, "crates-io"),
            chip_y(&plain, "gunnar-appliance")
        );
        assert_eq!(Role::Upstream.title(), "upstream");
        assert_eq!(Role::from_word("upstream"), Some(Role::Upstream));
    }
}