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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//! π **Metro tab** β the dedicated viz pane that renders nornir's button β gRPC β
//! warehouse coverage CHAINS through the reusable facett component
//! [`facett_graphview::MetroView`] (a [`facett_core::Facet`], imported here as
//! [`facett_jobview::Facet`] β the SAME `facett-core` trait both crates re-export).
//!
//! This pane owns the `MetroView` and pushes a fresh [`facett_graphview::MetroMap`]
//! into it each refresh (the push model). The map is produced by the PURE
//! [`super::metro_feed::build_metro_map`] over the live workspace's warehouse facts
//! + coverage; the app feeds it from the π Architecture tab's loaded data via
//! [`super::arch_tab::ArchTabState::facett_metro_map`] (one loader, no second scan).
//!
//! [`state_json`](Facet::state_json) surfaces the whole drawn map (lines, per-station
//! {label, kind, lit}, per-line + overall green) so the headless test matrix asserts
//! exactly what the line lit up WITHOUT a pixel β LAW 6.
use facett_graphview::{MetroMap, MetroView};
use facett_jobview::Facet; // == facett_core::Facet (both crates re-export the 0.1 trait)
use super::facett_theme::Theme;
/// π The Metro tab pane state β a thin host around the facett [`MetroView`].
pub struct MetroTabState {
view: MetroView,
/// Cached so a workspace switch re-feeds (the app pushes a fresh map on change).
fed: bool,
/// Lines in the last-fed map β 0 β the warehouse has no coverage chains yet
/// (the LAW-2 empty board), so the shared `repo_pane` empty-state shows the
/// "β³ in route for populateβ¦" / "not scanned yet" reason instead of a blank.
line_count: usize,
/// The active workspace (app pushes it via `set_workspace_name`) β the key the
/// shared `repo_pane` jobs lookup needs to know if a populate is in route.
workspace: String,
}
impl Default for MetroTabState {
fn default() -> Self {
Self {
view: MetroView::new("π Metro"),
fed: false,
line_count: 0,
workspace: String::new(),
}
}
}
impl MetroTabState {
/// **Discovery-contract `local()`** β the no-arg ctor the test matrix enumerates.
/// Starts empty; the app feeds the real workspace map via [`Self::set_map`]
/// (mirrors `facett-jobview`'s `JobList::default` host on the 𧬠Nornir tab).
pub fn local() -> Self {
Self::default()
}
/// **Discovery-contract `remote()`** β same surface; the thin/server-fed variant.
pub fn remote() -> Self {
Self::default()
}
/// Replace the drawn map (the push model β called each refresh from the app's
/// live workspace data). Marks the pane fed so a "no data yet" hint clears, and
/// records the line count so an EMPTY map (no coverage chains) shows the shared
/// LAW-2 empty-state instead of a blank board.
pub fn set_map(&mut self, map: MetroMap) {
self.line_count = map.lines.len();
self.view.set_map(map);
self.fed = true;
}
/// The active workspace, so the shared `repo_pane` empty-state can tell whether
/// a populate is in route. The app pushes it each refresh alongside `set_map`.
pub fn set_workspace_name(&mut self, workspace: String) {
self.workspace = workspace;
}
/// Whether a real map has been fed yet (else the pane shows a load hint).
pub fn is_fed(&self) -> bool {
self.fed
}
/// The pane's LAW-2 empty-state: a fed-but-line-less map is the warehouse
/// having no coverage chains yet β "β³ in route for populateβ¦" when a job is
/// active for this workspace, else "not scanned yet". `Populated` once lines
/// exist (or before the first feed, where the load hint already shows).
fn empty_state(&self) -> super::repo_pane::EmptyState {
let empty = self.fed && self.line_count == 0;
super::repo_pane::classify_empty(empty, &self.workspace, "")
}
/// The active facett palette (C8) β the Facet reads the ambient egui theme, so
/// this is a no-op seam kept for parity with the other tabs' `set_palette`.
pub fn set_palette(&mut self, _t: Theme) {}
/// Draw the pane: a header + the facett [`MetroView`] (scoped to the stable
/// `Metro` pane id so the AccessKit `id_salt` matches the surfaces registry).
pub fn draw(&mut self, ui: &mut egui::Ui) {
ui.push_id("Metro", |ui| {
ui.horizontal(|ui| {
ui.heading("π Metro");
ui.label(
egui::RichText::new(
"button β emitters β gRPC β warehouse table, lit by the latest coverage run",
)
.weak(),
);
});
if !self.fed {
ui.label(
egui::RichText::new(
"loading the workspace's coverage chainsβ¦ (open π Architecture once to warm the warehouse facts)",
)
.weak(),
);
} else if self.line_count == 0 {
// LAW 2: fed but no chains β the warehouse has no coverage data yet.
// Name WHY (in route for populate / not scanned) instead of an empty
// board, then still let the (empty) Facet draw its frame below.
let col = ui.visuals().text_color();
super::repo_pane::render_empty(ui, true, &self.workspace, "", col);
}
ui.separator();
Facet::ui(&mut self.view, ui);
});
}
/// The drawn map as DATA (LAW 6) β delegates to the facett `MetroView`'s
/// `state_json` (lines, per-station {label, kind, lit}, per-line + overall green).
pub fn state_json(&self) -> serde_json::Value {
let mut v = Facet::state_json(&self.view);
if let Some(obj) = v.as_object_mut() {
obj.insert("fed".into(), serde_json::Value::Bool(self.fed));
// LAW 2 empty-state tag (populated / in_route / not_scanned) so a
// headless drive asserts a fed-but-empty metro names its reason.
obj.insert(
"empty_state".into(),
serde_json::Value::String(self.empty_state().id().to_string()),
);
}
v
}
}
#[cfg(test)]
mod tests {
use super::*;
use facett_graphview::{MetroLine, MetroStation, StationKind};
/// inject-assert: feeding a real green line lights state_json up (green=true) and
/// the pane reports fed.
#[test]
fn fed_map_surfaces_in_state_json() {
let mut tab = MetroTabState::local();
assert!(!tab.is_fed());
assert_eq!(tab.state_json()["fed"], serde_json::json!(false));
let line = MetroLine::new(
"bβSvc.Verb",
"b",
vec![
MetroStation::new("ui::b", "b", StationKind::Start, true),
MetroStation::new("svc::emit", "emit", StationKind::Emitter, true),
MetroStation::new("grpc::Svc.Verb", "Svc.Verb", StationKind::Grpc, true),
MetroStation::new("wh::t", "t", StationKind::Terminus, true),
],
);
tab.set_map(MetroMap::new(vec![line]));
assert!(tab.is_fed());
let js = tab.state_json();
assert_eq!(js["fed"], serde_json::json!(true));
assert_eq!(js["lines"], serde_json::json!(1));
assert_eq!(js["green"], serde_json::json!(true));
}
}