diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 4ec0d4e..3ca2a8f 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -265,6 +265,9 @@ export default function App() { onSetSessionArchived={(sessionId, isArchived) => { void api.setSessionArchived({ sessionId, isArchived }); }} + onRefreshGitContext={(projectId) => { + void api.refreshProjectGitContext(projectId); + }} workspace={workspace} /> } diff --git a/src/renderer/components/ChatPane.tsx b/src/renderer/components/ChatPane.tsx index 342a28f..53382c9 100644 --- a/src/renderer/components/ChatPane.tsx +++ b/src/renderer/components/ChatPane.tsx @@ -1,5 +1,5 @@ import { type KeyboardEvent, useEffect, useRef, useState } from 'react'; -import { AlertCircle, ArrowUp, Bot, ChevronDown, Loader2, Sparkles, User } from 'lucide-react'; +import { AlertCircle, ArrowUp, Bot, ChevronDown, Circle, GitBranch, Loader2, Sparkles, User } from 'lucide-react'; import { MarkdownContent } from '@renderer/components/MarkdownContent'; import { getAssistantMessagePhase } from '@renderer/lib/messagePhase'; @@ -318,7 +318,20 @@ export function ChatPane({

{session.title}

- {isScratchpad ? `Scratchpad · ${pattern.name}` : `${project.name} · ${pattern.name} · ${pattern.mode}`} + {isScratchpad + ? `Scratchpad · ${pattern.name}` + : `${project.name} · ${pattern.name} · ${pattern.mode}`} + {!isScratchpad && project.git?.status === 'ready' && ( + + + {project.git.branch ?? project.git.head?.shortHash ?? 'HEAD'} + {project.git.isDirty && ( + + )} + {(project.git.ahead ?? 0) > 0 && ↑{project.git.ahead}} + {(project.git.behind ?? 0) > 0 && ↓{project.git.behind}} + + )}

diff --git a/src/renderer/components/Sidebar.tsx b/src/renderer/components/Sidebar.tsx index 752a1c8..b2b57bd 100644 --- a/src/renderer/components/Sidebar.tsx +++ b/src/renderer/components/Sidebar.tsx @@ -1,11 +1,14 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import { + AlertTriangle, Archive, ArrowLeftRight, ChevronDown, ChevronRight, + Circle, Copy, FolderOpen, + GitBranch, GitFork, ListOrdered, Lock, @@ -14,6 +17,7 @@ import { Pencil, Pin, Plus, + RefreshCw, Search, Settings, Sparkles, @@ -23,7 +27,7 @@ import { } from 'lucide-react'; import type { OrchestrationMode, PatternDefinition } from '@shared/domain/pattern'; -import { isScratchpadProject, type ProjectRecord } from '@shared/domain/project'; +import { isScratchpadProject, type ProjectRecord, type ProjectGitContext } from '@shared/domain/project'; import type { SessionRecord } from '@shared/domain/session'; import { querySessions } from '@shared/domain/sessionLibrary'; import type { WorkspaceState } from '@shared/domain/workspace'; @@ -39,6 +43,7 @@ interface SidebarProps { onDuplicateSession: (sessionId: string) => void; onSetSessionPinned: (sessionId: string, isPinned: boolean) => void; onSetSessionArchived: (sessionId: string, isArchived: boolean) => void; + onRefreshGitContext: (projectId: string) => void; } /* ── Mode icon + accent colour mapping ─────────────────────── */ @@ -68,6 +73,53 @@ function relativeTime(iso: string): string { return new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); } +/* ── Git context badge ──────────────────────────────────────── */ + +function GitContextBadge({ git }: { git: ProjectGitContext }) { + if (git.status === 'not-repository') { + return ( + + no repo + + ); + } + + if (git.status === 'git-missing') { + return ( + + + no git + + ); + } + + if (git.status === 'error') { + return ( + + + error + + ); + } + + const branchLabel = git.branch ?? git.head?.shortHash ?? 'HEAD'; + const parts: string[] = []; + if (git.isDirty && git.changedFileCount) parts.push(`${git.changedFileCount} changed`); + if (git.ahead) parts.push(`↑${git.ahead}`); + if (git.behind) parts.push(`↓${git.behind}`); + + return ( + + + {branchLabel} + {git.isDirty && } + + ); +} + /* ── Context menu item ─────────────────────────────────────── */ function ActionMenuItem({ @@ -257,6 +309,7 @@ function ProjectGroup({ onOpenMenu, onRenameSubmit, onRenameCancel, + onRefreshGitContext, }: { project: ProjectRecord; sessions: SessionRecord[]; @@ -267,7 +320,8 @@ function ProjectGroup({ onOpenMenu: (sessionId: string, e: React.MouseEvent) => void; onRenameSubmit: (sessionId: string, title: string) => void; onRenameCancel: () => void; -}) { + onRefreshGitContext?: (projectId: string) => void; +}){ const [expanded, setExpanded] = useState(true); const isScratchpad = isScratchpadProject(project); @@ -309,7 +363,24 @@ function ProjectGroup({ )} {project.name} + {!isScratchpad && project.git && ( + + )} +
+ {!isScratchpad && onRefreshGitContext && ( + { + e.stopPropagation(); + onRefreshGitContext(project.id); + }} + role="button" + title="Refresh git status" + > + + + )} {runningCount > 0 && ( @@ -362,6 +433,7 @@ export function Sidebar({ onDuplicateSession, onSetSessionPinned, onSetSessionArchived, + onRefreshGitContext, }: SidebarProps) { const scratchpadProject = workspace.projects.find((project) => isScratchpadProject(project)); const userProjects = workspace.projects.filter((project) => !isScratchpadProject(project)); @@ -570,6 +642,7 @@ export function Sidebar({ onOpenMenu={handleOpenMenu} onRenameSubmit={handleRenameSubmit} onRenameCancel={() => setRenamingSessionId(undefined)} + onRefreshGitContext={onRefreshGitContext} renamingSessionId={renamingSessionId} patterns={workspace.patterns} project={project}