feat: improve diff colors (#171)

This commit is contained in:
Carlos Florêncio
2026-02-04 00:02:50 +00:00
committed by GitHub
parent 9b826363f3
commit 6fe5b22ec8
6 changed files with 89 additions and 69 deletions
+8
View File
@@ -92,6 +92,10 @@
--scrollbar-thumb-hover: oklch(0.66 0 0);
--scrollbar-track: var(--background);
--sidebar-ring: oklch(0.708 0 0);
/* Git diff colors - centralized for easy maintenance */
--git-addition: 16 185 129; /* emerald-500 */
--git-deletion: 244 63 94; /* rose-500 */
}
.dark {
@@ -129,6 +133,10 @@
--scrollbar-thumb-hover: oklch(0.62 0 0 / 0.8);
--scrollbar-track: var(--background);
--sidebar-ring: oklch(0.556 0 0);
/* Git diff colors - same as light mode for consistency */
--git-addition: 16 185 129; /* emerald-500 */
--git-deletion: 244 63 94; /* rose-500 */
}
@layer base {
+21
View File
@@ -430,6 +430,22 @@ export const DiffViewer = memo(function DiffViewer({
disableFileHeader: !showHeader,
lineDiffType: 'word',
overflow: wordWrap ? 'wrap' : 'scroll',
unsafeCSS: `
* {
background: transparent !important;
}
/* Reduce icon size */
[data-change-icon] {
width: 12px !important;
height: 12px !important;
}
/* Adjust header padding */
[data-diffs-header] {
padding-inline: 12px !important;
}
`,
}),
[globalViewMode, resolvedTheme, enableComments, showHeader, handleLineSelectionEnd, wordWrap]
);
@@ -476,6 +492,11 @@ export const DiffViewer = memo(function DiffViewer({
renderAnnotation={renderAnnotation}
renderHeaderMetadata={showHeader ? renderHeaderMetadata : undefined}
renderHoverUtility={renderHoverUtility}
style={{
'--diffs-addition-color-override': 'rgb(var(--git-addition))',
'--diffs-deletion-color-override': 'rgb(var(--git-deletion))',
'--diffs-font-size': '12px',
} as React.CSSProperties}
className={cn(
'rounded-md border border-border/50',
compact && 'text-xs'
@@ -0,0 +1,44 @@
'use client';
import { IconPlus, IconCircleFilled, IconMinus } from '@tabler/icons-react';
import type { FileInfo } from '@/lib/state/store';
type FileStatusIconProps = {
status: FileInfo['status'];
};
export function FileStatusIcon({ status }: FileStatusIconProps) {
switch (status) {
case 'added':
case 'untracked':
return (
<div className="flex items-center justify-center h-3 w-3 rounded border border-emerald-600">
<IconPlus className="h-2 w-2 text-emerald-600" />
</div>
);
case 'modified':
return (
<div className="flex items-center justify-center h-3 w-3 rounded border border-yellow-600">
<IconCircleFilled className="h-1 w-1 text-yellow-600" />
</div>
);
case 'deleted':
return (
<div className="flex items-center justify-center h-3 w-3 rounded border border-rose-600">
<IconMinus className="h-2 w-2 text-rose-600" />
</div>
);
case 'renamed':
return (
<div className="flex items-center justify-center h-3 w-3 rounded border border-purple-600">
<IconCircleFilled className="h-1 w-1 text-purple-600" />
</div>
);
default:
return (
<div className="flex items-center justify-center h-3 w-3 rounded border border-muted-foreground">
<IconCircleFilled className="h-1 w-1 text-muted-foreground" />
</div>
);
}
}
@@ -24,6 +24,7 @@ import { useGitOperations } from '@/hooks/use-git-operations';
import { FileDiffViewer } from '@/components/diff';
import type { FileInfo } from '@/lib/state/slices';
import type { SelectedDiff } from './task-layout';
import { FileStatusIcon } from './file-status-icon';
type TaskChangesPanelProps = {
selectedDiff: SelectedDiff | null;
@@ -88,15 +89,18 @@ const TaskChangesPanel = memo(function TaskChangesPanel({
if (!cumulativeDiff?.files || Object.keys(cumulativeDiff.files).length === 0) {
return [];
}
// Create a Set of uncommitted file paths for efficient lookup
const uncommittedPaths = new Set(uncommittedFiles.map(f => f.path));
return Object.entries(cumulativeDiff.files)
.filter(([, file]) => file.diff) // Only include files with diffs
.filter(([path, file]) => file.diff && !uncommittedPaths.has(path)) // Exclude uncommitted files
.map(([path, file]) => ({
path,
diff: file.diff!,
additions: file.additions ?? 0,
deletions: file.deletions ?? 0,
}));
}, [cumulativeDiff]);
}, [cumulativeDiff, uncommittedFiles]);
const selectedFile = selectedDiffPath && gitStatus?.files ? gitStatus.files[selectedDiffPath] : null;
// Prioritize current git status for uncommitted files, use provided content only for historical diffs
@@ -253,9 +257,12 @@ const TaskChangesPanel = memo(function TaskChangesPanel({
return (
<div key={diffKey} className="space-y-2">
<div className="flex items-center justify-between">
<Badge variant="secondary" className="rounded-full text-xs">
{file.path}
</Badge>
<div className="flex items-center gap-2">
<FileStatusIcon status="modified" />
<Badge variant="secondary" className="rounded-full text-xs">
{file.path}
</Badge>
</div>
<span className="text-xs text-muted-foreground">
<span className="text-emerald-500">+{file.additions}</span>
{' / '}
+3 -39
View File
@@ -5,7 +5,6 @@ import {
IconArrowBackUp,
IconExternalLink,
IconPlus,
IconCircleFilled,
IconMinus,
IconCheck,
IconChevronRight,
@@ -47,6 +46,7 @@ import {
hasUserSelectedFilesPanelTab,
setUserSelectedFilesPanelTab,
} from '@/lib/local-storage';
import { FileStatusIcon } from './file-status-icon';
type TaskFilesPanelProps = {
onSelectDiff: (path: string, content?: string) => void;
@@ -56,42 +56,6 @@ type TaskFilesPanelProps = {
const StatusIcon = ({ status }: { status: FileInfo['status'] }) => {
switch (status) {
case 'added':
case 'untracked':
return (
<div className="flex items-center justify-center h-3 w-3 rounded border border-emerald-600">
<IconPlus className="h-2 w-2 text-emerald-600" />
</div>
);
case 'modified':
return (
<div className="flex items-center justify-center h-3 w-3 rounded border border-yellow-600">
<IconCircleFilled className="h-1 w-1 text-yellow-600" />
</div>
);
case 'deleted':
return (
<div className="flex items-center justify-center h-3 w-3 rounded border border-rose-600">
<IconMinus className="h-2 w-2 text-rose-600" />
</div>
);
case 'renamed':
return (
<div className="flex items-center justify-center h-3 w-3 rounded border border-purple-600">
<IconCircleFilled className="h-1 w-1 text-purple-600" />
</div>
);
default:
return (
<div className="flex items-center justify-center h-3 w-3 rounded border border-muted-foreground">
<IconCircleFilled className="h-1 w-1 text-muted-foreground" />
</div>
);
}
};
const splitPath = (path: string) => {
const lastSlash = path.lastIndexOf('/');
if (lastSlash === -1) return { folder: '', file: path };
@@ -398,7 +362,7 @@ const TaskFilesPanel = memo(function TaskFilesPanel({ onSelectDiff, onOpenFile,
</Tooltip>
</div>
<LineStat added={file.plus} removed={file.minus} />
<StatusIcon status={file.status} />
<FileStatusIcon status={file.status} />
</div>
</li>
);
@@ -458,7 +422,7 @@ const TaskFilesPanel = memo(function TaskFilesPanel({ onSelectDiff, onOpenFile,
className="flex items-center gap-2 text-xs rounded px-1 py-0.5 hover:bg-muted/50 cursor-pointer"
onClick={() => onSelectDiff(path, file.diff)}
>
<StatusIcon status={file.status} />
<FileStatusIcon status={file.status} />
<span className="truncate flex-1">
<span className="text-foreground/60">{folder}/</span>
<span className="text-foreground">{fileName}</span>
+1 -25
View File
@@ -46,9 +46,8 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@kandev/ui/tooltip';
import { Popover, PopoverContent, PopoverTrigger } from '@kandev/ui/popover';
import { Checkbox } from '@kandev/ui/checkbox';
import { Label } from '@kandev/ui/label';
import { CommitStatBadge, LineStat } from '@/components/diff-stat';
import { CommitStatBadge } from '@/components/diff-stat';
import { useSessionGitStatus } from '@/hooks/domains/session/use-session-git-status';
import { useSessionCommits } from '@/hooks/domains/session/use-session-commits';
import { useGitOperations } from '@/hooks/use-git-operations';
import type { FileInfo } from '@/lib/state/slices';
import { formatUserHomePath } from '@/lib/utils';
@@ -105,7 +104,6 @@ const TaskTopBar = memo(function TaskTopBar({
const router = useRouter();
const { toast } = useToast();
const gitStatus = useSessionGitStatus(activeSessionId ?? null);
const { commits } = useSessionCommits(activeSessionId ?? null);
const { pull, push, rebase, merge, commit, createPR, isLoading: isGitLoading } = useGitOperations(activeSessionId ?? null);
const { open: openFolder, isLoading: isOpenFolderLoading } = useOpenSessionFolder(activeSessionId);
@@ -128,14 +126,6 @@ const TaskTopBar = memo(function TaskTopBar({
}
}
// Calculate cumulative additions and deletions from commits.
const commitAdditions = commits.reduce((sum, c) => sum + c.insertions, 0);
const commitDeletions = commits.reduce((sum, c) => sum + c.deletions, 0);
// Combined total (uncommitted + commits)
const totalAdditions = uncommittedAdditions + commitAdditions;
const totalDeletions = uncommittedDeletions + commitDeletions;
const handleGitOperation = useCallback(async (
operation: () => Promise<{ success: boolean; output: string; error?: string; conflict_files?: string[] }>,
operationName: string
@@ -391,20 +381,6 @@ const TaskTopBar = memo(function TaskTopBar({
</div>
)}
{/* Git Status: Total Lines Changed (uncommitted + commits) */}
{(totalAdditions > 0 || totalDeletions > 0) && (
<Tooltip>
<TooltipTrigger asChild>
<span className="cursor-default">
<LineStat added={totalAdditions} removed={totalDeletions} />
</span>
</TooltipTrigger>
<TooltipContent>
Total changes{commits.length > 0 ? ` (${commits.length} commit${commits.length !== 1 ? 's' : ''}${uncommittedAdditions > 0 || uncommittedDeletions > 0 ? ' + uncommitted' : ''})` : ''}
</TooltipContent>
</Tooltip>
)}
</div>
<div className="flex items-center gap-2">
{DEBUG_UI && onToggleDebugOverlay && (