Skip to content

fix(display): stable per-display keys so refresh/HDR/resolution prefs stop resetting#32

Merged
carterscode merged 1 commit into
mainfrom
fix/display-key-stability
Jun 3, 2026
Merged

fix(display): stable per-display keys so refresh/HDR/resolution prefs stop resetting#32
carterscode merged 1 commit into
mainfrom
fix/display-key-stability

Conversation

@carterscode

Copy link
Copy Markdown
Owner

Problem

Per-display settings (refresh rate, HDR, resolution) are keyed by the monitor's DevicePath. Windows sometimes reports that empty — capture cards, some HDMI/TV targets, or transient driver state — and the code then minted a fresh default DisplayPreference under the fallback key (FriendlyName|GdiDeviceName). Effects:

  • Saved per-display settings silently reset (e.g. a Fixed 240 Hz refresh target reverting to "Maximum"), which read as "upgrades reset my settings."
  • Duplicate entries piled up — the same physical monitor saved under two keys.

This is also the latent risk behind the refresh-rate-notification gap: a panel temporarily capped low by a driver glitch could lose its Fixed target.

Changes

  • New DisplayPreferenceResolver
    • Resolve(...): on a key miss, reuses an existing entry for the same physical monitor by re-keying it onto the current StableKeyonly when the label maps to exactly one active monitor, so two identical panels never share a pref block. Falls back to a default otherwise.
    • DedupeDisplays(...): collapses value-identical duplicate entries (keeps the real DevicePath key). Run from ConfigStore.Load, so existing configs self-heal.
  • Wired the resolver into RefreshRateMonitor, HdrMonitor, ResolutionMonitor, and SettingsWindow.LoadDisplays.
  • Hardened the Fixed-Hz target: the saved FixedHz is always included in the Settings dropdown even when the panel is momentarily capped low, so opening Settings during a glitch can't drop it.
  • Tests: DisplayPreferenceResolverTests (7 cases — exact hit, DevicePath-disappeared re-key, unknown display, identical-monitor ambiguity, dedupe collapse, dedupe-keeps-differing).

Verification

  • dotnet build — 0 warnings (TreatWarningsAsErrors), 0 errors.
  • dotnet test — 114 passed, 0 failed.

🤖 Generated with Claude Code

… stop resetting

Per-display settings were keyed by the monitor's DevicePath, which Windows
sometimes reports empty (capture cards, some HDMI/TV targets, transient driver
state). When that happened, the code minted a fresh default DisplayPreference
under the fallback key (FriendlyName|GdiDeviceName) — silently resetting saved
settings, e.g. a Fixed 240 Hz refresh target back to "Maximum". This also left
duplicate entries behind (the same monitor saved under two keys).

- Add DisplayPreferenceResolver: resolves a display's prefs by StableKey and,
  on a miss, reuses an existing entry for the same physical monitor by re-keying
  it — but only when the label maps to exactly one active monitor, so two
  identical panels never share a pref block. Falls back to a default otherwise.
- Add DedupeDisplays migration: collapses value-identical duplicate entries
  (keeping the real DevicePath key), run from ConfigStore.Load so old configs
  self-heal.
- Wire the resolver into RefreshRateMonitor, HdrMonitor, ResolutionMonitor and
  SettingsWindow.LoadDisplays.
- Harden the Fixed-Hz target: always include the saved FixedHz in the Settings
  dropdown even when the panel is momentarily capped low, so opening Settings
  during a driver glitch can't drop the target.
- Add DisplayPreferenceResolverTests (7 cases).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment on lines +63 to +69
foreach (var kv in displays)
{
if (kv.Value is null || kv.Value.DisplayLabel != label) continue;
if (activeKeys.Contains(kv.Key)) continue; // belongs to another active display
if (matchKey is not null) { ambiguous = true; break; }
matchKey = kv.Key;
}
Comment on lines +106 to +115
foreach (var sameValue in byValue)
{
var entries = sameValue.ToList();
if (entries.Count < 2) continue; // nothing to collapse

var canonical = PickCanonical(entries.Select(e => e.Key));
foreach (var e in entries)
if (!string.Equals(e.Key, canonical, StringComparison.Ordinal))
displays.Remove(e.Key);
}
Comment on lines +112 to +114
foreach (var e in entries)
if (!string.Equals(e.Key, canonical, StringComparison.Ordinal))
displays.Remove(e.Key);
@carterscode carterscode merged commit f14b371 into main Jun 3, 2026
5 checks passed
@carterscode carterscode deleted the fix/display-key-stability branch June 3, 2026 18:21
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.

2 participants