Skip to content

Commit a8c3558

Browse files
sharpninjaCopilot
andcommitted
Decompose ICommandTarget into 8 granular service interfaces (P6-4)
Extract INavigationTarget, IRequestDetailsTarget, IPreviewTarget, IArchiveTarget, ISessionDataTarget, IClipboardTarget, IConfigTarget, and IUiDispatchTarget from the fat ICommandTarget interface. ICommandTarget now extends all sub-interfaces as a union type, so existing code that registers ICommandTarget still works. Handlers now depend only on the specific interface(s) they need, following the Interface Segregation Principle. DI registration forwards all granular interfaces to the singleton ICommandTarget instance. Tests updated for multi-parameter handlers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent aa98a9f commit a8c3558

14 files changed

Lines changed: 268 additions & 196 deletions

src/McpServerManager.Core.Tests/Commands/DataLoadingHandlerTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public async Task InitializeFromMcpHandler_HandleAsync_DispatchesAndTracksBackgr
2121
_target.Setup(t => t.DispatchToUi(It.IsAny<Action>()));
2222
_target.Setup(t => t.TrackBackgroundWork(It.IsAny<Task>()));
2323

24-
var handler = new InitializeFromMcpHandler(_target.Object);
24+
var handler = new InitializeFromMcpHandler(_target.Object, _target.Object);
2525
var result = await handler.HandleAsync(new InitializeFromMcpCommand(), _ctx);
2626

2727
result.IsSuccess.Should().BeTrue();
@@ -37,7 +37,7 @@ public async Task RefreshAndLoadAllJsonHandler_HandleAsync_DispatchesAndTracksBa
3737
_target.Setup(t => t.DispatchToUi(It.IsAny<Action>()));
3838
_target.Setup(t => t.TrackBackgroundWork(It.IsAny<Task>()));
3939

40-
var handler = new RefreshAndLoadAllJsonHandler(_target.Object);
40+
var handler = new RefreshAndLoadAllJsonHandler(_target.Object, _target.Object);
4141
var result = await handler.HandleAsync(new RefreshAndLoadAllJsonCommand(), _ctx);
4242

4343
result.IsSuccess.Should().BeTrue();
@@ -51,7 +51,7 @@ public async Task RefreshAndLoadAllJsonHandler_HandleAsync_WithPreselectedAgent(
5151
_target.Setup(t => t.DispatchToUi(It.IsAny<Action>()));
5252
_target.Setup(t => t.TrackBackgroundWork(It.IsAny<Task>()));
5353

54-
var handler = new RefreshAndLoadAllJsonHandler(_target.Object);
54+
var handler = new RefreshAndLoadAllJsonHandler(_target.Object, _target.Object);
5555
var result = await handler.HandleAsync(new RefreshAndLoadAllJsonCommand("Claude"), _ctx);
5656

5757
result.IsSuccess.Should().BeTrue();
@@ -66,7 +66,7 @@ public async Task RefreshAndLoadAgentJsonHandler_HandleAsync_DelegatesToAllJsonH
6666
_target.Setup(t => t.DispatchToUi(It.IsAny<Action>()));
6767
_target.Setup(t => t.TrackBackgroundWork(It.IsAny<Task>()));
6868

69-
var handler = new RefreshAndLoadAgentJsonHandler(_target.Object);
69+
var handler = new RefreshAndLoadAgentJsonHandler(_target.Object, _target.Object);
7070
var result = await handler.HandleAsync(new RefreshAndLoadAgentJsonCommand("Copilot"), _ctx);
7171

7272
result.IsSuccess.Should().BeTrue();
@@ -81,7 +81,7 @@ public async Task RefreshAndLoadSessionHandler_HandleAsync_DispatchesAndTracksBa
8181
_target.Setup(t => t.DispatchToUi(It.IsAny<Action>()));
8282
_target.Setup(t => t.TrackBackgroundWork(It.IsAny<Task>()));
8383

84-
var handler = new RefreshAndLoadSessionHandler(_target.Object);
84+
var handler = new RefreshAndLoadSessionHandler(_target.Object, _target.Object);
8585
var result = await handler.HandleAsync(new RefreshAndLoadSessionCommand("/path/to/session"), _ctx);
8686

8787
result.IsSuccess.Should().BeTrue();
@@ -95,7 +95,7 @@ public async Task RefreshAndLoadSessionHandler_HandleAsync_DispatchesAndTracksBa
9595
public async Task LoadJsonFileHandler_HandleAsync_CallsLoadJson()
9696
{
9797
_target.Setup(t => t.DispatchToUi(It.IsAny<Action>()));
98-
var handler = new LoadJsonFileHandler(_target.Object);
98+
var handler = new LoadJsonFileHandler(_target.Object, _target.Object);
9999
var result = await handler.HandleAsync(new LoadJsonFileCommand("test.json"), _ctx);
100100

101101
result.IsSuccess.Should().BeTrue();

src/McpServerManager.Core/Commands/AllCommands.cs

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace McpServerManager.Core.Commands;
77

88
public sealed record NavigateBackCommand() : ICommand<bool>;
99

10-
public sealed class NavigateBackHandler(ICommandTarget target) : ICommandHandler<NavigateBackCommand, bool>
10+
public sealed class NavigateBackHandler(INavigationTarget target) : ICommandHandler<NavigateBackCommand, bool>
1111
{
1212
public Task<Result<bool>> HandleAsync(NavigateBackCommand command, CallContext context)
1313
{
@@ -18,7 +18,7 @@ public Task<Result<bool>> HandleAsync(NavigateBackCommand command, CallContext c
1818

1919
public sealed record NavigateForwardCommand() : ICommand<bool>;
2020

21-
public sealed class NavigateForwardHandler(ICommandTarget target) : ICommandHandler<NavigateForwardCommand, bool>
21+
public sealed class NavigateForwardHandler(INavigationTarget target) : ICommandHandler<NavigateForwardCommand, bool>
2222
{
2323
public Task<Result<bool>> HandleAsync(NavigateForwardCommand command, CallContext context)
2424
{
@@ -31,7 +31,7 @@ public Task<Result<bool>> HandleAsync(NavigateForwardCommand command, CallContex
3131

3232
public sealed record RefreshViewCommand() : ICommand<bool>;
3333

34-
public sealed class RefreshViewHandler(ICommandTarget target) : ICommandHandler<RefreshViewCommand, bool>
34+
public sealed class RefreshViewHandler(INavigationTarget target) : ICommandHandler<RefreshViewCommand, bool>
3535
{
3636
public async Task<Result<bool>> HandleAsync(RefreshViewCommand command, CallContext context)
3737
{
@@ -44,7 +44,7 @@ public async Task<Result<bool>> HandleAsync(RefreshViewCommand command, CallCont
4444

4545
public sealed record ShowRequestDetailsCommand(Models.Json.SearchableEntry Entry) : ICommand<bool>;
4646

47-
public sealed class ShowRequestDetailsHandler(ICommandTarget target) : ICommandHandler<ShowRequestDetailsCommand, bool>
47+
public sealed class ShowRequestDetailsHandler(IRequestDetailsTarget target) : ICommandHandler<ShowRequestDetailsCommand, bool>
4848
{
4949
public Task<Result<bool>> HandleAsync(ShowRequestDetailsCommand command, CallContext context)
5050
{
@@ -55,7 +55,7 @@ public Task<Result<bool>> HandleAsync(ShowRequestDetailsCommand command, CallCon
5555

5656
public sealed record CloseRequestDetailsCommand() : ICommand<bool>;
5757

58-
public sealed class CloseRequestDetailsHandler(ICommandTarget target) : ICommandHandler<CloseRequestDetailsCommand, bool>
58+
public sealed class CloseRequestDetailsHandler(IRequestDetailsTarget target) : ICommandHandler<CloseRequestDetailsCommand, bool>
5959
{
6060
public Task<Result<bool>> HandleAsync(CloseRequestDetailsCommand command, CallContext context)
6161
{
@@ -66,7 +66,7 @@ public Task<Result<bool>> HandleAsync(CloseRequestDetailsCommand command, CallCo
6666

6767
public sealed record NavigateToPreviousRequestCommand() : ICommand<bool>;
6868

69-
public sealed class NavigateToPreviousRequestHandler(ICommandTarget target) : ICommandHandler<NavigateToPreviousRequestCommand, bool>
69+
public sealed class NavigateToPreviousRequestHandler(IRequestDetailsTarget target) : ICommandHandler<NavigateToPreviousRequestCommand, bool>
7070
{
7171
public Task<Result<bool>> HandleAsync(NavigateToPreviousRequestCommand command, CallContext context)
7272
{
@@ -77,7 +77,7 @@ public Task<Result<bool>> HandleAsync(NavigateToPreviousRequestCommand command,
7777

7878
public sealed record NavigateToNextRequestCommand() : ICommand<bool>;
7979

80-
public sealed class NavigateToNextRequestHandler(ICommandTarget target) : ICommandHandler<NavigateToNextRequestCommand, bool>
80+
public sealed class NavigateToNextRequestHandler(IRequestDetailsTarget target) : ICommandHandler<NavigateToNextRequestCommand, bool>
8181
{
8282
public Task<Result<bool>> HandleAsync(NavigateToNextRequestCommand command, CallContext context)
8383
{
@@ -90,7 +90,7 @@ public Task<Result<bool>> HandleAsync(NavigateToNextRequestCommand command, Call
9090

9191
public sealed record SelectSearchEntryCommand(Models.Json.SearchableEntry Entry) : ICommand<bool>;
9292

93-
public sealed class SelectSearchEntryHandler(ICommandTarget target) : ICommandHandler<SelectSearchEntryCommand, bool>
93+
public sealed class SelectSearchEntryHandler(IRequestDetailsTarget target) : ICommandHandler<SelectSearchEntryCommand, bool>
9494
{
9595
public Task<Result<bool>> HandleAsync(SelectSearchEntryCommand command, CallContext context)
9696
{
@@ -103,7 +103,7 @@ public Task<Result<bool>> HandleAsync(SelectSearchEntryCommand command, CallCont
103103

104104
public sealed record CopyTextCommand(string Text) : ICommand<bool>;
105105

106-
public sealed class CopyTextHandler(ICommandTarget target) : ICommandHandler<CopyTextCommand, bool>
106+
public sealed class CopyTextHandler(IClipboardTarget target) : ICommandHandler<CopyTextCommand, bool>
107107
{
108108
public async Task<Result<bool>> HandleAsync(CopyTextCommand command, CallContext context)
109109
{
@@ -114,7 +114,7 @@ public async Task<Result<bool>> HandleAsync(CopyTextCommand command, CallContext
114114

115115
public sealed record CopyOriginalJsonCommand(Models.Json.UnifiedRequestEntry? Entry) : ICommand<bool>;
116116

117-
public sealed class CopyOriginalJsonHandler(ICommandTarget target) : ICommandHandler<CopyOriginalJsonCommand, bool>
117+
public sealed class CopyOriginalJsonHandler(IClipboardTarget target) : ICommandHandler<CopyOriginalJsonCommand, bool>
118118
{
119119
public async Task<Result<bool>> HandleAsync(CopyOriginalJsonCommand command, CallContext context)
120120
{
@@ -127,7 +127,7 @@ public async Task<Result<bool>> HandleAsync(CopyOriginalJsonCommand command, Cal
127127

128128
public sealed record OpenPreviewInBrowserCommand() : ICommand<bool>;
129129

130-
public sealed class OpenPreviewInBrowserHandler(ICommandTarget target) : ICommandHandler<OpenPreviewInBrowserCommand, bool>
130+
public sealed class OpenPreviewInBrowserHandler(IPreviewTarget target) : ICommandHandler<OpenPreviewInBrowserCommand, bool>
131131
{
132132
public Task<Result<bool>> HandleAsync(OpenPreviewInBrowserCommand command, CallContext context)
133133
{
@@ -138,7 +138,7 @@ public Task<Result<bool>> HandleAsync(OpenPreviewInBrowserCommand command, CallC
138138

139139
public sealed record ToggleShowRawMarkdownCommand() : ICommand<bool>;
140140

141-
public sealed class ToggleShowRawMarkdownHandler(ICommandTarget target) : ICommandHandler<ToggleShowRawMarkdownCommand, bool>
141+
public sealed class ToggleShowRawMarkdownHandler(IPreviewTarget target) : ICommandHandler<ToggleShowRawMarkdownCommand, bool>
142142
{
143143
public Task<Result<bool>> HandleAsync(ToggleShowRawMarkdownCommand command, CallContext context)
144144
{
@@ -151,7 +151,7 @@ public Task<Result<bool>> HandleAsync(ToggleShowRawMarkdownCommand command, Call
151151

152152
public sealed record ArchiveCurrentCommand() : ICommand<bool>;
153153

154-
public sealed class ArchiveCurrentHandler(ICommandTarget target) : ICommandHandler<ArchiveCurrentCommand, bool>
154+
public sealed class ArchiveCurrentHandler(IArchiveTarget target) : ICommandHandler<ArchiveCurrentCommand, bool>
155155
{
156156
public Task<Result<bool>> HandleAsync(ArchiveCurrentCommand command, CallContext context)
157157
{
@@ -162,7 +162,7 @@ public Task<Result<bool>> HandleAsync(ArchiveCurrentCommand command, CallContext
162162

163163
public sealed record ArchiveTreeItemCommand(Models.FileNode? Node) : ICommand<bool>;
164164

165-
public sealed class ArchiveTreeItemHandler(ICommandTarget target) : ICommandHandler<ArchiveTreeItemCommand, bool>
165+
public sealed class ArchiveTreeItemHandler(IArchiveTarget target) : ICommandHandler<ArchiveTreeItemCommand, bool>
166166
{
167167
public Task<Result<bool>> HandleAsync(ArchiveTreeItemCommand command, CallContext context)
168168
{
@@ -175,7 +175,7 @@ public Task<Result<bool>> HandleAsync(ArchiveTreeItemCommand command, CallContex
175175

176176
public sealed record OpenTreeItemCommand(Models.FileNode? Node) : ICommand<bool>;
177177

178-
public sealed class OpenTreeItemHandler(ICommandTarget target) : ICommandHandler<OpenTreeItemCommand, bool>
178+
public sealed class OpenTreeItemHandler(INavigationTarget target) : ICommandHandler<OpenTreeItemCommand, bool>
179179
{
180180
public Task<Result<bool>> HandleAsync(OpenTreeItemCommand command, CallContext context)
181181
{
@@ -188,7 +188,7 @@ public Task<Result<bool>> HandleAsync(OpenTreeItemCommand command, CallContext c
188188

189189
public sealed record OpenAgentConfigCommand() : ICommand<bool>;
190190

191-
public sealed class OpenAgentConfigHandler(ICommandTarget target) : ICommandHandler<OpenAgentConfigCommand, bool>
191+
public sealed class OpenAgentConfigHandler(IConfigTarget target) : ICommandHandler<OpenAgentConfigCommand, bool>
192192
{
193193
public Task<Result<bool>> HandleAsync(OpenAgentConfigCommand command, CallContext context)
194194
{
@@ -199,7 +199,7 @@ public Task<Result<bool>> HandleAsync(OpenAgentConfigCommand command, CallContex
199199

200200
public sealed record OpenPromptTemplatesCommand() : ICommand<bool>;
201201

202-
public sealed class OpenPromptTemplatesHandler(ICommandTarget target) : ICommandHandler<OpenPromptTemplatesCommand, bool>
202+
public sealed class OpenPromptTemplatesHandler(IConfigTarget target) : ICommandHandler<OpenPromptTemplatesCommand, bool>
203203
{
204204
public Task<Result<bool>> HandleAsync(OpenPromptTemplatesCommand command, CallContext context)
205205
{
@@ -212,7 +212,7 @@ public Task<Result<bool>> HandleAsync(OpenPromptTemplatesCommand command, CallCo
212212

213213
public sealed record PhoneNavigateSectionCommand(string? SectionKey) : ICommand<bool>;
214214

215-
public sealed class PhoneNavigateSectionHandler(ICommandTarget target) : ICommandHandler<PhoneNavigateSectionCommand, bool>
215+
public sealed class PhoneNavigateSectionHandler(INavigationTarget target) : ICommandHandler<PhoneNavigateSectionCommand, bool>
216216
{
217217
public Task<Result<bool>> HandleAsync(PhoneNavigateSectionCommand command, CallContext context)
218218
{
@@ -225,7 +225,7 @@ public Task<Result<bool>> HandleAsync(PhoneNavigateSectionCommand command, CallC
225225

226226
public sealed record TreeItemTappedCommand(Models.FileNode? Node) : ICommand<bool>;
227227

228-
public sealed class TreeItemTappedHandler(ICommandTarget target) : ICommandHandler<TreeItemTappedCommand, bool>
228+
public sealed class TreeItemTappedHandler(INavigationTarget target) : ICommandHandler<TreeItemTappedCommand, bool>
229229
{
230230
public Task<Result<bool>> HandleAsync(TreeItemTappedCommand command, CallContext context)
231231
{
@@ -238,7 +238,7 @@ public Task<Result<bool>> HandleAsync(TreeItemTappedCommand command, CallContext
238238

239239
public sealed record JsonNodeDoubleTappedCommand(Models.Json.JsonTreeNode? Node) : ICommand<bool>;
240240

241-
public sealed class JsonNodeDoubleTappedHandler(ICommandTarget target) : ICommandHandler<JsonNodeDoubleTappedCommand, bool>
241+
public sealed class JsonNodeDoubleTappedHandler(INavigationTarget target) : ICommandHandler<JsonNodeDoubleTappedCommand, bool>
242242
{
243243
public Task<Result<bool>> HandleAsync(JsonNodeDoubleTappedCommand command, CallContext context)
244244
{
@@ -251,7 +251,7 @@ public Task<Result<bool>> HandleAsync(JsonNodeDoubleTappedCommand command, CallC
251251

252252
public sealed record SearchRowTappedCommand(Models.Json.SearchableEntry? Entry) : ICommand<bool>;
253253

254-
public sealed class SearchRowTappedHandler(ICommandTarget target) : ICommandHandler<SearchRowTappedCommand, bool>
254+
public sealed class SearchRowTappedHandler(IRequestDetailsTarget target) : ICommandHandler<SearchRowTappedCommand, bool>
255255
{
256256
public Task<Result<bool>> HandleAsync(SearchRowTappedCommand command, CallContext context)
257257
{
@@ -264,7 +264,7 @@ public Task<Result<bool>> HandleAsync(SearchRowTappedCommand command, CallContex
264264

265265
public sealed record SearchRowDoubleTappedCommand(Models.Json.SearchableEntry? Entry) : ICommand<bool>;
266266

267-
public sealed class SearchRowDoubleTappedHandler(ICommandTarget target) : ICommandHandler<SearchRowDoubleTappedCommand, bool>
267+
public sealed class SearchRowDoubleTappedHandler(IRequestDetailsTarget target) : ICommandHandler<SearchRowDoubleTappedCommand, bool>
268268
{
269269
public Task<Result<bool>> HandleAsync(SearchRowDoubleTappedCommand command, CallContext context)
270270
{

0 commit comments

Comments
 (0)