| 1 | //! Rendering for plan-update transcript cells. |
| 2 | |
| 3 | use ratatui::text::Line; |
| 4 | |
| 5 | use crate::tools::plan::{PlanSnapshot, StepStatus}; |
| 6 | |
| 7 | use super::{ |
| 8 | ToolStatus, render_compact_kv, render_tool_header, tool_status_label, tool_value_style, |
| 9 | }; |
| 10 | |
| 11 | /// Cell for plan updates emitted by the plan tool. |
| 12 | #[derive(Debug, Clone)] |
| 13 | pub struct PlanUpdateCell { |
| 14 | pub snapshot: PlanSnapshot, |
| 15 | pub status: ToolStatus, |
| 16 | } |
| 17 | |
| 18 | impl PlanUpdateCell { |
| 19 | /// Render the plan update cell into lines. |
| 20 | pub fn lines_with_motion(&self, width: u16, low_motion: bool) -> Vec<Line<'static>> { |
| 21 | let mut lines = Vec::new(); |
| 22 | lines.push(render_tool_header( |
| 23 | "Legacy plan", |
| 24 | tool_status_label(self.status), |
| 25 | self.status, |
| 26 | None, |
| 27 | low_motion, |
| 28 | )); |
| 29 | |
| 30 | render_plan_snapshot_lines(&self.snapshot, &mut lines, width); |
| 31 | |
| 32 | lines |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | fn render_plan_snapshot_lines(snapshot: &PlanSnapshot, lines: &mut Vec<Line<'static>>, width: u16) { |
| 37 | render_plan_optional(lines, "title", snapshot.title.as_deref(), width); |
| 38 | render_plan_optional(lines, "objective", snapshot.objective.as_deref(), width); |
| 39 | render_plan_optional(lines, "context", snapshot.context_summary.as_deref(), width); |
| 40 | render_plan_optional(lines, "explain", snapshot.explanation.as_deref(), width); |
| 41 | render_plan_list(lines, "source", &snapshot.sources_used, width); |
| 42 | render_plan_list(lines, "file", &snapshot.critical_files, width); |
| 43 | render_plan_list(lines, "constraint", &snapshot.constraints, width); |
| 44 | render_plan_optional( |
| 45 | lines, |
| 46 | "approach", |
| 47 | snapshot.recommended_approach.as_deref(), |
| 48 | width, |
| 49 | ); |
| 50 | render_plan_optional( |
| 51 | lines, |
| 52 | "verify", |
| 53 | snapshot.verification_plan.as_deref(), |
| 54 | width, |
| 55 | ); |
| 56 | render_plan_optional(lines, "risk", snapshot.risks_and_unknowns.as_deref(), width); |
| 57 | render_plan_optional(lines, "handoff", snapshot.handoff_packet.as_deref(), width); |
| 58 | |
| 59 | for step in &snapshot.items { |
| 60 | let marker = match step.status { |
| 61 | StepStatus::Completed => "done", |
| 62 | StepStatus::InProgress => "live", |
| 63 | StepStatus::Pending => "next", |
| 64 | }; |
| 65 | lines.extend(render_compact_kv( |
| 66 | marker, |
| 67 | &step.step, |
| 68 | tool_value_style(), |
| 69 | width, |
| 70 | )); |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | fn render_plan_optional( |
| 75 | lines: &mut Vec<Line<'static>>, |
| 76 | label: &str, |
| 77 | value: Option<&str>, |
| 78 | width: u16, |
| 79 | ) { |
| 80 | if let Some(value) = value.map(str::trim).filter(|value| !value.is_empty()) { |
| 81 | lines.extend(render_compact_kv(label, value, tool_value_style(), width)); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | fn render_plan_list(lines: &mut Vec<Line<'static>>, label: &str, values: &[String], width: u16) { |
| 86 | for value in values { |
| 87 | let value = value.trim(); |
| 88 | if !value.is_empty() { |
| 89 | lines.extend(render_compact_kv(label, value, tool_value_style(), width)); |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 |