use super::*;
#[test]
fn repeated_started_reparents_existing_root_without_stale_topology() {
let mut state = MissionControlState::default();
let parent = ActivityId::new("parent");
let child = ActivityId::new("child");
state.apply_activity_event(ActivityEvent::Started {
id: child.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("child root"),
});
state.apply_activity_event(ActivityEvent::Started {
id: parent.clone(),
parent_id: None,
kind: ActivityKind::SubagentBatch,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("parent"),
});
assert_eq!(
state.visible_nodes(),
vec![(0, child.clone()), (0, parent.clone())]
);
state.apply_activity_event(ActivityEvent::Started {
id: child.clone(),
parent_id: Some(parent.clone()),
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("child reparented"),
});
assert_eq!(state.roots, vec![parent.clone()]);
assert_eq!(state.children.get(&parent), Some(&vec![child.clone()]));
assert_eq!(state.visible_nodes(), vec![(0, parent), (1, child)]);
}
#[test]
fn repeated_primary_reasoning_started_keeps_one_root_and_stable_order() {
let mut state = MissionControlState::default();
let reasoning = ActivityId::new("run-1/reasoning/1");
let tool = ActivityId::new("call_read");
state.apply_activity_event(ActivityEvent::Started {
id: reasoning.clone(),
parent_id: None,
kind: ActivityKind::Assistant,
status: ActivityStatus::Success,
metadata: ActivityMetadata {
label: "reasoning summaries ×1".to_string(),
detail: Some("1. checked inputs".to_string()),
fields: Vec::new(),
},
});
state.apply_activity_event(ActivityEvent::Started {
id: tool.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("read src/lib.rs"),
});
state.apply_activity_event(ActivityEvent::Started {
id: reasoning.clone(),
parent_id: None,
kind: ActivityKind::Assistant,
status: ActivityStatus::Success,
metadata: ActivityMetadata {
label: "reasoning summaries ×3".to_string(),
detail: Some("1. checked inputs\n2. planned fix\n3. verified output".to_string()),
fields: Vec::new(),
},
});
assert_eq!(state.roots, vec![reasoning.clone(), tool]);
let visible = state.visible_nodes();
assert_eq!(visible.len(), 2);
assert_eq!(visible[0], (0, reasoning.clone()));
let node = state.nodes.get(&reasoning).unwrap();
assert_eq!(node.metadata.label, "reasoning summaries ×3");
assert_eq!(
node.metadata.detail.as_deref(),
Some("1. checked inputs\n2. planned fix\n3. verified output")
);
}
#[test]
fn activity_preview_delta_is_bounded() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-large");
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("large preview"),
});
state.apply_activity_event(ActivityEvent::Delta {
id: id.clone(),
preview: "x".repeat(crate::tui::activity::MAX_ACTIVITY_PREVIEW_BYTES * 2),
});
let node = state.nodes.get(&id).unwrap();
assert!(node.preview.len() <= crate::tui::activity::MAX_ACTIVITY_PREVIEW_BYTES);
assert!(node.preview_truncated);
assert!(!node.preview.contains("[… activity preview truncated …]"));
}
#[test]
fn activity_final_preview_is_bounded_and_replaces_delta() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-final-large");
state.apply_activity_event(ActivityEvent::Delta {
id: id.clone(),
preview: "old delta".to_string(),
});
state.apply_activity_event(ActivityEvent::FinalPreview {
id: id.clone(),
preview: "line\n".repeat(crate::tui::activity::MAX_ACTIVITY_PREVIEW_LINES + 20),
metadata: Some(ActivityMetadata::new("final preview")),
status: Some(ActivityStatus::Success),
});
let node = state.nodes.get(&id).unwrap();
assert!(!node.preview.contains("old delta"));
assert!(node.preview.len() <= crate::tui::activity::MAX_ACTIVITY_PREVIEW_BYTES);
assert!(node.preview_truncated);
assert!(!node.preview.contains("[… activity preview truncated …]"));
}
#[test]
fn activity_detail_omits_visible_preview_truncation_marker() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-detail-large");
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("detail preview"),
});
state.apply_activity_event(ActivityEvent::Delta {
id: id.clone(),
preview: "x".repeat(crate::tui::activity::MAX_ACTIVITY_PREVIEW_BYTES + 1),
});
let node = state.nodes.get(&id).unwrap();
assert!(node.preview_truncated);
let detail = state.selected_activity_text();
assert!(
!detail.contains("[… activity preview truncated …]"),
"{detail}"
);
assert!(!detail.contains("preview truncated"), "{detail}");
}
#[test]
fn activity_detail_renders_subagent_prompt_detail() {
let mut state = MissionControlState::default();
let id = ActivityId::new("call_subagents/g1");
let secret = format!("sk-{}", "x".repeat(24));
let prompt = format!(
"Subagent g1 task intent:\ninspect detail\n\nAgent label/persona: issue-43-plan\n\nTask context:\nvisible context line\napi_key={secret}\nfinal context line\n"
);
state.apply_activity_event(ActivityEvent::Started {
id,
parent_id: None,
kind: ActivityKind::SubagentTask,
status: ActivityStatus::Running,
metadata: ActivityMetadata {
label: "g1 • inspect detail".to_string(),
detail: Some(prompt),
fields: Vec::new(),
},
});
let detail = state.selected_activity_text();
assert!(detail.contains("type: subagent task"), "{detail}");
assert!(
detail.contains("summary: Subagent g1 task intent:"),
"{detail}"
);
assert!(detail.contains("visible context line"), "{detail}");
assert!(detail.contains("final context line"), "{detail}");
assert!(!detail.contains(&secret), "{detail}");
assert!(detail.contains("api_key=<redacted>"), "{detail}");
}
#[test]
fn activity_detail_cache_invalidates_after_preview_append() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-1");
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("read file"),
});
assert!(!state.selected_activity_text().contains("line 1"));
assert!(
state
.activity_render_cache
.borrow()
.selected_detail
.is_some()
);
state.apply_activity_event(ActivityEvent::Delta {
id,
preview: "line 1".to_string(),
});
assert!(state.selected_activity_text().contains("line 1"));
}
#[test]
fn activity_detail_cache_invalidates_after_status_change() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-1");
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("read file"),
});
assert!(state.selected_activity_text().contains("RUNNING"));
state.apply_activity_event(ActivityEvent::Finished {
id,
status: ActivityStatus::Success,
metadata: None,
});
assert!(state.selected_activity_text().contains("SUCCESS"));
}
#[test]
fn detail_body_visual_rows_reuses_width_cache_and_rebuilds_new_width() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-window-cache");
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("cache detail"),
});
state.apply_activity_event(ActivityEvent::Delta {
id,
preview: "abcdefghij".repeat(8),
});
let rows_at_20 = state.detail_body_visual_rows(20);
let cache_widths_after_first = state
.activity_render_cache
.borrow()
.selected_detail
.as_ref()
.map(|detail| detail.body_wrap_cache.len())
.unwrap_or_default();
let rows_at_20_again = state.detail_body_visual_rows(20);
let cache_widths_after_second = state
.activity_render_cache
.borrow()
.selected_detail
.as_ref()
.map(|detail| detail.body_wrap_cache.len())
.unwrap_or_default();
let rows_at_10 = state.detail_body_visual_rows(10);
let cache_widths_after_new_width = state
.activity_render_cache
.borrow()
.selected_detail
.as_ref()
.map(|detail| detail.body_wrap_cache.len())
.unwrap_or_default();
assert_eq!(rows_at_20, rows_at_20_again);
assert_eq!(cache_widths_after_first, 1);
assert_eq!(cache_widths_after_second, 1);
assert_eq!(cache_widths_after_new_width, 2);
assert!(rows_at_10 >= rows_at_20);
}
#[test]
fn detail_wrap_cache_filters_tabs_like_ratatui() {
fn state_with_preview(id: &str, preview: &str) -> MissionControlState {
let mut state = MissionControlState::default();
let id = ActivityId::new(id);
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("tab detail"),
});
state.apply_activity_event(ActivityEvent::Delta {
id,
preview: preview.to_string(),
});
state
}
let with_tab = state_with_preview("tool-tab-wrap", "a\tb");
let without_tab = state_with_preview("tool-plain-wrap", "ab");
for width in 1..=4 {
assert_eq!(
with_tab.detail_body_visual_rows(width),
without_tab.detail_body_visual_rows(width),
"width={width}"
);
assert_eq!(
with_tab
.visible_activity_body_lines(0, usize::MAX, width)
.len(),
without_tab
.visible_activity_body_lines(0, usize::MAX, width)
.len(),
"width={width}"
);
}
}
#[test]
fn round_ten_detail_projection_filters_overwide_graphemes_like_ratatui() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-wide-grapheme");
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id,
detail: crate::output::ToolActivityDetail {
tool_name: "dynamic".into(),
label: "wide grapheme".into(),
params: serde_json::json!({}),
metadata: serde_json::json!({}),
status: ActivityStatus::Success,
output: std::sync::Arc::from("🙂x"),
applied_diff: None,
},
});
let rows = state.visible_activity_body_lines(0, usize::MAX, 1);
let rendered = rows.iter().map(DisplayLine::plain_text).collect::<Vec<_>>();
assert!(!rendered.iter().any(|line| line.contains('🙂')));
assert!(rendered.iter().any(|line| line == "x"));
assert_eq!(
state.detail_body_visual_rows(1),
rendered.len(),
"projection and wrap index must describe the same rows"
);
}
#[test]
fn round_thirteen_detail_projection_keeps_wide_graphemes_observable() {
let mut state = MissionControlState::default();
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id: ActivityId::new("tool-wide-fallback"),
detail: crate::output::ToolActivityDetail {
tool_name: "dynamic".into(),
label: "wide fallback".into(),
params: serde_json::json!({}),
metadata: serde_json::json!({}),
status: ActivityStatus::Success,
output: std::sync::Arc::from("🙂"),
applied_diff: None,
},
});
let rendered = state
.visible_activity_body_lines(0, usize::MAX, 1)
.iter()
.map(DisplayLine::plain_text)
.collect::<Vec<_>>();
assert!(rendered.iter().any(|line| line == "�"));
assert_eq!(state.detail_body_visual_rows(1), rendered.len());
}
#[test]
fn cold_detail_scroll_clamps_empty_short_and_zero_height_content() {
let mut empty = MissionControlState::default();
assert!(!empty.scroll_detail_down_by(1, 10, 80));
assert_eq!(empty.detail_offset(), 0);
let mut short = MissionControlState::default();
let id = ActivityId::new("tool-short-detail");
short.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("short detail"),
});
short.apply_activity_event(ActivityEvent::Delta {
id,
preview: "one row".to_string(),
});
assert!(!short.scroll_detail_down_by(1, 10, 80));
assert_eq!(short.detail_offset(), 0);
let mut zero_height = MissionControlState::default();
let id = ActivityId::new("tool-zero-height-detail");
zero_height.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("zero height detail"),
});
zero_height.apply_activity_event(ActivityEvent::Delta {
id,
preview: "many rows\n".repeat(20),
});
assert!(!zero_height.scroll_detail_down_by(1, 0, 80));
assert_eq!(zero_height.detail_offset(), 0);
}
#[test]
fn detail_scroll_reaches_final_row_beyond_u16_limit() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-many-rows");
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id: id.clone(),
detail: crate::output::ToolActivityDetail {
tool_name: "write".into(),
label: "write large".into(),
params: serde_json::json!({"path":"large.txt"}),
metadata: serde_json::json!({}),
status: ActivityStatus::Success,
output: std::sync::Arc::from((0..70_000).map(|_| "x\n").collect::<String>()),
applied_diff: None,
},
});
let rows = state.detail_body_visual_rows(1);
assert!(rows > 65_535, "rows={rows}");
assert!(state.scroll_detail_down_by(u16::MAX, 1, 1));
assert!(state.detail_offset() > 65_000);
}
#[test]
fn detail_width_cache_has_small_bounded_cardinality() {
let mut state = MissionControlState::default();
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new("tool-width-bound"),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("width bound"),
});
state.apply_activity_event(ActivityEvent::Delta {
id: ActivityId::new("tool-width-bound"),
preview: "x".repeat(100),
});
for width in 1..=12 {
state.detail_body_visual_rows(width);
}
let cache = state.activity_render_cache.borrow();
let widths = cache
.selected_detail
.as_ref()
.map(|detail| detail.body_wrap_cache.len())
.unwrap_or_default();
assert!(widths <= 4, "retained widths: {widths}");
}
#[test]
fn provider_context_injection_child_does_not_fail_successful_parent_tool() {
let mut state = MissionControlState::default();
let tool = ActivityId::new("tool-1");
let injection = ActivityId::new("tool-1/after-provider-context-injection-0");
state.apply_activity_event(ActivityEvent::Started {
id: tool.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("read file"),
});
state.apply_activity_event(ActivityEvent::Started {
id: injection.clone(),
parent_id: Some(tool.clone()),
kind: ActivityKind::ProviderContextInjection,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("provider context injection after-context"),
});
state.apply_activity_event(ActivityEvent::Finished {
id: injection,
status: ActivityStatus::Failed,
metadata: None,
});
state.apply_activity_event(ActivityEvent::Finished {
id: tool.clone(),
status: ActivityStatus::Success,
metadata: None,
});
assert_eq!(state.nodes[&tool].status, ActivityStatus::Success);
}
#[test]
fn provider_context_injection_missing_parent_becomes_root() {
let mut state = MissionControlState::default();
let injection = ActivityId::new("missing/after-provider-context-injection-0");
state.apply_activity_event(ActivityEvent::Started {
id: injection.clone(),
parent_id: Some(ActivityId::new("missing-parent")),
kind: ActivityKind::ProviderContextInjection,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("provider context injection after-context"),
});
assert_eq!(state.roots, vec![injection.clone()]);
assert_eq!(state.nodes[&injection].parent_id, None);
}
#[test]
fn hook_activity_child_rows_attach_without_overriding_successful_tool() {
let mut state = MissionControlState::default();
let tool = ActivityId::new("tool-1");
let hook = ActivityId::new("tool-1/before_tool-hook-0");
state.apply_activity_event(ActivityEvent::Started {
id: tool.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("bash test"),
});
state.apply_activity_event(ActivityEvent::Started {
id: hook.clone(),
parent_id: Some(tool.clone()),
kind: ActivityKind::Hook,
status: ActivityStatus::Running,
metadata: ActivityMetadata {
label: "before_tool hook audit on bash".to_string(),
detail: Some("before_tool hook audit for tool bash running".to_string()),
fields: vec![
("phase".to_string(), "before_tool".to_string()),
("hook_label".to_string(), "audit".to_string()),
("target_tool".to_string(), "bash".to_string()),
("status".to_string(), "running".to_string()),
("policy".to_string(), "warn".to_string()),
],
},
});
state.apply_activity_event(ActivityEvent::Finished {
id: hook.clone(),
status: ActivityStatus::Failed,
metadata: Some(ActivityMetadata {
label: "before_tool hook audit on bash".to_string(),
detail: Some(
"before_tool hook audit for tool bash failed category=exit policy=warn".to_string(),
),
fields: vec![
("phase".to_string(), "before_tool".to_string()),
("hook_label".to_string(), "audit".to_string()),
("target_tool".to_string(), "bash".to_string()),
("status".to_string(), "failed".to_string()),
("policy".to_string(), "warn".to_string()),
("category".to_string(), "exit".to_string()),
],
}),
});
state.apply_activity_event(ActivityEvent::Finished {
id: tool.clone(),
status: ActivityStatus::Success,
metadata: Some(ActivityMetadata::new("bash test")),
});
assert_eq!(state.children.get(&tool).unwrap(), &vec![hook.clone()]);
assert_eq!(
state.nodes.get(&tool).unwrap().status,
ActivityStatus::Success
);
assert_eq!(state.nodes.get(&hook).unwrap().kind, ActivityKind::Hook);
state.selected = 1;
let detail = state.selected_activity_text();
assert!(detail.contains("type: hook"), "{detail}");
assert!(detail.contains("phase: before_tool"), "{detail}");
assert!(detail.contains("category: exit"), "{detail}");
}
#[test]
fn activity_finished_applies_canceled_status() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-1");
state.apply_activity_event(ActivityEvent::ToolStartedDetail {
id: id.clone(),
detail: crate::output::ToolActivityDetail {
tool_name: "read".into(),
label: "read file".into(),
params: serde_json::json!({"path":"file.txt"}),
metadata: serde_json::json!({}),
status: ActivityStatus::Running,
output: "".into(),
applied_diff: None,
},
});
state.apply_activity_event(ActivityEvent::Finished {
id: id.clone(),
status: ActivityStatus::Canceled,
metadata: None,
});
state.apply_activity_event(ActivityEvent::ToolResultDetail {
id: id.clone(),
detail: crate::output::ToolActivityDetail {
tool_name: "read".into(),
label: "read file".into(),
params: serde_json::json!({"path":"file.txt"}),
metadata: serde_json::json!({"bytes": 4}),
status: ActivityStatus::Success,
output: "late".into(),
applied_diff: None,
},
});
assert_eq!(
state.nodes.get(&id).unwrap().status,
ActivityStatus::Canceled
);
let detail = state.selected_activity_text();
assert!(detail.contains("Status: CANCELED"), "{detail}");
assert!(detail.contains("\"path\": \"file.txt\""), "{detail}");
assert!(detail.contains("\"metadata\": {}"), "{detail}");
assert!(!detail.contains("late"), "{detail}");
}
#[test]
fn activity_final_preview_applies_failure_transition() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-1");
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Writing,
metadata: ActivityMetadata::new("write file"),
});
state.apply_activity_event(ActivityEvent::FinalPreview {
id: id.clone(),
preview: "failed".to_string(),
metadata: None,
status: Some(ActivityStatus::Failed),
});
state.apply_activity_event(ActivityEvent::FinalPreview {
id: id.clone(),
preview: "failed again".to_string(),
metadata: None,
status: Some(ActivityStatus::Failed),
});
let node = state.nodes.get(&id).unwrap();
assert_eq!(node.status, ActivityStatus::Failed);
assert_eq!(node.preview, "failed again");
}
#[test]
fn activity_events_ignore_missing_finish_and_create_preview_nodes() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-1");
state.apply_activity_event(ActivityEvent::Finished {
id: id.clone(),
status: ActivityStatus::Success,
metadata: None,
});
let node = state.nodes.get(&id).unwrap();
assert_eq!(node.status, ActivityStatus::Success);
state.apply_activity_event(ActivityEvent::FinalPreview {
id: id.clone(),
preview: "still running".to_string(),
metadata: None,
status: Some(ActivityStatus::Running),
});
let node = state.nodes.get(&id).unwrap();
assert_eq!(node.status, ActivityStatus::Running);
assert_eq!(node.preview, "still running");
}
#[test]
fn activity_detail_cache_updates_after_selected_node_changes() {
let mut state = MissionControlState::default();
for label in ["first", "second"] {
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new(label),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new(label),
});
}
state.selected = 0;
assert!(state.selected_activity_text().contains("label: first"));
state.selected = 1;
assert!(state.selected_activity_text().contains("label: second"));
}
#[test]
fn detail_body_viewport_rows_subtracts_sticky_summary() {
let mut state = MissionControlState::default();
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new("tool-1"),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("long detail"),
});
state.apply_activity_event(ActivityEvent::Delta {
id: ActivityId::new("tool-1"),
preview: "body line 1\nbody line 2".to_string(),
});
let summary_rows = state
.selected_activity_summary_lines()
.iter()
.map(|line| ratatui_wrapped_visual_rows(&line.plain_text(), 80))
.sum::<usize>() as u16;
assert_eq!(summary_rows, 4);
assert_eq!(state.detail_body_viewport_rows(10, 80), 6);
assert_eq!(state.detail_overflow(0, 80), 0);
}
#[test]
fn activity_render_cache_clears_on_reset() {
let mut state = MissionControlState::default();
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new("tool-1"),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("read file"),
});
let _ = state.visible_nodes();
let _ = state.selected_activity_display_lines();
assert!(state.activity_render_cache.borrow().visible_nodes.is_some());
assert!(
state
.activity_render_cache
.borrow()
.selected_detail
.is_some()
);
state.reset_for_new_session();
assert!(state.nodes.is_empty());
assert!(state.activity_render_cache.borrow().visible_nodes.is_none());
assert!(
state
.activity_render_cache
.borrow()
.selected_detail
.is_none()
);
}
#[test]
fn activity_tree_cache_invalidates_after_expansion_change() {
let mut state = MissionControlState::default();
let parent = ActivityId::new("parent");
state.apply_activity_event(ActivityEvent::Started {
id: parent.clone(),
parent_id: None,
kind: ActivityKind::SubagentBatch,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("parent"),
});
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new("child"),
parent_id: Some(parent.clone()),
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("child"),
});
assert_eq!(state.visible_nodes().len(), 2);
state.selected = 0;
assert!(state.click_activity_tree_row(0, 5));
assert_eq!(state.visible_nodes().len(), 1);
}
#[test]
fn activity_tree_shows_subagent_reasoning_group_as_child() {
let mut state = MissionControlState::default();
let task = ActivityId::new("subagents/g1");
state.apply_activity_event(ActivityEvent::Started {
id: task.clone(),
parent_id: None,
kind: ActivityKind::SubagentTask,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("g1 · inspect"),
});
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new("subagents/g1/reasoning/1"),
parent_id: Some(task.clone()),
kind: ActivityKind::Assistant,
status: ActivityStatus::Success,
metadata: ActivityMetadata {
label: "reasoning summaries ×2".to_string(),
detail: Some("1. checked inputs\n2. verified output".to_string()),
fields: Vec::new(),
},
});
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new("subagents/g1/reasoning/1"),
parent_id: Some(task.clone()),
kind: ActivityKind::Assistant,
status: ActivityStatus::Success,
metadata: ActivityMetadata {
label: "reasoning summaries ×3".to_string(),
detail: Some("1. checked inputs\n2. verified output\n3. planned fix".to_string()),
fields: Vec::new(),
},
});
let visible = state.visible_nodes();
assert_eq!(visible.len(), 2);
assert_eq!(visible[0], (0, task));
assert_eq!(visible[1].0, 1);
state.selected = 1;
let detail = state.selected_activity_text();
assert!(detail.contains("label: reasoning summaries ×3"), "{detail}");
assert!(detail.contains("summary: 3 lines"), "{detail}");
assert!(
detail.contains("Detail\n1. checked inputs\n2. verified output\n3. planned fix"),
"{detail}"
);
}
#[test]
fn activity_tree_selects_new_tail_when_following_tail() {
let mut state = MissionControlState::default();
for index in 0..6 {
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new(format!("tool-{index}")),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new(format!("tool {index}")),
});
}
assert_eq!(state.selected, 5);
assert_eq!(state.activity_tree_render_scroll(3), 3);
}
#[test]
fn optimization_harness_projects_large_activity_tree_without_dropping_nodes() {
let mut state = MissionControlState::default();
let root_id = ActivityId::new("root");
state.apply_activity_event(ActivityEvent::Started {
id: root_id.clone(),
parent_id: None,
kind: ActivityKind::SubagentBatch,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("root"),
});
let child_count = 512;
for index in 0..child_count {
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new(format!("child-{index}")),
parent_id: Some(root_id.clone()),
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata {
label: format!("child {index}"),
detail: Some(format!("detail {index}")),
fields: vec![("index".to_string(), index.to_string())],
},
});
}
let visible = state.visible_nodes();
state.selected = child_count;
let selected_text = state.selected_activity_text();
assert_eq!(visible.len(), child_count + 1);
assert_eq!(visible[0], (0, root_id));
assert_eq!(visible[child_count].0, 1);
assert!(selected_text.contains(&format!("child {}", child_count - 1)));
assert!(selected_text.contains(&format!("detail {}", child_count - 1)));
}
#[test]
fn activity_tree_auto_scrolls_to_new_tail_on_append() {
let mut state = MissionControlState::default();
for index in 0..4 {
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new(format!("tool-{index}")),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new(format!("tool {index}")),
});
}
assert_eq!(state.activity_tree_render_scroll(3), 1);
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new("tool-4"),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("tool 4"),
});
assert_eq!(state.selected, 4);
assert!(state.activity_follow_tail);
assert_eq!(state.activity_tree_render_scroll(3), 2);
assert_eq!(state.activity_tree_render_scroll(3), 2);
}
#[test]
fn activity_tree_follows_after_manual_scroll_returns_to_bottom() {
let mut state = MissionControlState::default();
for index in 0..10 {
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new(format!("tool-{index}")),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new(format!("tool {index}")),
});
}
assert_eq!(state.activity_tree_render_scroll(3), 7);
assert!(state.scroll_activity_tree_up_by(5, 3));
assert_eq!(state.activity_tree_render_scroll(3), 2);
assert_eq!(state.selected, 9);
assert!(!state.activity_follow_tail);
assert!(state.scroll_activity_tree_down_by(5, 3));
assert_eq!(state.activity_tree_render_scroll(3), 7);
assert_eq!(state.selected, 9);
assert!(state.activity_follow_tail);
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new("tool-10"),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("tool 10"),
});
assert_eq!(state.selected, 10);
assert_eq!(state.activity_tree_render_scroll(3), 8);
}
#[test]
fn write_activity_node_starts_writing_and_finishes_success_with_same_id() {
let mut state = MissionControlState::default();
let id = ActivityId::new("call_write");
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Writing,
metadata: ActivityMetadata {
label: "write file.txt".to_string(),
detail: None,
fields: vec![("bytes".to_string(), "14".to_string())],
},
});
assert_eq!(state.roots, vec![id.clone()]);
assert_eq!(state.nodes[&id].status, ActivityStatus::Writing);
assert_eq!(state.nodes[&id].metadata.fields[0].1, "14");
assert!(state.selected_activity_text().contains("WRITING"));
state.apply_activity_event(ActivityEvent::Finished {
id: id.clone(),
status: ActivityStatus::Success,
metadata: Some(ActivityMetadata {
label: "write file.txt".to_string(),
detail: None,
fields: vec![("bytes".to_string(), "14".to_string())],
}),
});
assert_eq!(state.roots, vec![id.clone()]);
assert_eq!(state.nodes.len(), 1);
assert_eq!(state.nodes[&id].status, ActivityStatus::Success);
}
#[test]
fn hash_edit_activity_node_starts_running_and_finishes_failed_with_same_id() {
let mut state = MissionControlState::default();
let id = ActivityId::new("call_hash_edit");
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata {
label: "hash_edit".to_string(),
detail: None,
fields: vec![("bytes".to_string(), "42".to_string())],
},
});
state.apply_activity_event(ActivityEvent::Finished {
id: id.clone(),
status: ActivityStatus::Failed,
metadata: Some(ActivityMetadata {
label: "hash_edit".to_string(),
detail: None,
fields: vec![("bytes".to_string(), "42".to_string())],
}),
});
assert_eq!(state.roots, vec![id.clone()]);
assert_eq!(state.nodes.len(), 1);
assert_eq!(state.nodes[&id].status, ActivityStatus::Failed);
let debug = format!("{:?}", state.nodes[&id].metadata);
assert!(!debug.contains("SWAP"));
}
#[test]
fn activity_final_preview_updates_selected_child_detail() {
let mut state = MissionControlState::default();
let parent = ActivityId::new("parent");
let child = ActivityId::new("child_tool");
state.apply_activity_event(ActivityEvent::Started {
id: parent.clone(),
parent_id: None,
kind: ActivityKind::SubagentTask,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("g1"),
});
state.apply_activity_event(ActivityEvent::Started {
id: child.clone(),
parent_id: Some(parent.clone()),
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("bash old"),
});
state.apply_activity_event(ActivityEvent::Delta {
id: child.clone(),
preview: "stale preview".to_string(),
});
state.apply_activity_event(ActivityEvent::Finished {
id: child.clone(),
status: ActivityStatus::Failed,
metadata: None,
});
state.selected = 1;
state.apply_activity_event(ActivityEvent::FinalPreview {
id: child.clone(),
preview: "STATUS: failure\nOUTPUT:\nerror detail\n".to_string(),
metadata: Some(ActivityMetadata {
label: "bash failing-command".to_string(),
detail: Some("bash preview: final output only".to_string()),
fields: vec![("exit_code".to_string(), "2".to_string())],
}),
status: Some(ActivityStatus::Failed),
});
let detail = state.selected_activity_text();
assert!(detail.contains("label: bash failing-command"));
assert!(detail.contains("STATUS: failure"));
assert!(detail.contains("OUTPUT:\nerror detail"));
assert!(!detail.contains("stale preview"));
assert_eq!(
state.nodes.get(&parent).unwrap().status,
ActivityStatus::Running
);
assert_eq!(state.nodes.get(&child).unwrap().kind, ActivityKind::Tool);
}
#[test]
fn failed_child_aggregation_does_not_preempt_active_parent_finish() {
let mut state = MissionControlState::default();
let running_parent = ActivityId::new("running-parent");
let running_child = ActivityId::new("running-parent/child");
state.apply_activity_event(ActivityEvent::Started {
id: running_parent.clone(),
parent_id: None,
kind: ActivityKind::SubagentTask,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("g1"),
});
state.apply_activity_event(ActivityEvent::Started {
id: running_child.clone(),
parent_id: Some(running_parent.clone()),
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("bash fail"),
});
state.apply_activity_event(ActivityEvent::Finished {
id: running_child,
status: ActivityStatus::Failed,
metadata: None,
});
assert_eq!(
state.nodes.get(&running_parent).unwrap().status,
ActivityStatus::Running
);
state.apply_activity_event(ActivityEvent::Finished {
id: running_parent.clone(),
status: ActivityStatus::Failed,
metadata: None,
});
assert_eq!(
state.nodes.get(&running_parent).unwrap().status,
ActivityStatus::Failed
);
let terminal_parent = ActivityId::new("terminal-parent");
let terminal_child = ActivityId::new("terminal-parent/child");
state.apply_activity_event(ActivityEvent::Started {
id: terminal_parent.clone(),
parent_id: None,
kind: ActivityKind::SubagentTask,
status: ActivityStatus::Success,
metadata: ActivityMetadata::new("g2"),
});
state.apply_activity_event(ActivityEvent::Started {
id: terminal_child.clone(),
parent_id: Some(terminal_parent.clone()),
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("bash fail"),
});
state.apply_activity_event(ActivityEvent::Finished {
id: terminal_child,
status: ActivityStatus::Failed,
metadata: None,
});
assert_eq!(
state.nodes.get(&terminal_parent).unwrap().status,
ActivityStatus::Failed
);
}
#[test]
fn final_preview_replaces_previous_preview() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool_replace");
state.apply_activity_event(ActivityEvent::FinalPreview {
id: id.clone(),
preview: "first".to_string(),
metadata: Some(ActivityMetadata::new("tool")),
status: Some(ActivityStatus::Success),
});
state.apply_activity_event(ActivityEvent::FinalPreview {
id,
preview: "second".to_string(),
metadata: None,
status: None,
});
let detail = state.selected_activity_text();
assert!(detail.contains("second"));
assert!(!detail.contains("first"));
}
#[test]
fn thinking_summary_deltas_redact_split_secret_preview() {
let secret = "sentinelThinkingPreview123";
let mut state = MissionControlState::default();
state.apply_output_event(&OutputEvent::ThinkingSummaryDelta {
text: "Bear".to_string(),
});
state.apply_output_event(&OutputEvent::ThinkingSummaryDelta {
text: "er ".to_string(),
});
state.apply_output_event(&OutputEvent::ThinkingSummaryDelta {
text: format!("{secret}\n"),
});
let joined = state.transcript.join("\n");
assert!(joined.contains("Bearer <redacted>"), "{joined}");
assert!(!joined.contains(secret), "{joined}");
}
#[test]
fn namespaced_sequential_prompt_activity_ids_render_as_distinct_roots() {
let mut state = MissionControlState::default();
for id in ["run-1/turn-0/tool-0-read", "run-2/turn-0/tool-0-read"] {
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new(id),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("read"),
});
}
assert_eq!(state.roots.len(), 2);
assert_ne!(state.roots[0], state.roots[1]);
assert_eq!(state.visible_nodes().len(), 2);
}
#[test]
fn finish_before_start_creates_minimal_node() {
let mut state = MissionControlState::default();
state.apply_activity_event(ActivityEvent::Finished {
id: ActivityId::new("late"),
status: ActivityStatus::Success,
metadata: None,
});
assert_eq!(state.roots.len(), 1);
}
#[test]
fn preview_is_sanitized_without_truncating_detail_content() {
let mut state = MissionControlState::default();
let id = ActivityId::new("n");
state.apply_activity_event(ActivityEvent::Delta {
id: id.clone(),
preview: format!(
"\u{1b}[31msecret={} bearer sk-{}\u{1b}[0m",
"x".repeat(24),
"x".repeat(24)
),
});
let preview = &state.nodes.get(&id).unwrap().preview;
assert!(!preview.contains("\u{1b}"));
assert!(preview.contains("secret=<redacted>"));
assert!(preview.contains("bearer <redacted>"));
let long_output = "x".repeat(5000);
state.apply_activity_event(ActivityEvent::Delta {
id: id.clone(),
preview: long_output.clone(),
});
let node = state.nodes.get(&id).unwrap();
assert!(node.preview.ends_with(&long_output));
assert!(!node.preview_truncated);
assert!(state.selected_activity_text().contains(&long_output));
}
#[test]
fn selected_activity_detail_uses_human_status_metadata_and_sanitized_preview() {
let mut state = MissionControlState::default();
let id = ActivityId::new("tool-1");
let mut metadata = ActivityMetadata::new("bash cargo test");
metadata.detail = Some("bash preview: final output only".to_string());
metadata.fields = vec![
("exit_code".to_string(), "101".to_string()),
("output_lines".to_string(), "2".to_string()),
("api_key".to_string(), "sk-test123456789".to_string()),
];
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Failed,
metadata,
});
state.apply_activity_event(ActivityEvent::Delta {
id,
preview: "Authorization: Bearer abcdefghijklmnop\nfailed".to_string(),
});
let detail = state.selected_activity_text();
assert!(detail.contains("✗ FAILED"));
assert!(detail.contains("type: tool"));
assert!(detail.contains("summary: bash preview: final output only"));
assert!(detail.contains("exit: 101"));
assert!(detail.contains("lines: 2"));
assert!(detail.contains("api key: <redacted>"));
assert!(detail.contains("Authorization: <redacted>"));
assert!(detail.contains("Sanitized output preview"));
}
#[test]
fn selected_subagent_activity_detail_includes_identity_and_agent_metadata() {
let mut state = MissionControlState::default();
let mut metadata = ActivityMetadata::new("g1 • inspect UI");
metadata.fields = vec![
("identity".to_string(), "frontend-dev".to_string()),
("agent".to_string(), "reviewer".to_string()),
];
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new("subagents/g1"),
parent_id: None,
kind: ActivityKind::SubagentTask,
status: ActivityStatus::Running,
metadata,
});
let detail = state.selected_activity_text();
assert!(detail.contains("type: subagent task"));
assert!(detail.contains("label: g1 • inspect UI"));
assert!(detail.contains("identity: frontend-dev"));
assert!(detail.contains("agent: reviewer"));
}
#[test]
fn compact_activity_metadata_uses_readable_labels_and_limits_fields() {
let metadata = vec![
("exit_code".to_string(), "1".to_string()),
("output_lines".to_string(), "8".to_string()),
("timed_out".to_string(), "false".to_string()),
];
assert_eq!(compact_activity_metadata(&metadata, 2), "exit 1 • lines 8");
let subagent_metadata = vec![
("identity".to_string(), "frontend-dev".to_string()),
("agent".to_string(), "reviewer".to_string()),
];
assert_eq!(
compact_activity_metadata(&subagent_metadata, 2),
"identity: frontend-dev • agent reviewer"
);
}
#[test]
fn assistant_activity_preview_is_not_truncated_in_detail_panel() {
let mut state = MissionControlState::default();
let id = ActivityId::new("assistant-1");
let long_output = "x".repeat(5000);
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id: None,
kind: ActivityKind::Assistant,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("assistant"),
});
state.apply_activity_event(ActivityEvent::Delta {
id: id.clone(),
preview: long_output.clone(),
});
let detail = state.selected_activity_text();
let node = state.nodes.get(&id).unwrap();
assert_eq!(node.preview, long_output);
assert!(detail.contains(&long_output));
assert_eq!(
state.detail_visual_rows(80),
ratatui_wrapped_visual_rows(&detail, 80)
);
assert!(!node.preview_truncated);
assert!(!detail.contains("preview truncated"));
}
#[test]
fn preview_sanitization_reuses_shared_redaction_without_hiding_counts() {
let sanitized = sanitize_preview(
"\u{1b}[33mAuthorization: Bearer abcdefghijklmnop input_tokens=42 api_key='secret-value'\u{1b}[0m",
);
assert!(!sanitized.contains('\u{1b}'));
assert!(sanitized.contains("Authorization: <redacted>"));
assert!(sanitized.contains("input_tokens=42"));
assert!(sanitized.contains("api_key=<redacted>"));
}
#[test]
fn tool_result_reconciles_final_preview_and_metadata_when_delta_dropped() {
let call = crate::providers::ToolCall {
id: "call_bash".to_string(),
name: "bash".to_string(),
arguments: serde_json::json!({"command":"printf hi", "timeout": 5}),
};
let result = crate::tools::ToolResult {
tool_name: "bash".to_string(),
success: true,
content: "hi\n".to_string(),
metadata: serde_json::json!({"exit_code":0,"timed_out":false}),
display: crate::tools::ToolResultDisplay::default(),
};
let summary = crate::output::tool_display_summary(&call, &result);
let mut state = MissionControlState::default();
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new("call_bash"),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("bash printf hi"),
});
state.apply_output_event(&OutputEvent::ToolResult {
call: Box::new(call),
result: Box::new(result),
summary: Box::new(summary),
});
let detail = state.selected_activity_text();
assert!(detail.contains("Status: SUCCESS"));
assert!(detail.contains("Tool • bash • bash printf hi"));
assert!(detail.contains("Metadata/Params:"));
assert!(detail.contains("exit_code"));
assert!(detail.contains("hi"));
}
#[test]
fn selected_activity_text_uses_namespaced_tool_result_id() {
let mut state = MissionControlState::default();
state.apply_activity_event(ActivityEvent::Started {
id: ActivityId::new("run-1/call_bash"),
parent_id: None,
kind: ActivityKind::Tool,
status: ActivityStatus::Running,
metadata: ActivityMetadata::new("bash old"),
});
state.apply_output_event(&OutputEvent::ToolResult {
call: Box::new(crate::providers::ToolCall {
id: "run-1/call_bash".to_string(),
name: "bash".to_string(),
arguments: serde_json::json!({"command":"printf hi"}),
}),
result: Box::new(crate::tools::ToolResult {
tool_name: "bash".to_string(),
success: true,
content: "hi\n".to_string(),
metadata: serde_json::json!({"exit_code":0}),
display: crate::tools::ToolResultDisplay::default(),
}),
summary: Box::new(crate::output::ToolDisplaySummary {
tool_name: "bash".to_string(),
status: crate::output::ToolStatus::Success,
unicode_mark: "✓",
ascii_mark: "OK",
label: "bash printf hi".to_string(),
metadata: vec![("exit_code".to_string(), "0".to_string())],
}),
});
let detail = state.selected_activity_text();
assert!(detail.contains("Status: SUCCESS"));
assert!(detail.contains("Tool • bash • bash printf hi"));
assert!(detail.contains("Metadata/Params:"));
assert!(detail.contains("hi"));
}