use super::*;
use serde_json::Value;
use std::collections::BTreeMap;
pub(super) fn project_code_mode_card(
state: &MissionControlState,
id: TranscriptCardId,
link: Option<&TranscriptActivityLink>,
) -> TranscriptCard {
let node = link.and_then(|link| state.nodes.get(&link.activity_id));
let detail = node.and_then(|node| node.tool_detail.as_ref());
let status = node
.map(|node| transcript_status_from_activity(node.status))
.unwrap_or_else(|| transcript_tool_status(state, id.entry_index));
let running = status == TranscriptCardStatus::Running;
let execution = detail
.and_then(|detail| detail.metadata.get("code_mode_execution"))
.filter(|value| value.is_object());
let children: Vec<_> = node
.and_then(|node| state.children.get(&node.id))
.into_iter()
.flatten()
.filter_map(|id| state.nodes.get(id))
.filter(|node| node.kind == ActivityKind::Tool)
.collect();
let count = |key: &str, fallback: usize| {
execution
.and_then(|value| value.get(key))
.and_then(Value::as_u64)
.unwrap_or(fallback as u64)
};
let calls = count("inner_calls", children.len());
let succeeded = count(
"successful",
children
.iter()
.filter(|node| node.status == ActivityStatus::Success)
.count(),
);
let failed = count(
"recoverable_failures",
children
.iter()
.filter(|node| node.status == ActivityStatus::Failed)
.count(),
);
let unknown = count(
"unknown_outcomes",
children
.iter()
.filter(|node| {
!matches!(
node.status,
ActivityStatus::Success | ActivityStatus::Failed
)
})
.count(),
);
let warnings: Vec<_> = execution
.and_then(|value| value.get("warnings"))
.and_then(Value::as_array)
.into_iter()
.flatten()
.filter_map(Value::as_str)
.collect();
let mut title = format!("Code Mode • {}", counted(calls, "call", "calls"));
if let Some(elapsed) = execution.and_then(|value| value["elapsed_ms"].as_u64()) {
title.push_str(&if elapsed < 1000 {
format!(" • {elapsed} ms")
} else {
format!(" • {:.1}s", elapsed as f64 / 1000.0)
});
}
let mut rows = Vec::new();
if let Some(intent) = detail
.and_then(|detail| detail.params.get("intent"))
.and_then(Value::as_str)
.map(str::trim)
.filter(|intent| !intent.is_empty())
{
rows.push(compact_line(intent));
}
if !running {
if let Some(tokens) = detail
.and_then(|detail| detail.metadata.get("code_mode_response_token_estimate"))
.and_then(Value::as_u64)
{
rows.push(format!("Model response: ≈{tokens} tokens"));
} else {
rows.push("Model response: token estimate unavailable".into());
}
let mut tools = BTreeMap::<&str, usize>::new();
if let Some(actions) = execution.and_then(|value| value["actions"].as_array()) {
for name in actions.iter().filter_map(|action| action["tool"].as_str()) {
*tools.entry(name).or_default() += 1;
}
} else {
for child in &children {
*tools
.entry(child.transcript_tool_name.as_deref().unwrap_or("tool"))
.or_default() += 1;
}
}
if !tools.is_empty() {
rows.push(compact_line(
&tools
.into_iter()
.map(|(name, count)| format!("{name} ×{count}"))
.collect::<Vec<_>>()
.join(" · "),
));
}
let omitted = count("actions_omitted", 0);
if omitted > 0 {
rows.push(format!(
"… {} not listed",
counted(omitted, "call", "calls")
));
}
}
let has_warning = failed > 0 || (!running && unknown > 0) || !warnings.is_empty();
let mut summary = if running {
format!("{} completed", counted(succeeded + failed, "call", "calls"))
} else {
match status {
TranscriptCardStatus::Success if has_warning => {
format!("⚠ Script completed · {succeeded} succeeded")
}
TranscriptCardStatus::Success => format!("✓ {succeeded} succeeded"),
TranscriptCardStatus::Canceled => format!("⊘ Script canceled · {succeeded} succeeded"),
_ => format!("✗ Script failed · {succeeded} succeeded"),
}
};
if failed > 0 {
summary.push_str(&format!(" · {failed} failed"));
if running {
summary.insert_str(0, "⚠ ");
}
}
if !running && unknown > 0 {
summary.push_str(&format!(
" · {}",
counted(unknown, "unknown outcome", "unknown outcomes")
));
}
rows.push(summary);
if matches!(
status,
TranscriptCardStatus::Failed | TranscriptCardStatus::Canceled
) {
if status == TranscriptCardStatus::Failed {
let output = detail
.map(|detail| detail.output.as_ref())
.unwrap_or_default();
let parsed = serde_json::from_str::<Value>(output).ok();
if let Some(error) = parsed.as_ref().and_then(|value| value["error"].as_str()) {
rows.push(format!("✗ {}", compact_line(error)));
} else if !output.trim_start().starts_with('{') && !output.trim().is_empty() {
rows.push(format!(
"✗ {}",
compact_line(output.lines().next().unwrap_or_default())
));
}
}
rows.push("⚠ Completed actions remain applied; no rollback".into());
}
for child in children
.iter()
.filter(|node| node.status == ActivityStatus::Failed)
.take(2)
{
let name = child.transcript_tool_name.as_deref().unwrap_or("tool");
let error = child
.tool_detail
.as_ref()
.map_or(child.preview.as_str(), |detail| detail.output.as_ref());
rows.push(format!(
"✗ {}: {}",
compact_line(name),
super::tool_adapters::bounded_error_line(error)
));
}
for warning in warnings.iter().take(2) {
rows.push(format!("⚠ {}", compact_line(warning)));
}
if warnings.len() > 2 {
rows.push(format!(
"⚠ {} more warnings; inspect activity details",
warnings.len() - 2
));
}
let activity_rows = children
.iter()
.rev()
.take(TOOL_PREVIEW_LINES)
.rev()
.map(|child| {
let name = child.transcript_tool_name.as_deref().unwrap_or("tool");
let target = if ToolCapability::from_dispatch_name(name).is_some() {
let card = project_activity_card(state, child);
compact_line(card.body.lines().next().unwrap_or_default())
} else {
String::new()
};
TranscriptCardActivityRow {
label: format!("{} {target}", compact_line(name))
.trim_end()
.to_string(),
status: transcript_status_from_activity(child.status),
activity_id: child.id.clone(),
}
})
.collect();
if let Some(execution) = execution {
let changed = count("changed_path_count", 0);
let incomplete = execution["changed_paths_incomplete"].as_bool() == Some(true);
if changed > 0 || incomplete {
let qualifier = if incomplete { "at least " } else { "" };
rows.push(format!(
"{qualifier}{} changed",
counted(changed, "tracked file", "tracked files")
));
let paths = execution["changed_paths"].as_array();
for path in paths
.into_iter()
.flatten()
.take(3)
.filter_map(Value::as_str)
{
rows.push(format!(" {}", compact_line(path)));
}
let shown = paths.map_or(0, |paths| paths.len().min(3)) as u64;
if changed > shown {
rows.push(format!("… {} more tracked files", changed - shown));
}
}
}
TranscriptCard {
id,
role: TranscriptCardRole::CodeMode,
status,
title,
metadata: Vec::new(),
body: rows.join("\n"),
body_lines: rows.into_iter().map(DisplayLine::plain).collect(),
children: Vec::new(),
activity_rows,
subagent_card: None,
}
}
fn compact_line(text: &str) -> String {
truncate_preview_line(&sanitized_single_line(text), PREVIEW_LINE_MAX_DISPLAY_WIDTH)
}
fn counted(count: u64, singular: &str, plural: &str) -> String {
format!("{count} {}", if count == 1 { singular } else { plural })
}