From fa2f200d9a17a0bfa072c5054491aa92606747aa Mon Sep 17 00:00:00 2001 From: Kayshen-X Date: Tue, 2 Jun 2026 09:35:35 +0800 Subject: [PATCH] feat(panels): taller commit-detail card with initial-commit diff line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The expanded 里程碑详情 card was title+buttons only (60px) and showed no diff context. Match TS image: grow it to 84px and add the diff status line between title and buttons. The root commit shows '初始提交 — 没有父提交可对比' (git.history.diff.initialCommit); later commits leave it blank until the semantic node-diff summary lands. - op-git: Commit.is_initial (parent_count == 0) - op-editor-core: GitCommitSummary.is_initial - op-host-desktop: plumb is_initial into the snapshot - op-editor-ui: CARD_H 60→84, button row drops to +52, conditional diff line --- crates/op-editor-core/src/editor_ui_state.rs | 4 +++ .../src/widgets/git_panel_ready.rs | 36 +++++++++++++------ .../src/widgets/git_panel_tests.rs | 8 ++++- crates/op-git/src/history.rs | 5 +++ crates/op-host-desktop/src/git_jobs.rs | 1 + 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/crates/op-editor-core/src/editor_ui_state.rs b/crates/op-editor-core/src/editor_ui_state.rs index c0a75adc1..fdba4c9d0 100644 --- a/crates/op-editor-core/src/editor_ui_state.rs +++ b/crates/op-editor-core/src/editor_ui_state.rs @@ -130,6 +130,10 @@ pub struct GitCommitSummary { /// taken (TS `formatCompactTime`). The widget layer is platform-free /// and has no wall clock, so it cannot derive this itself. pub time_label: String, + /// `true` for the root commit (no parent). The expanded detail card + /// shows the "initial commit — nothing to diff" line for it (TS + /// `git.history.diff.initialCommit`). + pub is_initial: bool, } /// One changed file in the Git panel's staging list — plain data diff --git a/crates/op-editor-ui/src/widgets/git_panel_ready.rs b/crates/op-editor-ui/src/widgets/git_panel_ready.rs index e6bef28cc..051e0ea85 100644 --- a/crates/op-editor-ui/src/widgets/git_panel_ready.rs +++ b/crates/op-editor-ui/src/widgets/git_panel_ready.rs @@ -39,10 +39,11 @@ const HISTORY_FIRST: f32 = COMMIT_TOP + COMMIT_H + 24.0; const ROW_H: f32 = 26.0; const MAX_COMMITS: usize = 8; const SUMMARY_MAX: usize = 34; -/// Height of the inline commit-detail card (里程碑详情 title + a -/// restore/copy-hash button row) inserted under an expanded commit row -/// (TS `HistoryMilestoneRow` detail block). Pushes later rows down. -const CARD_H: f32 = 60.0; +/// Height of the inline commit-detail card (里程碑详情 title + a diff +/// status line + a restore/copy-hash button row) inserted under an +/// expanded commit row (TS `HistoryMilestoneRow` detail block). Pushes +/// later rows down. +const CARD_H: f32 = 84.0; /// Per-char advance heuristic for the branch label (keeps paint + /// hit-test aligned without measuring text). const BRANCH_CHAR_W: f32 = 7.5; @@ -96,7 +97,8 @@ impl GitPanel<'_> { /// `(恢复, 复制哈希)` button rects for the inline card whose top edge /// is `card_top`. Backend-free fixed widths keep paint + hit aligned. fn commit_card_button_rects(&self, rect: Rect, card_top: f32) -> (Rect, Rect) { - let btn_y = card_top + 30.0; + // Title at +18, diff line at +40, button row at +52 (h24) → 84. + let btn_y = card_top + 52.0; let h = 24.0; let x = rect.origin.x + 40.0; // align with the message column (`pl-10`) let restore = Rect { @@ -416,7 +418,7 @@ impl GitPanel<'_> { // `ready_commit_card_buttons` lands on the same geometry. if self.state.expanded_commit == Some(i) { let card_top = y - 6.0; - self.paint_commit_card(cx, rect, card_top); + self.paint_commit_card(cx, rect, card_top, commit.is_initial); y += CARD_H; } } @@ -424,10 +426,11 @@ impl GitPanel<'_> { } /// Paint the inline commit-detail card (里程碑详情) — a muted band - /// with the detail title and a `恢复` / `复制哈希` button row. TS - /// `HistoryMilestoneRow` detail block; the inline diff summary is - /// deferred (the semantic node-diff is a separate subsystem). - fn paint_commit_card(&self, cx: &mut PaintCx<'_>, rect: Rect, card_top: f32) { + /// with the detail title, a diff status line, and a `恢复` / `复制哈希` + /// button row. TS `HistoryMilestoneRow` detail block. The root commit + /// shows the "no parent to diff against" line; the semantic node-diff + /// summary for later commits is deferred (a separate subsystem). + fn paint_commit_card(&self, cx: &mut PaintCx<'_>, rect: Rect, card_top: f32, is_initial: bool) { let t = self.theme; cx.backend.fill_rect( Rect { @@ -445,6 +448,19 @@ impl GitPanel<'_> { 11.0, t.foreground, ); + // Diff status line. The root commit has no parent to diff against + // (TS `git.history.diff.initialCommit`); non-root commits leave it + // blank until the semantic node-diff summary lands. + if is_initial { + self.text( + cx, + self.t("git.history.diff.initialCommit"), + rect.origin.x + 40.0, + card_top + 40.0, + 11.0, + alpha(t.muted_foreground, 0.85), + ); + } let (restore, copy) = self.commit_card_button_rects(rect, card_top); // 恢复 — outline button. cx.backend.stroke_round_rect(restore, 6.0, t.border, 1.0); diff --git a/crates/op-editor-ui/src/widgets/git_panel_tests.rs b/crates/op-editor-ui/src/widgets/git_panel_tests.rs index fa6a1df63..59a46bbb3 100644 --- a/crates/op-editor-ui/src/widgets/git_panel_tests.rs +++ b/crates/op-editor-ui/src/widgets/git_panel_tests.rs @@ -52,6 +52,7 @@ fn open_panel_height_grows_with_commits() { summary: "first".into(), author: "Ada".into(), time_label: "now".into(), + is_initial: false, }; 3 ], @@ -70,6 +71,7 @@ fn empty_history_reserves_a_placeholder_row() { summary: "only".into(), author: "Ada".into(), time_label: "now".into(), + is_initial: false, }], ..open_repo() }); @@ -162,12 +164,14 @@ fn commit_rows_open_a_commit_diff() { summary: "first".into(), author: "Ada".into(), time_label: "now".into(), + is_initial: false, }, GitCommitSummary { short_hash: "bbb2222".into(), summary: "second".into(), author: "Bo".into(), time_label: "now".into(), + is_initial: false, }, ], ..open_repo() @@ -199,12 +203,14 @@ fn expanded_commit_card_maps_restore_and_copy_and_shifts_later_rows() { summary: "first".into(), author: "Ada".into(), time_label: "now".into(), + is_initial: false, }, GitCommitSummary { short_hash: "bbb2222".into(), summary: "second".into(), author: "Bo".into(), time_label: "now".into(), + is_initial: false, }, ]; let collapsed = state_with(GitPanelState { @@ -236,7 +242,7 @@ fn expanded_commit_card_maps_restore_and_copy_and_shifts_later_rows() { ); // Row 1 shifted down by exactly the card height; the panel grew too. let row1_expanded = panel.ready_commit_row_rects(rect)[1].origin.y; - assert!((row1_expanded - row1_collapsed - 60.0).abs() < 0.5); + assert!((row1_expanded - row1_collapsed - 84.0).abs() < 0.5); assert!(panel.height() > cp.height()); // The expanded card sits below row 0's click target. let row0 = panel.ready_commit_row_rects(rect)[0]; diff --git a/crates/op-git/src/history.rs b/crates/op-git/src/history.rs index 153c898a0..e3846a055 100644 --- a/crates/op-git/src/history.rs +++ b/crates/op-git/src/history.rs @@ -21,6 +21,9 @@ pub struct Commit { pub timestamp: i64, /// First line of the commit message. pub summary: String, + /// `true` for the root commit (no parent) — drives the panel's + /// "initial commit, nothing to diff" detail line. + pub is_initial: bool, } impl GitRepo { @@ -139,6 +142,7 @@ fn commit_to_record(commit: &git2::Commit<'_>) -> Commit { email: author.email().unwrap_or("").to_string(), timestamp: commit.time().seconds(), summary: commit.summary().unwrap_or("").to_string(), + is_initial: commit.parent_count() == 0, } } @@ -200,6 +204,7 @@ mod tests { email: "ada@x.dev".to_string(), timestamp: 1_700_000_000, summary: "first commit".to_string(), + is_initial: true, }; assert_eq!(c.hash, "abc123"); assert_eq!(c.short_hash, "abc"); diff --git a/crates/op-host-desktop/src/git_jobs.rs b/crates/op-host-desktop/src/git_jobs.rs index ec41e8c52..93a15c88d 100644 --- a/crates/op-host-desktop/src/git_jobs.rs +++ b/crates/op-host-desktop/src/git_jobs.rs @@ -140,6 +140,7 @@ fn snapshot(repo: &GitRepo) -> GitSnapshot { summary: c.summary, author: c.author, time_label: format_compact_time(c.timestamp, now_secs), + is_initial: c.is_initial, }) .collect(); let merging = repo.is_merging();