Skip to content

fix: v0.10.0 review follow-ups — window bounds under per-monitor DPI, git busy key, unmerged/quoted status (#181) - #185

Merged
mortenaslo merged 4 commits into
mainfrom
fix/181-review-followups
Nov 6, 2026
Merged

mortenaslo merged 4 commits into
mainfrom
fix/181-review-followups

Conversation

@mortenaslo

Copy link
Copy Markdown
Contributor

Summary

Follow-ups from the v0.10.0 pre-release review.

1. Window bounds restore under per-monitor DPI v2

  • Applied before the window is shown. The constructor reads the window fields of state.json synchronously (StateService.TryReadWindowState), and OnSourceInitialized applies them with SetWindowPlacement, in physical pixels.
  • No size drift between runs. If the placement changed the window's DPI, it is applied a second time so the saved size is exact. A restored normal window is then checked and corrected with SetWindowPos.
  • New AppState.LastPlacement stores physical pixels plus the DPI of the monitor the rect is on:
    • Normal windows save GetWindowRect, so a snapped window comes back snapped, as before.
    • Maximized and minimized windows save rcNormalPosition.
    • The offset and DPI always come from the rect's own monitor.
  • Off-screen fallback. If the saved position isn't reachable (monitor unplugged, caption under the taskbar), the window moves to the nearest monitor's work area. Reachable positions are left alone. The logic is in the WPF-free WindowPlacementMath.
  • Backward compatible. LastNormalBounds is still written. When there is no LastPlacement, it is read by trying each monitor's DPI as the scale: v0.10.0 saved DIPs per the window's own monitor, older builds DIPs per the primary monitor.

2. Git busy-slot key before RepoRoot resolves

  • The slot is claimed with a key resolved via SessionViewModel.EnsureRepoRootAsync. That runs off the UI thread and never caches a root for a folder that has since changed.
  • GetRepoRootAsync now returns one spelling per repo: canonicalised, and with a WSL worktree's Linux path mapped to \wsl$.
  • Two panes or worktrees of one repo can no longer run git at the same time.

3. ParseStatusV2

  • Unmerged entries show their real code (AA, DU, UD, AU, UA, DD, UU) instead of always UU.
  • Paths git C-quotes even with core.quotePath=false are unquoted, including octal escapes as UTF-8.

Review

A read-only review of the first commit found two problems, both fixed in the second commit:

  • The save took the DPI from the window's current monitor rather than the rect's own. For snapped windows that would bring the drift back.
  • The legacy fallback assumed DIPs per the primary monitor, but v0.10.0 saved them per the window's own monitor.

Tests

822 unit tests pass (57 new):

  • WindowPlacementMathTests: per-monitor offset and DPI round trips, saving a rect that sits on another monitor, legacy v0.10.0 and system-DPI bounds.
  • StateServiceTests, BusyKey and RepoRootFromCommonDir (local and WSL).
  • A real-git test: a main checkout and a linked worktree resolve the same busy key.
  • Real-git tests for a merge with UU/AA/DU/UD conflicts and for C-quoted index names.

Manual verification still needed (multi-monitor, not yet done)

  • Mixed DPI (100% primary, 150% secondary): normal and maximized windows on the secondary reopen there at the identical size across restarts.
  • Maximized stays maximized, including after minimizing.
  • A snapped window reopens in its snapped area.
  • With the secondary unplugged, the window reopens visible on the primary.
  • Upgrading from a v0.10.0 state.json with the window on the secondary puts it on the right monitor at the right size.
  • Taskbar docked top or left: the position is unchanged.
  • First run still opens maximized, with no startup flash.
  • Right after launch, Fetch on two panes of one repo: the second gets the "Git busy" toast.

Closes #181

🤖 Generated with Claude Code

https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX

mortenaslo and others added 4 commits November 6, 2026 16:51
…ey, unmerged/quoted status (#181)

- Window bounds: read the window fields of state.json synchronously in the
  constructor and apply them in OnSourceInitialized with SetWindowPlacement,
  before the window is shown, in physical pixels (new AppState.LastPlacement
  + DPI). Re-apply once if the move changed the window's DPI so the saved size
  is exact in mixed-DPI setups. Visibility/fallback decided against monitor work
  areas by the WPF-free WindowPlacementMath; legacy DIP bounds still honoured.
- Git busy slot: claim it with a key resolved via EnsureRepoRootAsync (off the
  UI thread) instead of falling back to the working folder before RepoRoot
  lands; keys folded by GitWorktrees.BusyKey.
- ParseStatusV2: unmerged entries keep their real XY code; C-quoted paths are
  unquoted (escapes, octal UTF-8 bytes), renames split before unquoting.

Closes #181

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX
…able repo-root keys

- Window bounds: a Normal window saves GetWindowRect (a snapped window's
  rcNormalPosition is its pre-snap rect); maximized/minimized save
  rcNormalPosition. Offset and DPI now come from the monitor the rect is on
  (WindowPlacementMath.SaveFromScreen / SaveFromWorkspace), not
  MonitorFromWindow. A restored Normal window is verified with GetWindowRect
  and corrected with SetWindowPos.
- Legacy LastNormalBounds: try every monitor's DPI as the scale and keep the
  one whose scaled rect lands on that monitor (v0.10.0 saved per-monitor DIPs);
  primary breaks ties.
- EnsureRepoRootAsync no longer caches a root probed for a folder that changed
  during the await.
- GetRepoRootAsync: map a Linux-absolute --git-common-dir from a WSL worktree
  to \wsl$\<distro>\..., and canonicalise the result so 8.3/junction spellings
  of one repo share a busy-slot key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX
Conflicts: CLAUDE.md (kept the window-bounds paragraph and the new Bundled
ConPTY section) and MainWindow.OnLoaded (kept PreferBundledConPty; the old
RestoreWindowState call stays removed — bounds are applied before show).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX
Conflict in MainWindow.OnLoaded: kept EnsureOpenWithSeeded(); the old
RestoreWindowState call stays removed (bounds are applied before show).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX
@mortenaslo
mortenaslo merged commit 62be268 into main Nov 6, 2026
1 check passed
@mortenaslo
mortenaslo deleted the fix/181-review-followups branch November 6, 2026 18:20
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.

Follow-ups from the v0.10.0 pre-release review (DPI window bounds, git busy key, unmerged status codes)

1 participant