fix: handle multi-file tool diffs safely

Split multi-file patches before diff rendering
Fixed hook dependency lint warnings
This commit is contained in:
Bohdan Triapitsyn
2026-04-26 16:58:07 +03:00
parent 8f3707a5f4
commit 209ffc16e4
22 changed files with 116 additions and 57 deletions
@@ -2341,7 +2341,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
toast.error(error instanceof Error ? error.message : t('chat.chatInput.toast.clipboardAttachFailed')); toast.error(error instanceof Error ? error.message : t('chat.chatInput.toast.clipboardAttachFailed'));
} }
} }
}, [addAttachedFile, currentSessionId, newSessionDraftOpen, insertTextAtSelection]); }, [addAttachedFile, currentSessionId, newSessionDraftOpen, insertTextAtSelection, t]);
const handleFileSelect = (file: { name: string; path: string; relativePath?: string }) => { const handleFileSelect = (file: { name: string; path: string; relativePath?: string }) => {
@@ -2650,7 +2650,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
setPendingInputText(mentions.join(' '), 'append-inline'); setPendingInputText(mentions.join(' '), 'append-inline');
toast.success(t('chat.chatInput.toast.addedFileMentions', { count: mentions.length })); toast.success(t('chat.chatInput.toast.addedFileMentions', { count: mentions.length }));
}, [normalizeDroppedPath, setPendingInputText, toProjectRelativeMentionPath]); }, [normalizeDroppedPath, setPendingInputText, t, toProjectRelativeMentionPath]);
const handleDragEnter = (e: React.DragEvent) => { const handleDragEnter = (e: React.DragEvent) => {
if (!hasDraggedFiles(e.dataTransfer)) { if (!hasDraggedFiles(e.dataTransfer)) {
@@ -2897,7 +2897,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
cancelled = true; cancelled = true;
if (unlisten) unlisten(); if (unlisten) unlisten();
}; };
}, [addAttachedFile, normalizeDroppedPath]); }, [addAttachedFile, normalizeDroppedPath, t]);
const fileInputRef = React.useRef<HTMLInputElement>(null); const fileInputRef = React.useRef<HTMLInputElement>(null);
@@ -2912,7 +2912,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
toast.error(error instanceof Error ? error.message : t('chat.chatInput.toast.attachFileFailed')); toast.error(error instanceof Error ? error.message : t('chat.chatInput.toast.attachFileFailed'));
} }
} }
}, [addAttachedFile]); }, [addAttachedFile, t]);
const handleVSCodePickFiles = React.useCallback(async () => { const handleVSCodePickFiles = React.useCallback(async () => {
try { try {
@@ -2956,7 +2956,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
console.error('VS Code file pick failed', error); console.error('VS Code file pick failed', error);
toast.error(error instanceof Error ? error.message : t('chat.chatInput.toast.vscodePickFailed')); toast.error(error instanceof Error ? error.message : t('chat.chatInput.toast.vscodePickFailed'));
} }
}, [attachFiles]); }, [attachFiles, t]);
const handlePickLocalFiles = React.useCallback(() => { const handlePickLocalFiles = React.useCallback(() => {
if (isVSCodeRuntime()) { if (isVSCodeRuntime()) {
@@ -3268,7 +3268,7 @@ const ChatInputComponent: React.FC<ChatInputProps> = ({ onOpenSettings, scrollTo
setSessionAutoAccept(permissionScopeSessionId, nextEnabled).catch(() => { setSessionAutoAccept(permissionScopeSessionId, nextEnabled).catch(() => {
toast.error(t('chat.chatInput.toast.togglePermissionAutoAcceptFailed')); toast.error(t('chat.chatInput.toast.togglePermissionAutoAcceptFailed'));
}); });
}, [permissionAutoAcceptEnabled, permissionScopeSessionId, setSessionAutoAccept]); }, [permissionAutoAcceptEnabled, permissionScopeSessionId, setSessionAutoAccept, t]);
React.useEffect(() => { React.useEffect(() => {
const pendingAbortBanner = Boolean(abortPromptSessionId) && abortPromptSessionId === currentSessionId; const pendingAbortBanner = Boolean(abortPromptSessionId) && abortPromptSessionId === currentSessionId;
@@ -1010,7 +1010,7 @@ const AssistantMessageBody = React.memo(({
setIsSavingPlan(false); setIsSavingPlan(false);
} }
}, },
[assistantPlanText, currentProjectRef] [assistantPlanText, currentProjectRef, t]
); );
const [isSharing, setIsSharing] = React.useState(false); const [isSharing, setIsSharing] = React.useState(false);
@@ -1123,7 +1123,7 @@ const AssistantMessageBody = React.memo(({
setIsSharing(false); setIsSharing(false);
} }
}, },
[messageId, isSharing] [messageId, isSharing, t]
); );
React.useEffect(() => { React.useEffect(() => {
@@ -534,7 +534,7 @@ export const TextSelectionMenu: React.FC<TextSelectionMenuProps> = ({ containerR
} finally { } finally {
setIsAddingToNotes(false); setIsAddingToNotes(false);
} }
}, [currentProjectRef, hideMenu, selectedText]); }, [currentProjectRef, hideMenu, selectedText, t]);
if (!position.show) return null; if (!position.show) return null;
@@ -782,7 +782,7 @@ const MermaidPreviewDialog: React.FC<{
setStatus('error'); setStatus('error');
setErrorMessage(error instanceof Error ? error.message : t('chat.toolOutputDialog.mermaid.loadFailed')); setErrorMessage(error instanceof Error ? error.message : t('chat.toolOutputDialog.mermaid.loadFailed'));
}); });
}, [decodeDataUrl, normalizeFilePath, popup.mermaid]); }, [decodeDataUrl, normalizeFilePath, popup.mermaid, t]);
React.useEffect(() => { React.useEffect(() => {
if (!popup.open || !popup.mermaid) { if (!popup.open || !popup.mermaid) {
@@ -1243,6 +1243,56 @@ type DiffPatchEntry = {
patch: string; patch: string;
}; };
const hasUnifiedDiffHunk = (patch: string): boolean => /^@@\s+-\d+(?:,\d+)?\s+\+\d+(?:,\d+)?\s+@@/m.test(patch);
const getUnifiedDiffPath = (patch: string, fallbackTitle: string): string => {
const plusHeader = patch.match(/^\+\+\+\s+(?:[ab]\/(.+)|(.+))$/m);
const rawPath = plusHeader?.[1] ?? plusHeader?.[2];
if (!rawPath || rawPath === '/dev/null') {
return fallbackTitle;
}
return rawPath;
};
const splitUnifiedDiffPatch = (patch: string): DiffPatchEntry[] => {
const normalized = patch.replace(/\r\n/g, '\n').trim();
if (!normalized) {
return [];
}
const lines = normalized.split('\n');
const starts: number[] = [];
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index] ?? '';
const nextLine = lines[index + 1] ?? '';
const isUnifiedFileHeader = /^---\s+(?:[ab]\/|\/dev\/null|\/)/.test(line)
&& /^\+\+\+\s+(?:[ab]\/|\/dev\/null|\/)/.test(nextLine);
if (line.startsWith('diff --git ') || line.startsWith('Index: ') || isUnifiedFileHeader) {
starts.push(index);
}
}
const chunks = starts.length > 0
? starts.map((start, index) => lines.slice(start, starts[index + 1] ?? lines.length).join('\n').trim())
: [normalized];
return chunks
.map((chunk, index) => {
if (!hasUnifiedDiffHunk(chunk)) {
return null;
}
const title = getUnifiedDiffPath(chunk, `Diff ${index + 1}`);
return {
id: `${title}-${index}`,
title,
patch: chunk,
} satisfies DiffPatchEntry;
})
.filter((entry): entry is DiffPatchEntry => entry !== null);
};
const renderPathLikeGitChanges = (path: string, grow = true) => { const renderPathLikeGitChanges = (path: string, grow = true) => {
const lastSlash = path.lastIndexOf('/'); const lastSlash = path.lastIndexOf('/');
if (lastSlash === -1) { if (lastSlash === -1) {
@@ -1338,7 +1388,7 @@ const getDiffPatchEntries = (
const record = file as { relativePath?: unknown; filePath?: unknown; patch?: unknown; diff?: unknown }; const record = file as { relativePath?: unknown; filePath?: unknown; patch?: unknown; diff?: unknown };
const patch = getPatchText(record.patch) ?? getPatchText(record.diff) ?? ''; const patch = getPatchText(record.patch) ?? getPatchText(record.diff) ?? '';
if (!patch) { if (!patch || !hasUnifiedDiffHunk(patch)) {
return null; return null;
} }
@@ -1364,13 +1414,16 @@ const getDiffPatchEntries = (
return entries; return entries;
} }
return [ const splitEntries = splitUnifiedDiffPatch(fallbackDiff).map((entry) => ({
{ ...entry,
id: 'diff-0', title: getRelativePath(entry.title, currentDirectory),
title: 'Diff', }));
patch: fallbackDiff,
}, if (splitEntries.length > 0) {
]; return splitEntries;
}
return [];
}; };
const DiffPreview: React.FC<DiffPreviewProps> = React.memo(({ diff, pierreTheme, pierreThemeType, diffViewMode }) => { const DiffPreview: React.FC<DiffPreviewProps> = React.memo(({ diff, pierreTheme, pierreThemeType, diffViewMode }) => {
@@ -1307,7 +1307,7 @@ export function DesktopHostSwitcherButton({ headerIconButtonClass }: DesktopHost
cancelled = true; cancelled = true;
window.clearInterval(interval); window.clearInterval(interval);
}; };
}, [connectDefaultSshInstance]); }, [connectDefaultSshInstance, t]);
if (!isDesktopShell()) { if (!isDesktopShell()) {
return null; return null;
+1 -1
View File
@@ -930,7 +930,7 @@ export const Header: React.FC<HeaderProps> = ({
} }
return groups; return groups;
}, [dropdownProviderIds, quotaResults, selectedModels]); }, [dropdownProviderIds, quotaResults, selectedModels, t]);
const hasRateLimits = rateLimitGroups.length > 0; const hasRateLimits = rateLimitGroups.length > 0;
React.useEffect(() => { React.useEffect(() => {
void loadQuotaSettings(); void loadQuotaSettings();
@@ -370,7 +370,7 @@ export const ProjectActionsButton = ({
} }
} }
}, [actions, openExternal, runningByKey, terminalSessions]); }, [actions, openExternal, runningByKey, t, terminalSessions]);
const normalizedDirectory = React.useMemo(() => { const normalizedDirectory = React.useMemo(() => {
return normalizeProjectActionDirectory(directory || stableProjectRef?.path || ''); return normalizeProjectActionDirectory(directory || stableProjectRef?.path || '');
@@ -424,6 +424,7 @@ export const ProjectActionsButton = ({
setActiveTab, setActiveTab,
setBottomTerminalOpen, setBottomTerminalOpen,
setTabLabel, setTabLabel,
t,
]); ]);
const runAction = React.useCallback(async (action: OpenChamberProjectAction) => { const runAction = React.useCallback(async (action: OpenChamberProjectAction) => {
@@ -529,6 +530,7 @@ export const ProjectActionsButton = ({
runtime.isVSCode, runtime.isVSCode,
setConnecting, setConnecting,
setTabSessionId, setTabSessionId,
t,
terminal, terminal,
]); ]);
@@ -422,7 +422,7 @@ export const AgentsPage: React.FC = () => {
} }
setPendingRuleName(''); setPendingRuleName('');
setPendingRulePattern('*'); setPendingRulePattern('*');
}, [globalPermission, pendingRuleName, pendingRulePattern, removeRule, setGlobalPermissionAndPrune, upsertRule]); }, [globalPermission, pendingRuleName, pendingRulePattern, removeRule, setGlobalPermissionAndPrune, t, upsertRule]);
const formatPermissionLabel = React.useCallback((permissionName: string): string => { const formatPermissionLabel = React.useCallback((permissionName: string): string => {
if (permissionName === '*') return t('settings.agents.page.permissions.defaultLabel'); if (permissionName === '*') return t('settings.agents.page.permissions.defaultLabel');
@@ -181,7 +181,7 @@ export const VoiceSettings: React.FC = () => {
window.speechSynthesis.cancel(); window.speechSynthesis.cancel();
window.speechSynthesis.speak(utterance); window.speechSynthesis.speak(utterance);
}, [browserVoice, browserVoices, speechRate, speechPitch, speechVolume, isBrowserPreviewPlaying]); }, [browserVoice, browserVoices, speechRate, speechPitch, speechVolume, isBrowserPreviewPlaying, t]);
useEffect(() => { useEffect(() => {
return () => { return () => {
@@ -150,6 +150,7 @@ export const ProjectsPage: React.FC = () => {
uploadProjectIcon, uploadProjectIcon,
removeProjectIcon, removeProjectIcon,
selectedProject, selectedProject,
t,
updateProjectMeta, updateProjectMeta,
]); ]);
@@ -207,7 +207,7 @@ export const ProvidersPage: React.FC = () => {
return () => { return () => {
isMounted = false; isMounted = false;
}; };
}, []); }, [t]);
React.useEffect(() => { React.useEffect(() => {
let isMounted = true; let isMounted = true;
@@ -244,7 +244,7 @@ export const ProvidersPage: React.FC = () => {
return () => { return () => {
isMounted = false; isMounted = false;
}; };
}, []); }, [t]);
const connectedProviderIds = React.useMemo( const connectedProviderIds = React.useMemo(
() => new Set(providers.map((provider) => provider.id)), () => new Set(providers.map((provider) => provider.id)),
@@ -280,7 +280,7 @@ export const ProvidersPage: React.FC = () => {
} }
setShowAuthPanel(false); setShowAuthPanel(false);
}, [selectedProviderId]); }, [selectedProviderId, t]);
React.useEffect(() => { React.useEffect(() => {
if (!selectedProviderId || selectedProviderId === ADD_PROVIDER_ID) { if (!selectedProviderId || selectedProviderId === ADD_PROVIDER_ID) {
@@ -320,7 +320,7 @@ export const ProvidersPage: React.FC = () => {
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [selectedProviderId]); }, [selectedProviderId, t]);
const selectedProvider = providers.find((provider) => provider.id === selectedProviderId); const selectedProvider = providers.find((provider) => provider.id === selectedProviderId);
const selectedSources = selectedProviderId ? providerSources[selectedProviderId] : undefined; const selectedSources = selectedProviderId ? providerSources[selectedProviderId] : undefined;
@@ -443,7 +443,7 @@ export const RemoteInstancesPage: React.FC = () => {
return false; return false;
} }
}, },
[createFromCommand, setSelectedId], [createFromCommand, setSelectedId, t],
); );
const closePatternDialog = React.useCallback(() => { const closePatternDialog = React.useCallback(() => {
@@ -136,7 +136,7 @@ export function GitHubIssuePickerDialog({
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}, [github, githubAuthChecked, githubAuthStatus, projectDirectory]); }, [github, githubAuthChecked, githubAuthStatus, projectDirectory, t]);
const loadMore = React.useCallback(async () => { const loadMore = React.useCallback(async () => {
if (!projectDirectory) return; if (!projectDirectory) return;
@@ -126,7 +126,7 @@ export function GitHubPrPickerDialog({
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}, [github, githubAuthChecked, githubAuthStatus, projectDirectory]); }, [github, githubAuthChecked, githubAuthStatus, projectDirectory, t]);
const loadMore = React.useCallback(async () => { const loadMore = React.useCallback(async () => {
if (!projectDirectory) return; if (!projectDirectory) return;
@@ -618,6 +618,7 @@ export function NewWorktreeDialog({
resolveDefaultAgentName, resolveDefaultAgentName,
resolveDefaultModelSelection, resolveDefaultModelSelection,
resolveDefaultVariant, resolveDefaultVariant,
t,
]); ]);
// Get current state based on mode // Get current state based on mode
@@ -788,6 +789,7 @@ export function NewWorktreeDialog({
validation.touched, validation.touched,
validationAbortController, validationAbortController,
isCreating, isCreating,
t,
]); ]);
// Extract branch name for dependency array // Extract branch name for dependency array
@@ -2329,7 +2329,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [files, isSelectedImage, isSelectedSvg, runtime.isDesktop, selectedFile?.path]); }, [files, isSelectedImage, isSelectedSvg, runtime.isDesktop, selectedFile?.path, t]);
const renderDialogs = () => ( const renderDialogs = () => (
<Dialog open={!!activeDialog} onOpenChange={(open) => !open && setActiveDialog(null)}> <Dialog open={!!activeDialog} onOpenChange={(open) => !open && setActiveDialog(null)}>
+9 -9
View File
@@ -785,7 +785,7 @@ export const GitView: React.FC = () => {
} }
} }
}, },
[currentDirectory, git, fetchStatus, fetchBranches] [currentDirectory, git, fetchStatus, fetchBranches, t]
); );
const refreshLog = React.useCallback(async () => { const refreshLog = React.useCallback(async () => {
@@ -972,7 +972,7 @@ export const GitView: React.FC = () => {
} finally { } finally {
setRemovingRemoteName(null); setRemovingRemoteName(null);
} }
}, [currentDirectory, git, refreshRemotes, refreshStatusAndBranches]); }, [currentDirectory, git, refreshRemotes, refreshStatusAndBranches, t]);
const handleCommit = async (options: { pushAfter?: boolean } = {}) => { const handleCommit = async (options: { pushAfter?: boolean } = {}) => {
if (!currentDirectory) return; if (!currentDirectory) return;
@@ -1066,7 +1066,7 @@ export const GitView: React.FC = () => {
} finally { } finally {
setIsGeneratingMessage(false); setIsGeneratingMessage(false);
} }
}, [currentDirectory, selectedPaths, settingsGitmojiEnabled, gitmojiEmojis, scrollActionPanelToBottom]); }, [currentDirectory, selectedPaths, settingsGitmojiEnabled, gitmojiEmojis, scrollActionPanelToBottom, t]);
const formatBlockingReason = (reason: ReturnType<typeof getMutationBlockingReasons>[number]): string => { const formatBlockingReason = (reason: ReturnType<typeof getMutationBlockingReasons>[number]): string => {
if (reason.reason === 'attention') { if (reason.reason === 'attention') {
@@ -1517,7 +1517,7 @@ export const GitView: React.FC = () => {
}); });
} }
}, },
[currentDirectory, refreshStatusAndBranches, git] [currentDirectory, refreshStatusAndBranches, git, t]
); );
const handleRevertAll = React.useCallback( const handleRevertAll = React.useCallback(
@@ -1575,7 +1575,7 @@ export const GitView: React.FC = () => {
setIsRevertingAll(false); setIsRevertingAll(false);
} }
}, },
[currentDirectory, git, isRevertingAll, refreshStatusAndBranches] [currentDirectory, git, isRevertingAll, refreshStatusAndBranches, t]
); );
const handleInsertHighlights = React.useCallback((sourceHighlights: string[]) => { const handleInsertHighlights = React.useCallback((sourceHighlights: string[]) => {
@@ -1787,7 +1787,7 @@ export const GitView: React.FC = () => {
const message = err instanceof Error ? err.message : `Failed to abort ${conflictOperation}`; const message = err instanceof Error ? err.message : `Failed to abort ${conflictOperation}`;
toast.error(message); toast.error(message);
} }
}, [currentDirectory, git, conflictOperation, refreshStatusAndBranches, refreshLog, clearConflictState]); }, [currentDirectory, git, conflictOperation, refreshStatusAndBranches, refreshLog, clearConflictState, t]);
// Check if there are unresolved conflicts (files with 'U' status) // Check if there are unresolved conflicts (files with 'U' status)
const hasUnresolvedConflicts = React.useMemo(() => { const hasUnresolvedConflicts = React.useMemo(() => {
@@ -1839,7 +1839,7 @@ export const GitView: React.FC = () => {
const message = err instanceof Error ? err.message : t('gitView.toast.continueOperationFailed'); const message = err instanceof Error ? err.message : t('gitView.toast.continueOperationFailed');
toast.error(message); toast.error(message);
} }
}, [currentDirectory, git, status, refreshStatusAndBranches, refreshLog, persistConflictState, clearConflictState]); }, [currentDirectory, git, status, refreshStatusAndBranches, refreshLog, persistConflictState, clearConflictState, t]);
const handleAbortOperation = React.useCallback(async () => { const handleAbortOperation = React.useCallback(async () => {
if (!currentDirectory) return; if (!currentDirectory) return;
@@ -1860,7 +1860,7 @@ export const GitView: React.FC = () => {
const message = err instanceof Error ? err.message : t('gitView.toast.abortOperationFailed'); const message = err instanceof Error ? err.message : t('gitView.toast.abortOperationFailed');
toast.error(message); toast.error(message);
} }
}, [currentDirectory, git, status, refreshStatusAndBranches, refreshLog, clearConflictState]); }, [currentDirectory, git, status, refreshStatusAndBranches, refreshLog, clearConflictState, t]);
const handleResolveWithAIFromBanner = React.useCallback(() => { const handleResolveWithAIFromBanner = React.useCallback(() => {
if (!currentDirectory) return; if (!currentDirectory) return;
@@ -1958,7 +1958,7 @@ export const GitView: React.FC = () => {
throw err; throw err;
} }
}, },
[currentDirectory, git, status, stashDialogOperation, stashDialogBranch, refreshStatusAndBranches, refreshLog] [currentDirectory, git, status, stashDialogOperation, stashDialogBranch, refreshStatusAndBranches, refreshLog, t]
); );
if (!currentDirectory) { if (!currentDirectory) {
@@ -429,6 +429,7 @@ export const ChangesSection: React.FC<ChangesSectionProps> = ({
revertingPaths, revertingPaths,
rowPaddingClassName, rowPaddingClassName,
selectedPaths, selectedPaths,
t,
toggleDirectoryExpanded, toggleDirectoryExpanded,
toggleDirectorySelection, toggleDirectorySelection,
]); ]);
@@ -66,7 +66,7 @@ export const ConflictDialog: React.FC<ConflictDialogProps> = ({
.finally(() => { .finally(() => {
setIsLoading(false); setIsLoading(false);
}); });
}, [open, directory]); }, [open, directory, t]);
const buildConflictContext = React.useCallback(async (): Promise<{ const buildConflictContext = React.useCallback(async (): Promise<{
visibleText: string; visibleText: string;
@@ -252,7 +252,7 @@ export const IntegrateCommitsSection: React.FC<{
{ text: context.payloadText, synthetic: true }, { text: context.payloadText, synthetic: true },
]); ]);
setActiveMainTab('chat'); setActiveMainTab('chat');
}, [currentSessionId, setActiveMainTab, buildConflictContext, openNewSessionDraft, setPendingInputText, setPendingSyntheticParts]); }, [currentSessionId, setActiveMainTab, buildConflictContext, openNewSessionDraft, setPendingInputText, setPendingSyntheticParts, t]);
const handleMove = React.useCallback(async () => { const handleMove = React.useCallback(async () => {
if (ui.kind !== 'ready') return; if (ui.kind !== 'ready') return;
@@ -288,7 +288,7 @@ export const IntegrateCommitsSection: React.FC<{
if (next) setUi({ kind: 'ready', plan: next }); if (next) setUi({ kind: 'ready', plan: next });
else setUi({ kind: 'idle' }); else setUi({ kind: 'idle' });
} }
}, [ui, onRefresh, repoRoot, sourceBranch, targetBranch, conflictStorageKey]); }, [ui, onRefresh, repoRoot, sourceBranch, targetBranch, conflictStorageKey, t]);
const handleAbort = React.useCallback(async () => { const handleAbort = React.useCallback(async () => {
if (ui.kind !== 'conflict') return; if (ui.kind !== 'conflict') return;
@@ -303,7 +303,7 @@ export const IntegrateCommitsSection: React.FC<{
if (next) setUi({ kind: 'ready', plan: next }); if (next) setUi({ kind: 'ready', plan: next });
else setUi({ kind: 'idle' }); else setUi({ kind: 'idle' });
} }
}, [ui, repoRoot, sourceBranch, targetBranch, conflictStorageKey]); }, [ui, repoRoot, sourceBranch, targetBranch, conflictStorageKey, t]);
const handleContinue = React.useCallback(async () => { const handleContinue = React.useCallback(async () => {
if (ui.kind !== 'conflict') return; if (ui.kind !== 'conflict') return;
@@ -330,7 +330,7 @@ export const IntegrateCommitsSection: React.FC<{
const message = e instanceof Error ? e.message : String(e); const message = e instanceof Error ? e.message : String(e);
toast.error(t('gitView.integrate.cherryPickContinueFailedToast'), { description: message }); toast.error(t('gitView.integrate.cherryPickContinueFailedToast'), { description: message });
} }
}, [ui, repoRoot, sourceBranch, targetBranch, onRefresh, conflictStorageKey]); }, [ui, repoRoot, sourceBranch, targetBranch, onRefresh, conflictStorageKey, t]);
if (!repoRoot || !sourceBranch) { if (!repoRoot || !sourceBranch) {
return null; return null;
@@ -538,7 +538,7 @@ export const PullRequestSection: React.FC<{
} finally { } finally {
setIsLoadingCheckDetails(false); setIsLoadingCheckDetails(false);
} }
}, [directory, github, pr]); }, [directory, github, pr, t]);
const openCommentsDialog = React.useCallback(async () => { const openCommentsDialog = React.useCallback(async () => {
if (!github?.prContext) { if (!github?.prContext) {
@@ -561,7 +561,7 @@ export const PullRequestSection: React.FC<{
} finally { } finally {
setIsLoadingCommentsDetails(false); setIsLoadingCommentsDetails(false);
} }
}, [directory, github, pr]); }, [directory, github, pr, t]);
const formatTimestamp = React.useCallback((value?: string) => { const formatTimestamp = React.useCallback((value?: string) => {
if (!value) return ''; if (!value) return '';
@@ -650,7 +650,7 @@ export const PullRequestSection: React.FC<{
currentAgentName: currentAgentName ?? null, currentAgentName: currentAgentName ?? null,
currentVariant: currentVariant ?? null, currentVariant: currentVariant ?? null,
}; };
}, [currentSessionId]); }, [currentSessionId, t]);
const dispatchSyntheticPrompt = React.useCallback(( const dispatchSyntheticPrompt = React.useCallback((
target: ChatDispatchTarget, target: ChatDispatchTarget,
@@ -674,7 +674,7 @@ export const PullRequestSection: React.FC<{
const message = e instanceof Error ? e.message : String(e); const message = e instanceof Error ? e.message : String(e);
toast.error(t('gitView.pr.toast.sendMessageFailed'), { description: message }); toast.error(t('gitView.pr.toast.sendMessageFailed'), { description: message });
}); });
}, []); }, [t]);
const renderCheckRunSummary = React.useCallback((run: GitHubCheckRun) => { const renderCheckRunSummary = React.useCallback((run: GitHubCheckRun) => {
const status = run.status || 'unknown'; const status = run.status || 'unknown';
@@ -804,7 +804,7 @@ export const PullRequestSection: React.FC<{
) : null} ) : null}
</div> </div>
); );
}, [expandedCheckStepKeys, formatTimestamp]); }, [expandedCheckStepKeys, formatTimestamp, t]);
const sendFailedChecksToChat = React.useCallback(async () => { const sendFailedChecksToChat = React.useCallback(async () => {
setActiveMainTab('chat'); setActiveMainTab('chat');
@@ -860,7 +860,7 @@ export const PullRequestSection: React.FC<{
const message = e instanceof Error ? e.message : String(e); const message = e instanceof Error ? e.message : String(e);
toast.error(t('gitView.pr.toast.loadChecksFailed'), { description: message }); toast.error(t('gitView.pr.toast.loadChecksFailed'), { description: message });
} }
}, [directory, dispatchSyntheticPrompt, github, pr, resolveChatDispatchTarget, setActiveMainTab]); }, [directory, dispatchSyntheticPrompt, github, pr, resolveChatDispatchTarget, setActiveMainTab, t]);
const sendCommentsToChat = React.useCallback(async () => { const sendCommentsToChat = React.useCallback(async () => {
setActiveMainTab('chat'); setActiveMainTab('chat');
@@ -899,7 +899,7 @@ export const PullRequestSection: React.FC<{
const message = e instanceof Error ? e.message : String(e); const message = e instanceof Error ? e.message : String(e);
toast.error(t('gitView.pr.toast.loadPrCommentsFailed'), { description: message }); toast.error(t('gitView.pr.toast.loadPrCommentsFailed'), { description: message });
} }
}, [directory, dispatchSyntheticPrompt, github, pr, resolveChatDispatchTarget, setActiveMainTab]); }, [directory, dispatchSyntheticPrompt, github, pr, resolveChatDispatchTarget, setActiveMainTab, t]);
const sendSingleCommentToChat = React.useCallback(async (comment: TimelineCommentItem) => { const sendSingleCommentToChat = React.useCallback(async (comment: TimelineCommentItem) => {
setCommentsDialogOpen(false); setCommentsDialogOpen(false);
@@ -1142,7 +1142,7 @@ export const PullRequestSection: React.FC<{
} finally { } finally {
setIsGenerating(false); setIsGenerating(false);
} }
}, [additionalContext, branch, directory, isGenerating, onGeneratedDescription, targetBaseBranch]); }, [additionalContext, branch, directory, isGenerating, onGeneratedDescription, targetBaseBranch, t]);
const createPr = React.useCallback(async () => { const createPr = React.useCallback(async () => {
if (!github?.prCreate) { if (!github?.prCreate) {
@@ -1188,7 +1188,7 @@ export const PullRequestSection: React.FC<{
} finally { } finally {
setIsCreating(false); setIsCreating(false);
} }
}, [body, branch, directory, draft, github, prStatusKey, refresh, scheduleActionRefresh, selectedRemote, targetBaseBranch, title, updatePrStatus]); }, [body, branch, directory, draft, github, prStatusKey, refresh, scheduleActionRefresh, selectedRemote, targetBaseBranch, title, updatePrStatus, t]);
const mergePr = React.useCallback(async (pr: GitHubPullRequest) => { const mergePr = React.useCallback(async (pr: GitHubPullRequest) => {
if (!github?.prMerge) { if (!github?.prMerge) {
@@ -1214,7 +1214,7 @@ export const PullRequestSection: React.FC<{
} finally { } finally {
setIsMerging(false); setIsMerging(false);
} }
}, [directory, github, mergeMethod, refresh, scheduleActionRefresh]); }, [directory, github, mergeMethod, refresh, scheduleActionRefresh, t]);
const markReady = React.useCallback(async (pr: GitHubPullRequest) => { const markReady = React.useCallback(async (pr: GitHubPullRequest) => {
if (!github?.prReady) { if (!github?.prReady) {
@@ -1236,7 +1236,7 @@ export const PullRequestSection: React.FC<{
} finally { } finally {
setIsMarkingReady(false); setIsMarkingReady(false);
} }
}, [directory, github, refresh, scheduleActionRefresh]); }, [directory, github, refresh, scheduleActionRefresh, t]);
const updatePr = React.useCallback(async (pr: GitHubPullRequest) => { const updatePr = React.useCallback(async (pr: GitHubPullRequest) => {
if (!github?.prUpdate) { if (!github?.prUpdate) {
@@ -1277,7 +1277,7 @@ export const PullRequestSection: React.FC<{
} finally { } finally {
setIsUpdating(false); setIsUpdating(false);
} }
}, [directory, editBody, editTitle, github, prStatusKey, refresh, scheduleActionRefresh, updatePrStatus]); }, [directory, editBody, editTitle, github, prStatusKey, refresh, scheduleActionRefresh, updatePrStatus, t]);
if (!canShow) { if (!canShow) {
return null; return null;