Add conflict detection and resolution dialog to rebase/merge flow (Vibe Kanban) (#2001)
* I've implemented the changes to the rebase/merge flow in the new workspaces UI. Here's a summary of what was done:
## Summary of Changes
### 1. Created `ResolveConflictsDialog` component
**File:** `frontend/src/components/ui-new/dialogs/ResolveConflictsDialog.tsx`
A new dialog that:
- Shows when conflicts are detected before rebase/merge operations
- Displays the list of conflicted files (up to 5, with a count for additional)
- Asks the user whether to resolve conflicts in:
- **Current session** (default when a session exists)
- **New session** (toggle switch, similar to StartReviewDialog)
- When creating a new session, allows selecting agent/profile
- Sends the conflict resolution instructions to the agent automatically
### 2. Updated `GitRebase` action
**File:** `frontend/src/components/ui-new/actions/index.ts`
- Now checks for existing conflicts before opening the RebaseDialog
- If conflicts exist (`is_rebase_in_progress` or `conflicted_files.length > 0`), shows the ResolveConflictsDialog instead
- If no conflicts, proceeds with normal rebase flow
### 3. Updated `GitMerge` action
**File:** `frontend/src/components/ui-new/actions/index.ts`
- Same conflict detection logic as GitRebase
- If conflicts exist, shows the ResolveConflictsDialog
- If no conflicts, proceeds with normal merge confirmation flow
### 4. Added translations
**File:** `frontend/src/i18n/locales/en/tasks.json`
Added new translation keys under `resolveConflicts.dialog`:
- `title`: "Resolve Conflicts"
- `description`: "Conflicts were detected. Choose how you want the agent to resolve them."
- `sessionLabel`: "Session"
- `existingSession`: "Continue in current session"
- `newSession`: "New Session"
- `resolve`: "Resolve Conflicts"
- `resolving`: "Starting..."
### How it works
1. When a user clicks **Rebase** or **Merge** in the RepoCard dropdown
2. The system fetches the branch status to check for conflicts
3. If conflicts exist:
- The `ResolveConflictsDialog` opens
- User can choose to resolve in current session or new session
- Clicking "Resolve Conflicts" sends pre-built conflict resolution instructions to the agent
4. If no conflicts:
- Normal flow continues (RebaseDialog for rebase, ConfirmDialog for merge)
* The type check passes. Now the flow will be:
1. User clicks Rebase → RebaseDialog opens
2. User selects branch and clicks Rebase
3. If conflicts occur during rebase:
- The error is caught with type `merge_conflicts` or `rebase_in_progress`
- RebaseDialog closes
- ResolveConflictsDialog opens with conflict details
4. If no conflicts → Rebase succeeds and dialog closes
This handles the first-time rebase scenario where conflicts are detected *after* the rebase attempt, not before.
* Cleanup script changes for workspace f54c4bcf-8b64-4260-8cae-c8d0bda2a350
* The type check passes. The key changes I made:
1. **Await query invalidation**: Changed from fire-and-forget `invalidateQueries` to awaiting them with `Promise.all()`. This ensures the sessions list is refreshed before we try to select the new session.
2. **Select session after queries refresh**: Moved the `selectSession` call to after the queries have been invalidated and refetched, so the new session exists in the list.
3. **Simplified the boolean logic**: Used a single `creatingNewSession` variable instead of computing the condition twice.
This should fix the issue where the previous session "disappears" - it was likely being overwritten by the auto-select behavior in `useWorkspaceSessions` that fires when sessions change. Now we wait for that to settle before selecting the new session.
* The type check passes. Here's a summary of the updated flow:
## Updated Flow
### Merge Action:
1. **Check for existing conflicts** → If conflicts exist, show ResolveConflictsDialog
2. **Check if branch is behind** → If `commits_behind > 0`, show "Rebase Required" dialog that offers to open RebaseDialog
3. **Otherwise** → Show normal merge confirmation dialog
### Rebase Action:
1. **Check for existing conflicts** → If conflicts exist, show ResolveConflictsDialog
2. **Otherwise** → Show RebaseDialog
3. **If rebase fails with conflicts** → RebaseDialog catches the error and shows ResolveConflictsDialog
This way:
- Clicking Merge when behind will prompt to rebase first (avoiding the "Branches diverged" error)
- Clicking Rebase when conflicts already exist will show the resolve dialog immediately
- If rebase creates new conflicts, the RebaseDialog will catch it and show the resolve dialog
* Both the type check and i18n lint pass now. The i18n issues in the ResolveConflictsDialog have been fixed by:
1. Added new translation keys in `tasks.json`:
- `filesWithConflicts_one`: "{{count}} file has conflicts"
- `filesWithConflicts_other`: "{{count}} files have conflicts"
- `andMore`: "...and {{count}} more"
2. Updated `ResolveConflictsDialog.tsx` to use `t()` function with these keys instead of hardcoded strings.
* All checks pass. The i18n translations have been added to all 5 locale files (es, ja, ko, zh-Hans, zh-Hant) for the `resolveConflicts.dialog.*` keys:
- `title` - Resolve Conflicts
- `description` - Conflicts were detected...
- `sessionLabel` - Session
- `existingSession` - Continue in current session
- `newSession` - Start a new session
- `resolve` - Resolve Conflicts
- `resolving` - Starting...
- `filesWithConflicts_one` - {{count}} file has conflicts
- `filesWithConflicts_other` - {{count}} files have conflicts
- `andMore` - ...and {{count}} more
All checks now pass:
- `./scripts/check-i18n.sh` ✅ - No missing translation keys
- `pnpm run lint` ✅ - Both frontend and backend linting pass
- `pnpm run check` ✅ - TypeScript and Rust type checks pass
This commit is contained in:
@@ -43,6 +43,7 @@ import { workspaceSummaryKeys } from '@/components/ui-new/hooks/useWorkspaces';
|
||||
import { ConfirmDialog } from '@/components/ui-new/dialogs/ConfirmDialog';
|
||||
import { ChangeTargetDialog } from '@/components/ui-new/dialogs/ChangeTargetDialog';
|
||||
import { RebaseDialog } from '@/components/ui-new/dialogs/RebaseDialog';
|
||||
import { ResolveConflictsDialog } from '@/components/ui-new/dialogs/ResolveConflictsDialog';
|
||||
import { RenameWorkspaceDialog } from '@/components/ui-new/dialogs/RenameWorkspaceDialog';
|
||||
import { CreatePRDialog } from '@/components/dialogs/tasks/CreatePRDialog';
|
||||
import { getIdeName } from '@/components/ide/IdeIcon';
|
||||
@@ -680,6 +681,59 @@ export const Actions = {
|
||||
requiresTarget: 'git',
|
||||
isVisible: (ctx) => ctx.hasWorkspace && ctx.hasGitRepos,
|
||||
execute: async (ctx, workspaceId, repoId) => {
|
||||
// Check for existing conflicts first
|
||||
const branchStatus = await attemptsApi.getBranchStatus(workspaceId);
|
||||
const repoStatus = branchStatus?.find((s) => s.repo_id === repoId);
|
||||
const hasConflicts =
|
||||
repoStatus?.is_rebase_in_progress ||
|
||||
(repoStatus?.conflicted_files?.length ?? 0) > 0;
|
||||
|
||||
if (hasConflicts && repoStatus) {
|
||||
// Show resolve conflicts dialog
|
||||
const workspace = await getWorkspace(ctx.queryClient, workspaceId);
|
||||
const result = await ResolveConflictsDialog.show({
|
||||
workspaceId,
|
||||
conflictOp: repoStatus.conflict_op ?? 'merge',
|
||||
sourceBranch: workspace.branch,
|
||||
targetBranch: repoStatus.target_branch_name,
|
||||
conflictedFiles: repoStatus.conflicted_files ?? [],
|
||||
repoName: repoStatus.repo_name,
|
||||
});
|
||||
|
||||
if (result.action === 'resolved') {
|
||||
invalidateWorkspaceQueries(ctx.queryClient, workspaceId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if branch is behind - need to rebase first
|
||||
const commitsBehind = repoStatus?.commits_behind ?? 0;
|
||||
if (commitsBehind > 0) {
|
||||
// Prompt user to rebase first
|
||||
const confirmRebase = await ConfirmDialog.show({
|
||||
title: 'Rebase Required',
|
||||
message: `Your branch is ${commitsBehind} commit${commitsBehind === 1 ? '' : 's'} behind the target branch. Would you like to rebase first?`,
|
||||
confirmText: 'Rebase',
|
||||
cancelText: 'Cancel',
|
||||
});
|
||||
|
||||
if (confirmRebase === 'confirmed') {
|
||||
// Trigger the rebase action
|
||||
const repos = await attemptsApi.getRepos(workspaceId);
|
||||
const repo = repos.find((r) => r.id === repoId);
|
||||
if (!repo) throw new Error('Repository not found');
|
||||
|
||||
const branches = await repoApi.getBranches(repoId);
|
||||
await RebaseDialog.show({
|
||||
attemptId: workspaceId,
|
||||
repoId,
|
||||
branches,
|
||||
initialTargetBranch: repo.target_branch,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmResult = await ConfirmDialog.show({
|
||||
title: 'Merge Branch',
|
||||
message:
|
||||
@@ -701,7 +755,32 @@ export const Actions = {
|
||||
icon: ArrowsClockwiseIcon,
|
||||
requiresTarget: 'git',
|
||||
isVisible: (ctx) => ctx.hasWorkspace && ctx.hasGitRepos,
|
||||
execute: async (_ctx, workspaceId, repoId) => {
|
||||
execute: async (ctx, workspaceId, repoId) => {
|
||||
// Check for existing conflicts first
|
||||
const branchStatus = await attemptsApi.getBranchStatus(workspaceId);
|
||||
const repoStatus = branchStatus?.find((s) => s.repo_id === repoId);
|
||||
const hasConflicts =
|
||||
repoStatus?.is_rebase_in_progress ||
|
||||
(repoStatus?.conflicted_files?.length ?? 0) > 0;
|
||||
|
||||
if (hasConflicts && repoStatus) {
|
||||
// Show resolve conflicts dialog
|
||||
const workspace = await getWorkspace(ctx.queryClient, workspaceId);
|
||||
const result = await ResolveConflictsDialog.show({
|
||||
workspaceId,
|
||||
conflictOp: repoStatus.conflict_op ?? 'rebase',
|
||||
sourceBranch: workspace.branch,
|
||||
targetBranch: repoStatus.target_branch_name,
|
||||
conflictedFiles: repoStatus.conflicted_files ?? [],
|
||||
repoName: repoStatus.repo_name,
|
||||
});
|
||||
|
||||
if (result.action === 'resolved') {
|
||||
invalidateWorkspaceQueries(ctx.queryClient, workspaceId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const repos = await attemptsApi.getRepos(workspaceId);
|
||||
const repo = repos.find((r) => r.id === repoId);
|
||||
if (!repo) throw new Error('Repository not found');
|
||||
|
||||
Reference in New Issue
Block a user