Files
codex/prs/bolinfest/study/PR-1810-study.md
2025-09-02 15:17:45 -07:00

256 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
**DOs**
- Bold, Exact Assertions: prefer `assert_eq!` over loose contains checks; use pretty diffs for failures.
```
use pretty_assertions::assert_eq;
assert_eq!(
items,
vec![ResponseItem::Message {
id: None,
role: "assistant".to_string(),
content: vec![ContentItem::OutputText { text: "Hello, world!".to_string() }],
}]
);
```
- Merge Duplicates In History: combine adjacent assistant messages and test the exact merged result.
```
// merge logic
match (&*item, history.items.last_mut()) {
(ResponseItem::Message { role: r1, content: c1, .. },
Some(ResponseItem::Message { role: r2, content: c2, .. }))
if r1 == "assistant" && r2 == "assistant" => append_text_content(c2, c1),
_ => history.items.push(item.clone()),
}
```
- Use Destructuring To Ignore Fields: make intent clear when final payload is unused due to streaming.
```
match event {
EventMsg::AgentMessage(AgentMessageEvent { message: _ }) => {
self.finalize_stream(StreamKind::Answer);
}
_ => {}
}
```
- Centralize Redraws And Control Flow: track whether a branch handled the update, then redraw once.
```
pub fn update_status_text(&mut self, text: String) {
let mut handled = false;
if let Some(view) = self.active_view.as_mut() {
handled |= matches!(view.update_status_text(text.clone()), NeedsRedraw);
} else {
let mut v = StatusIndicatorView::new(self.app_event_tx.clone());
v.update_text(text.clone());
self.active_view = Some(Box::new(v));
self.status_view_active = true;
handled = true;
}
if !handled {
self.live_status.get_or_insert_with(|| StatusIndicatorWidget::new(self.app_event_tx.clone()))
.update_text(text);
}
self.request_redraw();
}
```
- Import Types At Top: avoid fully-qualified paths in signatures.
```
use ratatui::text::Line;
pub fn set_live_ring_rows(&mut self, max_rows: u16, rows: Vec<Line<'static>>) { ... }
```
- Prefer &'static str When Possible: avoid allocating `String` for static content.
```
lines.push(ratatui::text::Line::from(""));
```
- Name Things Clearly: use descriptive names instead of one-letter variables.
```
if let Some(view) = &self.active_view {
view.render(view_rect, buf);
}
```
- Make Test Utilities Reusable: factor repeated setup into helpers or a scenario struct.
```
struct TestScenario { width: u16, height: u16, term: Terminal<TestBackend> }
impl TestScenario {
fn run_insert(&mut self, lines: Vec<Line<'static>>) -> Vec<u8> { ... }
fn screen_rows_from_bytes(&self, bytes: &[u8]) -> Vec<String> { ... }
}
```
- Gate Emulator Tests And Keep Them Dev-Only: put vt100 under `dev-dependencies` and behind a feature.
```
# Cargo.toml
[features]
vt100-tests = []
[dev-dependencies]
vt100 = { version = "0.16.2", optional = true }
pretty_assertions = "1"
```
- Use Writer-Based Output For Testability: separate rendering from I/O, then test via a buffer.
```
pub fn insert_history_lines_to_writer<B, W>(
term: &mut Terminal<B>,
writer: &mut W,
lines: Vec<Line>,
) where
B: ratatui::backend::Backend,
W: std::io::Write,
{ /* queue!(writer, ...); */ }
```
- Handle Unicode Width Correctly: wrap using display width (emoji/CJK) and provide invariance tests.
```
pub fn take_prefix_by_width(text: &str, max_cols: usize) -> (String, &str, usize) { ... }
let (prefix, _suffix, width) = take_prefix_by_width("😀你好", 4);
assert_eq!(prefix, "😀"); // 😀 is width 2
assert_eq!(width, 2);
```
- Strip ANSI Before Measuring Or Animating: prevent control bytes from corrupting rendering.
```
let line = ansi_escape_line(&self.text);
let plain = line.spans.iter().map(|s| s.content.as_ref()).collect::<String>();
```
- Be Explicit About Channel Backpressure And Logs: pick bounded vs. unbounded intentionally and log at the right level.
```
// backpressure on submissions; allow bursts on events
let (tx_sub, rx_sub) = async_channel::bounded(64);
let (tx_event, rx_event) = async_channel::unbounded();
debug!("Configuring session: model={model}; provider={provider:?}; resume={resume_path:?}");
```
- Keep UI Layout Invariants Tested: assert bottom padding behavior and overlay stacking.
```
// height=2: top has status, bottom is blank padding
assert!(row0.contains("Working"));
assert!(row1.trim().is_empty());
```
- Clean Module Visibility For Tests: make internal modules `pub` where practical within the workspace (e.g., `custom_terminal`, `insert_history`, `live_wrap`).
**DONTs**
- Dont Use Vague Tests: avoid `assert!(contains(...))` when you can assert the exact structure or full screen state.
- Dont Scatter `request_redraw()` Across Branches: avoid multiple early returns that obscure flow; consolidate redraw at the end.
- Dont Keep Emulator Crates In Runtime Deps: keep `vt100` in `[dev-dependencies]` and guard tests with `#[cfg(feature = "vt100-tests")]`.
- Dont Leave Single-Letter Identifiers: replace `ov`, `s`, etc., with clear names like `view`, `text`.
- Dont Hardcode Fully Qualified Types In Sigs: import once at the module top.
- Dont Allocate Strings For Literals: use `&'static str` where possible.
- Dont Leave Unused Code/Events/Comments: remove unused variants (e.g., stale `AppEvent`), “Removed …” comments, or unclear references (e.g., “TS ref?”) without context.
- Dont Emit Raw ANSI Into Buffers: always sanitize before measuring or rendering to avoid cursor jumps/artifacts.
- Dont Forget To Clear Live Overlays: call `clear_live_ring()` and reset `status_view_active` when tasks complete.
- Dont Duplicate Assistant Messages: merge adjacent assistant entries in conversation history.
- Dont Overuse `info!` For Noisy Logs: prefer `debug!` for verbose or frequent messages.
- Dont Handwave Backpressure: document why a channel is bounded/unbounded and choose deliberately.
- Dont Leave Padding/Height Edge Cases Untested: small heights should gracefully shrink padding while preserving essential content.
**Code Snippets Recap**
- Unified status update with single redraw:
```
pub fn update_status_text(&mut self, text: String) {
let mut handled = false;
if let Some(view) = self.active_view.as_mut() {
handled |= matches!(view.update_status_text(text.clone()), NeedsRedraw);
} else {
let mut v = StatusIndicatorView::new(self.app_event_tx.clone());
v.update_text(text.clone());
self.active_view = Some(Box::new(v));
self.status_view_active = true;
handled = true;
}
if !handled {
self.live_status.get_or_insert_with(|| StatusIndicatorWidget::new(self.app_event_tx.clone()))
.update_text(text);
}
self.request_redraw();
}
```
- Dev-only emulator tests gating:
```
// tests/vt100_history.rs
#![cfg(feature = "vt100-tests")]
#![expect(clippy::expect_used)]
use ratatui::backend::TestBackend;
use ratatui::layout::Rect;
use ratatui::text::Line;
struct TestScenario { /* ... */ }
#[test]
fn hist_001_basic_insertion_no_wrap() {
let area = Rect::new(0, 5, 20, 1);
let mut scenario = TestScenario::new(20, 6, area);
let buf = scenario.run_insert(vec![Line::from("first"), Line::from("second")]);
let rows = scenario.screen_rows_from_bytes(&buf);
assert_eq!(rows[4], "first");
assert_eq!(rows[5], "second");
}
```
- History merging test with exact expectation:
```
let mut h = ConversationHistory::default();
h.record_items([&assistant_msg("Hello"), &assistant_msg(", world!")]);
assert_eq!(
h.contents(),
vec![ResponseItem::Message {
id: None,
role: "assistant".to_string(),
content: vec![ContentItem::OutputText { text: "Hello, world!".to_string() }],
}]
);
```
- Writer-based insertion API usage:
```
let mut out: Vec<u8> = Vec::new();
insert_history_lines_to_writer(&mut terminal, &mut out, lines);
// feed `out` into vt100::Parser for assertions
```
- Unicode-aware wrapping helper:
```
pub fn take_prefix_by_width(text: &str, max_cols: usize) -> (String, &str, usize) {
use unicode_width::UnicodeWidthChar;
let mut cols = 0;
let mut end = 0;
for (i, ch) in text.char_indices() {
let w = UnicodeWidthChar::width(ch).unwrap_or(0);
if cols + w > max_cols { break; }
cols += w;
end = i + ch.len_utf8();
if cols == max_cols { break; }
}
(text[..end].to_string(), &text[end..], cols)
}
```