Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/dashboard/src/lib/project-sync-status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,23 @@ describe('getProjectSyncView', () => {
canSync: false,
});
});

it('surfaces a needs-human-merge conflict without suggesting force push', () => {
const view = getProjectSyncView({
configured: true,
available: true,
sync_status: 'needs_human_merge',
block_reason: 'Results branch agentv/results/v1 diverged and could not be auto-merged',
});
expect(view).toMatchObject({
state: 'needs_human_merge',
label: 'Needs human merge',
tone: 'danger',
canSync: false,
});
expect(view.nextAction).not.toMatch(/force/i);
expect(view.nextAction).toMatch(/pull request/i);
});
});

describe('buildProjectSyncFeedback', () => {
Expand Down
19 changes: 16 additions & 3 deletions apps/dashboard/src/lib/project-sync-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type ProjectSyncState =
| 'dirty'
| 'conflicted'
| 'push_conflict'
| 'needs_human_merge'
| 'syncing';

export type ProjectSyncTone = 'neutral' | 'good' | 'info' | 'warn' | 'danger';
Expand Down Expand Up @@ -122,6 +123,20 @@ export function getProjectSyncView(
}

const state = status.sync_status ?? 'clean';
if (state === 'needs_human_merge') {
return {
state: 'needs_human_merge',
label: 'Needs human merge',
actionLabel: 'Sync Project',
tone: 'danger',
summary:
status.block_reason ??
'The results branch diverged and a genuine content conflict could not be auto-merged.',
nextAction:
'The remote branch is unchanged and no history was rewritten. Resolve the conflict with a GitHub pull request, then sync again.',
canSync: false,
};
}
if (state === 'push_conflict') {
return {
state: 'push_conflict',
Expand All @@ -132,9 +147,7 @@ export function getProjectSyncView(
status.block_reason ??
'The remote results branch changed before local results could be pushed.',
nextAction:
status.push_conflict_policy === 'backup_and_force_push'
? 'Sync stopped before changing the results branch. Refresh status, then retry if this server should replace the remote branch.'
: 'Sync stopped before changing the results branch. Opt in to backup_and_force_push only if this server should replace the remote branch.',
'Sync stopped before changing the results branch. Refresh status, then retry — results sync auto-merges concurrent writes and never force-pushes.',
canSync: false,
};
}
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ export interface RemoteStatusResponse {
| 'dirty'
| 'conflicted'
| 'push_conflict'
| 'needs_human_merge'
| 'syncing';
branch?: string;
upstream?: string;
Expand Down
Loading
Loading