Skip to content

Rank Start Menu shortcuts so the primary launcher wins - #2

Open
rodchristiansen wants to merge 1 commit into
mainfrom
fix/shortcut-resolver-ranking
Open

Rank Start Menu shortcuts so the primary launcher wins#2
rodchristiansen wants to merge 1 commit into
mainfrom
fix/shortcut-resolver-ranking

Conversation

@rodchristiansen

Copy link
Copy Markdown
Contributor

Resolving an app took the first Start Menu shortcut that matched, in directory enumeration order. That order is alphabetical, and an app's uninstaller, documentation link, safe-mode variant or reduced edition frequently sorts ahead of the launcher itself — so those got pinned instead.

Asked for Pinned before Pinned now
ZBrush Uninstall ZBrush … ZBrush …
Rhino Rhino … in Safe Mode Rhino …
Nuke Nuke … Documentation Nuke …
Houdini Houdini Apprentice … Houdini FX/Education …
Firefox Firefox Private Browsing Firefox
Visual Studio Code Adobe Media Encoder … Visual Studio Code

The Rhino/Nuke/Firefox rows are one comparison: the unwanted name continues with a space (0x20) where the launcher continues with the dot (0x2E) of its extension, and space sorts first.

The last row is a second, separate defect. Matching was a bare case-insensitive substring test, so the Code alias of Visual Studio Code matched Adobe Media Encoder and pinned an unrelated application.

What changed

ShortcutRanker scores a candidate on match quality — exact, at the start, or on a word boundary — then demotes shortcuts that

  • carry a marker word in the name (uninstall, documentation, safe mode, apprentice, trial, …), matched as whole words so Assist does not fire on Assistant; or
  • have a target that opens a document rather than a program (.html, .chm, .url, …), which catches a documentation shortcut whose name gives nothing away.

Ties go to the shortest name — the launcher, whenever a variant is a suffix of it. Penalties are sized so a demoted shortcut can never outrank a primary one; there is a test asserting that property directly rather than trusting the arithmetic.

Matching now requires a word boundary at the leading edge, so an alias no longer matches inside a longer word. A trailing digit is still allowed, so a version number running straight on (Photoshop9) keeps matching. Where this finds nothing, it now resolves nothing rather than falling back to a loose substring hit — pinning no app is better than pinning the wrong one.

An exact name match is returned as-is, so asking for a variant by its full name still works and no app becomes unpinnable.

AppResolver applies the ranking in both paths — the known-app lookup, which returned the first file matching any alias, and the broader search, whose confidence was flat across all non-exact matches. Reading a shortcut's target stays behind a pre-filter, so the COM call only happens for candidates that could plausibly win.

Verification

15 new unit tests, one per reported failure plus the boundary rules; 38 pass in total.

Confirmed against a real Start Menu, resolving Firefox where both shortcuts exist:

BEFORE  [Desktop] Mozilla Firefox
          Link: …\Start Menu\Programs\Firefox Private Browsing.lnk
          Exe:  C:\Program Files\Mozilla Firefox\private_browsing.exe

AFTER   [Desktop] Mozilla Firefox
          Link: …\Start Menu\Programs\Firefox.lnk
          Exe:  C:\Program Files\Mozilla Firefox\firefox.exe

Uninstall Go and Uninstall Node.js likewise drop below their real launchers in find output.

Resolving an app took the first Start Menu shortcut that matched, in
directory enumeration order. That order is alphabetical, and an app's
uninstaller, documentation link, safe-mode variant or reduced edition
frequently sorts ahead of the launcher itself, so those got pinned
instead:

  Uninstall <app>       beat  <app>          (U before the app's initial)
  <app> Apprentice      beat  <app> Education
  <app> in Safe Mode    beat  <app>
  <app> Documentation   beat  <app>
  Firefox Private Browsing  beat  Firefox

The last three are one comparison: the unwanted name continues with a
space (0x20) where the launcher continues with the dot (0x2E) of its
extension, and space sorts first.

Matching was also a bare case-insensitive substring test, so the "Code"
alias of Visual Studio Code matched Adobe Media En-code-r and pinned an
unrelated application.

ShortcutRanker scores a candidate on how well it matches -- exact, at the
start, or on a word boundary -- then demotes shortcuts whose name carries
a marker word (uninstall, documentation, safe mode, apprentice, trial and
similar) or whose target opens a document rather than a program. Ties go
to the shortest name, which is the launcher whenever a variant is a
suffix. Penalties are sized so a demoted shortcut can never outrank a
primary one.

Matching now requires a word boundary at the leading edge, so an alias no
longer matches inside a longer word. A trailing digit is still allowed so
a version number running straight on ("Photoshop9") keeps matching. Where
this finds nothing it now resolves nothing rather than falling back to a
loose substring hit -- pinning no app is better than pinning the wrong one.

An exact name match is returned as-is, so asking for a variant by its full
name still works and no app becomes unpinnable.

AppResolver applies the ranking in both paths: the known-app lookup, which
returned the first file matching any alias, and the broader search, whose
confidence score was flat across all non-exact matches. Reading a
shortcut's target is left behind a pre-filter so the COM call only happens
for candidates that could plausibly win.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant