diff --git a/src/renderer/components/GitSidebar/GitSidebar.css b/src/renderer/components/GitSidebar/GitSidebar.css
index bda17b4..05b73cf 100644
--- a/src/renderer/components/GitSidebar/GitSidebar.css
+++ b/src/renderer/components/GitSidebar/GitSidebar.css
@@ -36,6 +36,61 @@
padding: 0 12px 8px;
}
+.git-sidebar-icon-button {
+ width: 20px;
+ height: 20px;
+ padding: 0;
+}
+
+.git-sidebar-icon-button svg {
+ width: 16px;
+ height: 16px;
+}
+
+.git-action-branch-line,
+.git-action-stem,
+.git-action-arrow,
+.git-action-prune-mark {
+ stroke: currentColor;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ fill: none;
+}
+
+.git-action-branch-line {
+ stroke-width: 1.3;
+ opacity: 0.9;
+}
+
+.git-action-branch-dot {
+ fill: currentColor;
+}
+
+.git-action-stem {
+ stroke-width: 1.4;
+}
+
+.git-action-stem--dotted {
+ stroke-dasharray: 1.4 1.8;
+}
+
+.git-action-stem--solid {
+ stroke-dasharray: none;
+}
+
+.git-action-arrow {
+ stroke-width: 1.4;
+}
+
+.git-action-prune-mark {
+ stroke-width: 1.3;
+}
+
+.git-sidebar-icon-button:disabled {
+ opacity: 0.45;
+ cursor: not-allowed;
+}
+
.git-sidebar-section {
display: flex;
flex-direction: column;
diff --git a/src/renderer/components/GitSidebar/GitSidebar.tsx b/src/renderer/components/GitSidebar/GitSidebar.tsx
index 0e6be39..9713b34 100644
--- a/src/renderer/components/GitSidebar/GitSidebar.tsx
+++ b/src/renderer/components/GitSidebar/GitSidebar.tsx
@@ -491,35 +491,93 @@ export const GitSidebar: React.FC = () => {
{actionLoading && (
diff --git a/tests/renderer/components/GitSidebar.test.tsx b/tests/renderer/components/GitSidebar.test.tsx
index f53cab4..945f5c0 100644
--- a/tests/renderer/components/GitSidebar.test.tsx
+++ b/tests/renderer/components/GitSidebar.test.tsx
@@ -661,6 +661,63 @@ describe('GitSidebar', () => {
});
});
+ it('renders repo actions as icon-only buttons with hover tooltips', async () => {
+ (window as any).electronAPI.git.getRepoState = vi.fn().mockResolvedValue({
+ isRepo: true,
+ rootPath: '/repo/path',
+ currentBranch: 'main',
+ hasRemote: true,
+ });
+
+ render();
+
+ const repoActions = await screen.findByRole('group', { name: /repository actions/i });
+ const fetchButton = within(repoActions).getByRole('button', { name: /^fetch$/i });
+ const pullButton = within(repoActions).getByRole('button', { name: /^pull$/i });
+ const pushButton = within(repoActions).getByRole('button', { name: /^push$/i });
+ const pruneButton = within(repoActions).getByRole('button', { name: /^prune lfs$/i });
+
+ expect(fetchButton).toHaveAttribute('title', 'Fetch');
+ expect(pullButton).toHaveAttribute('title', 'Pull');
+ expect(pushButton).toHaveAttribute('title', 'Push');
+ expect(pruneButton).toHaveAttribute('title', 'Prune LFS');
+
+ expect(within(repoActions).queryByText('Fetch')).not.toBeInTheDocument();
+ expect(within(repoActions).queryByText('Pull')).not.toBeInTheDocument();
+ expect(within(repoActions).queryByText('Push')).not.toBeInTheDocument();
+ expect(within(repoActions).queryByText('Prune LFS')).not.toBeInTheDocument();
+ });
+
+ it('uses unified branch baseline icons with action-specific arrow direction styles', async () => {
+ (window as any).electronAPI.git.getRepoState = vi.fn().mockResolvedValue({
+ isRepo: true,
+ rootPath: '/repo/path',
+ currentBranch: 'main',
+ hasRemote: true,
+ });
+
+ render();
+
+ const repoActions = await screen.findByRole('group', { name: /repository actions/i });
+ const fetchButton = within(repoActions).getByRole('button', { name: /^fetch$/i });
+ const pullButton = within(repoActions).getByRole('button', { name: /^pull$/i });
+ const pushButton = within(repoActions).getByRole('button', { name: /^push$/i });
+
+ const fetchIcon = within(fetchButton).getByTestId('git-action-icon-fetch');
+ expect(within(fetchIcon).getByTestId('git-action-branch-line')).toBeInTheDocument();
+ expect(within(fetchIcon).getByTestId('git-action-branch-dot')).toBeInTheDocument();
+ expect(within(fetchIcon).getByTestId('git-action-stem-fetch')).toHaveClass('git-action-stem--dotted');
+ expect(within(fetchIcon).getByTestId('git-action-arrow-fetch')).toHaveClass('git-action-arrow--towards-branch');
+
+ const pullIcon = within(pullButton).getByTestId('git-action-icon-pull');
+ expect(within(pullIcon).getByTestId('git-action-stem-pull')).toHaveClass('git-action-stem--solid');
+ expect(within(pullIcon).getByTestId('git-action-arrow-pull')).toHaveClass('git-action-arrow--towards-branch');
+
+ const pushIcon = within(pushButton).getByTestId('git-action-icon-push');
+ expect(within(pushIcon).getByTestId('git-action-stem-push')).toHaveClass('git-action-stem--solid');
+ expect(within(pushIcon).getByTestId('git-action-arrow-push')).toHaveClass('git-action-arrow--away-branch');
+ });
+
it('shows in-progress feedback while prune lfs is running', async () => {
let resolvePrune: ((value: { success: boolean; dryRun: boolean; verifyRemote: boolean; recentCommitsToKeep: number }) => void) | null = null;
(window as any).electronAPI.git.getRepoState = vi.fn().mockResolvedValue({
@@ -684,7 +741,7 @@ describe('GitSidebar', () => {
});
expect(screen.getByRole('status')).toHaveTextContent(/pruning local git lfs cache/i);
- expect(screen.getByRole('button', { name: /pruning/i })).toBeDisabled();
+ expect(screen.getByRole('button', { name: /prune lfs/i })).toBeDisabled();
await act(async () => {
resolvePrune?.({ success: true, dryRun: false, verifyRemote: true, recentCommitsToKeep: 2 });
@@ -818,7 +875,7 @@ describe('GitSidebar', () => {
});
expect(screen.getByRole('status')).toHaveTextContent(/pushing commits to remote/i);
- expect(screen.getByRole('button', { name: /pushing/i })).toBeDisabled();
+ expect(screen.getByRole('button', { name: /^push$/i })).toBeDisabled();
await act(async () => {
resolvePush?.({ success: true });