7.6 KiB
PR #1672 Review Takeaways — Easily Selectable History (bolinfest)
DOs
-
Keep the protocol boundary clean: move UI-only types out of
protocol.rs.- Example: put
FinalOutputincodex-common(or similar), notcore/protocol.
// codex-common/src/final_output.rs use std::fmt; use codex_core::protocol::TokenUsage; use serde::{Serialize, Deserialize}; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct FinalOutput { pub token_usage: TokenUsage, pub final_message: Option<String>, } impl From<TokenUsage> for FinalOutput { fn from(token_usage: TokenUsage) -> Self { Self { token_usage, final_message: None } } } impl fmt::Display for FinalOutput { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let u = &self.token_usage; write!( f, "Token usage: total={} input={}{} output={}{}", u.total_tokens, u.input_tokens, u.cached_input_tokens .map(|c| format!(" (cached {c})")) .unwrap_or_default(), u.output_tokens, u.reasoning_output_tokens .map(|r| format!(" (reasoning {r})")) .unwrap_or_default() ) } }// codex-rs/cli/src/main.rs let usage = codex_tui::run_main(tui_cli, codex_linux_sandbox_exe)?; println!("{}", codex_common::FinalOutput::from(usage)); - Example: put
-
Use doc comments and attach them to items.
/// Buffers assistant response deltas until a final message arrives. /// This avoids partial-line rendering in scrollback. struct ChatWidget<'a> { /// Accumulates streamed assistant text. answer_buffer: String, // ... } -
Keep comments current; remove stale references.
// Before: mentions a removed design doc /// … hybrid scrollback model described in `fix-history-plan.md`. // After: describe the behavior without referencing non-existent docs /// Appends immutable history above the inline viewport. -
DRY repetitive “append last history” code with a helper.
impl ChatWidget<'_> { fn emit_last_history_entry(&mut self) { if let Some(lines) = self.conversation_history.last_entry_plain_lines() { self.app_event_tx.send(AppEvent::InsertHistory(lines)); } } fn submit_user_message(&mut self, msg: String) { self.conversation_history.add_user_message(msg); self.emit_last_history_entry(); } } -
Encapsulate wrapping/normalization logic in a small struct (not closures).
// tui/src/insert_history.rs (outline) struct LineBuilder { term_width: usize, spans: Vec<Span<'static>>, width: usize } impl LineBuilder { fn new(term_width: usize) -> Self { Self { term_width, spans: vec![], width: 0 } } fn push_word(&mut self, word: &mut String, style: Style, out: &mut Vec<Line<'static>>) { /* … */ } fn consume_whitespace(&mut self, ws: &mut String, style: Style, out: &mut Vec<Line<'static>>) { /* … */ } fn flush_line(&mut self, out: &mut Vec<Line<'static>>) { /* … */ } } -
Normalize and wrap text before
Terminal::insert_before.pub(crate) fn insert_history_lines(terminal: &mut tui::Tui, lines: Vec<Line<'static>>) { let term_width = terminal.size().map(|a| a.width).unwrap_or(80) as usize; let mut physical: Vec<Line<'static>> = Vec::new(); for logical in lines { let mut b = LineBuilder::new(term_width); let mut ws = String::new(); for span in logical.spans { let style = span.style; let mut word = String::new(); for ch in span.content.chars() { if ch == '\n' { b.push_word(&mut word, style, &mut physical); ws.clear(); b.flush_line(&mut physical); continue; } if ch.is_whitespace() { b.push_word(&mut word, style, &mut physical); ws.push(ch); } else { b.consume_whitespace(&mut ws, style, &mut physical); word.push(ch); } } b.push_word(&mut word, style, &mut physical); } if !b.spans.is_empty() { physical.push(Line::from(std::mem::take(&mut b.spans))); } else { physical.push(Line::from(Vec::<Span<'static>>::new())); } } let total = physical.len() as u16; terminal.insert_before(total, |buf| { for (i, line) in physical.into_iter().enumerate() { Paragraph::new(line).render(Rect { x: 0, y: i as u16, width: buf.area.width, height: 1 }, buf); } }).ok(); } -
Preserve or clearly indicate unsupported outputs (e.g., images).
match cell { HistoryCell::CompletedMcpToolCallWithImageOutput { .. } => vec![ Line::from("tool result (image output omitted)"), Line::from(""), ], _ => view.lines.clone(), } -
Keep docs and config in sync when removing features.
// core/src/config_types.rs #[derive(Deserialize, Debug, Clone, PartialEq, Default)] pub struct Tui {} // remove disable_mouse_capture here // config.md [tui] # (mouse capture option removed to match code) -
Remove noisy/unnecessary comments.
// Before use crate::tui; // for the Tui type alias // After use crate::tui;
DON’Ts
-
Don’t put UI/CLI glue types in
core/src/protocol.rs.// Avoid // core/src/protocol.rs #[derive(Debug)] pub struct FinalOutput { /* UI concern */ } -
Don’t leave obsolete references in comments.
// Avoid: referring to deleted docs or plans /// See `fix-history-plan.md` for details. -
Don’t use
//where///doc comments are appropriate.// Avoid // Buffers assistant response deltas. // Prefer /// Buffers assistant response deltas. -
Don’t stream partial fragments that cause truncation/flicker; flush on full message or at newline boundaries.
// Avoid: re-rendering on every tiny delta self.answer_buffer.push_str(&delta); self.conversation_history.replace_prev_agent_message(&self.config, self.answer_buffer.clone()); // Prefer: flush only complete lines (or final) self.answer_buffer.push_str(&delta); if let Some(idx) = self.answer_buffer.rfind('\n') { let complete = self.answer_buffer[..=idx].to_string(); self.conversation_history.add_agent_message(&self.config, complete); self.emit_last_history_entry(); self.answer_buffer = self.answer_buffer[idx+1..].to_string(); } -
Don’t assume
Paragraph::wrapwill apply duringinsert_before; perform explicit wrapping.// Avoid: relying on implicit wrap with insert_before Paragraph::new(line).wrap(Wrap { trim: true }); // Prefer: pre-wrap into single-row Lines, then render each on its own row -
Don’t duplicate formatting/printing logic across binaries; centralize via
Display.// Avoid println!("{}", format!("Token usage: total={}", usage.total_tokens)); // Prefer println!("{}", codex_common::FinalOutput::from(usage)); -
Don’t leave half-removed features in code or docs.
// Avoid: keeping `disable_mouse_capture` in docs while code ignores it -
Don’t use large, stateful inline closures for formatting/flow; use small functions or structs.
// Avoid let flush_word = |word: &mut String, spans: &mut Vec<Span<'static>>, width: &mut usize, /* … */| { /* … */ }; // Prefer struct LineBuilder { /* … */ } impl LineBuilder { fn push_word(&mut self, /* … */) { /* … */ } }