diff --git a/src/renderer/src/components/right-sidebar/SourceControl.tsx b/src/renderer/src/components/right-sidebar/SourceControl.tsx index 11803eee85e..85080732d4c 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.tsx @@ -254,6 +254,7 @@ import { createPrIntentCurrentTargetConflictsWithToken, createPrIntentGitStatusMatchesToken, createPrIntentRunTokenMatches, + getCreatePrIntentCommitFailureNoticeMessage, getCreatePrIntentStagePaths, resolveCreatePrIntentReviewBase, resolveCreatePrIntentRemoteStep, @@ -1027,6 +1028,7 @@ function SourceControlInner(): React.JSX.Element { loadSessionCommitDrafts() ) const commitDraftsRef = useRef(commitDrafts) + const commitErrorsRef = useRef>({}) const [commitErrors, setCommitErrors] = useState>({}) const [remoteActionErrors, setRemoteActionErrors] = useState< Record @@ -1155,6 +1157,13 @@ function SourceControlInner(): React.JSX.Element { }, [] ) + const setCommitErrorForWorktree = useCallback( + (worktreeId: string, message: string | null): void => { + commitErrorsRef.current = { ...commitErrorsRef.current, [worktreeId]: message } + setCommitErrors((prev) => ({ ...prev, [worktreeId]: message })) + }, + [] + ) const isFolder = activeRepo ? isFolderRepo(activeRepo) : false const worktreePath = activeWorktree?.path ?? null @@ -1917,6 +1926,7 @@ function SourceControlInner(): React.JSX.Element { return changed ? next : prev } updateCommitDrafts((prev) => pruneRecord(prev)) + commitErrorsRef.current = pruneRecord(commitErrorsRef.current) setCommitErrors((prev) => pruneRecord(prev)) setRemoteActionErrors((prev) => pruneRecord(prev)) setCommitInFlightByWorktree((prev) => pruneRecord(prev)) @@ -2037,7 +2047,7 @@ function SourceControlInner(): React.JSX.Element { commitInFlightRef.current[target.worktreeId] = true setCommitInFlightByWorktree((prev) => ({ ...prev, [target.worktreeId]: true })) - setCommitErrors((prev) => ({ ...prev, [target.worktreeId]: null })) + setCommitErrorForWorktree(target.worktreeId, null) try { const commitResult = await commitRuntimeGit( { @@ -2050,10 +2060,7 @@ function SourceControlInner(): React.JSX.Element { message ) if (!commitResult.success) { - setCommitErrors((prev) => ({ - ...prev, - [target.worktreeId]: commitResult.error ?? 'Commit failed' - })) + setCommitErrorForWorktree(target.worktreeId, commitResult.error ?? 'Commit failed') return false } @@ -2071,7 +2078,7 @@ function SourceControlInner(): React.JSX.Element { } return writeCommitDraftForWorktree(prev, target.worktreeId, '') }) - setCommitErrors((prev) => ({ ...prev, [target.worktreeId]: null })) + setCommitErrorForWorktree(target.worktreeId, null) if (!options?.target) { void refreshActiveGitStatusAfterMutation() } @@ -2103,10 +2110,10 @@ function SourceControlInner(): React.JSX.Element { } return true } catch (error) { - setCommitErrors((prev) => ({ - ...prev, - [target.worktreeId]: error instanceof Error ? error.message : 'Commit failed' - })) + setCommitErrorForWorktree( + target.worktreeId, + error instanceof Error ? error.message : 'Commit failed' + ) return false } finally { setCommitInFlightByWorktree((prev) => ({ ...prev, [target.worktreeId]: false })) @@ -2122,6 +2129,7 @@ function SourceControlInner(): React.JSX.Element { compareBaseRef, grouped.staged.length, refreshActiveGitStatusAfterMutation, + setCommitErrorForWorktree, updateCommitDrafts, unresolvedConflicts.length, worktreePath @@ -3822,12 +3830,21 @@ function SourceControlInner(): React.JSX.Element { if (abortIfStale()) { return } + const commitFailure = commitErrorsRef.current[token.worktreeId] ?? null setCreatePrIntentNoticeForWorktree(token.worktreeId, { tone: 'destructive', - message: translate( - 'auto.components.right.sidebar.SourceControl.createPrIntentCommitFailed', - 'Could not commit changes. Fix the issue, then retry Create PR.' - ) + message: getCreatePrIntentCommitFailureNoticeMessage(commitFailure, { + fallback: translate( + 'auto.components.right.sidebar.SourceControl.createPrIntentCommitFailed', + 'Could not commit changes. Fix the issue, then retry Create PR.' + ), + withSummary: (summary) => + translate( + 'auto.components.right.sidebar.SourceControl.createPrIntentCommitBlockedSummary', + 'Commit blocked: {{value0}} Fix the issue, then retry Create PR.', + { value0: summary } + ) + }) }) return } diff --git a/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-flow.test.ts b/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-flow.test.ts index 07bff95171e..74853dd4147 100644 --- a/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-flow.test.ts +++ b/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-flow.test.ts @@ -5,6 +5,7 @@ import { createPrIntentCurrentTargetConflictsWithToken, createPrIntentGitStatusMatchesToken, createPrIntentRunTokenMatches, + getCreatePrIntentCommitFailureNoticeMessage, getCreatePrIntentStagePaths, resolveCreatePrIntentReviewBase, resolveCreatePrIntentRemoteStep @@ -241,4 +242,23 @@ describe('source-control Create PR intent flow helpers', () => { }) ).toBe('blocked') }) + + it('surfaces the commit failure summary in the Create PR intent notice', () => { + expect( + getCreatePrIntentCommitFailureNoticeMessage( + 'husky - pre-commit hook\neslint found 2 errors\nfull output' + ) + ).toBe('Commit blocked: Lint failed during commit. Fix the issue, then retry Create PR.') + + expect(getCreatePrIntentCommitFailureNoticeMessage(null)).toBe( + 'Could not commit changes. Fix the issue, then retry Create PR.' + ) + + expect( + getCreatePrIntentCommitFailureNoticeMessage('pre-commit hook failed', { + fallback: 'fallback', + withSummary: (summary) => `localized ${summary}` + }) + ).toBe('localized Pre-commit hook failed.') + }) }) diff --git a/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-flow.ts b/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-flow.ts index 009315fb197..7e183122ec5 100644 --- a/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-flow.ts +++ b/src/renderer/src/components/right-sidebar/source-control-create-pr-intent-flow.ts @@ -5,6 +5,7 @@ import { normalizeHostedReviewHeadRef } from '../../../../shared/hosted-review-refs' import type { GitStatusEntry, GitUpstreamStatus } from '../../../../shared/types' +import { summarizeCommitFailure } from './commit-failure-summary' import { getStageAllPaths } from './discard-all-sequence' export type CreatePrIntentRemoteStep = 'publish' | 'push' | 'force_push' | 'blocked' | 'none' @@ -145,3 +146,23 @@ export function resolveCreatePrIntentRemoteStep({ return 'none' } + +export function getCreatePrIntentCommitFailureNoticeMessage( + commitError: string | null | undefined, + copy: { + fallback: string + withSummary: (summary: string) => string + } = { + fallback: 'Could not commit changes. Fix the issue, then retry Create PR.', + withSummary: (summary: string) => + `Commit blocked: ${summary} Fix the issue, then retry Create PR.` + } +): string { + const summary = commitError ? summarizeCommitFailure(commitError) : null + + if (!summary) { + return copy.fallback + } + + return copy.withSummary(summary) +} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index ca2bbd195a2..0897b9c2929 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -9177,7 +9177,8 @@ "b715ef615b": "{{value0}} commits ahead of {{value1}}", "c1a8f3e204": "1 commit behind {{value0}}", "d2b9g4f315": "{{value0}} commits behind {{value1}}", - "e9c2a5d416": "Even with {{value0}}" + "e9c2a5d416": "Even with {{value0}}", + "createPrIntentCommitBlockedSummary": "Commit blocked: {{value0}} Fix the issue, then retry Create PR." }, "SourceControlAgentActionDialog": { "8e856842d1": "Could not start the selected agent.", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 9d4cdf90b27..f5800a8c709 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -9177,7 +9177,8 @@ "b715ef615b": "{{value0}} commits por delante de {{value1}}", "c1a8f3e204": "1 commit por detrás de {{value0}}", "d2b9g4f315": "{{value0}} commits por detrás de {{value1}}", - "e9c2a5d416": "A la par con {{value0}}" + "e9c2a5d416": "A la par con {{value0}}", + "createPrIntentCommitBlockedSummary": "Commit blocked: {{value0}} Fix the issue, then retry Create PR." }, "SourceControlAgentActionDialog": { "8e856842d1": "No se pudo iniciar el agente seleccionado.", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 775f4b584b3..5ec2d6318a4 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -9177,7 +9177,8 @@ "b715ef615b": "{{value1}}より{{value0}}commits 進んでいます", "c1a8f3e204": "{{value0}}より1commit 遅れています", "d2b9g4f315": "{{value1}}より{{value0}}commits 遅れています", - "e9c2a5d416": "{{value0}}と同じ状態" + "e9c2a5d416": "{{value0}}と同じ状態", + "createPrIntentCommitBlockedSummary": "Commit blocked: {{value0}} Fix the issue, then retry Create PR." }, "SourceControlAgentActionDialog": { "8e856842d1": "選択した agent を開始できませんでした。", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 62132106ac7..92e32723833 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -9177,7 +9177,8 @@ "b715ef615b": "{{value1}}보다 {{value0}} commits 앞서 있음", "c1a8f3e204": "{{value0}}보다 1 commit 뒤처짐", "d2b9g4f315": "{{value1}}보다 {{value0}} commits 뒤처짐", - "e9c2a5d416": "{{value0}}와 동일한 상태" + "e9c2a5d416": "{{value0}}와 동일한 상태", + "createPrIntentCommitBlockedSummary": "Commit blocked: {{value0}} Fix the issue, then retry Create PR." }, "SourceControlAgentActionDialog": { "8e856842d1": "선택한 agent를 시작할 수 없습니다.", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 41d3bbeb76c..9fa432eaa7a 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -9177,7 +9177,8 @@ "b715ef615b": "领先 {{value1}} {{value0}} 个提交", "c1a8f3e204": "落后 {{value0}} 1 个提交", "d2b9g4f315": "落后 {{value1}} {{value0}} 个提交", - "e9c2a5d416": "与 {{value0}} 同步" + "e9c2a5d416": "与 {{value0}} 同步", + "createPrIntentCommitBlockedSummary": "Commit blocked: {{value0}} Fix the issue, then retry Create PR." }, "SourceControlAgentActionDialog": { "8e856842d1": "无法启动选定的智能体。",