feat(panels): taller commit-detail card with initial-commit diff line

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
This commit is contained in:
Kayshen-X 2026-06-02 09:35:35 +08:00
parent f58a8e55c1
commit fa2f200d9a
5 changed files with 43 additions and 11 deletions

View file

@ -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

View file

@ -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);

View file

@ -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];

View file

@ -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");

View file

@ -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();