- {buildSummaryLabel(list)}
+ {(list || loading) && (
+
+ {buildSummaryLabel(list)}
+ {loading && }
+
+ {list && (
+
+ +{list.total_additions ?? 0}{' '}
+ -{list.total_deletions ?? 0}
+
+ )}
+
+
)}
@@ -387,6 +396,7 @@ export function GitChangeDetails({
open={open}
selected={selected}
onToggle={() => toggleFile(row.path, open)}
+ onOpenFile={onOpenFilePreview}
/>
{open && (
diff --git a/web/src/components/GitChangesPanel.jsx b/web/src/components/GitChangesPanel.jsx
index 59919ae59..8bec92b6e 100644
--- a/web/src/components/GitChangesPanel.jsx
+++ b/web/src/components/GitChangesPanel.jsx
@@ -40,6 +40,7 @@ export function GitChangesPanel({
visible = true,
selectedFile = '',
onOpenFile,
+ onOpenFilePreview,
onBaseChange,
}) {
const { candidates, initial } = useMemo(
@@ -260,6 +261,25 @@ export function GitChangesPanel({
)
: {row.statLabel}}
+ {!!onOpenFilePreview && (
+ // 已删除文件磁盘上不存在,按钮隐形占位保持各行 ± 计数对齐。
+ event.stopPropagation()}
+ onMouseDown={(event) => event.stopPropagation()}
+ onClick={(event) => {
+ event.preventDefault();
+ event.stopPropagation();
+ if (row.status !== 'D') onOpenFilePreview(row.path);
+ }}
+ >
+
+
+ )}
))}
diff --git a/web/src/components/PreviewDetailsPanel.jsx b/web/src/components/PreviewDetailsPanel.jsx
index 7a9f64f3c..b083b4277 100644
--- a/web/src/components/PreviewDetailsPanel.jsx
+++ b/web/src/components/PreviewDetailsPanel.jsx
@@ -185,6 +185,7 @@ export function PreviewDetailsPanel({
onToggleMaximize,
onSelectChangeFile,
onSelectGitChangeFile,
+ onOpenFilePreview,
}) {
const tabListRef = useRef(null);
const tabDragRef = useRef(null);
@@ -250,6 +251,7 @@ export function PreviewDetailsPanel({
expandedFileRevision={active.expandedFileRevision || 0}
busy={busy}
onSelectFile={onSelectGitChangeFile}
+ onOpenFilePreview={onOpenFilePreview}
/>
);
}
@@ -263,7 +265,7 @@ export function PreviewDetailsPanel({
onToggleWrapPreview={() => setWrapPreview((prev) => !prev)}
/>
);
- }, [active, api, busy, changeGroups, changeSummary, cwd, onSelectChangeFile, onSelectGitChangeFile, setWrapPreview, wrapPreview]);
+ }, [active, api, busy, changeGroups, changeSummary, cwd, onOpenFilePreview, onSelectChangeFile, onSelectGitChangeFile, setWrapPreview, wrapPreview]);
const handleTabWheel = useCallback((event) => {
const el = tabListRef.current;
diff --git a/web/src/components/SidePanel.jsx b/web/src/components/SidePanel.jsx
index f48f040ef..d2cd74914 100644
--- a/web/src/components/SidePanel.jsx
+++ b/web/src/components/SidePanel.jsx
@@ -630,6 +630,7 @@ export function SidePanel({
visible={!collapsed && activeTab === 'changes'}
selectedFile={selectedGitChangeFile}
onOpenFile={onOpenGitChangePreview}
+ onOpenFilePreview={onOpenFilePreview}
onBaseChange={onGitBaseChange}
/>
) : (
diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css
index c7f7f6834..60f5b6895 100644
--- a/web/src/styles/globals.css
+++ b/web/src/styles/globals.css
@@ -2273,6 +2273,8 @@ body {
min-height: 28px;
display: grid;
grid-template-columns: 18px minmax(0, 1fr) auto;
+ grid-auto-columns: max-content;
+ grid-auto-flow: column;
align-items: center;
gap: 5px;
padding: 3px 8px;
@@ -2323,6 +2325,60 @@ body {
white-space: nowrap;
}
+/* 变更文件行的行内动作按钮(转到文件):hover / 选中 / 键盘聚焦时浮现,
+ 平时透明占位保持 ± 计数各行对齐;is-hidden = 已删除文件的隐形占位。 */
+.ace-change-row-action {
+ width: 20px;
+ height: 20px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ border-radius: 4px;
+ color: var(--ace-fg-mute);
+ opacity: 0;
+ transition: opacity .12s ease, color .15s ease, background .15s ease;
+}
+.ace-change-compact-row:hover .ace-change-row-action,
+.ace-change-compact-row.is-selected .ace-change-row-action,
+.ace-change-file-row:hover .ace-change-row-action,
+.ace-change-file-row.is-selected .ace-change-row-action,
+.ace-change-row-action:focus-visible {
+ opacity: 1;
+}
+.ace-change-row-action:hover {
+ color: var(--ace-fg);
+ background: var(--ace-surface-alt);
+}
+.ace-change-row-action.is-hidden {
+ visibility: hidden;
+ pointer-events: none;
+}
+
+/* 变更详情栏「N 个文件已更改」行右端的折叠全部 diff 按钮。 */
+.ace-review-collapse-all-btn {
+ width: 24px;
+ height: 20px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ border-radius: 4px;
+ background: transparent;
+ color: var(--ace-fg-mute);
+ cursor: pointer;
+ flex-shrink: 0;
+ transition: color .15s ease, background .15s ease;
+}
+.ace-review-collapse-all-btn:hover {
+ color: var(--ace-fg);
+ background: var(--ace-surface-alt);
+}
+.ace-review-collapse-all-btn:focus-visible {
+ outline: 2px solid var(--ace-accent);
+ outline-offset: -2px;
+}
+
.ace-review-panel {
flex: 1;
min-height: 0;