Skip to content

[CmdPal] Per-provider search weight (Lower/Normal/Higher) - #49191

Open
Michael Jolley (michaeljolley) wants to merge 3 commits into
dev/mjolley/frecency-redesignfrom
dev/mjolley/provider-weighting
Open

[CmdPal] Per-provider search weight (Lower/Normal/Higher)#49191
Michael Jolley (michaeljolley) wants to merge 3 commits into
dev/mjolley/frecency-redesignfrom
dev/mjolley/provider-weighting

Conversation

@michaeljolley

@michaeljolley Michael Jolley (michaeljolley) commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Warning

This PR is one in a series of PRs focused on rearchitecting the search/scoring logic of the MainListPage. An explanation of the entire search/scoring logic can be found in the first PR of the change (#49189).

This PR should not be merged until PR #49194 is merged into it.

Phase 3 of the stacked CmdPal search overhaul. Based on and targeting dev/mjolley/frecency-redesign (phase 2), NOT main.

What

Adds a per-provider Search weight (Lower / Normal / Higher, default Normal) that nudges main-page search results within their relevance tier only. Tier always dominates; the weight can reorder near-ties but can never move an item across a tier boundary.

Changes

  • ProviderSettings: new ProviderSearchWeight enum (Lower = -1, Normal = 0, Higher = 1) + SearchWeight property. Missing/legacy JSON deserializes to Normal.
  • MainListRanker: ProviderWeightBonus = 5.0 constant and ProviderBonus(weight) mapping (signed magnitude). Half a point of lexical quality (LexicalScale = 10), so it only breaks near-ties.
  • MainListPage.ScoreTopLevelItem: resolves each item's provider weight (top-level commands by their provider id, installed apps by the well-known AllApps provider) and feeds it into the within-tier score. Backward-compatible optional parameter.
  • ProviderSettingsViewModel + ExtensionPage.xaml: a Lower/Normal/Higher ComboBox per provider (shown for built-ins and apps too). New resw strings. Ran applyXamlStyling.

Design constant

  • Provider bonus per level: +/- 5.0 points (Higher = +5, Normal = 0, Lower = -5), vs LexicalScale = 10 per lexical point and FrecencyScale = 1. Modest by design.

Tests (all green, x64 host)

  • Provider weight reorders within a tier; never crosses a tier boundary (even Higher + heavy history vs a Lower exact match).
  • Default Normal == the previous provider-unaware behavior (no-op).
  • Applies to app-style (non-TopLevel) items.
  • Serialization round-trips; legacy JSON without the property defaults to Normal.
  • Build CommandPalette.slnf x64/Debug = exit 0. ViewModels 157/157. All other CmdPal unit suites pass (Registry + WindowWalker not run - known pre-existing non-green).

Atomic to phase 3; host-side only, no extension SDK changes.

<value>Search weight</value>
</data>
<data name="Settings_ExtensionPage_SearchWeight_SettingsCard.Description" xml:space="preserve">
<value>Nudge this provider's results up or down among equally-relevant matches. It never overrides a stronger text match.</value>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we can probably drop "It never overrides a stronger text match.". that feels redundantly verbose

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Dropped that sentence, so the description now just reads "Nudge this provider's results up or down among equally-relevant matches."

// Per-provider weight is a within-tier nudge only. Resolving it here (rather than in
// the tier classifier) guarantees it can never promote an item across a tier boundary.
var providerWeight = providerWeightLookup?.Invoke(topLevelOrAppItem) ?? ProviderSearchWeight.Normal;
var providerBonus = MainListRanker.ProviderBonus(providerWeight);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yea this is another case of what I mentioned over in #49189 - we're like, going into MainListRanker to get a value, then just passing it back into MainListRanker. Feels like it should all be in MainListRanker

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same theme as the MainListRanker note on #49189. I'm keeping ProviderBonus as its own public method on the ranker because it's independently unit tested (ProviderWeightingTests and RelevanceHarnessTests assert the Lower < Normal < Higher mapping directly). Folding it into WithinTierScore would remove that tested seam, so I'd rather keep the map then combine split. Happy to revisit as part of the same "move it all into MainListRanker" cleanup.

Michael Jolley (michaeljolley) added a commit that referenced this pull request Aug 6, 2026
…red ranker (#49189)

>[!WARNING]
> This PR is one in a series of PRs focused on rearchitecting the
search/scoring logic of the `MainListPage`. An explanation of the entire
search/scoring logic can be found below.
> 
> **This PR should not be merged until PR #49190 is merged into it.**

>[!NOTE]
> To test the final result, run the branch associated with PR #49249.

This stack rebuilds how Command Palette ranks and displays results on
its main page.

Strong text matches now consistently appear above weaker ones. Usage
history and provider preferences can improve ordering between similarly
relevant results, but they cannot push a poor match above an obvious
one.

The stack also makes search feel faster. Results appear without waiting
for slower providers, app scoring runs more efficiently, and weak
matches are hidden while the user has typed only one or two characters.
Automated tests protect the new behavior, while privacy conscious
telemetry measures performance and relevance without recording searches.

## Pull requests

1. [#49189](#49189)
introduces the new ranking foundation. Results are grouped by match
strength, ensuring exact names, prefixes, and acronyms rank above loose
fuzzy matches.

2. [#49190](#49190) improves
how Command Palette learns from command usage. Recent and frequently
used commands receive a sensible boost, and that history now persists
across restarts.

3. [#49191](#49191) lets
users give each provider a Lower, Normal, or Higher search preference.
This preference helps resolve close matches without overriding result
relevance.

4. [#49194](#49194) makes the
first set of results appear sooner. Commands and apps are shown
immediately, while slower fallback results are added when they become
available.

5. [#49195](#49195) adds a
comprehensive relevance test suite. It verifies that common searches
return the expected results and protects ranking quality from future
regressions.

6. [#49197](#49197) adds
privacy conscious search telemetry. It measures result counts, response
time, and which result position was selected without recording search
text, result names, paths, or other user content.

7. [#49246](#49246) adds a
performance measurement suite. It identifies where search time is spent
and provides a reliable way to evaluate performance improvements.

8. [#49247](#49247) delivers
the main performance improvement. App results are scored in parallel and
expensive work no longer blocks rendering, while the final result order
remains unchanged.

9. [#49249](#49249) prevents
misleading results from flashing when a search begins. For one or two
character searches, weak fuzzy app matches remain hidden until the query
is specific enough to produce useful results.

> [!WARNING]
> These PRs should be merged in LIFO order starting with #49249 with
this PR being the last.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add a per-provider SearchWeight (Lower/Normal/Higher, default Normal) that
nudges main-page search results within their relevance tier only. The nudge is
a small additive within-tier bonus (+/- 5 points, half a point of lexical
quality) so it breaks near-ties but can never move an item across a tier
boundary. Applies to installed apps too via the well-known AllApps provider.

- ProviderSettings: new ProviderSearchWeight enum + SearchWeight property;
  legacy/missing JSON deserializes to Normal.
- MainListRanker: ProviderWeightBonus constant + ProviderBonus() mapping.
- MainListPage.ScoreTopLevelItem: resolves each item's provider weight and
  feeds it into the within-tier score.
- ProviderSettingsViewModel + ExtensionPage.xaml: a Lower/Normal/Higher
  ComboBox per provider.
- Tests: within-tier reorder, never crosses a tier boundary, Normal is a
  no-op, applies to app items, serialization round-trip + legacy default.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A per-provider SearchWeight change only affected new queries: scoring reads the
weight live but scored results are cached, so an already-settled query kept its
old order until the next keystroke. HotReloadSettings now detects a provider
weight change and re-scores the active query in place.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5459b847-afeb-4163-a803-977759dd92df
Addresses review feedback: the 'It never overrides a stronger text match.'
sentence was redundantly verbose next to the existing description.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 49185697-186e-406f-b081-8c985c134274
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

0.101 Product-Command Palette Refers to the Command Palette utility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants