fix(dashboard): clear "needs action" on recent advances after manual sync

The Git Manager's recent integration-advances list derived `needsAction`
purely from the original `merge:auto-sync` audit-event outcome, so it kept
showing "(N need action)" after the operator clicked "Sync working tree" or
fixed up the worktree by hand. `collectRecentMergeAdvances` now also checks
whether each advance's `toSha` is reachable from HEAD — if it is, the
worktree already contains that advance and `needsAction` is false
regardless of what the audit trail recorded.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-23 20:49:30 -07:00
parent 6aa1ff0fb5
commit 4f38ed152a
2 changed files with 33 additions and 1 deletions

View File

@@ -0,0 +1,9 @@
---
"@fusion/dashboard": patch
---
fix(dashboard): clear `needs action` on recent integration-branch advances after manual sync
The Git Manager's "Recent integration-branch advances" list derived `needsAction` purely from the original `merge:auto-sync` audit-event outcome. When the operator clicked "Sync working tree" — or fixed up the worktree by hand — the worktree caught up to the integration tip, but the list kept showing "(N need action)" because the historical audit events still recorded the original failure/disabled state.
`collectRecentMergeAdvances` now also checks whether each advance's `toSha` is reachable from the current HEAD. If it is, the worktree already contains that advance and `needsAction` is false regardless of what the audit trail recorded.

View File

@@ -586,6 +586,7 @@ async function collectRecentMergeAdvances(
}) => RunAuditEvent[];
},
worktreePath: string,
headSha: string | undefined,
): Promise<ExtendedGitStatus["recentMergeAdvances"]> {
if (typeof scopedStore.getRunAuditEvents !== "function") return [];
const advances = scopedStore.getRunAuditEvents({
@@ -642,13 +643,34 @@ async function collectRecentMergeAdvances(
const autoSyncOutcome =
autoSyncByAdvance.get(pairKey(tid, md.toSha))
?? autoSyncByTaskFallback.get(tid);
// The worktree may already contain `toSha` — either because auto-sync
// succeeded, the operator manually ran "Sync working tree" / pulled, or
// they checked out a later commit by hand. In all those cases there's
// nothing left to do, regardless of what the original auto-sync audit
// event recorded. Treat reachability from HEAD as authoritative.
let alreadyInHead = false;
if (headSha) {
if (headSha === md.toSha) {
alreadyInHead = true;
} else {
try {
await runGitCommand(["merge-base", "--is-ancestor", md.toSha, headSha], worktreePath, 5_000);
alreadyInHead = true;
} catch {
alreadyInHead = false;
}
}
}
const needsAction = alreadyInHead
? false
: (autoSyncOutcome === undefined || !successOutcomes.has(autoSyncOutcome));
out.push({
taskId: tid,
fromSha: typeof md.fromSha === "string" ? md.fromSha : null,
toSha: md.toSha,
advancedAt: ev.timestamp,
autoSyncOutcome,
needsAction: autoSyncOutcome === undefined || !successOutcomes.has(autoSyncOutcome),
needsAction,
});
if (out.length >= 5) break;
}
@@ -731,6 +753,7 @@ export async function computeExtendedGitStatus(rootDir: string, scopedStore: Tas
getRunAuditEvents?: (filters: { taskId?: string; domain?: "database" | "git" | "filesystem" | "sandbox"; mutationType?: string; limit?: number }) => RunAuditEvent[];
},
rootDir,
headSha,
),
]);