fix: Reconcile orphaned changesets when phases are manually deleted
Manually deleting phases left their parent changeset as "applied", causing the Plan tab to show a stale "Created N phases" banner with no phases visible. - deletePhase now checks if all phases from a changeset are gone and marks it reverted - PlanSection filters out dismissed agents so dismissed banners stay hidden - revertChangeSet marks reverted before entity deletion to prevent ghost state on partial failure - deletePhase invalidation now includes listChangeSets
This commit is contained in:
@@ -91,6 +91,9 @@ export function changeSetProcedures(publicProcedure: ProcedureBuilder) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mark reverted FIRST to avoid ghost state if entity deletion fails partway
|
||||
await repo.markReverted(input.id);
|
||||
|
||||
// Apply reverts in reverse entry order
|
||||
const reversedEntries = [...cs.entries].reverse();
|
||||
for (const entry of reversedEntries) {
|
||||
@@ -159,8 +162,6 @@ export function changeSetProcedures(publicProcedure: ProcedureBuilder) {
|
||||
}
|
||||
}
|
||||
|
||||
await repo.markReverted(input.id);
|
||||
|
||||
ctx.eventBus.emit({
|
||||
type: 'changeset:reverted' as const,
|
||||
timestamp: new Date(),
|
||||
|
||||
@@ -6,7 +6,7 @@ import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
import type { Phase } from '../../db/schema.js';
|
||||
import type { ProcedureBuilder } from '../trpc.js';
|
||||
import { requirePhaseRepository, requireTaskRepository, requireBranchManager, requireInitiativeRepository, requireProjectRepository, requireExecutionOrchestrator, requireReviewCommentRepository } from './_helpers.js';
|
||||
import { requirePhaseRepository, requireTaskRepository, requireBranchManager, requireInitiativeRepository, requireProjectRepository, requireExecutionOrchestrator, requireReviewCommentRepository, requireChangeSetRepository } from './_helpers.js';
|
||||
import { phaseBranchName } from '../../git/branch-naming.js';
|
||||
import { ensureProjectClone } from '../../git/project-clones.js';
|
||||
|
||||
@@ -98,6 +98,29 @@ export function phaseProcedures(publicProcedure: ProcedureBuilder) {
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const repo = requirePhaseRepository(ctx);
|
||||
await repo.delete(input.id);
|
||||
|
||||
// Reconcile any applied changesets that created this phase.
|
||||
// If all created phases in a changeset are now deleted, mark it reverted.
|
||||
if (ctx.changeSetRepository) {
|
||||
try {
|
||||
const csRepo = requireChangeSetRepository(ctx);
|
||||
const affectedChangeSets = await csRepo.findAppliedByCreatedEntity('phase', input.id);
|
||||
for (const cs of affectedChangeSets) {
|
||||
const createdPhaseIds = cs.entries
|
||||
.filter(e => e.entityType === 'phase' && e.action === 'create')
|
||||
.map(e => e.entityId);
|
||||
const survivingPhases = await Promise.all(
|
||||
createdPhaseIds.map(id => repo.findById(id)),
|
||||
);
|
||||
if (survivingPhases.every(p => p === null)) {
|
||||
await csRepo.markReverted(cs.id);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Best-effort reconciliation — don't fail the delete
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user