Skip to content

Fix shortcut drag-reorder and duplicate prevention - #2380

Merged
stvncode merged 4 commits into
music-assistant:mainfrom
dmoo500:fix/shortcut-identity-matching
Aug 11, 2026
Merged

stvncode merged 4 commits into
music-assistant:mainfrom
dmoo500:fix/shortcut-identity-matching

Conversation

@dmoo500

@dmoo500 dmoo500 commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes two shortcut-related bugs:

  1. Drag-reorder failure: Reordering shortcuts failed when the stored preference URI differed from the item's current URI
  2. Duplicate shortcuts: Same item could appear multiple times in sidebar due to identity mismatches

Changes

  • NavShortcuts.vue: Use findPinnedUriForItem() to look up actual stored URIs for drag-reorder (instead of constructing new ones)
  • useShortcuts.ts:
    • Introduce isUriMatchingItem() helper for provider-mappings-aware URI-to-item matching
    • Export findPinnedUriForItem() for components that need to resolve stored URIs
    • Update pinShortcutStandalone() to check identity match (not just exact URI)
    • Add Set-based deduplication in pinnedItems computed
    • Restore original findPinnedUriByIdentities() implementation to minimize diff

Testing

  • ✅ Drag-reorder shortcuts works correctly (even when stored URI differs from item URI)
  • ✅ No duplicate shortcuts appear in sidebar
  • ✅ Context menu shows correct pinned state
  • ✅ All useShortcuts tests pass (9 tests)

Related Issues

Fixes bugs identified by @stvncode in review.

Copilot AI lite review requested due to automatic review settings August 10, 2026 18:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 fixes shortcut “pinned item” identity matching so that provider-instance items (e.g., smart playlists pinned from recommendation rows) consistently resolve and appear in the sidebar. It aligns the shortcut system’s matching logic around provider mappings/identities instead of relying on direct URI equality in some code paths.

Changes:

  • Introduces a unified isUriMatchingItem() helper that matches a pinned URI against an item using identity matching via provider_mappings.
  • Replaces several URI-vs-item comparisons (watch filtering, pin/unpin flow, update event handling, sidebar resolution) to use the unified helper.
  • Removes the now-unused findPinnedUriByIdentities() helper and simplifies related exported helpers.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Moos, Daniel added 2 commits August 10, 2026 20:53
Replace inconsistent use of isSameShortcutUri() with a unified
isUriMatchingItem() helper that consistently checks provider_mappings
across all shortcut operations. This fixes an issue where shortcuts
pinned from recommendation rows (e.g., smart playlists) would not
appear in the sidebar.

The inconsistency caused the context menu to correctly recognize
pinned state (using getShortcutIdentities), while watch() and other
functions used direct URI matching (isSameShortcutUri), leading to
items being incorrectly filtered out.

Changes:
- Add isUriMatchingItem() helper for consistent identity matching
- Update watch(), pinItem(), unpinItem(), pinnedItems computed,
  and MEDIA_ITEM_UPDATED subscription to use identity matching
- Simplify isShortcutPinnedItem() and other exported functions
- Remove unused findPinnedUriByIdentities() helper
Replace inconsistent use of isSameShortcutUri() with a unified
isUriMatchingItem() helper that consistently checks provider_mappings
across all shortcut operations. This fixes an issue where shortcuts
pinned from recommendation rows (e.g., smart playlists) would not
appear in the sidebar.

The inconsistency caused the context menu to correctly recognize
pinned state (using getShortcutIdentities), while watch() and other
functions used direct URI matching (isSameShortcutUri), leading to
items being incorrectly filtered out.

Changes:
- Add isUriMatchingItem() helper for consistent identity matching
- Update watch(), pinItem(), unpinItem(), pinnedItems computed,
  and MEDIA_ITEM_UPDATED subscription to use identity matching
- Simplify isShortcutPinnedItem() and other exported functions
- Remove unused findPinnedUriByIdentities() helper
@dmoo500
dmoo500 force-pushed the fix/shortcut-identity-matching branch from e364ea1 to 405a8c4 Compare August 10, 2026 18:54
Copilot AI review requested due to automatic review settings August 10, 2026 18:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@stvncode stvncode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • Drag-reorder in NavShortcuts.vue (line 163–168) silently fails for shortcuts whose stored URI differs from the resolved item's URI
  • Duplicate-identity preference entries (legacy state from this very bug, or newly created via pinShortcutStandalone) render the same item twice with duplicate :keys via the new pinnedItems matching

@stvncode stvncode assigned stvncode and unassigned stvncode Aug 11, 2026
Addresses STVNCode's review comments:
- Export and use findPinnedUriForItem() in drag-reorder to work with actual preference URIs
- Fix pinShortcutStandalone() to check identity match, not just exact URI match
- Add deduplication to pinnedItems computed to prevent duplicate rendering
- Restore original findPinnedUriByIdentities() implementation for minimal diff from main
Copilot AI review requested due to automatic review settings August 11, 2026 15:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/composables/useShortcuts.ts:413

  • unpinItem removes resolvedItems using identity matching, but it still updates the stored pinnedUris via isSameShortcutUri(u, uri). If uri is an equivalent URI (e.g. provider instance/domain or a URI that only matches through provider_mappings), the preference entry may not be removed and the watch() will re-resolve/re-add the item, leaving the shortcut effectively pinned.
    await setPreference(
      PREF_KEY,
      pinnedUris.value.filter((u) => !isSameShortcutUri(u, uri)),
    );

Copilot AI review requested due to automatic review settings August 11, 2026 15:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/composables/useShortcuts.ts:288

  • findPinnedUriForItem is now exported and used for reorder operations, but there is no test coverage for its identity matching behavior (especially around provider_mappings/provider-instance vs provider-domain). Since there are already standalone helper tests for this module, adding a focused test would help prevent regressions.
export function findPinnedUriForItem(
  item: ShortcutItem | ItemMapping,
): string | null {
  const identities = getShortcutIdentities(item);
  return findPinnedUriByIdentities(identities, _getPinnedUris());

src/composables/useShortcuts.ts:407

  • The new identity-based matching can cause multiple pinned URIs that represent the same underlying shortcut (e.g. encoded vs raw, provider-domain vs provider-instance) to collapse to a single displayed item later (see pinnedItems dedupe). However, pinnedUris still retains duplicates, which can inflate the shortcut count (MAX_SHORTCUTS/cap checks) and make it impossible to pin new items even though fewer are shown. Consider normalizing/deduping pinnedUris on change using the same parsed identity key (decoded itemId + base provider).
  watch(pinnedUris, async (newUris) => {
    const currentItems = resolvedItems.value;

@dmoo500

dmoo500 commented Aug 11, 2026

Copy link
Copy Markdown
Contributor Author

Fixed both issues:

  1. Drag-reorder: Now exports findPinnedUriForItem() so NavShortcuts can look up the actual stored preference URIs (instead of constructing new ones from the resolved item). This ensures reordering works even when the stored URI differs from the item's current URI.

  2. Duplicate shortcuts:

    • pinShortcutStandalone() now checks identity match (provider_mappings aware) instead of just exact URI match
    • pinnedItems computed uses a Set to deduplicate before rendering

Also restored the original findPinnedUriByIdentities() implementation from main to minimize the diff.

@dmoo500 dmoo500 changed the title Fix shortcut identity matching for provider instances Fix shortcut drag-reorder and duplicate prevention Aug 11, 2026

@stvncode stvncode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @dmoo500 🙏

@stvncode
stvncode merged commit b5145c8 into music-assistant:main Aug 11, 2026
5 checks passed
musicassistant-bot Bot added a commit to music-assistant/server that referenced this pull request Aug 12, 2026
Update music-assistant-frontend to version
[2.17.268](https://github.com/music-assistant/frontend/releases/tag/2.17.268)


## 🚀 Features and enhancements

- Compact the mobile player in settings (by @marcelveldt in
[#2428](music-assistant/frontend#2428))
- Add a radio group rendering for expanded config entry options (by
@arturpragacz in
[#2401](music-assistant/frontend#2401))
- Give the player list, group and volume panels more room (by
@marcelveldt in
[#2410](music-assistant/frontend#2410))
- Keep the fullscreen player open when picking a player (by @marcelveldt
in [#2406](music-assistant/frontend#2406))
- Consistent settings screens and a save button that stays clear of the
player bar (by @marcelveldt in
[#2407](music-assistant/frontend#2407))
- Add Milkdrop visualizer plugin (by @jozefKruszynski in
[#2340](music-assistant/frontend#2340))
- Add ability to collapse collections in the author / narrator details
view (by @fmunkes in
[#2351](music-assistant/frontend#2351))

## 🐛 Bugfixes

- Keep the mobile save button close to the player (by @marcelveldt in
[#2429](music-assistant/frontend#2429))
- Keep the mobile menu clear of phone controls (by @marcelveldt in
[#2427](music-assistant/frontend#2427))
- Keep the settings save button above the mobile player (by @marcelveldt
in [#2425](music-assistant/frontend#2425))
- No more keyboard popping up over short filter lists (by @marcelveldt
in [#2419](music-assistant/frontend#2419))
- Apply the mobile sidebar side setting right away (by @marcelveldt in
[#2423](music-assistant/frontend#2423))
- Stop the "items selected" box floating too far above the player bar
(by @marcelveldt in
[#2424](music-assistant/frontend#2424))
- Don't open the on-screen keyboard when a search list opens (by
@marcelveldt in
[#2420](music-assistant/frontend#2420))
- Let the group volume popout scroll instead of running off screen (by
@marcelveldt in
[#2418](music-assistant/frontend#2418))
- Remember the last selected player when opening from Home Assistant (by
@marcelveldt in
[#2417](music-assistant/frontend#2417))
- Stop the "items selected" box covering the player menus (by
@marcelveldt in
[#2412](music-assistant/frontend#2412))
- Don't open the on-screen keyboard when a search dialog opens (by
@marcelveldt in
[#2411](music-assistant/frontend#2411))
- Keep the selection bar and playback speed dialog clear of the player
bar (by @marcelveldt in
[#2408](music-assistant/frontend#2408))
- Fix shortcut drag-reorder and duplicate prevention (by @dmoo500 in
[#2380](music-assistant/frontend#2380))
- Fix podcast episode played/unplayed state in the episode list (by
@OzGav in
[#2238](music-assistant/frontend#2238))
- Fix scrolling to the last players in the player list (by @marcelveldt
in [#2397](music-assistant/frontend#2397))
- Keep the mobile menu bar clear of the phone's navigation buttons (by
@marcelveldt in
[#2396](music-assistant/frontend#2396))

## 🧰 Maintenance and dependency bumps

<details>
<summary>20 changes</summary>

- Document the app's z-index scale (by @marcelveldt in
[#2422](music-assistant/frontend#2422))
- Clean up duplicate import in the player settings screen (by
@marcelveldt in
[#2421](music-assistant/frontend#2421))
- Announce the player list to screen readers wherever it is opened from
(by @marcelveldt in
[#2415](music-assistant/frontend#2415))
- Share one advanced settings toggle across the settings screens (by
@marcelveldt in
[#2414](music-assistant/frontend#2414))
- Make the player bar popout stacking independent of CSS load order (by
@marcelveldt in
[#2413](music-assistant/frontend#2413))
- Remove unused reset-to-defaults confirmation text (by @marcelveldt in
[#2416](music-assistant/frontend#2416))
- Add a test for the popout spacing above the mobile player bar (by
@marcelveldt in
[#2409](music-assistant/frontend#2409))
- Remove unused mobile code from the player bar volume control (by
@marcelveldt in
[#2404](music-assistant/frontend#2404))
- Remove an unused style rule from the mobile player bar (by
@marcelveldt in
[#2403](music-assistant/frontend#2403))
- Limit the player bar popout spacing to mobile (by @marcelveldt in
[#2405](music-assistant/frontend#2405))
- Remove unused PWA mode flag (by @marcelveldt in
[#2402](music-assistant/frontend#2402))
- Migrate the DSP pipeline, toolbars and dialogs to shadcn (by @OzGav in
[#2198](music-assistant/frontend#2198))
- Lokalise translations update (by
@[github-actions[bot]](https://github.com/apps/github-actions) in
[#2383](music-assistant/frontend#2383))
- Bump eslint-plugin-vue from 10.9.2 to 10.10.0 (by
@[dependabot[bot]](https://github.com/apps/dependabot) in
[#2394](music-assistant/frontend#2394))
- Bump marked from 18.0.7 to 18.0.9 (by
@[dependabot[bot]](https://github.com/apps/dependabot) in
[#2390](music-assistant/frontend#2390))
- Bump vue-i18n from 11.4.7 to 11.4.8 (by
@[dependabot[bot]](https://github.com/apps/dependabot) in
[#2389](music-assistant/frontend#2389))
- Update terminology from 'chain' to 'pipeline' (by @OzGav in
[#2395](music-assistant/frontend#2395))
- Bump sass from 1.101.0 to 1.102.0 (by
@[dependabot[bot]](https://github.com/apps/dependabot) in
[#2387](music-assistant/frontend#2387))
- Bump swiper from 14.0.5 to 14.1.0 (by
@[dependabot[bot]](https://github.com/apps/dependabot) in
[#2386](music-assistant/frontend#2386))
- Bump vue from 3.5.39 to 3.5.41 (by
@[dependabot[bot]](https://github.com/apps/dependabot) in
[#2385](music-assistant/frontend#2385))
</details>


## 🙇 Contributors

@arturpragacz, @dmoo500, @fmunkes, @jozefKruszynski, @marcelveldt,
@OzGav, @claude, @Copilot and Moos, Daniel

Co-authored-by: musicassistant-bot[bot] <304008617+musicassistant-bot[bot]@users.noreply.github.com>
@dmoo500
dmoo500 deleted the fix/shortcut-identity-matching branch August 12, 2026 04:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants