use super::*;
use crate::sessions::SessionEvent;
use std::io::{BufRead, Read};
const AREA: Rect = Rect::new(0, 0, 120, 36);
const FRAMES: usize = 30;
const TOTAL_NODES: usize = 30_000;
const BATCHES: usize = 120;
const TASKS_PER_BATCH: usize = 10;
const MAX_SESSION_BYTES: u64 = 16 * 1024 * 1024;
const MAX_SESSION_EVENTS: usize = 20_000;
const PREVIEWS: [&str; 4] = [
"# Inspection\n\n- checked **synthetic** input\n- no external work\n",
"```rust\nfn example() -> usize {\n 42\n}\n```\n",
"diff --git a/example.rs b/example.rs\n--- a/example.rs\n+++ b/example.rs\n@@ -1 +1 @@\n-old\n+new\n",
"stdout:\nsynthetic check started\nsynthetic check finished\nexit status: 0\n",
];
fn require_test_backend() {
let backend = std::env::var_os("PROFILE_TUI_RENDER_BACKEND");
assert!(
backend
.as_deref()
.is_none_or(|value| value == "test" || value.is_empty()),
"mixed/private profiles require the test backend"
);
}
fn add_synthetic_node(
state: &mut MissionControlState,
id: ActivityId,
parent_id: Option<ActivityId>,
kind: ActivityKind,
index: usize,
) {
let status = match index % 3 {
0 => ActivityStatus::Success,
1 => ActivityStatus::Failed, _ => ActivityStatus::Running,
};
let label = match kind {
ActivityKind::Tool => ["read", "bash", "hash_edit", "grep"][index % 4],
ActivityKind::Assistant => "reasoning • synthetic inspection",
_ => "synthetic activity",
};
state.apply_activity_event(ActivityEvent::Started {
id: id.clone(),
parent_id,
kind,
status,
metadata: ActivityMetadata::new(label),
});
state.apply_activity_event(ActivityEvent::Delta {
id,
preview: PREVIEWS[index % PREVIEWS.len()].repeat(4),
});
}
fn synthetic_massive_state() -> MissionControlState {
let mut state = MissionControlState::default();
for batch_index in 0..BATCHES {
state.apply_output_event(&OutputEvent::UserPrompt {
text: "Inspect the synthetic example.".into(),
});
let batch = ActivityId::new(format!("mixed-batch-{batch_index}"));
add_synthetic_node(
&mut state,
batch.clone(),
None,
ActivityKind::SubagentBatch,
batch_index,
);
for task_index in 0..TASKS_PER_BATCH {
let task = batch.child(format!("task-{task_index}"));
add_synthetic_node(
&mut state,
task.clone(),
Some(batch.clone()),
ActivityKind::SubagentTask,
task_index,
);
add_synthetic_node(
&mut state,
task.child("tool"),
Some(task),
ActivityKind::Tool,
task_index,
);
}
}
let hierarchy_nodes = BATCHES * (1 + TASKS_PER_BATCH * 2);
for index in hierarchy_nodes..TOTAL_NODES {
let kind = match index % 6 {
0 => ActivityKind::Tool,
1 => ActivityKind::Assistant,
2 => ActivityKind::Hook,
3 => ActivityKind::ProviderContextInjection,
4 => ActivityKind::Diagnostic,
_ => ActivityKind::Compaction,
};
add_synthetic_node(
&mut state,
ActivityId::new(format!("mixed-root-{index}")),
None,
kind,
index,
);
if index % 120 == 0 {
state.apply_output_event(&OutputEvent::UserPrompt {
text: "Review the synthetic changes.".into(),
});
state.apply_output_event(&OutputEvent::AssistantComplete {
text: PREVIEWS[0].into(),
});
}
}
assert_eq!(state.nodes.len(), TOTAL_NODES);
assert_eq!(
state
.nodes
.values()
.filter(|node| node.kind == ActivityKind::SubagentTask)
.count(),
1_200
);
state
}
fn print_state_counts(source: &'static str, state: &MissionControlState) {
let mut kinds = BTreeMap::new();
for kind in [
ActivityKind::Tool,
ActivityKind::Hook,
ActivityKind::ProviderContextInjection,
ActivityKind::SubagentBatch,
ActivityKind::SubagentTask,
ActivityKind::Assistant,
ActivityKind::Diagnostic,
ActivityKind::Compaction,
] {
kinds.insert(format!("{kind:?}"), 0usize);
}
for node in state.nodes.values() {
*kinds.entry(format!("{:?}", node.kind)).or_default() += 1;
}
println!(
"mixed_render source={source} nodes={} kinds={kinds:?} retained_transcript_entries={}",
state.nodes.len(),
state.transcript.len()
);
}
fn representative_selection_indices(state: &MissionControlState) -> Vec<usize> {
let mut kinds = Vec::new();
state
.cached_visible_nodes()
.iter()
.enumerate()
.filter_map(|(index, (_, id))| {
let kind = &state.nodes.get(id)?.kind;
if kinds.contains(kind) {
None
} else {
kinds.push(kind.clone());
Some(index)
}
})
.collect()
}
fn open_representative_popup(state: &mut MissionControlState) -> bool {
let pair = state.roots.iter().find_map(|batch| {
if state.nodes.get(batch)?.kind != ActivityKind::SubagentBatch {
return None;
}
let task = state.children.get(batch)?.iter().find(|task| {
state
.nodes
.get(*task)
.is_some_and(|node| node.kind == ActivityKind::SubagentTask)
})?;
Some((batch.clone(), task.clone()))
});
pair.is_some_and(|(batch, task)| state.open_subagent_viewer(batch, task))
}
fn run_mixed_profiles(source: &'static str, mut make_state: impl FnMut() -> MissionControlState) {
require_test_backend();
let mut setup = Duration::ZERO;
let mut summaries = Vec::new();
for scenario in [
"mixed_steady",
"mixed_scroll_selected_detail",
"mixed_subagent_popup",
] {
let started = Instant::now();
let mut state = make_state();
setup += started.elapsed();
if summaries.is_empty() {
print_state_counts(source, &state);
}
let selections = representative_selection_indices(&state);
if scenario == "mixed_subagent_popup" && !open_representative_popup(&mut state) {
println!(
"mixed_render source={source} scenario=mixed_subagent_popup skipped=no_subagent_batch"
);
continue;
}
if scenario == "mixed_scroll_selected_detail" {
state.focus_activity_detail();
}
summaries.push(run_render_scenario(
scenario,
AREA,
FRAMES,
state,
|state, frame, area| match scenario {
"mixed_scroll_selected_detail" => {
if let Some(index) = selections.get(frame % selections.len().max(1)) {
state.selected = *index;
state.activity_follow_tail = false;
state.reset_detail();
state.ensure_activity_selection_visible(area.height / 2);
}
state.set_scroll_offset(&state.scroll_views.transcript, frame * 3);
state.scroll_activity_tree_up_by(3, area.height / 2);
}
"mixed_subagent_popup" => {
if let Some(timeline) = crate::tui::render::subagent_viewer_timeline_area(area)
{
state.scroll_subagent_viewer_up(
3,
timeline.height,
timeline.width.saturating_sub(1).max(1),
);
}
}
_ => {}
},
));
}
let metric = if source == "private_session" {
"hydration_ms"
} else {
"setup_ms"
};
println!(
"mixed_render source={source} aggregate_{metric}={} state_builds=3",
setup.as_millis()
);
assert_render_summaries(&summaries);
}
#[test]
#[ignore = "synthetic in-memory render profile; not disk-history hydration"]
fn profile_mixed_massive_activity_render() {
run_mixed_profiles(
"synthetic_in_memory_bypasses_session_loader",
synthetic_massive_state,
);
}
fn read_private_events(path: &std::ffi::OsStr) -> Vec<SessionEvent> {
let file = fs::File::open(path)
.unwrap_or_else(|_| panic!("private profile input could not be opened"));
let metadata = file
.metadata()
.unwrap_or_else(|_| panic!("private profile metadata unavailable"));
assert!(
metadata.is_file(),
"private profile input must be a regular file"
);
assert!(
metadata.len() <= MAX_SESSION_BYTES,
"private profile exceeds byte limit"
);
let mut bytes = Vec::new();
file.take(MAX_SESSION_BYTES)
.read_to_end(&mut bytes)
.unwrap_or_else(|_| panic!("private profile read failed"));
assert_eq!(
bytes.len() as u64,
metadata.len(),
"private profile input changed during read"
);
let mut events = Vec::new();
for line in bytes.as_slice().lines() {
let line = line.unwrap_or_else(|_| panic!("private profile line decoding failed"));
if line.trim().is_empty() {
continue;
}
assert!(
events.len() < MAX_SESSION_EVENTS,
"private profile exceeds event limit"
);
let event = serde_json::from_str::<SessionEvent>(&line)
.unwrap_or_else(|_| panic!("private profile contains an invalid session event"));
events.push(event);
}
println!(
"mixed_render source=private_session input_events={}",
events.len()
);
events
}
#[test]
#[ignore = "private render profile; requires explicit PROFILE_PRIVATE_SESSION_PATH"]
fn profile_private_session_render() {
let Some(path) = std::env::var_os("PROFILE_PRIVATE_SESSION_PATH") else {
println!("mixed_render source=private_session skipped=explicit_path_required");
return;
};
require_test_backend();
let events = read_private_events(&path);
run_mixed_profiles("private_session", || {
crate::tui::hydrate_profile_session_events(&events)
});
}