Repository navigation
fix(dpi): per-monitor DPI awareness, and isolate --clean's WebView2 folder - #175
Merged
Merged
Conversation
…older The app had no manifest, so WPF ran system-DPI aware: each process fixed its scale to the primary monitor's DPI at start and kept it for life. After a monitor, dock or scale change Windows bitmap-stretched the window. Observed: the installed app stuck at 150% on 100% monitors, and a dev build whose WPF chrome was correct while its terminals - rendered by the installed copy's shared WebView2 browser process - were 1.5x too large. - app.manifest declares PerMonitorV2 (WFO0003 suppressed: it recommends the WinForms API, which a WPF app never calls). - --clean uses its own WebView2-clean data folder. A per-monitor build cannot attach to a system-DPI copy's browser process: EnsureCoreWebView2Async fails with 0x8007139F. - LaunchSessionAsync now catches a WebView2 start failure and tears down like the PTY-failure path. Before, the exception escaped an unawaited launch: the pane spun forever and a new session stayed in SessionManager with no row. 0x8007139F gets a plain "close the other copy" message, and restore batches it with the access-denied case, leaving those sessions asleep in the sidebar. - Removed the dead per-session UserDataFolder (never used; the explicit environment wins). Tests: DpiAndWebView2FolderTests, including that the built exe embeds PerMonitorV2 (the pre-change build does not). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX
mortenaslo
added a commit
that referenced
this pull request
Nov 6, 2026
… relaunch, worktree remove safety (#182) * fix(explorer): open the right folder for forward-slash and comma paths "Open in Explorer" ran explorer.exe with the folder as raw command-line text, and Explorer silently opened Documents instead for two kinds of path — measured by asking the Shell which folder each new Explorer window showed: - forward slashes (C:/repos/wt-feat): the form git prints, and so the form a session opened through "New session in sibling worktree" — and now the Worktrees menu — stored as its WorkingFolder; - a comma in the name: Explorer splits its arguments on commas, and quoting the path does not help (also measured). Services/FolderOpener opens the folder itself through the shell (ShellExecute "open" on the full Windows path), which opened the right folder for every case tried: plain, spaces, comma, &, %, forward slashes, trailing separator, 8.3. It refuses anything that is not an existing directory first — ShellExecute on a FILE runs it, and a session's folder comes from state.json and imports — and runs off the UI thread (the check is \wsl$ I/O for WSL). Used by the toolbar 📁, the sidebar menu and note search results. LaunchSessionInSiblingWorktreeAsync now stores the Windows form of the git path, so new worktree sessions don't carry forward slashes at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX * fix: pre-release review — never delete a session on a failed relaunch; worktree remove safety From two read-only reviews of everything since v0.9.0. Each finding was reproduced before fixing. Relaunch (restore, wake) — data loss since before v0.9.0: - Both called LaunchSessionAsync with the default removeOnFailure: true, so a session whose launch was refused or whose PTY failed to start (WSL distro uninstalled, claude briefly off PATH) was DELETED, and the next save made it permanent. Wake's catch never ran: the PTY-failure path returns, not throws. Both now pass removeOnFailure: false and end in FallBackToDormantIfNotLive, which also covers every WebView2 failure on restore (#175 only covered the two batched ones). Verified before/after with a real state.json via CSM_STATE_PATH: old build — session gone from sidebar and file; fixed — kept, asleep. Worktree remove (#180): - Refused when another worktree lives inside the target. git's clean check skips ignored folders: with B in A's .gitignored .worktrees/ holding uncommitted work, `git worktree remove A` exited 0 and deleted B (reproduced with git 2.54). Checked against a list re-read at click time. - Sleeping sessions are deleted only after git removed the folder — they never go through Recently closed, so a refusal used to lose them for good. - WSL: \wsl.localhost folded into \wsl$ (otherwise no match at all), and below the distro paths compare case-sensitively (/home/u/WT is not .../wt). Not fixed here (follow-up issue): window bounds restore under per-monitor DPI on mixed-DPI setups, the busy-slot key before RepoRoot resolves, and ParseStatusV2 showing every unmerged entry as "UU". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the app per-monitor DPI aware (v2), so it re-scales properly when moved between monitors, docked/undocked, or when the Windows scale changes. Gives
--cleanruns their own WebView2 data folder, and makes a WebView2 start failure fail visibly instead of leaving a pane spinning forever.Why
The app had no manifest, so WPF ran system-DPI aware: each process fixed its scale to the primary monitor's DPI at the moment it started and kept it for life. Observed on a 3×100% setup:
Fixing DPI exposed the next problem: a per-monitor build cannot attach to a system-DPI copy's browser process —
EnsureCoreWebView2AsyncthrowsCOMException 0x8007139F. That exception escaped a launch nobody awaited, so it reachedcrash.logonly later from the finalizer as an unobservedTASK:, the pane spun forever, and a new session stayed saved instate.jsonwith no sidebar row.Implementation notes
app.manifest:PerMonitorV2, PerMonitor(+true/pmfallback).WFO0003is suppressed: it recommends WinForms'Application.SetHighDpiMode, which a WPF app never calls.Terminal/WebView2DataFolder: normal runs keep%AppData%\CodeShellManager\WebView2(renaming it would orphan every profile);--cleanusesWebView2-clean.LaunchSessionAsynccatches anyInitializeAsyncfailure and tears down like the PTY-failure path. New session → plain message ("close the other copy"); restore / wake / restart → session ends asleep in the sidebar. The restore loop batches0x8007139Fwith the existing access-denied case.UserDataFolderinMainWindow— the explicit environment always won, and no such folder ever existed on disk.--cleandocs now say what it does not isolate (output.db,crash.log).Test plan
DpiAndWebView2FolderTests, incl. that the builtCodeShellManager.exeembedsPerMonitorV2— verified the pre-change build does not.--cleandev build next to the 150%-stuck installed copy — terminal at true size, WebView2 up in 0.4s in its own folder.0x8007139Fcaught in 75ms, logged, message shown, no orphan pane or session.🤖 Generated with Claude Code
https://claude.ai/code/session_01NsYm9iLc2aRnRDfV2uZRQX