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

4.0 KiB

DOs

  • Gate on key press events: Process one-shot actions only on KeyEventKind::Press to avoid duplicate or unintended triggers, and apply this consistently across handlers.

    use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
    
    fn handle_key_event(&mut self, ev: KeyEvent) {
        if ev.kind != KeyEventKind::Press {
            return;
        }
        match ev {
            KeyEvent { code: KeyCode::Char('c'), modifiers, .. }
                if modifiers.contains(KeyModifiers::CONTROL) => self.quit(),
            _ => {}
        }
    }
    
  • Use LazyLock option sets: Define context-specific approval options (command vs patch) once, with styled labels and direct key bindings.

    use std::sync::LazyLock;
    use crossterm::event::KeyCode;
    use ratatui::text::Line;
    use codex_core::protocol::ReviewDecision;
    
    struct SelectOption {
        label: Line<'static>,
        description: &'static str,
        key: KeyCode,
        decision: ReviewDecision,
    }
    
    static COMMAND_SELECT_OPTIONS: LazyLock<Vec<SelectOption>> = LazyLock::new(|| vec![
        SelectOption {
            label: Line::from(vec!["Y".underlined(), "es".into()]),
            description: "Approve and run the command",
            key: KeyCode::Char('y'),
            decision: ReviewDecision::Approved,
        },
        SelectOption {
            label: Line::from(vec!["A".underlined(), "lways".into()]),
            description: "Approve for this session",
            key: KeyCode::Char('a'),
            decision: ReviewDecision::ApprovedForSession,
        },
        SelectOption {
            label: Line::from(vec!["N".underlined(), "o".into()]),
            description: "Do not run the command",
            key: KeyCode::Char('n'),
            decision: ReviewDecision::Denied,
        },
    ]);
    
  • Prefer Stylize helpers for concise UI: Build prompts and labels using Stylize and inline format! variables.

    use ratatui::text::Line;
    use ratatui::prelude::Stylize;
    
    let cwd_str = cwd.display().to_string();
    let cmd = shell_words::join(&command);
    let prompt = vec![
        Line::from(vec!["codex".bold().magenta(), " wants to run:".into()]),
        Line::from(vec![cwd_str.dim(), "$".into(), format!(" {cmd}").into()]),
    ];
    
  • Verify contrast on light and dark terminals: Choose styles that remain legible across themes; consider reversed or high-contrast fg/bg pairs.

    use ratatui::style::{Color, Style};
    use ratatui::prelude::Stylize;
    
    let selected = " Yes ".into().style(Style::new().bg(Color::Cyan).fg(Color::Black));
    let unselected = " No ".into().reversed(); // safe contrast across themes
    
  • Support direct shortcuts and selection: Let users press y/a/n (and Esc) in addition to arrow/enter navigation.

    use crossterm::event::KeyCode;
    
    if ev.kind == KeyEventKind::Press {
        if let Some(opt) = self.select_options.iter().find(|o| o.key == ev.code) {
            self.send_decision(opt.decision);
        }
    }
    

DON'Ts

  • Handle non-press events for single-shot actions: Avoid mutating state on Repeat/Release or on every event without gating.

    // Anti-pattern: fires on press, repeat, and release
    fn on_key(&mut self, ev: KeyEvent) {
        self.bottom_pane.clear_ctrl_c_quit_hint(); // runs too often
    }
    
  • Keep stale cross-implementation comments: Remove comments that tie Rust code to TS ordering; make Rust the source of truth.

    // Anti-pattern:
    // keep in same order as in the TS implementation
    // Preferred: model the options here without external coupling.
    
  • Assume colors work everywhere: Avoid low-contrast combinations that disappear on light backgrounds (e.g., DarkGray on default fg).

    // Anti-pattern: may be unreadable on light themes
    let unselected = " No ".into().style(Style::new().bg(Color::DarkGray));
    
  • Clear UI hints on every key transition: Only clear “press any key” or similar hints on Press, not on Repeat/Release.

    // Preferred
    if key_event.kind == KeyEventKind::Press {
        self.bottom_pane.clear_ctrl_c_quit_hint();
    }