feat: improve diff colors (#171)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
{' / '}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
Reference in New Issue
Block a user