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

210 lines
6.5 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**
- **Explain Scoring Constant:** Add a clear comment for the prefix bonus and how the score is computed.
```rust
// Score is "extra span" outside the needle between first/last hits.
// Lower is better. Strongly reward prefix matches to surface commands quickly.
let mut score = window.max(0);
/*
Prefix bonus rationale:
-100 is large enough to dominate small window differences, so a prefix match
beats a slightly tighter non-prefix match but not a wildly better one.
*/
if first_lower_pos == 0 {
score -= 100; // prefix bonus
}
```
- **Assert Scores In Tests:** Treat tests as executable documentation by checking both indices and scores.
```rust
#[test]
fn prefer_contiguous_prefix() {
let (idx, score) = fuzzy_match("abc", "abc").expect("match");
assert_eq!(idx, vec![0, 1, 2]);
assert_eq!(score, -100); // contiguous prefix → window 0 + prefix bonus
}
#[test]
fn spread_match_is_worse() {
let (_idx, score) = fuzzy_match("a-b-c", "abc").expect("match");
assert_eq!(score, -98); // window 2 - 100 bonus
// Prefix contiguous (-100) still beats spread (-98).
}
```
- **Centralize Popup Limits:** Keep popup row limits in one place and use them consistently.
```rust
// popup_consts.rs
pub(crate) const MAX_POPUP_ROWS: usize = 8;
// usage
let vis = MAX_POPUP_ROWS.min(matches_len);
state.ensure_visible(matches_len, vis);
```
- **Cache Filtered Results:** Recompute only when the filter or source data changes; reuse on up/down.
```rust
pub(crate) struct CommandPopup {
command_filter: String,
all_commands: Vec<(&'static str, SlashCommand)>,
cached: Vec<(&'static SlashCommand, Option<Vec<usize>>, i32)>,
state: ScrollState,
}
impl CommandPopup {
fn rebuild_cache(&mut self) {
self.cached = self.compute_filtered(); // called on filter change only
let len = self.cached.len();
self.state.clamp_selection(len);
self.state.ensure_visible(len, len.min(MAX_POPUP_ROWS));
}
pub(crate) fn move_down(&mut self) {
let len = self.cached.len();
self.state.move_down_wrap(len);
self.state.ensure_visible(len, len.min(MAX_POPUP_ROWS));
}
}
```
- **Clamp And Keep Selection Visible:** Always clamp selection to bounds and update scroll.
```rust
let len = rows.len();
state.clamp_selection(len);
state.ensure_visible(len, len.min(MAX_POPUP_ROWS));
```
- **Adjust Highlight Indices When Prefixing:** If you add a "/" or other prefix to the displayed name, shift indices.
```rust
// internal indices are for "help"
let indices = fuzzy_indices("help", "hp").unwrap();
// UI shows "/help" → shift by 1 so bold aligns with UI text
let shifted: Vec<usize> = indices.into_iter().map(|i| i + 1).collect();
```
- **Use ratatui Stylize Helpers:** Prefer concise styling over manual Style building.
```rust
use ratatui::style::Stylize;
let spans = vec![
"/".into(),
"help".bold(), // matched chars can be bolded one-by-one
" ".into(),
"Show help".dim(), // description is dim gray
];
// Selected row styling
let cell = Cell::from(Line::from(spans)).style("".yellow().bold().style());
```
- **Deterministic Sorting:** Sort by score, then by command for stability.
```rust
out.sort_by(|a, b| a.2.cmp(&b.2).then_with(|| a.0.command().cmp(b.0.command())));
```
- **Preserve “Searching…” State:** Distinguish waiting from no-results to avoid confusing users.
```rust
if waiting && rows_all.is_empty() {
// show an explicit searching state
let row = Row::new(vec![Cell::from(Line::from("(searching …)".dim()))]);
Table::new(vec![row], [Constraint::Percentage(100)]).render(area, buf);
} else {
render_rows(area, buf, &rows_all, &state, MAX_POPUP_ROWS);
}
```
- **Keep Comments Accurate:** If a helper is used at runtime, dont label it “tests only”.
```rust
/// Returns filtered commands for both UI rendering and tests.
fn filtered_commands(&self) -> Vec<&SlashCommand> { /* ... */ }
```
- **Make Wrap-Around Explicit:** Gate wrap-around behavior for clarity and easier UX tweaks.
```rust
pub(crate) struct ScrollState {
pub selected_idx: Option<usize>,
pub scroll_top: usize,
pub wrap: bool,
}
pub fn move_down(&mut self, len: usize) {
if len == 0 { self.selected_idx = None; return; }
self.selected_idx = Some(match (self.selected_idx, self.wrap) {
(Some(i), _) if i + 1 < len => i + 1,
(_, true) => 0,
(Some(i), false) => i,
(None, _) => 0,
});
}
```
- **Be Explicit About Unicode Behavior:** Dedup indices after lowercase expansion (e.g., İ → i̇).
```rust
result_orig_indices.sort_unstable();
result_orig_indices.dedup(); // safe for multi-char case mappings
```
**DONTs**
- **Dont Leave Magic Numbers Unexplained:** Avoid bare “-100” without comment or rationale.
```rust
// BAD
if first_lower_pos == 0 { score -= 100; }
```
- **Dont Skip Score Assertions:** Verifying only indices omits critical ranking behavior.
```rust
// BAD: indices only
let (idx, _score) = fuzzy_match("hello", "hl").unwrap();
assert_eq!(idx, vec![0, 2]);
```
- **Dont Recompute On Every Keypress:** Avoid re-filtering on Up/Down when input hasnt changed.
```rust
// BAD
pub fn move_up(&mut self) {
let matches = self.compute_filtered(); // unnecessary work per keypress
}
```
- **Dont Let Selection Drift Out Of Range:** Always clamp after data changes; otherwise panics or stale UI can occur.
```rust
// BAD: no clamp, potential OOB
self.state.selected_idx = Some(self.state.selected_idx.unwrap() + 1);
```
- **Dont Show “no matches” While Searching:** Users read it as terminal, not in-progress.
```rust
// BAD
if matches.is_empty() { show("no matches"); }
```
- **Dont Keep Unused Fields:** Remove or wire `is_current`; dead fields confuse future maintainers.
```rust
// BAD: never set, always false
pub is_current: bool,
```
- **Dont Bypass Stylize:** Avoid verbose Style plumbing when helpers are available.
```rust
// BAD
Span::styled("text", Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD));
```
- **Dont Use Unstable Ordering:** Ties without deterministic tiebreakers jitter the UI.
```rust
// BAD: score ties produce arbitrary order
out.sort_by(|a, b| a.2.cmp(&b.2));
```
- **Dont Forget Prefix Offset For Highlights:** Your bolding will be misaligned.
```rust
// BAD: indices for "help" applied to "/help" without +1 shift
```
- **Dont Assume Wrap-Around Is Always Desired:** Either make it configurable or document it explicitly.
```rust
// BAD: hardcoded wrap-around with no way to disable
self.state.move_down_wrap(len);
```