diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs index ce5cbcf69d..aa46853ae0 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/CommandProviderWrapper.cs @@ -301,8 +301,19 @@ public sealed class CommandProviderWrapper : ICommandProviderContext var dockSettings = settings.DockSettings; var allPinnedCommands = dockSettings.AllPinnedCommands; var pinnedBandsForThisProvider = allPinnedCommands.Where(c => c.ProviderId == ProviderId); + + // Track which command IDs we've already added to avoid duplicates + // from settings that were pinned multiple times. + HashSet seenCommandIds = new(bands.Select(b => b.Id)); + foreach (var (providerId, commandId) in pinnedBandsForThisProvider) { + if (!seenCommandIds.Add(commandId)) + { + Logger.LogWarning($"Skipping duplicate pinned dock band command {commandId} for provider {providerId}"); + continue; + } + Logger.LogDebug($"Looking for pinned dock band command {commandId} for provider {providerId}"); // First, try to lookup the command as one of this provider's @@ -440,6 +451,17 @@ public sealed class CommandProviderWrapper : ICommandProviderContext { var settingsService = serviceProvider.GetRequiredService(); var settings = settingsService.Settings; + var dockSettings = settings.DockSettings; + + // Prevent duplicate pins — check all sections + if (dockSettings.StartBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId) || + dockSettings.CenterBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId) || + dockSettings.EndBands.Any(b => b.CommandId == commandId && b.ProviderId == this.ProviderId)) + { + Logger.LogDebug($"Dock band '{commandId}' from provider '{this.ProviderId}' is already pinned; skipping."); + return; + } + var bandSettings = new DockBandSettings { CommandId = commandId, diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Dock/DockViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Dock/DockViewModel.cs index 652b62410a..2f72efc3c3 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Dock/DockViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Dock/DockViewModel.cs @@ -21,6 +21,7 @@ public sealed partial class DockViewModel private readonly IContextMenuFactory _contextMenuFactory; private DockSettings _settings; + private bool _isEditing; public TaskScheduler Scheduler { get; } @@ -52,6 +53,12 @@ public sealed partial class DockViewModel private void DockBands_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { + if (_isEditing) + { + Logger.LogDebug("Skipping DockBands_CollectionChanged during edit mode"); + return; + } + Logger.LogDebug("Starting DockBands_CollectionChanged"); SetupBands(); Logger.LogDebug("Ended DockBands_CollectionChanged"); @@ -302,7 +309,12 @@ public sealed partial class DockViewModel _snapshotCenterBands = null; _snapshotEndBands = null; _snapshotBandViewModels = null; - _settingsService.Save(); + + // Save without hotReload to avoid triggering SettingsChanged → SetupBands, + // which could race with stale DockBands_CollectionChanged work items and + // re-add bands that were just unpinned. + _settingsService.Save(hotReload: false); + _isEditing = false; Logger.LogDebug("Saved band order to settings"); } @@ -317,6 +329,8 @@ public sealed partial class DockViewModel /// public void SnapshotBandOrder() { + _isEditing = true; + var dockSettings = _settingsService.Settings.DockSettings; _snapshotStartBands = dockSettings.StartBands.Select(b => b.Clone()).ToList(); _snapshotCenterBands = dockSettings.CenterBands.Select(b => b.Clone()).ToList(); @@ -391,6 +405,7 @@ public sealed partial class DockViewModel _snapshotCenterBands = null; _snapshotEndBands = null; _snapshotBandViewModels = null; + _isEditing = false; Logger.LogDebug("Restored band order from snapshot"); }