fix(settings-ui): preserve Dashboard shortcut rows during refresh #50581

Open
niels9001 opened 7:46am on September 15, 2026 wants to merge 553 Ξ” into microsoft/powertoys main from
niels9001-stable-dashboard-shortcuts

Pull Request Overview

  • Opened on September 15, 2026
  • Status Open
  • Commit count 1 with first commit September 15, 2026

Total Delta

553 Total Diff Delta

Open Days

Open 4 weekdays

Test Delta

0 Diff Delta in Test Files
Breakdown by Phase

How long has this pull request spent in each phase of its lifecycle?

Fraction of total time Business days Phase
 
0.0 days Authoring 1 commit before pull request opened for review
 
0.4 days Awaiting first review
 
3.0 days Revising work with 0 commits in response to 0 reviews that left 1 comment

Total time for pull request still awaiting merge: 3.4 business days

Author avatar

fix(settings-ui): preserve Dashboard shortcut rows during refresh

Summary of the Pull Request

Preserve unrelated Dashboard shortcut-overview rows when a module such as Color Picker is toggled. RefreshShortcutModules previously cleared the collection and created every enabled row and inner collection again, including on echoed settings notifications. That reset causes unnecessary template and icon realization churn.

Reconcile the shortcut projection by ModuleType instead: keep existing rows, icon properties, and inner collections, and only add, remove, or move changed entries. An identical refresh is notification-free. This is an independent change from current main, not a stacked startup/lifetime PR.

PR Checklist


  • [ ] Closes: N/A - approved follow-up to an observed Dashboard flicker; no new issue filed.
    <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) -->

  • [x] Communication: I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected

  • [x] Tests: Added/updated and all pass

  • [ ] Localization: All end-user-facing strings can be localized - N/A, no new UI strings.

  • [ ] Dev docs: Added/updated - N/A, no developer-facing contract changes.

  • [ ] New binaries: Added on the required places - N/A, no new binaries or projects.



  • [ ] Documentation updated: If checked, please file a pull request on our docs repo and link it here: N/A.

Detailed Description of the Pull Request / Additional comments



  • src\settings-ui\Settings.UI\ViewModels\DashboardShortcutProjection.cs: data-only production reconciliation. The exposed collection owns the rows; there is no static or disabled-row cache. Removed/re-enabled modules may get a new row, but unaffected rows retain their identity. Source ordering, filtering, replacement objects with the same module type, metadata, callbacks, and inner-item changes remain authoritative. Programmatic enabled-state updates use UpdateStatus, not the user-toggle callback path.


  • src\settings-ui\Settings.UI\ViewModels\DashboardViewModel.cs: wires the helper into the actual UI-thread refresh path and removes the redundant ShortcutModules property rebind. Observable collection Count notifications still drive the empty state. The ActionModules clear/rebuild behavior is unchanged.


  • src\settings-ui\Settings.UI\ViewModels\DashboardModuleItem.cs and src\settings-ui\Settings.UI\SettingsXAML\Views\DashboardPage.xaml: equality-guarded label/shortcut/activation notifications and a one-way tooltip binding keep actual data changes visible in retained templates.


  • src\settings-ui\Settings.UI.UnitTests\ViewModelTests\DashboardShortcutProjectionTests.cs: 14 deterministic MSTest cases execute the production helper using only in-memory model data, without constructing the Settings app or real settings repositories/watchers.

Scope/risk: low-to-medium, concentrated in projection ordering/filtering, retained metadata bindings, empty-state notifications, and callback suppression. No icon deferral, hidden Quick Access deferral, global icon cache, animation changes, hotkey semantics, general settings notification changes, IPC/JSON, GPO/elevation, installer, or lifetime changes.

Merge attention: open #49891 also edits RefreshShortcutModules for lazy detail loading and removes ActionModules. This PR does neither and does not depend on it; resolve that textual overlap deliberately. It also does not include or depend on the closed, unmerged #50553 lifetime changes.

Validation Steps Performed


  • Built the affected Settings.UI.UnitTests x64 Debug dependency graph with tools\build\build.ps1, /m:2, and an explicit worktree SolutionDir. Final build exit code: 0.

  • Ran the existing MSTest/Microsoft.Testing.Platform executable with --filter FullyQualifiedName~DashboardShortcutProjectionTests: 14 passed, 0 failed, 0 skipped, exit 0.


  • 25 identical refreshes over three rows: zero outer/inner collection events, zero row property notifications, and stable row/icon/inner-collection/item identities.


  • 10 off/on cycles with an echoed no-op refresh after each transition: exactly 10 Remove + 10 Add, 20 Count notifications, no Reset or Move, and zero unrelated row-property or inner-collection notifications; unrelated row identities remain stable.

  • Additional cases cover initial filtering/order, source moves, keyed source replacement with updated metadata/items, notifying retained item bindings, final empty Count and repopulation, button-only filtering, separate owners, duplicate module rejection, and UpdateStatus callback suppression while preserving user callbacks.


  • Executed negative control: temporarily substituted the old Clear/new-row shortcut algorithm into the production helper. Both selected regressions failed (exit 2): the 25 identical refreshes emitted 100 collection changes, and the unrelated-row identity assertion failed on the first toggle cycle. Removed the temporary implementation, forced a scoped Settings rebuild to discard stale incremental output, rebuilt the test graph, and reran all 14 cases successfully.

  • Prepared same-worktree x64 Debug Runner, native Color Picker, ColorPickerUI, ColorPicker.ModuleServices, and QuickAccess.UI; every final project build exited 0. Missing dependency assets were restored only after actual build failures, using targeted restores. No packages, dependencies, or projects were added.

  • Built Settings with /p:GenerateSettingsSearchIndex=true and inspected the output assembly's PE metadata: embedded Microsoft.PowerToys.Settings.UI.Assets.search.index.json is 132,990 bytes.

Runner-backed functional comparison

Launched the actual candidate through its matching Debug Runner, with Color Picker's native module and app included. The running Settings DLL hash matched the tested artifact. Compared an off/on/off Color Picker cycle against the previously running a20497fd build, whose shortcut projection still uses the old reset algorithm:


  • Baseline: an unrelated Activation shortcut text element received a different UIA selector after each toggle transition.

  • Candidate 2bdc6357: the same unrelated text element retained its UIA selector throughout the cycle.

  • On both builds, Pick a color appeared only while Color Picker was enabled, and the original disabled state was restored afterward.

  • Captured and inspected the candidate's rendered Home screenshot. Settings snapshots were retained. No policy changes, clipboard operations, or injected global keyboard input were used for this comparison.

Live visual limit: this establishes live element continuity and functional projection updates, not frame-by-frame icon rendering. The two processes used different viewport/DPI geometry, so this is not a controlled pixel, timing, FPS, or Release-performance comparison. This PR does not claim per-icon disk reads or that every source of Dashboard flicker is eliminated. The candidate remains available for manual visual review; keep draft pending approval.

Comments Threads Pending Resolution

Resolved Comment Threads

No resolved comments have been left on this PR.