microsoft / microsoft/PowerToys
[CmdPal] Context-item `RequestedShortcut` navigates twice, requiring two Esc / two Back presses to leave the page
- Dominant language
- C
- Stars
- 139k
- Forks
- 8.6k
- PR merge metrics
- PR metrics pending
Description
### Microsoft PowerToys version
0.100.0
### Installation method
GitHub
### Area(s) with issue?
Command Palette
### Steps to reproduce
This affects extension authors using `CommandContextItem.RequestedShortcut` on a top-level command's `MoreCommands`, where the bound command is a page (navigation).
1. Create an extension from the Command Palette extension template.
2. Replace `TemplateCmdPalExtensionCommandsProvider.cs` with the minimal repro below.
3. Run the extension and open Command Palette.
4. Select the "ShortcutRepro" item so it is highlighted.
5. Press **Ctrl+1** (the shortcut bound to "MoreCommand2") to navigate into `ReproListPage`.
6. Press **Esc** once.
Optional, to make the double-navigation visible directly: after step 5, click the back button (the `<` arrow) at the top-left instead of pressing Esc.
### Minimal repro (`TemplateCmdPalExtensionCommandsProvider.cs`)
```csharp
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Windows.System;
namespace TemplateCmdPalExtension;
public partial class TemplateCmdPalExtensionCommandsProvider : CommandProvider
{
private readonly ICommandItem[] _commands;
public TemplateCmdPalExtensionCommandsProvider()
{
DisplayName = "ReproExtension";
var page = new ReproListPage();
_commands = [
new CommandItem(page)
{
Title = "ShortcutRepro",
MoreCommands = [
new CommandContextItem(new ReproListPage())
{
Title = "MoreCommand1 (no shortcut)",
},
new CommandContextItem(new ReproListPage())
{
Title = "MoreCommand2 (Ctrl+1)",
RequestedShortcut = KeyChordHelpers.FromModifiers(
ctrl: true, vkey: VirtualKey.Number1),
},
],
},
];
}
public override ICommandItem[] TopLevelCommands() => _commands;
}
// Reproduces with both ListPage and DynamicListPage.
internal sealed partial class ReproListPage : ListPage
{
public ReproListPage()
{
Title = "ReproListPage";
Name = "Open";
}
public override IListItem[] GetItems() => [];
}
```
### ✔️ Expected Behavior
After navigating into the page via the context-item shortcut (Ctrl+1), pressing Esc once (with "Escape key behavior" set to "Always go back", and also with the default) returns to the previous page — the same as when navigating with Enter or Ctrl+Enter. A single back-button click also returns to the previous page.
### ❌ Actual Behavior
After navigating via the context-item shortcut, **two** Esc presses (or two back-button clicks) are required to leave the page. On the first press, nothing appears to happen except that the progress bar starts animating (this happens with a plain `ListPage` as well as a `DynamicListPage`). The second press returns to the top page.
Navigating into the same page with **Enter** or **Ctrl+Enter** behaves correctly: a single Esc / single back-button click returns.
The single back-button click reproducing the same problem indicates the navigation stack contains two entries, i.e. the page is navigated to twice.
### Upload Bug Report ZIP-file
_No response_
### Additional Information
OS version: Windows 11 x64
.NET version: .NET 10
System Language: Japanese
User or System Installation: Unknown
Running as admin: No
## Root cause (from source reading, v0.100.0)
The shortcut path sends a `TryCommandKeybindingMessage` from `ShellPage_OnPreviewKeyDown`. This message has **two** registered recipients:
- `CommandBar` (`Microsoft.CmdPal.UI/Controls/CommandBar.xaml.cs`)
- `ContextMenu` (`Microsoft.CmdPal.UI/Controls/ContextMenu.xaml.cs`)
In `CommandBar.xaml`, the `ContextMenu` control (`ContextControl`) uses the default `SubscribeToCommandBar = true`, so both controls subscribe to `TryCommandKeybindingMessage`.
`ContextMenuViewModel` keeps the selected item's `MoreCommands` in `ContextMenuStack` even while the context menu is closed: its `SelectedItem` setter calls `UpdateContextItems()`, which is driven by `UpdateCommandBarMessage` on every selection change.
As a result, pressing the context-item shortcut matches the keybinding in **both** recipients:
- `CommandBar.Receive(TryCommandKeybindingMessage)` → `CommandBarViewModel.CheckKeybinding` → `PerformCommand` → sends `PerformCommandMessage` (navigation 1).
- `ContextMenu.Receive(TryCommandKeybindingMessage)` → `ContextMenuViewModel.CheckKeybinding` → `InvokeCommand` → sends `PerformCommandMessage` (navigation 2).
Both `PerformCommandMessage`s navigate to the bound page, pushing two entries onto `RootFrame`'s back stack. `NavigateBackMessage` (used by both Esc and the back button) pops one entry per press, so two presses are needed.
Enter / Ctrl+Enter use different messages (`ActivateSelectedListItemMessage` / `ActivateSecondaryCommandMessage`, handled only by `ListItemsView`), so they navigate once and behave correctly.
Note: the official `SamplePagesExtension` contains list items annotated "Try pressing Ctrl+1 with me selected", so this is an expected, documented usage pattern rather than an edge case.
## Possible fix direction
Avoid handling `TryCommandKeybindingMessage` in both recipients. For example, `ContextMenu` could skip keybinding handling while its flyout is closed (or mark `msg.Handled` so only one recipient acts), leaving `CommandBar` as the single handler when no context menu is open.
Note that the same `SubscribeToCommandBar` mechanism already exists and is used to solve essentially the same double-handling problem for the Dock: PR #46420 ("CmdPal: Fix Dock context menu following active item in Command Bar") added the `SubscribeToCommandBar` dependency property and set `SubscribeToCommandBar="False"` on the Dock's `ContextMenu` so it no longer reacts to CommandBar selection/keybinding messages. The main palette's `ContextControl` in `CommandBar.xaml` still uses the default `SubscribeToCommandBar="True"`, so it is not covered by that fix and the double-navigation described here remains. A fix for the main palette could follow the same pattern (e.g. only let the `ContextMenu` handle the keybinding while its flyout is actually open).
### Other Software
_No response_
Contributor guide
Research direction
Start with Microsoft.CmdPal.UI/Controls/CommandBar.xaml.cs and ContextMenu.xaml.cs, then inspect the ContextControl subscription in CommandBar.xaml and the existing SubscribeToCommandBar handling from PR #46420. Reproduce the Ctrl+1 case with the provided extension and ensure the keybinding produces one navigation, so one Esc or back-button click returns to the previous page.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100