Skip to content

fix: clear conflict UI after resolve commit - #240

Open
Ziinc wants to merge 3 commits into
mainfrom
cursor/conflict-clear-after-commit-322b
Open

fix: clear conflict UI after resolve commit#240
Ziinc wants to merge 3 commits into
mainfrom
cursor/conflict-clear-after-commit-322b

Conversation

@Ziinc

@Ziinc Ziinc commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Conflicts could linger in the Review tab Conflicts section and workspace sidebar after a commit that resolved them. The UI copied conflicted_files into local state (one render behind status refetches) and useFileLoading unioned that stale hint with a fresh empty getWorkspaceDiff result.

Changes

  • Derive conflictedFiles directly from the workspace-status query
  • Treat diff.conflicted_files as authoritative in Review file loading
  • Await status/sidebar refetches before reloading Review files after commit
  • Rust tests: merge, rebase, and committed-tip resolve+commit clear status + sidebar
  • App QA narrowed to the two permutations that failed on the first screenshot run:
    • line-delete vs modify (replacement for the broken delete/modify setup)
    • sequential second conflict after the first was resolved

App QA

2/2 narrowed permutations passed. Before: Conflicts section + sidebar indicator. After: both gone + “Commit created” toast.
Line-delete conflict cleared after commit
Sequential second resolve cleared

Test plan

  • cargo test --manifest-path src-tauri/Cargo.toml --test core_changes_test test_resolve_and_commit_clears
  • vitest screenshot spec conflict-clear-after-resolve-commit.spec.tsx (2 failing permutations only)
  • npm run format && npm run lint && npm run check
    To show artifacts inline, enable in settings.
Open in Web Open in Cursor 

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stopped unioning the status hint with diff.conflicted_files.

  • After resolve+commit, getWorkspaceDiff already returns [], but the hint could still hold pre-resolve paths for a frame and re-inject synthetic committed conflict rows:
    const fromDiff = diff.conflicted_files ?? [];
    // Diff is authoritative for live conflict state. The status hint can
    // lag a frame behind resolve+commit; never re-introduce paths a fresh
    // diff reports as resolved.
    const conflictedHint = new Set<string>(fromDiff);
  • diff.conflicted_files remains enough for the “Committed hidden but conflicted tip still visible” case — that list is populated even when committed rows are filtered.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Await status/sidebar refetches before reloading Review files after commit.

  • invalidateQueries alone raced loadChangedFiles() against a still-stale hint; refetchQueries makes the clear land in the same turn as the resolve commit:
    // Await status refetches before reloading Review files so conflict
    // hints / sidebar indicators clear in the same turn as the commit
    // (resolve+commit must not leave a stale Conflicts section).
    await Promise.all([
    queryClient.refetchQueries({
    queryKey: ["workspace-status", repoPath, workspaceId ?? null],
    }),
    queryClient.refetchQueries({
    queryKey: ["workspace-statuses", repoPath],
    }),
    queryClient.invalidateQueries({
    queryKey: ["workspace-commits", repoPath, workspaceId ?? null],
    }),
    invalidateReviewChangeCount(queryClient, repoPath, workspaceId),
    ]);
    await Promise.all([loadChangedFiles(), refreshCommittedChanges()]);
    return true;
    } catch (error) {
    addToast({
    title: "Commit failed",
  • Sidebar conflict indicators depend on workspace-statuses, so that key is refetched here too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Derived conflictedFiles from the live workspace-status query instead of mirroring into useState.

  • The old useEffect copy lagged one render behind resolve+commit refetches, so Review’s Conflicts section and the Code-tab alert could stay visible after the backend had already cleared them:
    const seen = new Set<string>();
    const normalized: string[] = [];
    for (const path of conflictedFiles) {
    const trimmed = path.trim();
    if (!trimmed || seen.has(trimmed)) continue;
    seen.add(trimmed);
    normalized.push(trimmed);
    }
    return normalized;
    }, [conflictedFiles]);
    const conflictCount = normalizedConflictedFiles.length;
    // Review badge: unique working-copy + committed files, independent of
    // whether the Review tab (ChangesDiffViewer) is mounted. Mounting Review
    // must not change this number.
    const includeCommittedInReviewCount =
    Boolean(workspace) && workspace!.branch_name !== defaultTargetBranch;
    const { data: reviewChangeCount = 0 } = useQuery({
    queryKey: [
    ...reviewChangeCountQueryKey(effectiveRepoPath, workspace?.id ?? null),
    includeCommittedInReviewCount,
    ],
    enabled: Boolean(effectiveRepoPath),
    queryFn: async () => {
    if (includeCommittedInReviewCount && workspace?.id !== undefined) {
    const diff = await getWorkspaceDiff(effectiveRepoPath, workspace.id);
  • Deriving from query data also clears the stale-state window on workspace switch (same root cause as the old “don’t reset conflictedFiles on id change” gap).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

App-qa coverage for 12 conflict-origin permutations of “resolve + commit clears Review Conflicts + sidebar indicator”.

  • Includes merge (newCommitWithParents), rebase, add/add, multi-file, nested path, committed-tip, line-delete/modify, remote sync, stacked child, keep-workspace/keep-main content, and sequential second conflict
  • Each case asserts DOM clear then captures before/after PNGs for visual QA

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Backend regression coverage for the same clear contract the UI depends on.

  • Merge, rebase (add/add), and committed-tip-only conflicts all assert workspace_status + list_workspace_statuses flip to has_conflicts=false after write-resolved-content + commit_workspace
  • Locks the jj-lib MergedTree::conflicts() source of truth against sticky sidebar/status bits after a fixing commit

cursoragent and others added 3 commits August 12, 2026 05:41
Derive conflicted files from workspace-status query data and treat
getWorkspaceDiff conflicted_files as authoritative so Review / sidebar
conflict indicators do not linger after a fixing commit. Cover merge,
rebase, multi-file, remote-sync, and related permutations with Rust and
app-qa specs.

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>
Fix delete/modify and sequential remount setups, format the screenshot
spec, and drop an unused import after the full 12-permutation suite passed.

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>
Drop the 10 permutations that passed on the first screenshot run. Retain
line-delete/modify (replacement for the broken delete/modify setup) and
sequential resolve — the only cases that failed initially.

Co-authored-by: Ziinc <Ziinc@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/conflict-clear-after-commit-322b branch from d670d9c to b426ddd Compare August 12, 2026 05:42
@Ziinc
Ziinc marked this pull request as ready for review August 12, 2026 08:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants