src/viz/surfaces.rs
//! **S3c — the PANE DISCOVERY REGISTRY: the formal, stable enumeration of every
//! addressable viz surface the atom-walk + functional matrix walk.**
//!
//! Before this, the only surface enumeration was [`Tab::ALL`] (20 variants) plus
//! ad-hoc `state_json` slicing in the matrix. That left the 🧬 Nornir tab's two
//! SUB-PANELS — `populate_status` (Viz.CloneEvents) and `jobs` (Viz.Jobs) — with
//! NO place in any list (they hide *inside* a single `Tab::ALL` variant), so they
//! slipped the walk before. [`viz_surfaces`] is the canonical list the walk drives
//! instead: one [`VizSurface`] per `Tab::ALL` variant, PLUS the known sub-panels.
//!
//! Each surface carries the STABLE AccessKit `id_salt` ([`VizSurface::id`], = the
//! pane's `ui.push_id` salt at its render root) so an atom is uniquely addressable
//! by the kit, and the `has_remote`/`has_local` capability flags the matrix reads.
//!
//! ANTI-DRIFT: `surfaces_cover_every_tab` (in `app.rs`, where `Tab::ALL` is in
//! scope) fails the build if [`viz_surfaces`] stops covering a `Tab::ALL` variant
//! — so adding a tab forces a registry entry, mirroring `viz_tabs_const_matches_tab_enum`.
use super::app::Tab;
/// One mechanically-addressable viz surface — either a top-level tab pane (one per
/// [`Tab::ALL`] variant) or a known sub-panel that hides inside a tab. The atom-walk
/// and functional matrix enumerate `viz_surfaces()` to cover EVERY surface; the
/// `id` is the stable AccessKit `id_salt` pushed at the pane's render root.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VizSurface {
/// The tab this surface lives on (top-level pane, or the tab a sub-panel hides
/// inside). The atom-walk drives here via `apply_command(VizCommand{tab})`.
pub tab: Tab,
/// The STABLE pane id == the AccessKit `id_salt` pushed at this surface's render
/// root (`Tab::id` for top-level panes; `"<Tab>.<panel>"` for sub-panels). Never
/// collides across surfaces, so `By::label`/the drift gate match precisely.
pub id: &'static str,
/// Human-readable label (the header glyph+words for tabs; a short name for
/// sub-panels). Kept readable — distinct from the machine `id`.
pub label: &'static str,
/// True if this surface reads from a remote `nornir-server` (thin/RPC mode).
pub has_remote: bool,
/// True if this surface reads from a local embedded warehouse.
pub has_local: bool,
/// True if this is a SUB-PANEL nested inside a tab (not a top-level pane) — the
/// `populate_status`/`jobs` panels that no per-tab list covered before.
pub is_subpanel: bool,
}
impl VizSurface {
const fn tab_pane(tab: Tab, id: &'static str, label: &'static str) -> Self {
VizSurface {
tab,
id,
label,
has_remote: true,
has_local: true,
is_subpanel: false,
}
}
const fn subpanel(tab: Tab, id: &'static str, label: &'static str) -> Self {
VizSurface {
tab,
id,
label,
has_remote: true,
has_local: true,
is_subpanel: true,
}
}
}
/// The PANE DISCOVERY REGISTRY: every addressable viz surface, in header order,
/// followed by the known sub-panels. This SUPERSEDES `Tab::ALL` + ad-hoc
/// `state_json` slicing as the surface enumerator the atom-walk + matrix walk.
///
/// Invariant (enforced by `app::surfaces_cover_every_tab`): there is exactly one
/// top-level [`VizSurface`] per [`Tab::ALL`] variant, with `id == Tab::id()`.
pub fn viz_surfaces() -> Vec<VizSurface> {
let mut v: Vec<VizSurface> = Tab::ALL
.iter()
.map(|t| VizSurface::tab_pane(*t, t.id(), t.label()))
.collect();
// KNOWN SUB-PANELS that hide inside a tab (no `Tab::ALL` variant of their own),
// so the walk would miss them without an explicit registry entry. Both live on
// the 🧬 Nornir root pane and read Viz.CloneEvents / Viz.Jobs (remote) or the
// local warehouse / jobs.redb (local).
v.push(VizSurface::subpanel(
Tab::Nornir,
"Nornir.populate_status",
"Populate status (CloneEvents)",
));
v.push(VizSurface::subpanel(
Tab::Nornir,
"Nornir.jobs",
"Jobs (Viz.Jobs)",
));
v
}
/// The id_salts of just the top-level tab panes (one per `Tab::ALL` variant), in
/// header order — the list each tab pane's render root pushes via `ui.push_id`.
pub fn tab_pane_ids() -> Vec<&'static str> {
viz_surfaces()
.iter()
.filter(|s| !s.is_subpanel)
.map(|s| s.id)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
/// inject-assert: the registry enumerates every tab pane PLUS the two known
/// sub-panels, and every id is unique (no AccessKit `id_salt` collision).
#[test]
fn registry_has_every_tab_plus_subpanels_with_unique_ids() {
let surfaces = viz_surfaces();
// 20 tab panes + 2 sub-panels.
let tab_panes = surfaces.iter().filter(|s| !s.is_subpanel).count();
let subpanels = surfaces.iter().filter(|s| s.is_subpanel).count();
assert_eq!(
tab_panes,
Tab::ALL.len(),
"one VizSurface per Tab::ALL variant"
);
assert_eq!(
subpanels, 2,
"the populate_status + jobs sub-panels are enumerated"
);
// Ids are globally unique (so id_salt never collides -> atoms addressable).
let mut ids: Vec<&str> = surfaces.iter().map(|s| s.id).collect();
let n = ids.len();
ids.sort_unstable();
ids.dedup();
assert_eq!(ids.len(), n, "every VizSurface id must be unique");
// The two sub-panels are the ones that slipped the per-tab list before.
assert!(
surfaces
.iter()
.any(|s| s.id == "Nornir.populate_status" && s.is_subpanel),
"populate_status sub-panel enumerated"
);
assert!(
surfaces
.iter()
.any(|s| s.id == "Nornir.jobs" && s.is_subpanel),
"jobs sub-panel enumerated"
);
}
/// Each top-level pane's id is exactly its `Tab::id()` (so the `ui.push_id`
/// salt at the render root and the registry agree — the addressing contract).
#[test]
fn tab_pane_ids_equal_tab_id() {
for s in viz_surfaces().iter().filter(|s| !s.is_subpanel) {
assert_eq!(s.id, s.tab.id(), "tab pane id must equal Tab::id()");
}
}
}