d163ae68
Render linked commits section in TUI issue detail
alex emery 2026-04-12 08:39
Mirrors the CLI renderer: --- Linked Commits --- section after comments, with short SHA + subject + commit author + linked-by metadata. Degrades gracefully when the commit isn't locally available.
diff --git a/src/tui/events.rs b/src/tui/events.rs index 520a77c..7333293 100644 --- a/src/tui/events.rs +++ b/src/tui/events.rs @@ -21,7 +21,7 @@ pub(crate) fn run_loop( repo: &Repository, ) -> Result<(), Error> { loop { terminal.draw(|frame| ui(frame, app))?; terminal.draw(|frame| ui(frame, app, Some(repo)))?; if event::poll(Duration::from_millis(100))? { if let Event::Key(key) = event::read()? { diff --git a/src/tui/mod.rs b/src/tui/mod.rs index 6eea9d9..c96eaa8 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -296,7 +296,7 @@ mod tests { fn render_app(app: &mut App) -> Buffer { let backend = TestBackend::new(80, 24); let mut terminal = Terminal::new(backend).unwrap(); terminal.draw(|frame| ui(frame, app)).unwrap(); terminal.draw(|frame| ui(frame, app, None)).unwrap(); terminal.backend().buffer().clone() } @@ -960,7 +960,7 @@ mod tests { let mut app = make_app(3, 3); let backend = TestBackend::new(20, 10); let mut terminal = Terminal::new(backend).unwrap(); terminal.draw(|frame| ui(frame, &mut app)).unwrap(); terminal.draw(|frame| ui(frame, &mut app, None)).unwrap(); } // ── Integration: full browse flow ──────────────────────────────────── diff --git a/src/tui/widgets.rs b/src/tui/widgets.rs index 1f462d2..e1f2d0a 100644 --- a/src/tui/widgets.rs +++ b/src/tui/widgets.rs @@ -1,4 +1,4 @@ use git2::Oid; use git2::{Oid, Repository}; use ratatui::prelude::*; use ratatui::widgets::{Block, Borders, List, ListItem, Paragraph, Wrap}; @@ -121,7 +121,7 @@ pub(crate) fn format_event_detail(oid: &Oid, event: &crate::event::Event) -> Str detail } pub(crate) fn ui(frame: &mut Frame, app: &mut App) { pub(crate) fn ui(frame: &mut Frame, app: &mut App, repo: Option<&Repository>) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ @@ -139,7 +139,7 @@ pub(crate) fn ui(frame: &mut Frame, app: &mut App) { .split(main_area); render_list(frame, app, panes[0]); render_detail(frame, app, panes[1]); render_detail(frame, app, panes[1], repo); render_footer(frame, app, footer_area); } @@ -219,7 +219,7 @@ fn render_list(frame: &mut Frame, app: &mut App, area: Rect) { } } fn render_detail(frame: &mut Frame, app: &mut App, area: Rect) { fn render_detail(frame: &mut Frame, app: &mut App, area: Rect, repo: Option<&Repository>) { let border_style = if app.pane == Pane::Detail { Style::default().fg(Color::Yellow) } else { @@ -303,7 +303,7 @@ fn render_detail(frame: &mut Frame, app: &mut App, area: Rect) { let visible = app.visible_issues(); let selected_idx = app.list_state.selected().unwrap_or(0); let content: Text = match visible.get(selected_idx) { Some(issue) => build_issue_detail(issue, &app.patches), Some(issue) => build_issue_detail(issue, &app.patches, repo), None => Text::raw("No matches for current filter."), }; @@ -342,7 +342,11 @@ fn render_detail(frame: &mut Frame, app: &mut App, area: Rect) { } } fn build_issue_detail(issue: &IssueState, patches: &[PatchState]) -> Text<'static> { fn build_issue_detail( issue: &IssueState, patches: &[PatchState], repo: Option<&Repository>, ) -> Text<'static> { let status = issue.status.as_str(); let mut lines: Vec<Line> = vec![ @@ -465,6 +469,43 @@ fn build_issue_detail(issue: &IssueState, patches: &[PatchState]) -> Text<'stati } } if !issue.linked_commits.is_empty() { lines.push(Line::raw("")); lines.push(Line::styled( "--- Linked Commits ---", Style::default() .fg(Color::Magenta) .add_modifier(Modifier::BOLD), )); for lc in &issue.linked_commits { let short_sha: String = lc.commit.chars().take(7).collect(); let (subject, commit_author) = repo .and_then(|r| { Oid::from_str(&lc.commit) .ok() .and_then(|oid| r.find_commit(oid).ok()) .map(|commit| { let subject = commit.summary().unwrap_or("").to_string(); let author = commit.author().name().unwrap_or("unknown").to_string(); (subject, author) }) }) .unwrap_or_else(|| (String::new(), String::new())); let line_text = if commit_author.is_empty() { format!( "· linked {} (commit {} not in local repo) (linked by {}, {})", short_sha, short_sha, lc.event_author.name, lc.event_timestamp ) } else { format!( "· linked {} \"{}\" by {} (linked by {}, {})", short_sha, subject, commit_author, lc.event_author.name, lc.event_timestamp ) }; lines.push(Line::raw(line_text)); } } Text::from(lines) }