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
//! ⚒ The **Dwarfs tab** — nornir's CI/build forge, merging the former 🔨 Build and
//! ⚒ Brokkr tabs into one surface (N5).
//!
//! Recent nornir work renamed the CI crate/tool to **dwarfs** (`crates/dwarfs`,
//! the brokkr forge + the `sindri` scheduler). The two viz tabs were
//! **complementary**, not duplicates:
//!
//! - the **Build runner** ([`super::build_tab::BuildTabState`]) reflects a repo's
//! `BuildSurface` into choosable target rows (binary × features × profile ×
//! target), runs `nornir build --attest --seal`, and TEST-BOOTs the result as a
//! job — a *single-target* builder;
//! - the **Forge** ([`super::brokkr_tab::BrokkrTabState`]) is the CI pipeline:
//! the results/tree/DAG/agents/log facets over a `brokkr.toml`, plus the
//! pipeline builder — a *multi-stage* pipeline runner.
//!
//! This tab hosts both behind a top-level pane switch (`🔨 Build` ⇄ `⚒ Forge`),
//! so the whole dwarfs story lives under one header. Each sub-pane keeps its own
//! `state_json` slice (surfaced at the top level as `build` / `brokkr` for
//! back-compat), and the tab adds its own `dwarfs` slice carrying the active pane.
use std::path::PathBuf;
use eframe::egui;
use super::build_tab::BuildTabState;
use super::brokkr_tab::BrokkrTabState;
use super::facett_theme::Theme;
/// The two panes of the merged dwarfs tab.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DwarfsPane {
/// 🔨 the single-target build runner (`BuildTabState`).
Build,
/// ⚒ the CI pipeline forge (`BrokkrTabState`).
Forge,
}
impl DwarfsPane {
/// The exact selector label a user reads / a robot clicks.
fn label(self) -> &'static str {
match self {
DwarfsPane::Build => "🔨 Build",
DwarfsPane::Forge => "⚒ Forge",
}
}
/// The `state_json["dwarfs"]["pane"]` name.
fn id(self) -> &'static str {
match self {
DwarfsPane::Build => "Build",
DwarfsPane::Forge => "Forge",
}
}
const ALL: [DwarfsPane; 2] = [DwarfsPane::Build, DwarfsPane::Forge];
}
/// ⚒ The Dwarfs tab: the build runner + the CI forge under one header.
pub struct DwarfsTabState {
pane: DwarfsPane,
/// 🔨 the single-target build runner. `pub(crate)` so the app can inject a
/// test runner + read its `state_json` slice at the top level.
pub(crate) build: BuildTabState,
/// ⚒ the CI pipeline forge (the former Brokkr tab). `pub(crate)` for the same
/// reasons.
pub(crate) forge: BrokkrTabState,
}
impl DwarfsTabState {
/// The tab seeded with the build runner (over `workspace_root`) + the demo
/// pipeline forge.
pub fn new(workspace_root: PathBuf) -> Self {
Self {
pane: DwarfsPane::Build,
build: BuildTabState::new(workspace_root),
forge: BrokkrTabState::new(),
}
}
/// Push the active palette into both sub-panes.
pub fn set_palette(&mut self, p: Theme) {
self.build.set_palette(p);
self.forge.set_palette(p);
}
/// The observable state a headless test / operator reads: the active pane plus
/// each sub-pane's slice (also mirrored at the top level of the app's
/// `state_json` as `build` / `brokkr`).
pub fn state_json(&self) -> serde_json::Value {
serde_json::json!({
"surface": "dwarfs",
"pane": self.pane.id(),
"build": self.build.state_json(),
"brokkr": self.forge.state_json(),
})
}
/// Draw the tab: a top-level pane switch, then the active sub-pane.
pub fn draw(&mut self, ui: &mut egui::Ui) {
ui.push_id("Dwarfs", |ui| {
ui.horizontal(|ui| {
ui.heading("⚒ Dwarfs — build + CI forge");
ui.separator();
for p in DwarfsPane::ALL {
ui.selectable_value(&mut self.pane, p, p.label());
}
});
ui.separator();
match self.pane {
DwarfsPane::Build => self.build.draw(ui),
DwarfsPane::Forge => self.forge.draw(ui),
}
});
}
}