Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8f21613
Add ConflictTone enum and ConflictRow value object to Domain layer
Chris0Jeky Apr 25, 2026
6c56082
Add ProposalConflictDetector service and ConflictRowDto
Chris0Jeky Apr 25, 2026
e116daf
Add GET /api/automation/proposals/{id}/conflicts endpoint
Chris0Jeky Apr 25, 2026
de0ac4d
Add ConflictRow domain tests
Chris0Jeky Apr 25, 2026
923e21c
Add ProposalConflictDetector service tests
Chris0Jeky Apr 25, 2026
f1b407d
Fix positive signal tracking to use ID sets instead of text parsing
Chris0Jeky Apr 25, 2026
e102b41
Fix 5 review findings in ProposalConflictDetector
Chris0Jeky Apr 25, 2026
03f0bb6
Add GetPendingByOperationTargetAsync to IAutomationProposalRepository
Chris0Jeky Apr 25, 2026
43872fb
Implement GetPendingByOperationTargetAsync in AutomationProposalRepos…
Chris0Jeky Apr 25, 2026
b2929d7
Update conflict detector tests for review fixes
Chris0Jeky Apr 25, 2026
6d76ce4
Add GetPendingByOperationTargetAsync stubs to test repository fakes
Chris0Jeky Apr 25, 2026
390b966
Add CountByCardIdAsync to avoid loading full entity graphs for counting
Chris0Jeky Apr 26, 2026
8ef4af8
Normalize line endings to match .gitattributes
Chris0Jeky Apr 26, 2026
12d09c9
Merge origin/main into paper/1022-conflicts
Chris0Jeky Apr 26, 2026
e55fc6b
Merge origin/main into paper/1022-conflicts
Chris0Jeky Apr 26, 2026
d97861d
Address proposal conflict review findings
Chris0Jeky Apr 27, 2026
40dba4f
Align conflict webhook edge cases
Chris0Jeky Apr 27, 2026
c6f6927
Project proposal WIP conflict checks
Chris0Jeky Apr 27, 2026
62f202c
Launch demo director Playwright without shell
Chris0Jeky Apr 27, 2026
1d75ae9
Refine projected WIP conflict detection
Chris0Jeky Apr 27, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class AutomationProposalsController : AuthenticatedControllerBase
private readonly IAutomationExecutorService _executorService;
private readonly ISimilarDecisionService _similarDecisionService;
private readonly BoardAuthorizationService _authorizationService;
private readonly IProposalConflictDetector _conflictDetector;
private readonly ICardHistoryService _cardHistoryService;
private readonly ISideEffectAnalyzer _sideEffectAnalyzer;

Expand All @@ -36,6 +37,7 @@ public AutomationProposalsController(
IAutomationExecutorService executorService,
ISimilarDecisionService similarDecisionService,
BoardAuthorizationService authorizationService,
IProposalConflictDetector conflictDetector,
ICardHistoryService cardHistoryService,
ISideEffectAnalyzer sideEffectAnalyzer,
IUserContext userContext) : base(userContext)
Expand All @@ -44,6 +46,7 @@ public AutomationProposalsController(
_executorService = executorService;
_similarDecisionService = similarDecisionService;
_authorizationService = authorizationService;
_conflictDetector = conflictDetector;
_cardHistoryService = cardHistoryService;
_sideEffectAnalyzer = sideEffectAnalyzer;
}
Expand Down Expand Up @@ -270,6 +273,23 @@ public async Task<IActionResult> DismissProposals(
: result.ToErrorActionResult();
}

/// <summary>
/// Gets tone-classified conflict/warning/status rows for a proposal.
/// </summary>
[HttpGet("{id}/conflicts")]
public async Task<IActionResult> GetProposalConflicts(Guid id, CancellationToken cancellationToken = default)
{
if (!TryGetCurrentUserId(out var callerUserId, out var errorResult))
return errorResult!;

var auth = await AuthorizeProposalAsync(id, callerUserId, requireWriteAccess: false, cancellationToken);
if (auth.ErrorResult is not null)
return auth.ErrorResult;

var result = await _conflictDetector.DetectConflictsAsync(id, callerUserId, cancellationToken);
return result.IsSuccess ? Ok(result.Value) : result.ToErrorActionResult();
}

/// <summary>
/// Gets the card history ledger for a proposal, showing all touches on affected cards.
/// </summary>
Expand Down Expand Up @@ -304,7 +324,6 @@ public async Task<IActionResult> GetProposalSideEffects(Guid id, CancellationTok
var result = await _sideEffectAnalyzer.AnalyzeAsync(id, cancellationToken);
return result.IsSuccess ? Ok(result.Value) : result.ToErrorActionResult();
}

/// <summary>
/// Gets a diff preview for a proposal showing what changes will be made.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection
services.AddScoped<HistoryService>();
services.AddScoped<IHistoryService>(sp => sp.GetRequiredService<HistoryService>());
services.AddScoped<IAutomationProposalService, AutomationProposalService>();
services.AddScoped<IProposalConflictDetector, ProposalConflictDetector>();
services.AddScoped<ICardHistoryService, CardHistoryService>();
services.AddScoped<ISideEffectAnalyzer, SideEffectAnalyzer>();
services.AddScoped<IProposalRevisionService, ProposalRevisionService>();
Expand Down
18 changes: 18 additions & 0 deletions backend/src/Taskdeck.Application/DTOs/ConflictRowDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Taskdeck.Domain.Entities;

namespace Taskdeck.Application.DTOs;

/// <summary>
/// DTO for a single conflict/warning/status row returned by the conflict detector.
/// </summary>
public record ConflictRowDto(
ConflictTone Tone,
string Key,
string Value
)
{
public static ConflictRowDto FromDomain(ConflictRow row)
{
return new ConflictRowDto(row.Tone, row.Key, row.Value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public interface IAutomationProposalRepository : IRepository<AutomationProposal>
string actionType,
ProposalSourceType sourceType,
CancellationToken cancellationToken = default);
Task<IReadOnlyList<AutomationProposal>> GetPendingByOperationTargetAsync(string targetType, string targetId, CancellationToken cancellationToken = default);
Task<IEnumerable<AutomationProposal>> GetExpiredAsync(CancellationToken cancellationToken = default);

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ namespace Taskdeck.Application.Interfaces;
public interface ICardCommentRepository : IRepository<CardComment>
{
Task<IEnumerable<CardComment>> GetByCardIdAsync(Guid cardId, CancellationToken cancellationToken = default);
Task<int> CountByCardIdAsync(Guid cardId, CancellationToken cancellationToken = default);
Task<CardComment?> GetByIdWithMentionsAsync(Guid id, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Taskdeck.Application.DTOs;
using Taskdeck.Domain.Common;

namespace Taskdeck.Application.Services;

/// <summary>
/// Detects conflicts, warnings, and status signals for a proposal.
/// Returns a tone-classified list of rows for the review UI.
/// </summary>
public interface IProposalConflictDetector
{
Task<Result<IReadOnlyList<ConflictRowDto>>> DetectConflictsAsync(
Guid proposalId,
Guid userId,
CancellationToken cancellationToken = default);
}
Loading
Loading