-
Notifications
You must be signed in to change notification settings - Fork 7.7k
CmdPal: Fallback ranking and global results #43549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/FallbackOpenURLItem.cs
Fixed
Show fixed
Hide fixed
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@michaeljolley Ugh, lot of conflicts popping up :( |
Cleaning now. |
This comment has been minimized.
This comment has been minimized.
src/modules/cmdpal/Microsoft.CmdPal.UI/Settings/AppearancePage.xaml.cs
Outdated
Show resolved
Hide resolved
| { | ||
| } | ||
|
|
||
| public FallbackSettings(bool isBuiltIn) |
There was a problem hiding this comment.
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?
src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/FallbackSettingsViewModel.cs
Outdated
Show resolved
Hide resolved
| } | ||
|
|
||
| var fallbackRankings = new List<Scored<FallbackSettingsViewModel>>(fallbacks.Count); | ||
| foreach (var fallback in fallbacks) |
There was a problem hiding this comment.
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?
….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
|
@jiripolasek @michaeljolley is this good to go in? |
|
The answer was yes. 😄 |
There was a problem hiding this 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
IFallbackCommandItem2interface 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" |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| if (isFallback && commandItem is FallbackCommandItem fallback) | ||
| { | ||
| _fallbackId = fallback.Id; | ||
| } |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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).
| public FallbackCommandItem(string displayTitle, string id) | ||
| { | ||
| DisplayTitle = displayTitle; | ||
| Id = id; | ||
| } |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| // 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", | ||
| ]; |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| _fallbackSettings.IncludeInGlobalResults = value; | ||
|
|
||
| if (!_fallbackSettings.IsEnabled) | ||
| { | ||
| _fallbackSettings.IsEnabled = true; | ||
| } |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| 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; |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
|
|
||
| public IAsyncOperation<ContentDialogResult> ShowAsync() | ||
| { | ||
| return FallbackRankerContentDialog!.ShowAsync()!; |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| if (!_fallbackSettings.IsEnabled) | ||
| { | ||
| _fallbackSettings.IsEnabled = true; | ||
| } |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| <value>Use system settings</value> | ||
| </data> | ||
| <data name="Settings_FallbacksPage_GlobalResults_SettingsCard.Header" xml:space="preserve"> | ||
| <value>Include in the Global result</value> |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
| 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"> |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
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.
Important
For extension developers, this release includes a new required
string Idproperty forFallbackCommandItem. While your existing extensions will continue to work, without thisIdbeing 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:
com.microsoft.cmdpal.builtin.calculatorcom.microsoft.cmdpal.builtin.calculator.fallbackWhile 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
GitHub issue maintenance details
Closes #38312
Closes #38288
Closes #42524
Closes #41024
Closes #40351
Closes #41696
Closes #40193