Compare commits

..
Author SHA1 Message Date
atlas
c7feeb7f5b test(#2591): assert hivectl header shows source, not a derived label 2026-07-23 18:01:26 +02:00
atlas
0ee4647d1e refactor(#2591): drop the client-side DAG label map — show source + node kinds
Per mara (#2660): the client shouldn't re-derive a friendly name from
node kinds — that re-bakes the domain knowledge the raw-graph redesign
removed. The queue card / hivectl header now shows what the backend sends
(the DAG's source + the raw node kinds); the node chain conveys the
operation. Removes the DagView::label() helper (hivectl was its only
consumer after iris dropped the frontend map in #2660) and points
hivectl's header at d.source instead.
2026-07-23 18:01:26 +02:00
2 changed files with 16 additions and 42 deletions

View file

@ -192,32 +192,4 @@ impl DagView {
State::Done State::Done
} }
} }
/// A short human label for the DAG, derived from its node kinds — the
/// shared replacement for the old `Template` wire string now that the kind
/// isn't sent. Priority-ordered so the most distinctive node wins (an
/// approval deploy reads as "deploy" even though it also rebuilds). Purely
/// cosmetic (progress/queue display); consumers that need exactness inspect
/// the node kinds directly.
#[must_use]
pub fn label(&self) -> &'static str {
let has = |k: &str| self.nodes.iter().any(|n| n.kind == k);
if has("approval_deploy") {
"deploy"
} else if has("meta_lock") {
"meta-update"
} else if has("create") || has("provision") {
"spawn"
} else if has("swap") || has("prebuild") || has("post_swap") {
"rebuild"
} else if has("write_perm_file") {
"perm-change"
} else if has("signal") || has("drain") {
"graceful"
} else if has("set_wanted") || has("stop_for_update") {
"power"
} else {
"reconcile"
}
}
} }

View file

@ -67,7 +67,7 @@ async fn wait_for_dags_plain(socket: &Path, ids: Vec<u64>) -> Result<()> {
// sees whether the agent came back. // sees whether the agent came back.
if d.nodes.iter().all(|n| n.state.is_terminal()) { if d.nodes.iter().all(|n| n.state.is_terminal()) {
if d.rollup_state() == hive_sh4re::jobs::State::Failed { if d.rollup_state() == hive_sh4re::jobs::State::Failed {
failed.push(format!("{} {}", d.label(), dag_agents(d))); failed.push(format!("{} {}", d.source.as_str(), dag_agents(d)));
} }
} else { } else {
all_terminal = false; all_terminal = false;
@ -135,7 +135,7 @@ async fn wait_for_dags_animated(socket: &Path, ids: Vec<u64>) -> Result<()> {
hdr.set_message(format!( hdr.set_message(format!(
"{} {} {} · {}", "{} {} {} · {}",
state_glyph(d.rollup_state()), state_glyph(d.rollup_state()),
d.label(), d.source.as_str(),
dag_agents(d), dag_agents(d),
fmt_dur(dag_elapsed(d, now)), fmt_dur(dag_elapsed(d, now)),
)); ));
@ -164,7 +164,7 @@ async fn wait_for_dags_animated(socket: &Path, ids: Vec<u64>) -> Result<()> {
} }
if d.nodes.iter().all(|n| n.state.is_terminal()) { if d.nodes.iter().all(|n| n.state.is_terminal()) {
if d.rollup_state() == hive_sh4re::jobs::State::Failed { if d.rollup_state() == hive_sh4re::jobs::State::Failed {
failed.push(format!("{} {}", d.label(), dag_agents(d))); failed.push(format!("{} {}", d.source.as_str(), dag_agents(d)));
} }
} else { } else {
all_terminal = false; all_terminal = false;
@ -290,16 +290,16 @@ fn state_glyph(state: hive_sh4re::jobs::State) -> &'static str {
} }
} }
/// One progress line for a DAG: roll-up glyph, derived label, agents, then /// One progress line for a DAG: roll-up glyph, `source`, agents, then the
/// the node chain — the CLI twin of the dashboard's queue card. Both the /// node chain — the CLI twin of the dashboard's queue card. The header shows
/// roll-up state and the label are derived from the node set (the wire no /// what the backend sends (`source` + the raw node kinds); only the roll-up
/// longer carries them). Used by the plain (non-TTY) path. /// state glyph is derived from the node set. Used by the plain (non-TTY) path.
fn render_dag_line(d: &hive_sh4re::jobs::DagView) -> String { fn render_dag_line(d: &hive_sh4re::jobs::DagView) -> String {
use std::fmt::Write as _; use std::fmt::Write as _;
let mut out = format!( let mut out = format!(
"{} {} {:<12}", "{} {} {:<12}",
state_glyph(d.rollup_state()), state_glyph(d.rollup_state()),
d.label(), d.source.as_str(),
dag_agents(d) dag_agents(d)
); );
for (i, n) in d.nodes.iter().enumerate() { for (i, n) in d.nodes.iter().enumerate() {
@ -337,10 +337,12 @@ mod tests {
} }
#[test] #[test]
fn render_dag_line_shows_chain_with_derived_label_and_state() { fn render_dag_line_shows_source_and_chain() {
// Roll-up state (Running) + label ("rebuild") are derived from the node // The header shows what the backend sends: the roll-up state glyph
// set — the wire no longer carries them. (`Done` nodes are included // (derived — Running here) + the DAG `source` ("manual"); the operation
// here to exercise glyph rendering; production filters them off.) // is read off the node chain, not a client-side label. (`Done` nodes
// are included here to exercise glyph rendering; production filters
// them off.)
let dag = DagView { let dag = DagView {
id: 7, id: 7,
source: Source::Manual, source: Source::Manual,
@ -356,7 +358,7 @@ mod tests {
], ],
}; };
let line = render_dag_line(&dag); let line = render_dag_line(&dag);
assert!(line.starts_with("rebuild alice"), "{line}"); assert!(line.starts_with("manual alice"), "{line}");
assert!( assert!(
line.contains("✔ prebuild → ✔ stop_for_update → ▶ swap → ⏸ reconcile"), line.contains("✔ prebuild → ✔ stop_for_update → ▶ swap → ⏸ reconcile"),
"{line}" "{line}"
@ -377,7 +379,7 @@ mod tests {
nodes: vec![failed], nodes: vec![failed],
}; };
let line = render_dag_line(&dag); let line = render_dag_line(&dag);
assert!(line.contains("rebuild"), "{line}"); assert!(line.contains("manual"), "{line}");
assert!(line.contains("— nix build exploded"), "{line}"); assert!(line.contains("— nix build exploded"), "{line}");
} }
} }