Skip to content

Conversation

@michaeljolley
Copy link
Contributor

@michaeljolley michaeljolley commented Nov 13, 2025

Important

For extension developers, this release includes a new required string Id property for FallbackCommandItem. While your existing extensions will continue to work, without this Id being set, your fallbacks will not display and will not be rankable.
Before this is released, you will want to prepare your extension fallbacks.

As an example, we are naming our built-in extensions as:

  • Calculator extension provider Id: com.microsoft.cmdpal.builtin.calculator
  • Calculator extension fallback: com.microsoft.cmdpal.builtin.calculator.fallback

While the content of the Id isn't important, what is important is that it is unique to your extension and fallback to avoid conflicting with other extensions.

Now the good stuff:

What the heck does it do!?

The backstory

In PowerToys 0.95, we released performance improvements to Command Palette. One of the many ways we improved its speed is by no longer ranking fallback commands with other "top level" commands. Instead, all fallbacks would surface at the bottom of the results and be listed in the order they were registered with Command Palette. But this was only a temporary solution until the work included in this pull request was ready.

In reality, not all fallbacks were treated equally. We marked the calculator and run fallbacks as "special." Special fallbacks were ranked like top-level commands and allowed to surface to the top of the results.

The new "hotness"

This PR brings the power of fallback management back to the people. In the Command Palette settings, you, dear user, can specify what order you want fallbacks to display in at the bottom of the results. This keeps those fallbacks unranked by Command Palette but displays them in an order that makes sense for you. But keep in mind, these will still live at the bottom of search results.

But alas, we have also heard your cries that you'd like some fallbacks to be ranked by Command Palette and surface to the top of the results. So, this PR allows you to mark any fallback as "special" by choosing to include them in the global results. Special (Global) fallbacks are treated like "top level" commands and appear in the search result based on their title & description.

Screenshots/video

image image image image

GitHub issue maintenance details

Closes #38312
Closes #38288
Closes #42524
Closes #41024
Closes #40351
Closes #41696
Closes #40193

@michaeljolley michaeljolley added the Product-Command Palette Refers to the Command Palette utility label Nov 13, 2025
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@niels9001
Copy link
Collaborator

@michaeljolley Ugh, lot of conflicts popping up :(

@michaeljolley
Copy link
Contributor Author

@michaeljolley Ugh, lot of conflicts popping up :(

Cleaning now.

@michaeljolley michaeljolley marked this pull request as ready for review December 11, 2025 03:48
@github-actions

This comment has been minimized.

@michaeljolley michaeljolley marked this pull request as draft December 11, 2025 03:55
@michaeljolley michaeljolley marked this pull request as ready for review December 11, 2025 18:09
{
}

public FallbackSettings(bool isBuiltIn)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The automatic conversion of from isBuildIn to IncludeInGlobalResults feels far from intuitive. It's probably just an old name of the arg?

}

var fallbackRankings = new List<Scored<FallbackSettingsViewModel>>(fallbacks.Count);
foreach (var fallback in fallbacks)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this runs while the fallbacks list is unstable or uncomplete (reload is in progress), the ranking will be broken, and the user preference might be lost?

michaeljolley and others added 5 commits December 12, 2025 19:42
….xaml.cs

Co-authored-by: Jiří Polášek <me@jiripolasek.com>
…ingsViewModel.cs

Co-authored-by: Jiří Polášek <me@jiripolasek.com>
…xaml

Co-authored-by: Jiří Polášek <me@jiripolasek.com>
## Summary of the Pull Request

Add "Manage fallback order" menu item to "More options" menu on the
extensions list page

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist

- [ ] Closes: #xxx
<!-- - [ ] Closes: #yyy (add separate lines for additional resolved
issues) -->
- [ ] **Communication:** I've discussed this with core contributors
already. If the work hasn't been agreed, this work might be rejected
- [ ] **Tests:** Added/updated and all pass
- [ ] **Localization:** All end-user-facing strings can be localized
- [ ] **Dev docs:** Added/updated
- [ ] **New binaries:** Added on the required places
- [ ] [JSON for
signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json)
for new binaries
- [ ] [WXS for
installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs)
for new binaries and localization folder
- [ ] [YML for CI
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml)
for new test projects
- [ ] [YML for signed
pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml)
- [ ] **Documentation updated:** If checked, please file a pull request
on [our docs
repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys)
and link it here: #xxx

<!-- Provide a more detailed description of the PR, other things fixed,
or any additional comments/features here -->
## Detailed Description of the Pull Request / Additional comments

<!-- Describe how you validated the behavior. Add automated tests
wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
- Revert misguided suggestions
- Fix whitespace
@niels9001
Copy link
Collaborator

@jiripolasek @michaeljolley is this good to go in?

@michaeljolley michaeljolley merged commit f1e0457 into main Dec 22, 2025
14 checks passed
@michaeljolley michaeljolley deleted the dev/mjolley/fallbacks branch December 22, 2025 23:08
@michaeljolley
Copy link
Contributor Author

The answer was yes. 😄

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces user-configurable fallback ranking and global results functionality to Command Palette. It adds a new required Id property to fallback commands via the IFallbackCommandItem2 interface, allowing users to reorder fallbacks and mark specific fallbacks as "global" (ranked with top-level commands) rather than appearing only at the bottom of results.

Changes:

  • Introduced IFallbackCommandItem2 interface requiring fallback commands to have unique string IDs
  • Added UI for managing fallback order and global results settings in Extensions settings pages
  • Implemented fallback ranking logic where users control display order and can mark fallbacks for global ranking

Reviewed changes

Copilot reviewed 40 out of 41 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
Microsoft.CommandPalette.Extensions.idl Added IFallbackCommandItem2 interface with Id property
FallbackCommandItem.cs Updated constructors to require and validate Id parameter
Built-in fallback extensions (8 files) Updated all built-in fallbacks to provide consistent IDs
FallbackSettings.cs New settings class for individual fallback configuration
FallbackSettingsViewModel.cs New ViewModel for managing fallback settings in UI
ProviderSettings.cs Refactored to use FallbackSettings dictionary instead of boolean
SettingsModel.cs Added FallbackRanks array and GetGlobalFallbacks method
SettingsViewModel.cs Added FallbackRankings collection and ApplyFallbackSort method
FallbackRankerDialog/FallbackRanker (4 files) New UI controls for drag-and-drop fallback reordering
ExtensionPage.xaml/xaml.cs Added Settings expander for fallbacks with global results toggle
ExtensionsPage.xaml/xaml.cs Added menu item to open fallback ranking dialog
MainListPage.cs Updated to use global fallbacks and fallback rankings for filtering
MainListPageResultFactory.cs Added fallbacks section header in results
TopLevelViewModel.cs Updated IsEnabled to read from FallbackSettings
Resources.resw Added localized strings for new UI elements
MainListPageResultFactoryTests.cs Updated tests to account for fallback section header
Files not reviewed (1)
  • src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Properties/Resources.Designer.cs: Language not supported
<controls:SettingsCard.HeaderIcon>
<DataTemplate x:DataType="viewModels:FallbackSettingsViewModel">
<controls:SettingsExpander
Grid.Column="1"
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SettingsExpander in the DataTemplate has Grid.Column="1" set on line 157, but it's not inside a Grid with column definitions. This control is inside an ItemsRepeater, not a Grid, so this property has no effect and should be removed. It appears to be a leftover from refactoring the previous SettingsCard layout.

Copilot uses AI. Check for mistakes.
Comment on lines +197 to +200
if (isFallback && commandItem is FallbackCommandItem fallback)
{
_fallbackId = fallback.Id;
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback ID extraction on lines 197-200 only checks if commandItem is a FallbackCommandItem, but the new interface is IFallbackCommandItem2. If an extension implements IFallbackCommandItem2 without inheriting from FallbackCommandItem, the Id won't be extracted here. Consider checking for IFallbackCommandItem2 interface instead: if (isFallback && commandItem is IFallbackCommandItem2 fallback2).

Copilot uses AI. Check for mistakes.
Comment on lines +11 to 15
public FallbackCommandItem(string displayTitle, string id)
{
DisplayTitle = displayTitle;
Id = id;
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first constructor (line 11) doesn't validate the id parameter like the second constructor does (lines 20-23). This creates an inconsistency where one constructor enforces non-empty/non-whitespace IDs while the other allows empty or whitespace IDs. Both constructors should validate the id parameter to ensure consistency and prevent invalid IDs from being created.

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +15
// List of built-in fallbacks that should not have global results enabled by default
private readonly string[] _excludedBuiltInFallbacks = [
"com.microsoft.cmdpal.builtin.indexer.fallback",
"com.microsoft.cmdpal.builtin.calculator.fallback",
];
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _excludedBuiltInFallbacks array is marked as readonly but is a field of a serializable class. When deserialized via the JsonConstructor (line 36), this field won't be initialized, causing _excludedBuiltInFallbacks to be null. If Connect() is called after deserialization, line 54 will throw a NullReferenceException. Consider making this a static readonly field or a const, or ensuring it's properly initialized in all constructors.

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +55
_fallbackSettings.IncludeInGlobalResults = value;

if (!_fallbackSettings.IsEnabled)
{
_fallbackSettings.IsEnabled = true;
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IncludeInGlobalResults setter has incorrect logic. When setting IncludeInGlobalResults to true on line 50, it checks if IsEnabled is false on line 52 and then sets IsEnabled to true on line 54. However, this should only auto-enable when IncludeInGlobalResults is being set to true (value is true), not when it's being set to false. The condition should be: if (value && !_fallbackSettings.IsEnabled) to only auto-enable when enabling global results.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +18
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Windows.Foundation.Collections;
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file contains unused imports that should be removed to improve code cleanliness. Lines 5-9 and 12-18 import namespaces (System.Collections.Generic, System.IO, System.Linq, System.Runtime.InteropServices.WindowsRuntime, Microsoft.UI.Xaml.Controls.Primitives, Microsoft.UI.Xaml.Data, Microsoft.UI.Xaml.Input, Microsoft.UI.Xaml.Media, Microsoft.UI.Xaml.Navigation, Windows.Foundation.Collections) that are not used anywhere in this simple control.

Copilot uses AI. Check for mistakes.

public IAsyncOperation<ContentDialogResult> ShowAsync()
{
return FallbackRankerContentDialog!.ShowAsync()!;
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method uses double null-forgiving operators on line 31, which suppresses null warnings but doesn't handle the actual null case. If FallbackRankerContentDialog is null at runtime (which shouldn't happen but could due to initialization issues), or if ShowAsync() returns null, this will throw a NullReferenceException. Consider adding proper null checking or at least documenting why these nulls are impossible.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +55
if (!_fallbackSettings.IsEnabled)
{
_fallbackSettings.IsEnabled = true;
}
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IsEnabled property change notification is missing when IncludeInGlobalResults is enabled. On line 54, IsEnabled is set to true but OnPropertyChanged(nameof(IsEnabled)) is never called. This means the UI won't be notified of the IsEnabled change, potentially leading to UI elements not updating correctly. Add OnPropertyChanged(nameof(IsEnabled)) after line 54.

Copilot uses AI. Check for mistakes.
<value>Use system settings</value>
</data>
<data name="Settings_FallbacksPage_GlobalResults_SettingsCard.Header" xml:space="preserve">
<value>Include in the Global result</value>
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The header text should use plural "results" consistently. The text says "Include in the Global result" but should say "Include in the global results" (lowercase "global" and plural "results") to match the description and be grammatically correct.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +21
Width="420"
MinWidth="420"
PrimaryButtonText="OK">
<ContentDialog.Title>
<TextBlock x:Uid="ManageFallbackRank" />
</ContentDialog.Title>
<ContentDialog.Resources>
<x:Double x:Key="ContentDialogMaxWidth">800</x:Double>
</ContentDialog.Resources>
<Grid Width="560" MinWidth="420">
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ContentDialog has a Width of 420 and MinWidth of 420 (lines 12-13), but the Grid inside has a Width of 560 and MinWidth of 420 (line 21). This creates a layout conflict where the Grid (560px) is wider than its containing ContentDialog (420px), which could cause clipping or layout issues. The values should be aligned, or the ContentDialog should be at least 560px wide to accommodate the Grid.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-Command Palette Refers to the Command Palette utility

5 participants