Skip to content

CmdPal: Multi-monitor support for dock#46054

Draft
michaeljolley wants to merge 14 commits intomainfrom
dev/mjolley/multi-monitor-dock
Draft

CmdPal: Multi-monitor support for dock#46054
michaeljolley wants to merge 14 commits intomainfrom
dev/mjolley/multi-monitor-dock

Conversation

@michaeljolley
Copy link
Contributor

@michaeljolley michaeljolley commented Mar 10, 2026

Adding multi-monitor support for dock. Users can enable dock on any monitor and determine where on each monitor it should display, or have them default to the overall position.

Closes: #45836

michaeljolley and others added 9 commits February 23, 2026 14:23
Refactor settings and app state persistence from data models into
service classes, improving separation of concerns and testability.

Changes:
- Add PersistenceService: shared generic JSON load/save with merge
  semantics, replacing duplicated logic in SettingsModel and
  AppStateModel
- Add SettingsService: owns settings lifecycle, file I/O, migrations,
  and the SettingsChanged event (previously on SettingsModel itself)
- Add AppStateService: owns app state lifecycle and StateChanged event
  (previously on AppStateModel itself)
- Add CmdPalLogger: ILogger adapter over ManagedCommon.Logger,
  replacing scattered Debug.WriteLine calls with structured logging
- Extract JsonSerializationContext into its own file
- Update all consumers to inject services instead of raw models
- Fix missing SettingsChanged unsubscription in
  AppearanceSettingsViewModel.Dispose()
- Fix SearchBar caching stale settings by subscribing to
  SettingsChanged

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Added orchestration logs for Morpheus, Trinity, Oracle (2026-03-10T15:43Z)
- Created session log for multi-monitor settings UI feature work
- Merged 4 inbox decisions into decisions.md (D-004 through D-007)
- Deleted inbox files after merge
- Updated cross-agent awareness in morpheus/trinity/oracle history.md files

Session Summary:
✅ DockMonitorConfigViewModel with SideOverrideIndex binding (0–4 index mapping)
✅ SettingsViewModel IMonitorService injection + RefreshMonitorConfigs()
✅ Monitors UI section with ItemsRepeater showing per-monitor controls
✅ 8 localization strings for monitor configuration
✅ 27 unit tests for multi-monitor dock logic (all passing)
✅ Fixed pre-existing build warnings (CS0169, SA1512)
✅ AOT discipline applied (no LINQ, partial keywords verified)

All ViewModels and UI projects build clean. Tests execute cleanly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix three bugs in multi-monitor dock support:

1. DockWindowManager: Pass config.Side (nullable) instead of
   config.ResolveSide() so EffectiveSide follows global setting
   dynamically when no per-monitor override is configured.

2. DockWindow constructor: Destroy and recreate AppBar after
   _targetMonitor and _sideOverride are set, since the base
   constructor positions the AppBar on the primary monitor before
   these fields are available.

3. UpdateSettingsOnUiThread: Use EffectiveSide instead of
   _settings.Side for edge comparison so it matches what
   UpdateWindowPosition actually uses.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Defer dock initialization from ShellPage constructor to Loaded event
  to ensure WinUI framework is fully materialized (Bug 1: startup)
- Subscribe DockWindowManager to SettingsService.SettingsChanged so
  SyncDocksToSettings runs when per-monitor configs or side changes
  (Bug 3: monitor toggle, Bug 4: position change)
- Add MonitorConfigReconciler to handle unstable GDI DeviceIds across
  reboots by falling back to IsPrimary matching and pruning orphans
  (Bug 2: persistence)
- Add IsPrimary field to DockMonitorConfig for stable matching key
- Add 6 unit tests for MonitorConfigReconciler
- Include remaining multi-monitor settings UI (DockSettingsPage,
  DockMonitorConfigViewModel, MonitorService, IMonitorService)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add _closed guard to DockWindow.UpdateSettingsOnUiThread to prevent
  accessing SystemBackdrop after the WinUI window has been torn down.
  Race: DockWindowManager closes the window via HideDocks, then the
  already-enqueued UpdateSettingsOnUiThread runs on disposed state.
- Refresh _sideOverride from MonitorConfigs in SettingsChangedHandler
  so EffectiveSide returns the current per-monitor side, not the stale
  construction-time snapshot.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Create ExitDockEditModeMessage and broadcast from button click
  handlers so Save/Discard closes the teaching tip on ALL dock
  windows, not just the local one
- Broadcast from click handlers only (not from ExitEditMode/
  DiscardEditMode) to prevent reentrancy infinite loop

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@michaeljolley michaeljolley added the Product-Command Palette Refers to the Command Palette utility label Mar 10, 2026
## Session Work (2026-03-10T15:43Z)

**Task:** Build DockMonitorConfigViewModel + update SettingsViewModel
**Outcome:** Created full ViewModel with SideOverrideIndex binding (0–4 index mapping to nullable DockSide for "inherit" semantics). Updated SettingsViewModel with IMonitorService injection and RefreshMonitorConfigs(). Both projects build clean. Fixed pre-existing CS0169/SA1512 build warnings that were blocking Oracle's tests.

Check failure

Code scanning / check-spelling

Forbidden Pattern

[pre-existing](#security-tab) matches a line_forbidden.patterns entry: "[Pp\]re[- \]existing". \(forbidden-pattern\)
- Test project: `Tests/Microsoft.CmdPal.UI.ViewModels.UnitTests` — MSTest + Moq, references ViewModels project
- Added `DockMultiMonitorTests.cs` with 27 tests covering `ScreenRect`, `MonitorInfo`, `DockMonitorConfig.ResolveSide`, `DockSettings` defaults, effective-config fallback logic, and `DockBandSettings` resolve pattern
- `DockWindowManager` is not directly unit-testable (creates `DockWindow` UI objects); tested the pure data logic it depends on instead
- Pre-existing build errors in ViewModels project (`CS0169` in SettingsViewModel, `SA1512` in DockMonitorConfigViewModel) block full test build — not caused by test code

Check failure

Code scanning / check-spelling

Forbidden Pattern

[Pre-existing](#security-tab) matches a line_forbidden.patterns entry: "[Pp\]re[- \]existing". \(forbidden-pattern\)
## Session Work (2026-03-10T15:43Z)

**Task:** Write unit tests for multi-monitor dock types
**Outcome:** Created DockMultiMonitorTests.cs with 27 tests covering ScreenRect, MonitorInfo, DockMonitorConfig.ResolveSide, DockSettings defaults, and DockBandSettings. Tests compile clean. Identified and reported pre-existing CS0169/SA1512 build errors; Morpheus fixed these. All 27 tests pass.

Check failure

Code scanning / check-spelling

Forbidden Pattern

[pre-existing](#security-tab) matches a line_forbidden.patterns entry: "[Pp\]re[- \]existing". \(forbidden-pattern\)
✅ Monitors UI section with ItemsRepeater showing per-monitor controls
✅ 8 localization strings for monitor configuration
✅ 27 unit tests for multi-monitor dock logic (all passing)
✅ Fixed pre-existing build warnings (CS0169, SA1512)

Check failure

Code scanning / check-spelling

Forbidden Pattern

[pre-existing](#security-tab) matches a line_forbidden.patterns entry: "[Pp\]re[- \]existing". \(forbidden-pattern\)
- **Decision:**
1. **Project boundary:** We ONLY work on projects listed in `src/modules/cmdpal/CommandPalette.slnf`. No other PowerToys modules may be touched.
2. **No commits to main:** Never commit directly to the `main` branch. All work must be on a feature branch.
3. **Branch naming:** `dev/mjolley/{branch-title-here}` — lowercase, hyphen-separated title describing the work.

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[mjolley](#security-tab) is not a recognized word. \(unrecognized-spelling\)
### D-003: Base branch is dev/mjolley/persistence
- **Date:** 2026-03-10
- **By:** Neo (directive from project owner)
- **Decision:** All multi-monitor dock work branches from `dev/mjolley/persistence`, NOT `main`. This branch has settings/app-state/personalization persistence changes we depend on. Feature branches for this work: `dev/mjolley/{feature-name}` created from this base.

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[mjolley](#security-tab) is not a recognized word. \(unrecognized-spelling\)
- **Date:** 2026-03-10
- **By:** Neo (directive from project owner)
- **Decision:** All multi-monitor dock work branches from `dev/mjolley/persistence`, NOT `main`. This branch has settings/app-state/personalization persistence changes we depend on. Feature branches for this work: `dev/mjolley/{feature-name}` created from this base.
- **Enforcement:** Before creating a feature branch, confirm current branch is `dev/mjolley/persistence`.

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[mjolley](#security-tab) is not a recognized word. \(unrecognized-spelling\)
3. **Any new list/collection type used in persisted settings must be explicitly registered in `JsonSerializationContext`.**
- **Enforcement:** All agents working on CmdPal must check for LINQ usage and missing `partial` keywords before marking work as done.

### D-006: ViewModels project had pre-existing build errors (RESOLVED)

Check failure

Code scanning / check-spelling

Forbidden Pattern

[pre-existing](#security-tab) matches a line_forbidden.patterns entry: "[Pp\]re[- \]existing". \(forbidden-pattern\)
3. When a `squad:{member}` label is applied, that member picks up the issue in their next session.
4. When `squad:copilot` is applied and auto-assign is enabled, `@copilot` is assigned on the issue and picks it up autonomously.
5. Members can reassign by removing their label and adding another member's label.
6. The `squad` label is the "inbox" — untriaged issues waiting for Lead review.

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[untriaged](#security-tab) is not a recognized word. \(unrecognized-spelling\)

### Code Style

<!-- Example: Linting, formatting, naming conventions -->

Check failure

Code scanning / check-spelling

Unrecognized Spelling

[Linting](#security-tab) is not a recognized word. \(unrecognized-spelling\)
Add per-monitor band customization so each monitor dock can show
different pinned commands. Monitors inherit global bands by default
and can be individually customized via the settings UI.

Changes:
- DockMonitorConfig: add IsCustomized, StartBands/CenterBands/EndBands,
  ForkFromGlobal(), and Resolve*Bands() helpers
- DockViewModel: convert from singleton to per-monitor instances with
  GetActiveBandLists() routing and EnsureMonitorForked() auto-fork
- DockWindowManager: create per-monitor DockViewModels, pass to windows
- DockWindow: accept DockViewModel as constructor parameter
- App.xaml.cs: remove singleton DockViewModel DI registration
- Settings UI: add Customize bands toggle per monitor with band toggles
- MonitorBandSettingsViewModel: per-monitor band pin/unpin ViewModel
- JsonSerializationContext: register DockBandSettings list type
- 14 unit tests covering band resolution, fork independence, reconciler

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@michaeljolley michaeljolley force-pushed the dev/mjolley/multi-monitor-dock branch 2 times, most recently from 106f771 to d5cedfd Compare March 10, 2026 21:10
- Move band initialization from DockViewModel constructor to
  DockWindowManager after window.Show() via InitializeBands(),
  avoiding ExecutionEngineException in AOT
- Remove per-monitor band list from settings UI — users manage
  bands through dock edit mode instead
- Keep Customize Bands toggle (label left, toggle right) to
  fork/unfork per-monitor band config from global
- Clean up unused BandItems, PopulateBandItems, GetAvailableBands,
  MonitorCustomizeBands_Toggled, and 3 RESW strings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@michaeljolley michaeljolley force-pushed the dev/mjolley/multi-monitor-dock branch from d5cedfd to 67c1afd Compare March 10, 2026 21:22
@niels9001
Copy link
Collaborator

/azp run

@azure-pipelines
Copy link

Azure Pipelines will not run the associated pipelines, because the pull request was updated after the run command was issued. Review the pull request again and issue a new run command.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-Command Palette Refers to the Command Palette utility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CmdPal: Multi-monitor support for dock

2 participants