CmdPal: use unique IDs for PowerToys fallback commands (#50047)

## Summary of the Pull Request

PowerToys fallback commands currently all use the same fallback ID
(`com.microsoft.powertoys.fallback`). Because Command Palette persists
fallback settings by ID, disabling one command writes a setting that is
then read by every fallback command in the provider.

This change derives each fallback item's ID from the command's existing
stable ID by appending `.fallback`, so fallback settings are stored
independently without colliding with the underlying command IDs. It also
adds a focused regression test covering the generated fallback IDs.

## PR Checklist

* [x] Closes: #48607
* [x] **Communication:** The issue is labeled `Help Wanted`;
implementation intent and approach were posted in #28769.
* [ ] **Tests:** Added; official CI pending. The local PowerToys build
is blocked because the available Visual Studio/MSBuild 17.10 cannot load
the repository's .NET 10 SDK, which requires MSBuild 18.
* [x] **Localization:** No end-user-facing strings were added or
changed.
* [x] **Dev docs:** Not applicable; no public behavior or API contract
was added.
* [x] **New binaries:** No shipping binaries were added. The new
unit-test assembly is included in `PowerToys.slnx` and the applicable
Command Palette solution filters, and matches the existing
`*UnitTest*.dll` CI test discovery pattern.
* [x] **Documentation updated:** Not applicable.

## Detailed Description of the Pull Request / Additional comments

`ProviderSettingsViewModel` stores fallback state in a dictionary keyed
by `IFallbackCommandItem.Id`. The PowerToys extension generated many
fallback items with one shared ID, causing the last persisted state for
that key to apply to the entire provider after reopening settings.

All commands produced by the PowerToys module catalog already have
stable, unique IDs used for command identity and pinning. Appending
`.fallback` to those IDs gives each fallback item a stable, unique
identity while keeping it distinct from the underlying command. Existing
legacy settings under `com.microsoft.powertoys.fallback` are left
harmlessly unused because there is no meaningful way to map that shared
value back to one specific command.

The new unit-test project is also included in the applicable Command
Palette solution filters so it is available in the relevant development
and test configurations.

## Validation Steps Performed

* Added `FallbackItemsAppendFallbackSuffixToCommandIds`, covering two
commands and verifying that fallback IDs append `.fallback`, remain
distinct from the underlying command IDs, and remain unique across
commands.
* Added the new unit-test project to `PowerToys.slnx`,
`CommandPalette.slnf`, `CommandPalette - no UI tests.slnf`, and
`Microsoft.CmdPal.Ext.PowerToys.slnf`.
* Included `Microsoft.CmdPal.Ext.UnitTestsBase` in the
PowerToys-specific solution filter because it is a direct dependency of
the new unit-test project.
* Validated the modified solution and project files.
* Confirmed all 64 literal command IDs in the PowerToys module providers
are unique.
* Ran `git diff --check` successfully.
* Attempted the repository-prescribed targeted Release/x64 build. It
reached MSBuild but was blocked by the local toolchain version noted
above; authoritative build and test results are therefore left to CI.
This commit is contained in:
Zhibo Lin
2026-08-24 04:43:22 +08:00
committed by GitHub
parent 5759a62d4a
commit 19c4d80532
7 changed files with 66 additions and 1 deletions

View File

@@ -349,6 +349,10 @@
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.PowerToys.UnitTests/Microsoft.CmdPal.Ext.PowerToys.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />
</Project>
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/Microsoft.CmdPal.Ext.Registry.UnitTests.csproj">
<Platform Solution="*|ARM64" Project="ARM64" />
<Platform Solution="*|x64" Project="x64" />

View File

@@ -22,6 +22,7 @@
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Indexer.UnitTests\\Microsoft.CmdPal.Ext.Indexer.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests\\Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.PowerToys.UnitTests\\Microsoft.CmdPal.Ext.PowerToys.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Registry.UnitTests\\Microsoft.CmdPal.Ext.Registry.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests\\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Shell.UnitTests\\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj",

View File

@@ -25,6 +25,7 @@
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Indexer.UnitTests\\Microsoft.CmdPal.Ext.Indexer.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests\\Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.PowerToys.UnitTests\\Microsoft.CmdPal.Ext.PowerToys.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Registry.UnitTests\\Microsoft.CmdPal.Ext.Registry.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests\\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Shell.UnitTests\\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj",

View File

@@ -17,6 +17,8 @@
"src\\modules\\Workspaces\\WorkspacesCsharpLibrary\\WorkspacesCsharpLibrary.csproj",
"src\\modules\\ZoomIt\\ZoomItSettingsInterop\\ZoomItSettingsInterop.vcxproj",
"src\\modules\\awake\\Awake.ModuleServices\\Awake.ModuleServices.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.PowerToys.UnitTests\\Microsoft.CmdPal.Ext.PowerToys.UnitTests.csproj",
"src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.UnitTestsBase\\Microsoft.CmdPal.Ext.UnitTestBase.csproj",
"src\\modules\\cmdpal\\ext\\Microsoft.CmdPal.Ext.PowerToys\\Microsoft.CmdPal.Ext.PowerToys.csproj",
"src\\modules\\cmdpal\\extensionsdk\\Microsoft.CommandPalette.Extensions.Toolkit\\Microsoft.CommandPalette.Extensions.Toolkit.csproj",
"src\\modules\\cmdpal\\extensionsdk\\Microsoft.CommandPalette.Extensions\\Microsoft.CommandPalette.Extensions.vcxproj",

View File

@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<RootNamespace>Microsoft.CmdPal.Ext.PowerToys.UnitTests</RootNamespace>
<OutputPath>$(RepoRoot)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MSTest" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\ext\Microsoft.CmdPal.Ext.PowerToys\Helpers\PowerToysFallbackCommandItem.cs" Link="PowerToysFallbackCommandItem.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\Common.Search\Common.Search.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PowerToysExtension.Helpers;
namespace Microsoft.CmdPal.Ext.PowerToys.UnitTests;
[TestClass]
public class PowerToysFallbackCommandItemTests
{
[TestMethod]
public void FallbackItemsAppendFallbackSuffixToCommandIds()
{
var firstCommand = new NoOpCommand { Id = "com.microsoft.powertoys.first" };
var secondCommand = new NoOpCommand { Id = "com.microsoft.powertoys.second" };
var firstFallback = new PowerToysFallbackCommandItem(firstCommand, "First", string.Empty, null, null);
var secondFallback = new PowerToysFallbackCommandItem(secondCommand, "Second", string.Empty, null, null);
Assert.AreEqual($"{firstCommand.Id}.fallback", firstFallback.Id);
Assert.AreEqual($"{secondCommand.Id}.fallback", secondFallback.Id);
Assert.AreNotEqual(firstCommand.Id, firstFallback.Id);
Assert.AreNotEqual(secondCommand.Id, secondFallback.Id);
Assert.AreNotEqual(firstFallback.Id, secondFallback.Id);
}
}

View File

@@ -20,7 +20,7 @@ internal sealed partial class PowerToysFallbackCommandItem : FallbackCommandItem
private readonly Command? _mutableCommand;
public PowerToysFallbackCommandItem(ICommand command, string title, string subtitle, IIconInfo? icon, IContextItem[]? moreCommands)
: base(command, title, "com.microsoft.powertoys.fallback")
: base(command, title, $"{command.Id}.fallback")
{
_baseTitle = title ?? string.Empty;
_baseSubtitle = subtitle ?? string.Empty;