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

215 lines
6.9 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**
- **Keep app.rs lean:** Drive the event loop and delegate event-specific handling to modules.
```
// app.rs
match event {
TuiEvent::AttachImage { path, width, height, format_label } => {
self.chat_widget.attach_image(path, width, height, format_label);
}
TuiEvent::Key(key) => self.chat_widget.handle_key_event(key),
TuiEvent::Paste(s) => self.chat_widget.handle_paste(s),
TuiEvent::Draw => self.redraw()?,
}
```
- **Generate rich events in the TUI layer:** Map platform input to semantic TuiEvents before app.rs sees them.
```
// tui.rs
match crossterm_event {
Event::Key(k) if is_ctrl_or_cmd_v(&k) => {
if let Ok((path, info)) = paste_image_to_temp_png() {
yield TuiEvent::AttachImage { path, width: info.width, height: info.height, format_label: info.encoded_format.label() };
} else {
yield TuiEvent::Key(k);
}
}
Event::Paste(s) => yield TuiEvent::Paste(s),
Event::Key(k) => yield TuiEvent::Key(k),
Event::Resize(_, _) => yield TuiEvent::Draw,
_ => {}
}
```
- **Handle cross-platform shortcuts:** Dont assume macOS Cmd maps to Control; accept Control, Super, or Meta for V.
```
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
fn is_ctrl_or_cmd_v(k: &KeyEvent) -> bool {
k.kind == KeyEventKind::Press
&& matches!(k.code, KeyCode::Char('v'))
&& (k.modifiers.contains(KeyModifiers::CONTROL)
|| k.modifiers.contains(KeyModifiers::SUPER)
|| k.modifiers.contains(KeyModifiers::META))
}
```
- **Model image formats with an enum, not strings:** Add a label() to present user-friendly text.
```
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EncodedImageFormat { Png }
impl EncodedImageFormat {
fn label(self) -> &'static str {
match self { EncodedImageFormat::Png => "PNG" }
}
}
```
- **Write clipboard images via tempfile and persist safely:**
```
use tempfile::NamedTempFile;
let (png_bytes, info) = paste_image_as_png()?;
let tmp = NamedTempFile::new()?; // unique, race-free
std::fs::write(tmp.path(), &png_bytes)?;
let (_file, path) = tmp.keep()?; // persist and get PathBuf
```
- **Prefer small structs over tuples for clarity:** Make stored state self-documenting.
```
#[derive(Clone, Debug, PartialEq)]
struct AttachedImage {
placeholder: String,
path: PathBuf,
}
```
- **Make image placeholders atomic UI elements and keep mappings in sync:**
```
// Insert atomic element so a single Backspace removes it.
self.textarea.insert_element(&placeholder);
self.attached_images.push(AttachedImage { placeholder, path });
// On edit, keep only as many AttachedImage entries as visible placeholders.
let visible = text_after.matches(&img.placeholder).count();
```
- **Strip placeholders before submit; drain images separately:**
```
// composer.rs (on Enter)
for img in &self.attached_images {
text = text.replace(&img.placeholder, "");
}
text = text.trim().to_string();
// chatwidget.rs
let images = self.bottom_pane.take_recent_submission_images();
self.submit_user_message(UserMessage { text, image_paths: images });
```
- **Inline variables in logs and formats:** Use modern capture in tracing/format! calls.
```
tracing::info!("attach_image path={path:?} width={width} height={height} format={format_label}");
let msg = format!("added {count} images from {source}");
```
- **Use local imports and concise types; move imports to the module top:**
```
use std::path::PathBuf; // at top of file
// ...
fn attach_image(&mut self, path: PathBuf, width: u32, height: u32, format_label: &str) { /* ... */ }
```
- **Test user-visible behaviors thoroughly:** Attach + submit, deletion at both ends, and duplicates.
```
#[test]
fn deleting_one_of_duplicate_image_placeholders_removes_matching_entry() {
composer.attach_image(p1.clone(), 10, 5, "PNG");
composer.handle_paste(" ".into());
composer.attach_image(p2.clone(), 10, 5, "PNG");
composer.textarea.set_cursor(end_of_first_placeholder);
composer.handle_key_event(KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE));
assert_eq!(1, composer.textarea.text().matches(&ph).count());
assert_eq!(vec![p2], composer.take_recent_submission_images());
}
```
- **Treat @file-selected images as images (with fallback):**
```
if is_image_path(&sel_path) {
if let Ok((w, h)) = image::image_dimensions(&sel_path) {
self.attach_image(PathBuf::from(&sel_path), w, h, "PNG"); // or "JPEG"
self.textarea.insert_str(" ");
} else {
self.insert_selected_path(&sel_path); // fallback
}
} else {
self.insert_selected_path(&sel_path);
}
```
**DONTs**
- **Dont put event logic in app.rs:** Keep it for orchestration; push specifics into TUI/chat modules.
```
// ❌ Heavy logic in app.rs
if is_ctrl_or_cmd_v(&key) { do_clipboard_io_here(); }
// ✅ Delegate
TuiEvent::AttachImage { .. } => self.chat_widget.attach_image(...)
```
- **Dont assume Cmd==Control on macOS:** Accept SUPER/META too; fall back gracefully.
```
// ❌ Only CONTROL
k.modifiers.contains(KeyModifiers::CONTROL)
// ✅ Cross-platform
k.modifiers.intersects(KeyModifiers::CONTROL | KeyModifiers::SUPER | KeyModifiers::META)
```
- **Dont write temp files with ad-hoc names in temp_dir:** Avoid collisions and TOCTOU races.
```
// ❌
let mut p = std::env::temp_dir(); p.push("clipboard.png"); std::fs::write(p, &bytes)?;
// ✅
let tmp = tempfile::NamedTempFile::new()?;
```
- **Dont use ad-hoc strings for image format:** Prefer a typed enum.
```
// ❌
struct PastedImageInfo { encoded_format_label: &'static str }
// ✅
struct PastedImageInfo { encoded_format: EncodedImageFormat }
```
- **Dont return meaningless booleans or keep dead code:** Remove unused return values and placeholders.
```
// ❌
pub fn attach_image(&mut self, path: PathBuf, w: u32, h: u32, fmt: &str) -> bool { /* always true */ }
// ✅
pub fn attach_image(&mut self, path: PathBuf, w: u32, h: u32, fmt: &str) { /* ... */ }
```
- **Dont fully qualify common std types everywhere or import inside tests:** Keep files tidy and readable.
```
// ❌
fn f(p: std::path::PathBuf) {}
// ✅
use std::path::PathBuf;
fn f(p: PathBuf) {}
```
- **Dont let placeholders and image state drift:** Always update mappings when text changes.
```
// ❌ Forgetting to drop mapping when placeholder deleted.
// ✅ Remove mapping when matching placeholder instance is removed.
self.attached_images.remove(idx);
```
- **Dont log with positional/format-args noise:** Use inline captures for clarity and consistency.
```
// ❌
tracing::info!("path: {:?}, width: {}, height: {}", path, width, height);
// ✅
tracing::info!("path={path:?} width={width} height={height}");
```