Skip to content

feat: v2.1.0 — single-instance signal + Memory card layout polish#28

Merged
Layellie merged 16 commits into
mainfrom
v2.1.0
Jun 28, 2026
Merged

feat: v2.1.0 — single-instance signal + Memory card layout polish#28
Layellie merged 16 commits into
mainfrom
v2.1.0

Conversation

@Layellie

Copy link
Copy Markdown
Owner

Summary

Bundled minor release replacing what would otherwise have been two patch bumps. First release under the new "batch fixes, don't ship per-commit" cadence.

  • Single-instance signal. Relaunching the app while it's in the tray no longer pops a dialog — the secondary process opens the primary's named EventWaitHandle, grants ASFW_ANY foreground rights, pulses Set(), and exits silently. The primary's background listener dispatches to the UI thread and surfaces the window via ShowFromOffscreen() + SetForegroundWindow(hwnd). CTS-cancelled, disposed cleanly in OnExit.
  • Memory Stats card layout. Inner layout switched from a top-anchored StackPanel to a 3-row Grid (Auto / * / Auto). KPI block sits centered in the slack the sibling Standby Purge card forces on the row, and the Total / Free strip pins to the bottom — eliminates the ~40% dead band at the bottom of the box.
  • "X.X GB clearable" subtitle font 10 → 12 for legibility next to the 28pt KPI.

Files

  • App.xaml.cs + AppConstants.cs + NativeMethods.cs — single-instance plumbing (named EventWaitHandle, AllowSetForegroundWindow, SetForegroundWindow)
  • Views/Cards/MemoryStatsCard.xaml — layout / typography
  • StandbyAndTimer.csproj, installer/Setup.iss, winget-manifests/*.yaml — version bumped to 2.1.0, InstallerSha256 reset to placeholder (the release pipeline updates it after build-installer.ps1).

Release plan after merge

  1. .\build-installer.ps1 -Version 2.1.0 to produce the .exe and the SHA256.
  2. Cut GitHub release v2.1.0 with the installer attached.
  3. Follow-up tiny PR to fill the winget InstallerSha256 (matches the pattern used for every prior release).

Test plan

  • Release build clean (dotnet build -c Release) — 0 warnings / 0 errors.
  • Smoke test single-instance: primary running, second .exe launch → 3 sec later still one process, no MessageBox.
  • Manual: launch primary, minimize to tray, double-click shortcut → window surfaces to foreground.
  • Manual: visually verify Memory Stats card has no dead band at the bottom and clearable subtitle is comfortably readable.

🤖 Generated with Claude Code

Layellie and others added 16 commits June 28, 2026 12:37
Bundled minor release instead of two separate patches, per the new
"don't ship a release for every fix" cadence.

- Single-instance launcher: re-launching while the app is in the tray
  no longer pops an "already running" dialog. The secondary process
  signals a named EventWaitHandle, grants ASFW_ANY foreground rights,
  and exits silently; the primary's listener thread dispatches to the
  UI and surfaces the window from offscreen via SetForegroundWindow.

- Memory Stats card: switched the inner layout from a top-anchored
  StackPanel to a 3-row Grid (Auto / * / Auto) so the KPI block sits
  vertically centered in the cell and the Total / Free strip pins to
  the bottom — eliminates the ~40 % dead band that appeared when the
  sibling Standby Purge card stretched the row taller.

- Clearable subtitle FontSize bumped 10 → 12 for legibility next to
  the 28-pt KPI.

Version metadata bumped in csproj + Setup.iss + winget manifests
(InstallerSha256 reset to a placeholder; the release pipeline fills
it after build-installer.ps1 hashes the produced .exe).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The locale yaml shipped "License: Proprietary" while the repository
itself is MIT-licensed. The mismatch surfaced as confusing metadata
on the winget package page. Aligns with LICENSE; also adds
LicenseUrl so users can read the terms inline from winget output,
and fills in the missing copyright year.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Foundation for the next 11 tasks (hotkey + tray toggle + notify
prefs + idle-only auto-purge). Pure data + strings — no behavior
change yet; subsequent tasks wire the UI and runtime guards.

- AppSettings: 7 new fields with sensible defaults (notify flags
  default ON for upgrade parity, idle threshold 5 min, hotkeys
  Ctrl+Alt+P / Ctrl+Alt+T)
- SettingsService: persist and round-trip the new fields; ints
  via DWORD, hotkeys as REG_SZ so a future capture-textbox can
  edit them in-place
- Strings (en + tr): 10 new keys for Memory Purges KPI, idle-only
  controls, Notifications section, Hotkeys section
- Plan: docs/superpowers/plans/2026-06-28-v2.1.0-bundle.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- RegisterHotKey / UnregisterHotKey for system-wide chord routing
- GetLastInputInfo + LASTINPUTINFO struct for idle-gate auto-purge
- MOD_* flag constants + WM_HOTKEY message id

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bottom strip becomes Total | Free | Purges. PurgeCount binds via
the inherited MainViewModel DataContext; AppFg colour to avoid
competing with Total's accent yellow or Free's success green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The "× N" chip on the right of the header was asymmetric with the
left card and pushed the title off-centre. Count now lives on the
Memory card's bottom strip; the StandbyPurge header is just the
title, matching MemoryStats / GameMode / Timer header style.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- IIdleMonitor wraps GetLastInputInfo. Survives lock screen — the
  kernel keeps ticking, which matches Windows' "Away" semantics.
- IMemoryMonitorService gains AutoPurgeIdleOnly + IdleThresholdMs.
  Loop body gates the existing purge condition with: !IdleOnly ||
  TimeSinceLastInput >= IdleThresholdMs. Default OFF → no behaviour
  change for existing users on upgrade.
- MainViewModel exposes both as ObservableProperty; ApplySettings,
  SyncMonitorThresholds, BuildCurrentSettings, and partial change
  handlers all extended. Idle-threshold uses debounced persist
  (same TextBox-keystroke pattern as Standby / Free limits).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New row sits between the title and the AUTO PURGE master toggle.
Greyed via IsEnabled binding when AUTO PURGE is off (the gate has
no effect without the master switch). Minutes input mirrors the
Standby / Free limit pattern (Width=78, Height=26, FontSize=12)
for visual symmetry.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ITrayIconService grows ToggleRequested distinct from ShowRequested:
  Show is "always surface" (right-click menu); Toggle is "flip"
  (left-click / double-click).
- TrayIconService routes MouseClick.Left + DoubleClick to Toggle.
- MainWindow exposes IsOffscreen so App can decide which way to flip.
- App.ToggleMainWindow uses SurfaceMainWindow (foreground promote)
  when hidden, HideOffscreen when visible.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- App caches the latest AppSettings via MainViewModel.SettingsPersisted
  so balloon guards don't hit the registry on every event.
- Each of the three balloon handlers returns early when its
  NotifyOnX flag is false. Defaults stay ON, so users who never
  open Settings see no behaviour change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previous commit added an AppSettings? field but forgot to import
StandbyAndTimer.Core.Models, so Release build failed with CS0246.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- SettingsViewModel: 5 new [ObservableProperty] fields (3 notify
  flags + 2 hotkey display strings). Initialize signature grows
  to take all of them; partial change handlers fire SettingsChanged
  so PersistSettings runs on every toggle.
- MainViewModel.ApplySettings + BuildCurrentSettings round-trip
  the new SettingsViewModel state to/from AppSettings.
- SettingsPanelView: Notifications section (3 DarkCheckBoxStyle
  checkboxes) and Hotkeys section (read-only labels showing the
  current bindings; rebind UI parked for v2.2) inserted between
  Theme picker and Backup section. Row count grew 28 → 42 but the
  * Logs row absorbs the height — no overall layout shift, no
  outer scrollbar.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- HotkeyBinding record + Parse("Ctrl+Alt+P") covers Ctrl/Alt/Shift/Win
  modifiers, A-Z / 0-9 alphanumeric keys, and F1-F24. Returns null on
  malformed input so callers can log + fall back.
- IHotkeyService AttachTo(hwnd) + Register(id, binding, handler) +
  Unregister(id) + Dispose. Replaces prior registration for the same
  id so a future Settings rebind UI is safe.
- HotkeyService installs an HwndSource.AddHook that routes WM_HOTKEY
  to the matching handler. MOD_NOREPEAT is auto-OR'd into the mask so
  holding the chord doesn't fire the handler at keyboard-repeat rate.
- Registered in DI as a singleton.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- MainWindow.WindowHandle: exposes the HwndSource-backed HWND. Non-zero
  after Show() (or HideOffscreen, which internally Show()s), so it's
  available at the point App attaches HotkeyService.
- App.OnStartup: AttachTo(WindowHandle) then RegisterHotkeyFromSetting
  for both purge + timer. Hotkey strings come from saved settings so a
  power user can rebind them via the registry today; the Settings UI
  is read-only for v2.1.0.
- App.OnExit: dispose HotkeyService BEFORE the HWND tears down so the
  OS releases each RegisterHotKey cleanly — without this, a fast
  restart would fail with "hot key already registered."
- Handlers dispatch via Application.Current.Dispatcher.BeginInvoke to
  avoid racing the WPF binding system off-thread.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Each of the three KPI cells now anchors its label + value to the
matching side of its column (Left for Total, Center for Free,
Right for Purges) instead of all three flushing left. The row
breaks cleanly into thirds visually, matching the bottom-strip
pattern used in the rest of the dashboard.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
User found 12pt still small next to the 28pt KPI. Top margin
nudged 3 → 4 to keep the visual gap proportional.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Layellie Layellie merged commit 7df8388 into main Jun 28, 2026
1 check passed
@Layellie Layellie deleted the v2.1.0 branch June 28, 2026 11:28
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