From bbceccfb64737580ba82a30852eff377bac9ee72 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Fri, 19 Dec 2025 02:48:58 +0200 Subject: [PATCH] feat: redesigned Git tab layout with improved organization --- CHANGELOG.md | 5 +- .../desktop/src-tauri/src/commands/git.rs | 3 +- .../src/components/session/SessionSidebar.tsx | 96 +- packages/ui/src/components/views/GitView.tsx | 1226 +++-------------- .../components/views/git/AIHighlightsBox.tsx | 56 + .../components/views/git/BranchSelector.tsx | 272 ++++ .../ui/src/components/views/git/ChangeRow.tsx | 140 ++ .../components/views/git/ChangesSection.tsx | 82 ++ .../src/components/views/git/CommitInput.tsx | 74 + .../components/views/git/CommitSection.tsx | 151 ++ .../components/views/git/GitEmptyState.tsx | 42 + .../ui/src/components/views/git/GitHeader.tsx | 244 ++++ .../components/views/git/HistoryCommitRow.tsx | 160 +++ .../components/views/git/HistorySection.tsx | 126 ++ .../src/components/views/git/SyncActions.tsx | 93 ++ packages/ui/src/components/views/git/index.ts | 11 + packages/web/server/lib/git-service.js | 3 +- 17 files changed, 1726 insertions(+), 1058 deletions(-) create mode 100644 packages/ui/src/components/views/git/AIHighlightsBox.tsx create mode 100644 packages/ui/src/components/views/git/BranchSelector.tsx create mode 100644 packages/ui/src/components/views/git/ChangeRow.tsx create mode 100644 packages/ui/src/components/views/git/ChangesSection.tsx create mode 100644 packages/ui/src/components/views/git/CommitInput.tsx create mode 100644 packages/ui/src/components/views/git/CommitSection.tsx create mode 100644 packages/ui/src/components/views/git/GitEmptyState.tsx create mode 100644 packages/ui/src/components/views/git/GitHeader.tsx create mode 100644 packages/ui/src/components/views/git/HistoryCommitRow.tsx create mode 100644 packages/ui/src/components/views/git/HistorySection.tsx create mode 100644 packages/ui/src/components/views/git/SyncActions.tsx create mode 100644 packages/ui/src/components/views/git/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b7a6551..8149e9bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,10 @@ All notable changes to this project will be documented in this file. - Polished chat expirience for longer session - Fixed file link from git view to diff - +- Enhancements to the inactive state management of the desktop app +- Redesigned Git tab layout with improved organization +- Fixed untracked files in new directories not showing individually +- Smoother session rename experience ## [1.2.4] - 2025-12-18 diff --git a/packages/desktop/src-tauri/src/commands/git.rs b/packages/desktop/src-tauri/src/commands/git.rs index 79a5bdf1..07c61b75 100644 --- a/packages/desktop/src-tauri/src/commands/git.rs +++ b/packages/desktop/src-tauri/src/commands/git.rs @@ -452,7 +452,8 @@ pub async fn get_git_status( .map_err(|e| e.to_string())?; // 1. Get porcelain status - let status_output = run_git(&["status", "--porcelain", "-b", "-z"], &path) + // Use -uall to show all untracked files individually, not just directories + let status_output = run_git(&["status", "--porcelain", "-b", "-z", "-uall"], &path) .await .map_err(|e| e.to_string())?; diff --git a/packages/ui/src/components/session/SessionSidebar.tsx b/packages/ui/src/components/session/SessionSidebar.tsx index ec7994d5..08b0a66e 100644 --- a/packages/ui/src/components/session/SessionSidebar.tsx +++ b/packages/ui/src/components/session/SessionSidebar.tsx @@ -2,7 +2,6 @@ import React from 'react'; import type { Session } from '@opencode-ai/sdk'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; import { DropdownMenu, DropdownMenuContent, @@ -690,44 +689,73 @@ export const SessionSidebar: React.FC = ({ if (editingId === session.id) { return ( -
-
{ - event.preventDefault(); - handleSaveEdit(); - }} - > - setEditTitle(event.target.value)} - className="h-7 flex-1 border-none bg-transparent px-0 py-0 typography-micro focus-visible:ring-0 focus-visible:ring-offset-0" - autoFocus - placeholder="Rename session" - onKeyDown={(event) => { - if (event.key === 'Escape') handleCancelEdit(); +
0 && 'pl-[20px]', + )} + > +
+ { + event.preventDefault(); + handleSaveEdit(); }} - /> -
- - + + + + +
+ {hasChildren ? ( + + {isExpanded ? ( + + ) : ( + + )} + + ) : null} + {formatDateLabel(session.time?.created || Date.now())} + {session.share ? ( + + ) : null} + {hasSummary && ((additions ?? 0) !== 0 || (deletions ?? 0) !== 0) ? ( + + +{Math.max(0, additions ?? 0)} + / + -{Math.max(0, deletions ?? 0)} + + ) : null} + {hasChildren ? ( + + {node.children.length} {node.children.length === 1 ? 'task' : 'tasks'} + + ) : null}
- -
- {formatDateLabel(session.time?.created || Date.now())} - {session.share && ( - - )}
); diff --git a/packages/ui/src/components/views/GitView.tsx b/packages/ui/src/components/views/GitView.tsx index 8a22f8ed..4eb1452a 100644 --- a/packages/ui/src/components/views/GitView.tsx +++ b/packages/ui/src/components/views/GitView.tsx @@ -1,12 +1,7 @@ import React from 'react'; import { useSessionStore } from '@/stores/useSessionStore'; import { useFireworksCelebration } from '@/contexts/FireworksContext'; -import type { - GitStatus, - GitIdentityProfile, - GitLogEntry, - CommitFileEntry, -} from '@/lib/api/types'; +import type { GitIdentityProfile, CommitFileEntry } from '@/lib/api/types'; import { useGitIdentitiesStore } from '@/stores/useGitIdentitiesStore'; import { useDirectoryStore } from '@/stores/useDirectoryStore'; import { @@ -17,66 +12,22 @@ import { useGitIdentity, useIsGitRepo, } from '@/stores/useGitStore'; -import { Button } from '@/components/ui/button'; -import { ButtonLarge } from '@/components/ui/button-large'; -import { Input } from '@/components/ui/input'; -import { Textarea } from '@/components/ui/textarea'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, -} from '@/components/ui/command'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; -import { RiAddLine, RiAiGenerate2, RiArrowDownLine, RiArrowDownSLine, RiArrowUpLine, RiBriefcaseLine, RiCheckboxBlankLine, RiCheckboxLine, RiCodeLine, RiFileCopyLine, RiGitBranchLine, RiGitCommitLine, RiGraduationCapLine, RiHeartLine, RiHomeLine, RiLoader4Line, RiRefreshLine, RiUser3Line } from '@remixicon/react'; -import { cn } from '@/lib/utils'; +import { RiGitBranchLine, RiLoader4Line } from '@remixicon/react'; import { toast } from 'sonner'; import type { Session } from '@opencode-ai/sdk'; import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs'; import { useUIStore } from '@/stores/useUIStore'; +import { GitHeader } from './git/GitHeader'; +import { GitEmptyState } from './git/GitEmptyState'; +import { ChangesSection } from './git/ChangesSection'; +import { CommitSection } from './git/CommitSection'; +import { HistorySection } from './git/HistorySection'; + type SyncAction = 'fetch' | 'pull' | 'push' | null; type CommitAction = 'commit' | 'commitAndPush' | null; -const sanitizeBranchNameInput = (value: string): string => { - return value - .trim() - .replace(/\s+/g, '-') - .replace(/[^A-Za-z0-9._/-]/g, '-') - .replace(/-+/g, '-') - .replace(/\/{2,}/g, '/') - .replace(/\/-+/g, '/') - .replace(/-+\//g, '/') - .replace(/^[-/]+/, '') - .replace(/[-/]+$/, ''); -}; - -const renderToastDescription = (text?: string) => - text ? {text} : undefined; - -const LOG_SIZE_OPTIONS = [ - { label: '25 commits', value: 25 }, - { label: '50 commits', value: 50 }, - { label: '100 commits', value: 100 }, -]; - type GitViewSnapshot = { directory?: string; selectedPaths: string[]; @@ -89,10 +40,14 @@ const useEffectiveDirectory = () => { const { currentSessionId, sessions, worktreeMetadata: worktreeMap } = useSessionStore(); const { currentDirectory: fallbackDirectory } = useDirectoryStore(); - const worktreeMetadata = currentSessionId ? worktreeMap.get(currentSessionId) ?? undefined : undefined; + const worktreeMetadata = currentSessionId + ? worktreeMap.get(currentSessionId) ?? undefined + : undefined; const currentSession = sessions.find((session) => session.id === currentSessionId); type SessionWithDirectory = Session & { directory?: string }; - const sessionDirectory: string | undefined = (currentSession as SessionWithDirectory | undefined)?.directory; + const sessionDirectory: string | undefined = ( + currentSession as SessionWithDirectory | undefined + )?.directory; return worktreeMetadata?.path ?? sessionDirectory ?? fallbackDirectory ?? undefined; }; @@ -101,9 +56,12 @@ export const GitView: React.FC = () => { const { git } = useRuntimeAPIs(); const currentDirectory = useEffectiveDirectory(); const { currentSessionId, worktreeMetadata: worktreeMap } = useSessionStore(); - const worktreeMetadata = currentSessionId ? worktreeMap.get(currentSessionId) ?? undefined : undefined; + const worktreeMetadata = currentSessionId + ? worktreeMap.get(currentSessionId) ?? undefined + : undefined; - const { profiles, globalIdentity, loadProfiles, loadGlobalIdentity } = useGitIdentitiesStore(); + const { profiles, globalIdentity, loadProfiles, loadGlobalIdentity } = + useGitIdentitiesStore(); const isGitRepo = useIsGitRepo(currentDirectory ?? null); const status = useGitStatus(currentDirectory ?? null); @@ -128,21 +86,13 @@ export const GitView: React.FC = () => { return gitViewSnapshot; }, [currentDirectory]); - const [commitMessage, setCommitMessage] = React.useState(initialSnapshot?.commitMessage ?? ''); - const [newBranchName, setNewBranchName] = React.useState(''); - const sanitizedNewBranch = React.useMemo( - () => sanitizeBranchNameInput(newBranchName), - [newBranchName] + const [commitMessage, setCommitMessage] = React.useState( + initialSnapshot?.commitMessage ?? '' ); const [syncAction, setSyncAction] = React.useState(null); const [commitAction, setCommitAction] = React.useState(null); - const [creatingBranch, setCreatingBranch] = React.useState(false); - const [lastSyncMessage, setLastSyncMessage] = React.useState(null); const [logMaxCountLocal, setLogMaxCountLocal] = React.useState(25); const [isSettingIdentity, setIsSettingIdentity] = React.useState(false); - const [branchPickerOpen, setBranchPickerOpen] = React.useState(false); - const [branchSearch, setBranchSearch] = React.useState(''); - const [error] = React.useState(null); const { triggerFireworks } = useFireworksCelebration(); const [selectedPaths, setSelectedPaths] = React.useState>( @@ -155,54 +105,68 @@ export const GitView: React.FC = () => { const clearGeneratedHighlights = React.useCallback(() => { setGeneratedHighlights([]); }, []); - const [selectedCommitHash, setSelectedCommitHash] = React.useState(null); - const [commitFiles, setCommitFiles] = React.useState([]); - const [isLoadingCommitFiles, setIsLoadingCommitFiles] = React.useState(false); - - const selectedCommit = React.useMemo(() => { - if (!selectedCommitHash || !log) return null; - return log.all.find((entry) => entry.hash === selectedCommitHash) ?? null; - }, [selectedCommitHash, log]); + const [expandedCommitHashes, setExpandedCommitHashes] = React.useState>(new Set()); + const [commitFilesMap, setCommitFilesMap] = React.useState>(new Map()); + const [loadingCommitHashes, setLoadingCommitHashes] = React.useState>(new Set()); const handleCopyCommitHash = React.useCallback((hash: string) => { - navigator.clipboard.writeText(hash).then(() => { - toast.success('Commit hash copied'); - }).catch(() => { - toast.error('Failed to copy'); + navigator.clipboard + .writeText(hash) + .then(() => { + toast.success('Commit hash copied'); + }) + .catch(() => { + toast.error('Failed to copy'); + }); + }, []); + + const handleToggleCommit = React.useCallback((hash: string) => { + setExpandedCommitHashes((prev) => { + const next = new Set(prev); + if (next.has(hash)) { + next.delete(hash); + } else { + next.add(hash); + } + return next; }); }, []); React.useEffect(() => { - if (!selectedCommitHash || !currentDirectory || !git) { - setCommitFiles([]); - return; - } + if (!currentDirectory || !git) return; - let cancelled = false; - setIsLoadingCommitFiles(true); + // Find hashes that are expanded but not yet loaded or loading + const hashesToLoad = Array.from(expandedCommitHashes).filter( + (hash) => !commitFilesMap.has(hash) && !loadingCommitHashes.has(hash) + ); - git.getCommitFiles(currentDirectory, selectedCommitHash) - .then((response) => { - if (!cancelled) { - setCommitFiles(response.files); - } - }) - .catch((error) => { - console.error('Failed to fetch commit files:', error); - if (!cancelled) { - setCommitFiles([]); - } - }) - .finally(() => { - if (!cancelled) { - setIsLoadingCommitFiles(false); - } - }); + if (hashesToLoad.length === 0) return; - return () => { - cancelled = true; - }; - }, [selectedCommitHash, currentDirectory, git]); + setLoadingCommitHashes((prev) => { + const next = new Set(prev); + hashesToLoad.forEach((h) => next.add(h)); + return next; + }); + + hashesToLoad.forEach((hash) => { + git + .getCommitFiles(currentDirectory, hash) + .then((response) => { + setCommitFilesMap((prev) => new Map(prev).set(hash, response.files)); + }) + .catch((error) => { + console.error('Failed to fetch commit files:', error); + setCommitFilesMap((prev) => new Map(prev).set(hash, [])); + }) + .finally(() => { + setLoadingCommitHashes((prev) => { + const next = new Set(prev); + next.delete(hash); + return next; + }); + }); + }); + }, [expandedCommitHashes, currentDirectory, git, commitFilesMap, loadingCommitHashes]); React.useEffect(() => { return () => { @@ -268,7 +232,7 @@ export const GitView: React.FC = () => { const changeEntries = React.useMemo(() => { if (!status) return []; const files = status.files ?? []; - const unique = new Map(); + const unique = new Map(); files.forEach((file) => { unique.set(file.path, file); @@ -310,18 +274,21 @@ export const GitView: React.FC = () => { toast.success('Fetched latest updates'); } else if (action === 'pull') { const result = await git.gitPull(currentDirectory); - toast.success(`Pulled ${result.files.length} file${result.files.length === 1 ? '' : 's'}`); + toast.success( + `Pulled ${result.files.length} file${result.files.length === 1 ? '' : 's'}` + ); } else if (action === 'push') { await git.gitPush(currentDirectory); toast.success('Pushed to remote'); } - setLastSyncMessage(`${action.toUpperCase()} completed at ${new Date().toLocaleTimeString()}`); await refreshStatusAndBranches(false); await refreshLog(); } catch (err) { const message = - err instanceof Error ? err.message : `Failed to ${action === 'pull' ? 'pull' : action}`; + err instanceof Error + ? err.message + : `Failed to ${action === 'pull' ? 'pull' : action}`; toast.error(message); } finally { setSyncAction(null); @@ -352,6 +319,7 @@ export const GitView: React.FC = () => { setCommitMessage(''); setSelectedPaths(new Set()); setHasUserAdjustedSelection(false); + clearGeneratedHighlights(); await refreshStatusAndBranches(); @@ -382,7 +350,10 @@ export const GitView: React.FC = () => { setIsGeneratingMessage(true); try { - const { message } = await git.generateCommitMessage(currentDirectory, Array.from(selectedPaths)); + const { message } = await git.generateCommitMessage( + currentDirectory, + Array.from(selectedPaths) + ); const subject = message.subject?.trim() ?? ''; const highlights = Array.isArray(message.highlights) ? message.highlights : []; @@ -393,41 +364,42 @@ export const GitView: React.FC = () => { toast.success('Commit message generated'); } catch (error) { - const message = error instanceof Error ? error.message : 'Failed to generate commit message'; + const message = + error instanceof Error ? error.message : 'Failed to generate commit message'; toast.error(message); } finally { setIsGeneratingMessage(false); } }, [currentDirectory, selectedPaths, git]); - const handleCreateBranch = async () => { + const handleCreateBranch = async (branchName: string) => { if (!currentDirectory || !status) return; - const finalName = sanitizedNewBranch; - if (!finalName) { - toast.error('Provide a branch name using letters, numbers, ".", "_", "-" or "/".'); - return; - } const checkoutBase = status.current ?? null; - setCreatingBranch(true); try { - await git.createBranch(currentDirectory, finalName, checkoutBase ?? 'HEAD'); - toast.success(`Created branch ${finalName}`); + await git.createBranch(currentDirectory, branchName, checkoutBase ?? 'HEAD'); + toast.success(`Created branch ${branchName}`); let pushSucceeded = false; try { - await git.checkoutBranch(currentDirectory, finalName); + await git.checkoutBranch(currentDirectory, branchName); await git.gitPush(currentDirectory, { remote: 'origin', - branch: finalName, + branch: branchName, options: ['--set-upstream'], }); pushSucceeded = true; } catch (pushError) { const message = - pushError instanceof Error ? pushError.message : 'Unable to push new branch to origin.'; + pushError instanceof Error + ? pushError.message + : 'Unable to push new branch to origin.'; toast.warning('Branch created locally', { - description: renderToastDescription(`Upstream setup failed: ${message}`), + description: ( + + Upstream setup failed: {message} + + ), }); } finally { if (checkoutBase) { @@ -439,18 +411,16 @@ export const GitView: React.FC = () => { } } - setNewBranchName(''); await refreshStatusAndBranches(); await refreshLog(); if (pushSucceeded) { - toast.success(`Upstream set for ${finalName}`); + toast.success(`Upstream set for ${branchName}`); } } catch (err) { const message = err instanceof Error ? err.message : 'Failed to create branch'; toast.error(message); - } finally { - setCreatingBranch(false); + throw err; } }; @@ -459,24 +429,17 @@ export const GitView: React.FC = () => { const normalized = branch.replace(/^remotes\//, ''); if (status?.current === normalized) { - setBranchPickerOpen(false); return; } try { await git.checkoutBranch(currentDirectory, normalized); toast.success(`Checked out ${normalized}`); - setBranchPickerOpen(false); - setBranchSearch(''); await refreshStatusAndBranches(); - const activeBranch = status?.current; - if (activeBranch) { - await git.checkoutBranch(currentDirectory, activeBranch); - toast.success(`Checked out ${activeBranch}`); - } await refreshLog(); } catch (err) { - const message = err instanceof Error ? err.message : `Failed to checkout ${normalized}`; + const message = + err instanceof Error ? err.message : `Failed to checkout ${normalized}`; toast.error(message); } }; @@ -499,7 +462,9 @@ export const GitView: React.FC = () => { const localBranches = React.useMemo(() => { if (!branches?.all) return []; - return branches.all.filter((branchName: string) => !branchName.startsWith('remotes/')).sort(); + return branches.all + .filter((branchName: string) => !branchName.startsWith('remotes/')) + .sort(); }, [branches]); const remoteBranches = React.useMemo(() => { @@ -510,27 +475,6 @@ export const GitView: React.FC = () => { .sort(); }, [branches]); - const branchOptions = React.useMemo(() => { - const search = branchSearch.trim().toLowerCase(); - if (!search) { - return { - locals: localBranches, - remotes: remoteBranches, - }; - } - - return { - locals: localBranches.filter((branch: string) => branch.toLowerCase().includes(search)), - remotes: remoteBranches.filter((branch: string) => branch.toLowerCase().includes(search)), - }; - }, [branchSearch, localBranches, remoteBranches]); - - React.useEffect(() => { - if (!branchPickerOpen) { - setBranchSearch(''); - } - }, [branchPickerOpen]); - const availableIdentities = React.useMemo(() => { const unique = new Map(); if (globalIdentity) { @@ -579,6 +523,7 @@ export const GitView: React.FC = () => { const uniqueChangeCount = changeEntries.length; const selectedCount = selectedPaths.size; const isBusy = isLoading || syncAction !== null || commitAction !== null; + const hasChanges = uniqueChangeCount > 0; const toggleFileSelection = (path: string) => { setSelectedPaths((previous) => { @@ -632,6 +577,33 @@ export const GitView: React.FC = () => { [currentDirectory, refreshStatusAndBranches, git] ); + const handleInsertHighlights = React.useCallback(() => { + if (generatedHighlights.length === 0) return; + const normalizedHighlights = generatedHighlights + .map((text) => text.trim()) + .filter(Boolean); + if (normalizedHighlights.length === 0) { + clearGeneratedHighlights(); + return; + } + setCommitMessage((current) => { + const base = current.trim(); + const separator = base.length > 0 ? '\n\n' : ''; + return `${base}${separator}${normalizedHighlights.join('\n')}`.trim(); + }); + }, [generatedHighlights, clearGeneratedHighlights]); + + const handleLogMaxCountChange = React.useCallback( + (count: number) => { + setLogMaxCountLocal(count); + if (currentDirectory) { + setLogMaxCount(currentDirectory, count); + fetchLog(currentDirectory, git, count); + } + }, + [currentDirectory, setLogMaxCount, fetchLog, git] + ); + if (!currentDirectory) { return (
@@ -647,7 +619,7 @@ export const GitView: React.FC = () => {
- Checking repository… + Checking repository...
); @@ -657,7 +629,9 @@ export const GitView: React.FC = () => { return (
-

Not a Git repository

+

+ Not a Git repository +

Choose a different directory or initialize Git to use this workspace.

@@ -665,870 +639,80 @@ export const GitView: React.FC = () => { ); } - const hasChanges = uniqueChangeCount > 0; - return (
- {} - {status && ( -
-
- - - {status.current || 'Detached HEAD'} - - {status.tracking && ( - - → {status.tracking} - - )} -
-
- - - {status.ahead} - - - - {status.behind} - - - {status.files.length} changes - -
-
- -
- )} + handleSyncAction('fetch')} + onPull={() => handleSyncAction('pull')} + onPush={() => handleSyncAction('push')} + onCheckoutBranch={handleCheckoutBranch} + onCreateBranch={handleCreateBranch} + activeIdentityProfile={activeIdentityProfile} + availableIdentities={availableIdentities} + onSelectIdentity={handleApplyIdentity} + isApplyingIdentity={isSettingIdentity} + isWorktreeMode={!!worktreeMetadata} + /> - {} -
- handleSyncAction('fetch')} - disabled={syncAction !== null || !status} - > - {syncAction === 'fetch' ? ( - - ) : ( - - )} - Fetch - - handleSyncAction('pull')} - disabled={syncAction !== null || !status} - > - {syncAction === 'pull' ? ( - - ) : ( - - )} - Pull - - handleSyncAction('push')} - disabled={syncAction !== null || !status} - > - {syncAction === 'push' ? ( - - ) : ( - - )} - Push - - -
- - {} - {!worktreeMetadata && ( - <> - - - - - - - - - Switch branch ({localBranches.length} local · {remoteBranches.length} remote) - - - - - - - No branches found. - - {branchOptions.locals.map((branchName: string) => ( - handleCheckoutBranch(branchName)} - > - - - {branchName} - - {branches?.branches?.[branchName]?.ahead || - branches?.branches?.[branchName]?.behind ? ( - - {branches.branches[branchName].ahead || 0} ahead ·{' '} - {branches.branches[branchName].behind || 0} behind - - ) : null} - - {status?.current === branchName && ( - Current - )} - - ))} - {branchOptions.locals.length === 0 && ( - - - No local branches - - - )} - - - - {branchOptions.remotes.map((branchName: string) => ( - handleCheckoutBranch(branchName)} - > - {branchName} - - ))} - {branchOptions.remotes.length === 0 && ( - - - No remote branches - - - )} - - - - - - setNewBranchName(event.target.value)} - className="h-8 w-32 sm:w-40 rounded-lg bg-background/80 text-sm" - /> - - - - - - {sanitizedNewBranch ? `Create branch "${sanitizedNewBranch}"` : 'Enter branch name'} - - - - )} - -
- {lastSyncMessage && ( - - {lastSyncMessage} - - )} -
- - {} - {error && ( -
-

{error}

-

- Try refreshing or confirm the repository is accessible. -

-
- )} - - {} -
- {} -
-
-

Changes

-
- - {selectedCount}/{uniqueChangeCount} - - {uniqueChangeCount > 0 && ( - <> - - - - )} -
-
- - {status?.isClean || uniqueChangeCount === 0 ? ( -
-
-
- -

- Working tree clean -

-
-
-
- ) : ( -
    - {changeEntries.map((file) => ( - toggleFileSelection(file.path)} - onViewDiff={() => useUIStore.getState().navigateToDiff(file.path)} - onRevert={() => handleRevertFile(file.path)} - isReverting={revertingPaths.has(file.path)} - /> - ))} -
- )} -
-
- - {} -
-
-

Commit

-
-
- {generatedHighlights.length > 0 && ( -
-
-

AI highlights

- - - - - Append highlights to commit message - -
-
    - {generatedHighlights.map((highlight, index) => ( -
  • - {highlight} -
  • - ))} -
-
- )} -