1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
//! **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()");
}
}
}