mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-02 02:15:34 +08:00
fix(git-ui): prevent large commit diffs from freezing the history tab
Harden commit diff loading/rendering so opening a very large commit no longer hangs the browser tab. Problem - commit history diff viewer rendered every diff line as a React node - very large commits could create thousands of nodes and lock the UI thread - backend always returned full commit patch payloads, amplifying frontend pressure Backend safeguards - add `COMMIT_DIFF_CHARACTER_LIMIT` (500,000 chars) in git routes - update GET `/api/git/commit-diff` to truncate oversized diff payloads - include `isTruncated` flag in response for observability/future UI handling - append truncation marker text when server-side limit is applied Frontend safeguards - update `GitDiffViewer` to use bounded preview rendering: - character cap: 200,000 - line cap: 1,500 - move diff preprocessing into `useMemo` for stable, one-pass preview computation - show a clear "Large diff preview" notice when truncation is active Impact - commit diff expansion remains responsive even for high-change commits - UI still shows useful diff content while avoiding tab lockups - changes apply to shared diff viewer usage and improve resilience broadly Validation - `node --check server/routes/git.js` - `npm run typecheck` - `npx eslint src/components/git-panel/view/shared/GitDiffViewer.tsx`
This commit is contained in:
@@ -7,6 +7,7 @@ import { queryClaudeSDK } from '../claude-sdk.js';
|
||||
import { spawnCursor } from '../cursor-cli.js';
|
||||
|
||||
const router = express.Router();
|
||||
const COMMIT_DIFF_CHARACTER_LIMIT = 500_000;
|
||||
|
||||
function spawnAsync(command, args, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -769,8 +770,13 @@ router.get('/commit-diff', async (req, res) => {
|
||||
'git', ['show', commit],
|
||||
{ cwd: projectPath }
|
||||
);
|
||||
|
||||
res.json({ diff: stdout });
|
||||
|
||||
const isTruncated = stdout.length > COMMIT_DIFF_CHARACTER_LIMIT;
|
||||
const diff = isTruncated
|
||||
? `${stdout.slice(0, COMMIT_DIFF_CHARACTER_LIMIT)}\n\n... Diff truncated to keep the UI responsive ...`
|
||||
: stdout;
|
||||
|
||||
res.json({ diff, isTruncated });
|
||||
} catch (error) {
|
||||
console.error('Git commit diff error:', error);
|
||||
res.json({ error: error.message });
|
||||
|
||||
@@ -1,10 +1,38 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
type GitDiffViewerProps = {
|
||||
diff: string | null;
|
||||
isMobile: boolean;
|
||||
wrapText: boolean;
|
||||
};
|
||||
|
||||
const PREVIEW_CHARACTER_LIMIT = 200_000;
|
||||
const PREVIEW_LINE_LIMIT = 1_500;
|
||||
|
||||
type DiffPreview = {
|
||||
lines: string[];
|
||||
isCharacterTruncated: boolean;
|
||||
isLineTruncated: boolean;
|
||||
};
|
||||
|
||||
function buildDiffPreview(diff: string): DiffPreview {
|
||||
const isCharacterTruncated = diff.length > PREVIEW_CHARACTER_LIMIT;
|
||||
const previewText = isCharacterTruncated ? diff.slice(0, PREVIEW_CHARACTER_LIMIT) : diff;
|
||||
const previewLines = previewText.split('\n');
|
||||
const isLineTruncated = previewLines.length > PREVIEW_LINE_LIMIT;
|
||||
|
||||
return {
|
||||
lines: isLineTruncated ? previewLines.slice(0, PREVIEW_LINE_LIMIT) : previewLines,
|
||||
isCharacterTruncated,
|
||||
isLineTruncated,
|
||||
};
|
||||
}
|
||||
|
||||
export default function GitDiffViewer({ diff, isMobile, wrapText }: GitDiffViewerProps) {
|
||||
// Render a bounded preview to keep huge commit diffs from freezing the UI thread.
|
||||
const preview = useMemo(() => buildDiffPreview(diff || ''), [diff]);
|
||||
const isPreviewTruncated = preview.isCharacterTruncated || preview.isLineTruncated;
|
||||
|
||||
if (!diff) {
|
||||
return (
|
||||
<div className="p-4 text-center text-sm text-muted-foreground">
|
||||
@@ -35,7 +63,12 @@ export default function GitDiffViewer({ diff, isMobile, wrapText }: GitDiffViewe
|
||||
|
||||
return (
|
||||
<div className="diff-viewer">
|
||||
{diff.split('\n').map((line, index) => renderDiffLine(line, index))}
|
||||
{isPreviewTruncated && (
|
||||
<div className="mb-2 rounded-md border border-border bg-card px-3 py-2 text-xs text-muted-foreground">
|
||||
Large diff preview: rendering is limited to keep the tab responsive.
|
||||
</div>
|
||||
)}
|
||||
{preview.lines.map((line, index) => renderDiffLine(line, index))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user