Pre-flight: WSL session support + Copilot-feedback fixes#1
Closed
Bitblade wants to merge 44 commits into
Closed
Conversation
…mage-ai#55 umage-ai#39 umage-ai#46 Six low-effort / high-impact changes bundled on one branch: umage-ai#53 Run-command output: strip CSI private-mode (e.g. ESC[?9001h) and OSC ST. The shared regex in RunInstance + OutputIndexer now accepts `?` in CSI params and recognises both BEL and ESC\ as OSC terminators. umage-ai#54 RunCommandItem.Mode — Process (default, cmd /c) vs PowerShell (pwsh -EncodedCommand UTF-16LE base64; falls back to powershell.exe). SSH runs ignore Mode — remote always goes through bash. umage-ai#55 RunCommandItem.PostRunUrl — opens URL via ShellExecute on exit code 0. Editor dialog grows Mode + URL columns; dialog widened 720→900. umage-ai#51 LayoutMode.ThreeByThree — new 3×3 (9-pane) grid layout and toolbar btn. umage-ai#39 PseudoTerminal MonitorExitAsync now waits on a DuplicateHandle so the Dispose path can close _hProcess without racing the wait. Eliminates the Win32 UB that could fire Exited before the child actually exited. umage-ai#46 Recently-closed ring buffer (cap 10, persisted to state.json): - new Models/RecentlyClosedEntry.cs - Ctrl+Shift+T pops & reopens the newest entry (browser convention) - duplicate-session moves to Ctrl+Alt+T - "Recently closed" list at the top of the New Session dialog - FromSession deep-copies RunCommands with fresh Ids - --clean mode skips push, matching SaveStateAsync semantics Verification • dotnet build: 0 errors, 30 pre-existing warnings • dotnet test: 90/90 pass (was 82; +8 new tests covering BuildPwshArgs, RecentlyClosedEntry.FromSession, Subtitle, RunCommandItem defaults) CLAUDE.md updated: new keybindings, Recently Closed section, RunCommandItem data model with Mode + PostRunUrl semantics. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…SearchService 83 tests across four services that were untested: - AlertDetector: regex patterns for tool-approval / input-required prompts, ANSI stripping, idle-timer cancellation, NotifyUserInteracted - ColorService: FNV-1a determinism, palette distribution, hex format, case-insensitivity - OutputIndexer: CSI/OSC/keypad ANSI sequence stripping, CR handling - SearchService: FTS5 search, notes, history, prune/clear, usage stats (per-test temp SQLite for isolation)
- New IPseudoTerminal interface with the minimum surface RunInstance uses (DataReceived, Exited, ExitCode, Start) - PseudoTerminal implements it; no behavioral change to ConPTY code - RunInstance and SessionRunner gain an internal ctor accepting Func<IPseudoTerminal> factory; production ctors preserved so the WPF UI is untouched - 15 new SessionRunnerTests using a hand-rolled FakePseudoTerminal — covers Run/Stop/Dismiss lifecycle, kill-and-restart semantics, state transitions, and the 1MB output-buffer cap
- Services table grew from 6 to 20 rows; all files under src/CodeShellManager/Services/ are now represented (was ~30% coverage) - New "## Per-Session Notes" section documenting the project_notes SQLite table, lazy load + 1s debounce save, FTS5 search integration, and the folder-key consequence - One-line inline comment on AlertDetector's 1500ms idle constant explaining the choice (long enough for Claude's prompt redraw bursts to settle, short enough to feel responsive)
Fixes the eight issues identified as actionable in the PR review: - DuplicateSessionAsync now copies the Mode and PostRunUrl fields onto the cloned run-commands so duplicated sessions don't silently drop PowerShell mode and post-run URLs. - ResolvePwsh checks WaitForExit AND ExitCode before caching pwsh.exe, and kills probes that haven't exited so a hung probe can't poison the cache. - Recently-closed reopen now removes the entry from the ring only on launch success — adds PeekMostRecentlyClosed and a ReopenAndRemoveOnSuccessAsync wrapper that checks SessionManager membership as the success signal. - Boot overlay has an 8s fallback (PostBootDoneIfNeeded fires from a Task.Delay scheduled in NavCompleted) so silent sessions don't get stuck behind the spinner indefinitely. - SearchServiceTests seeds session_history rows with explicit unix timestamps instead of Task.Delay(5), eliminating clock-resolution flakiness on CI. - CodeShellManager.csproj copies Assets\app.png to publish output so the WiX manifest entry from the prior commit isn't dangling at install time. - PostRunUrl failures now log to crash.log instead of being silently swallowed. - SessionRunCommandsDialog Mode-combobox tooltip now accurately says "cmd /c <cmd>" instead of claiming Process mode is a direct ConPTY child. Tests: 206/206 pass.
- CodeShellManager.csproj: set MetadataUpdaterSupport=false in Debug to disable Hot Reload. F5 currently crashes with System.ExecutionEngineException immediately after Microsoft.Extensions.DotNetDeltaApplier.dll loads under .NET 10.0.8 + VS 18. Disabling metadata updates makes the delta applier a no-op so debugging works. Remove when the runtime bug is fixed upstream. - MainWindow.OnLoaded: --clean mode now also clears in-memory groups, not just sessions. SaveStateAsync is a no-op in --clean so this doesn't touch state.json; it just means leftover groups from prior debug sessions don't bleed into the isolated run. - CLAUDE.md: clarify the --clean description so it matches the new behavior.
Adds MainViewModel.ClearRecentlyClosed() and calls it from the --clean block in MainWindow.OnLoaded. SaveStateAsync is a no-op in --clean mode so this only affects in-memory state; state.json is untouched. Matches the existing session/group clearing behavior so --clean gives a truly blank slate.
Adds a shared VS launch profile so any contributor on .NET 10.0.8 + VS 18 doesn't hit the System.ExecutionEngineException crash that the Hot Reload delta applier triggers at F5 startup. "hotReloadEnabled": false stops VS from injecting Microsoft.Extensions.DotNetDeltaApplier.dll in the first place — the real fix. Csproj's MetadataUpdaterSupport=false remains as a belt-and-braces runtime switch for anyone with a divergent local profile. commandLineArgs="--clean" is also in the profile, matching the documented debug-isolation default in CLAUDE.md. Remove both when the .NET 10 / VS 18 runtime bug is fixed upstream.
…fruit feat(bundle): low-hanging fruit + tests, refactor, docs
CLAUDE.md: - New "Session Spinners" section covering the launch + shutdown overlays, the 8s silent-session fallback, and the OnClosing yield pattern. - New "Visual Studio: Hot Reload disabled" callout under Build & Run explaining the launchSettings.json + MetadataUpdaterSupport workaround for the .NET 10.0.8 + VS 18 ExecutionEngineException crash. - Architecture > Key layers: PseudoTerminal entry now mentions the IPseudoTerminal seam so contributors see how RunInstance/SessionRunner get tested without spawning a real ConPTY. - Testing section: documents IPseudoTerminal injection, SearchService's per-test temp-SQLite + ClearAllPools pattern, and the timestamp-seeding rule (don't rely on Task.Delay for ordering). README.md: - Adds bullets for the visible features that were missing: sidebar groups, recently-closed (Ctrl+Shift+T), per-session run commands (F5), Windows Terminal profile import, and the launching/shutdown spinners. - Keyboard shortcuts table now lists Ctrl+Shift+T, Ctrl+Alt+T, Ctrl+Shift+Tab, F5, and Shift+F5.
docs: spinner design spec + doc refresh
Replaces the binary IsRemote flag with a three-valued SessionKind enum so a WSL session can live alongside Local and SSH ones — picking a distro from a combobox (auto-detected via `wsl -l -v`), pointing at a Linux working folder, and optionally pinning a -u user. wsl.exe is the launch process; PTY plumbing is unchanged. Legacy state.json that only carried IsRemote still deserializes cleanly: the IsRemote setter promotes Kind from Local to Ssh, so older files migrate on first load without bespoke conversion code. WSL sessions store their WorkingFolder as a `\\wsl$\<distro>\...` UNC view of the Linux path so Git for Windows, the "Open in Explorer" menu, and the sidebar's git-branch poll all keep working unmodified — git status, dirty state, and repo-root-based accent colors light up the same as for Local sessions. Run-commands (the F5 / chips strip) now also dispatch correctly inside WSL by wrapping the command in `wsl.exe -d ... -- bash -lc '<escaped>'`. The three IsRemote display-label / accent-key ternaries that were drifting across MainWindow + SessionViewModel are consolidated onto three small helpers on ShellSession (DefaultDisplayName / FolderShort / AccentKey) so adding a fourth session kind in future doesn't require chasing call sites.
Mirrors the local Browse… button on the WSL panel. Opens FolderBrowserDialog rooted at \\wsl$\<selected-distro> (the WSL filesystem appears there as a native UNC share); on result, parses the picked path back to a Linux path and, if the user drilled into a different distro than the combo had, updates the combo too. ParseWslUncPath is extracted as an internal static so it can be covered headlessly via InternalsVisibleTo — accepts both the \\wsl$\ and the newer \\wsl.localhost\ prefixes, plus forward-slash variants.
Symmetry with IsRemote (which remains the SSH predicate). Keeps the "remote = ssh" convention intact — just gives WSL its own one-liner so call sites don't have to spell out `Kind == SessionKind.Wsl`.
Git for Windows can't reliably operate on \\wsl$\<distro>\... paths —
the dubious-ownership check refuses to run, and "not a git repo" pops
on perfectly valid repos. Detecting the WSL UNC in the GitService funnel
and dispatching to `wsl.exe -d <distro> -- git -C <linuxPath> <args>`
sidesteps both. Two small translators make the seam invisible:
TranslateUncArgsToLinux: rewrites \\wsl$\<distro>\foo tokens in the
arg string to /foo before invocation, so callers can keep passing
Windows-shaped paths (e.g. `worktree add <unc-target>`).
TranslateLinuxPathsToUnc: walks git stdout for absolute Linux paths
(rev-parse --git-common-dir, worktree list --porcelain) and rewrites
them back to UNC, so the rest of the app sees uniform paths.
Both translators are conservative about what they touch — `refs/heads/foo`
and `M README.md` are passed through untouched per tests.
Three improvements bundled because they're all about the picker landing somewhere useful: - Browse seed = the user's home inside the distro, resolved via `wsl -d <distro> [-u <user>] -- sh -c "cd ~ && pwd"` and cached per (distro, user) so repeated clicks don't re-shell. - Also set FolderBrowserDialog.InitialDirectory in addition to SelectedPath. SelectedPath alone left the COM dialog rooted at the user's last folder (typically Documents) and only pre-typed the UNC in the entry field — clicking Browse felt broken. - The WSL panel now has a visible "User (optional)" column header. Previously the user textbox was identifiable only by tooltip, which read as an empty unlabeled box.
The early-return guard "if NameBox not empty, leave alone" was too greedy: once we auto-filled the name from the first context, it preserved that stale value forever — switching distros wouldn't update the suggestion. Track our own last auto-fill so the guard fires only on truly-user-edited content. Empty box and "still shows our previous suggestion" are both treated as free-to-overwrite; anything else is preserved as user input.
…sion Right-click → "New session here" on a WSL session used to open the dialog in Local mode with the parent's `\\wsl$\…` UNC pre-filled into the working-folder textbox — a layer-cake of subtle wrongness. Now the dialog auto-selects the WSL radio, pre-fills user and Linux working folder from the parent in the constructor, and remembers the parent's distro so PopulateWslDistrosAsync can mark the right combo entry as selected once the async distro list lands.
PowerShell-here on a WSL parent still opens a Windows shell (PS handles the UNC path well enough as cwd, so it works), but the natural shell to ask for from a WSL session is bash inside the same distro. Add a sibling menu item that creates a fresh WSL session pointed at the parent's distro / user / Linux folder. Only shown when the parent IsWsl.
A WSL session whose user picked "New worktree from this branch" got a fresh session at the UNC path of the new worktree — but as a Local kind running PowerShell, so the parent's command (claude, codex, etc.) failed with "not recognized" inside a PS prompt at \\wsl$\<distro>\.... Same fix applies to "New session in sibling worktree" and the sibling- worktree checkbox in the New Session dialog: all three created child sessions without copying Kind/SSH/WSL fields, so a WSL parent quietly demoted its children to Local. Centralized in InheritSessionKindFrom, which also derives the child's WslWorkingFolder from its WorkingFolder UNC (the worktree path the caller built). DuplicateSessionAsync refactored to use the same helper — its inline copy logic was about to drift.
…starts clean Passing the seed UNC to both InitialDirectory and SelectedPath made the COM file dialog show the raw UNC in the bottom "Folder:" textbox — which Windows then renders as a truncated, slash-flipped tail (e.g. "bu/home/bitblade" for \\wsl$\Ubuntu\home\bitblade). InitialDirectory alone navigates the tree to the right place; SelectedPath was only making the visible textbox look broken.
…dlock GetDistrosAsync correctly drains both streams; GetDistroHomeAsync didn't. If wsl.exe writes enough to stderr (transient init notices, a stopped distro error) the child would block on its stderr buffer and the stdout await would never complete — silently falling through to the 3s timeout on every Browse click for WSL sessions. Per Copilot review.
The comment claimed Git for Windows handles WSL UNCs natively — but the preceding GitService routing through wsl.exe exists precisely because it doesn't. Comment now reflects the actual dispatch. Per Copilot review.
The previous guard "Kind != Local → return" was too conservative — RunCommandTemplatesService.SeedFor calls Directory.EnumerateFiles, which works fine on `\\wsl$\<distro>\…` UNCs, and RunInstance already runs WSL sessions' commands via `wsl.exe -- bash -lc`. So a WSL project with a package.json or Cargo.toml at its root now gets its templates the same way a Local one does. Only SSH stays opted out. Per Copilot review.
… WSL RecentlyClosedEntry only captured IsRemote + SSH fields. Closing a WSL session and reopening it via Ctrl+Shift+T (or the Recently Closed list in the New Session dialog) resurrected it as Local at the `\\wsl$\…` UNC path — same failure mode Copilot called out for the worktree path, just on a different code route. - Add Kind / WslDistro / WslUser / WslWorkingFolder to the entry record; FromSession copies them. - Mirror the IsRemote→Ssh migration shim on RecentlyClosedEntry too, so state.json files written before this commit (no Kind key) still render the right subtitle and reopen as SSH. - ReopenClosedSessionAsync copies Kind first (so a WSL entry doesn't get demoted by the IsRemote shim) and the WSL fields second. - Subtitle now keys on Kind so a WSL entry shows `<distro>: <path>`. Per Copilot review.
The unquoted regex stops at whitespace, so a quoted UNC like "\\wsl\$\Ubuntu\home\alice\my repo" (the shape `worktree add <target>` would produce for a Linux path with a space) used to be half-translated and yield a broken git command. Add a two-pass approach: first replace quoted runs (consuming the content up to the closing quote and re-quoting the Linux output), then fall back to the existing unquoted pass for arguments that never needed quoting in the first place. Per Copilot review.
ShellSession.BuildWslArgs, RunInstance.BuildWslArgs, and GitService's RunGitInWslAsync concatenated WslDistro/WslUser/WslWorkingFolder straight into a single argument string. Most distro names are space-free in practice, but Linux working folders genuinely can have spaces (`/home/alice/my proj`) and `wsl --cd` then sees two arguments. Add a conservative QuoteForCmd helper on ShellSession (internal, so tests reach it via InternalsVisibleTo): leaves space-free values alone so existing call sites and tests don't churn, and double-quotes anything that needs it (with embedded `"` escaped as `\"`). Not migrating to ProcessStartInfo.ArgumentList — PseudoTerminal's API takes a single command-line string, and reshaping it is out of scope for this PR. The quoting helper is the surgical fix. Per Copilot review.
If the user navigated out of `\\wsl$\` (e.g. into `C:\Users\…`) and picked there, we silently stuffed the Windows path into WslWorkingFolderBox — `wsl.exe --cd C:\…` then failed at session start with a confusing message. Show a clear MessageBox naming the picked path and explaining which folders are valid, and leave the textbox unchanged so the user can try again. Per Copilot review.
If the user picked a WSL distro but left the Linux Working Folder empty, the launcher omitted `--cd` (so the shell correctly landed in \$HOME) but ToUncPath produced `\\wsl\$\<distro>` — the distro root. GitService then keyed on that UNC, asked git for status at "/", and came back empty even when \$HOME was a real repo. Sidebar branch info silently disappeared. Make Start_Click async and call GetDistroHomeAsync (the cached lookup we already use for the Browse picker) when WslWorkingFolder is empty, so the session's WorkingFolder UNC and its Linux path stay aligned. Best-effort: if WSL is unreachable, fall through to the existing behavior (land in \$HOME, no git info). Per Copilot review.
GetDistrosAsync and GetDistroHomeAsync caught Win32Exception and FileNotFoundException only, but Process.Start can throw InvalidOperationException / PlatformNotSupportedException and the read pipeline can throw IOException — so the doc claim "Never throws — every failure mode collapses to an empty list" wasn't quite accurate. A stray exception here crashes the New Session dialog's Loaded handler, which is a worse outcome than "no WSL distros listed." Broaden the outer catch to Exception and document why. Per Copilot review (round 2).
The token splitter took tokens[idx] as the name, so a `wsl --import "My Distro" …` row was tokenized as name="My", state="Distro", version=0 (the actual "Running"/"Stopped" text isn't a digit, so int.TryParse silently failed). The picker would then list a phantom "My" distro and `wsl -d My` would error at session start. Switch to consuming from the trailing end of the line: VERSION is always the last token, STATE the second-to-last, and everything in between (after an optional leading `*` for the default-distro marker) is the name joined by spaces. Per Copilot review (round 2, suppressed comment).
Last unquoted wsl.exe-arg interpolation we missed in the earlier sweep. With Parse now accepting space-containing distro names, the launcher side has to be ready to receive one — without quoting, a "My Distro" distro would arrive as `-d My Distro` (two args to wsl.exe) and the home-resolution would silently fail. Per Copilot review (round 2, suppressed comment).
RunInstance.BuildWslArgs wrapped the command in POSIX single quotes via SingleQuoteEscape. But wsl.exe is started directly by CreateProcess (no outer shell), so Windows command-line tokenization runs first and only treats "..." as grouping. With single quotes, `bash -lc 'cargo test'` reached bash split at the space into the two args `'cargo` and `test'` — bash choked on the unbalanced quote and the run-command failed. Mirror the double-quote shape ShellSession.BuildWslArgs already uses: wrap the commandLine in `"..."` and escape embedded `"` as `\"`. The SSH variant keeps SingleQuoteEscape because the WHOLE bash command there is itself inside outer SSH double quotes — the structure is different, so the same trick would actually break it. Updates the existing tests to expect the double-quote shape and adds coverage for the embedded-double-quote case. Per Copilot review.
Same class of regex-stops-at-whitespace bug Copilot already flagged for TranslateUncArgsToLinux, on the return trip. A worktree path like `/home/alice/My Projects/repo` came back as `\\wsl$\Ubuntu\home\alice\My` with the rest left as forward-slashed garbage attached. All of our current callers (rev-parse --git-common-dir, worktree list --porcelain) emit the path as the full remainder of the line, so widening the tail to `[^\r\n'"<>|]+` (anything but newline / shell-meta) is safe and recovers space-containing paths. Comment documents the caller-coupled contract. Per Copilot review.
Two near-identical manual leaf-extraction loops were doing what System.IO.Path.GetFileName already does: trim trailing separators, return the segment after the last one, handle empty input. Path on Windows recognizes both `/` and `\` so it works fine for the Linux- style paths these call sites deal with. - ShellSession.DefaultDisplayName + BuildWslFolderShort drop the local LeafName helper. - NewSessionDialog.AutoFillName drops its inline copy. No behavior change; the two functions returned identical results for every input these call sites would ever see.
There was a problem hiding this comment.
Pull request overview
Adds first-class WSL session support and related UX/runtime improvements (run-command enhancements, spinners, recently-closed reopen), plus targeted fixes for WSL quoting and git path translation, with extensive new unit coverage to keep the new surfaces stable.
Changes:
- Introduces
SessionKind.Wsl+ WSL distro discovery, WSL path handling (UNC ↔ Linux), and WSL-aware git routing. - Expands run-commands with
RunMode(Process/PowerShell) and optionalPostRunUrlon success; adds a PTY test seam (IPseudoTerminal) and lifecycle tests. - Adds “Recently closed” session ring buffer + reopen flows, and launch/shutdown spinner overlays (WebView2 boot overlay + WPF shutdown overlay).
Reviewed changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/CodeShellManager.Tests/WslDiscoveryServiceTests.cs | Adds parsing/UNC conversion tests for WSL distro discovery helpers. |
| tests/CodeShellManager.Tests/ShellSessionTests.cs | Adds WSL arg-building, quoting, and SessionKind/IsRemote behavior tests. |
| tests/CodeShellManager.Tests/ShellSessionMigrationTests.cs | Adds JSON migration tests for legacy IsRemote → Kind behavior. |
| tests/CodeShellManager.Tests/SessionRunnerTests.cs | Adds lifecycle tests for SessionRunner/RunInstance using a fake PTY. |
| tests/CodeShellManager.Tests/SearchServiceTests.cs | Adds broad SearchService coverage (FTS output, notes, history, pruning, stats). |
| tests/CodeShellManager.Tests/RunInstanceTests.cs | Adds PowerShell encoded-command and WSL arg-building tests. |
| tests/CodeShellManager.Tests/RunCommandItemTests.cs | Locks defaults for new Mode/PostRunUrl fields for back-compat. |
| tests/CodeShellManager.Tests/RecentlyClosedEntryTests.cs | Adds snapshot/deep-copy/subtitle behavior tests for recently-closed entries. |
| tests/CodeShellManager.Tests/OutputIndexerTests.cs | Adds reflection-based tests for ANSI stripping regex used by indexing. |
| tests/CodeShellManager.Tests/NewSessionDialogTests.cs | Adds headless parsing tests for WSL UNC path parsing helper. |
| tests/CodeShellManager.Tests/GitServiceWslRoutingTests.cs | Adds tests for WSL UNC parsing and arg/stdout translation in GitService. |
| tests/CodeShellManager.Tests/ColorServiceTests.cs | Adds deterministic/distribution tests for color hashing. |
| tests/CodeShellManager.Tests/AlertDetectorTests.cs | Adds timer-backed tests for alert detection and ANSI handling. |
| src/CodeShellManager/Views/SessionRunCommandsDialog.xaml.cs | Persists new run-command fields (Mode, PostRunUrl) from the editor dialog. |
| src/CodeShellManager/Views/SessionRunCommandsDialog.xaml | Updates run-commands UI to select host mode and optional post-run URL. |
| src/CodeShellManager/Views/NewSessionDialog.xaml.cs | Adds WSL session creation path, WSL browsing, recents list selection, and improved name autofill. |
| src/CodeShellManager/Views/NewSessionDialog.xaml | Adds WSL UI panel and “Recently closed” list to the new session dialog. |
| src/CodeShellManager/ViewModels/SessionViewModel.cs | Switches display/accent key logic to SessionKind + WSL-aware git refresh behavior. |
| src/CodeShellManager/ViewModels/MainViewModel.cs | Adds recently-closed ring buffer plumbing and new layout option. |
| src/CodeShellManager/Terminal/TerminalBridge.cs | Implements WebView boot overlay messaging + fallback hide, and sets WebView background. |
| src/CodeShellManager/Terminal/PseudoTerminal.cs | Implements IPseudoTerminal and adjusts exit monitoring to avoid handle wait races. |
| src/CodeShellManager/Terminal/OutputIndexer.cs | Updates ANSI stripping regex to match RunInstance and cover more sequences. |
| src/CodeShellManager/Terminal/IPseudoTerminal.cs | Introduces minimal PTY interface for unit-test fakes. |
| src/CodeShellManager/Services/WslDiscoveryService.cs | Adds distro listing + parsing, UNC conversion, and cached home resolution for WSL. |
| src/CodeShellManager/Services/SessionRunner.cs | Adds injectable PTY factory seam for deterministic tests. |
| src/CodeShellManager/Services/RunInstance.cs | Adds RunMode/PostRunUrl, PowerShell encoded-command mode, and WSL run args. |
| src/CodeShellManager/Services/GitService.cs | Adds WSL UNC detection and routes git through wsl.exe, translating paths both directions. |
| src/CodeShellManager/Services/AlertDetector.cs | Adds inline comment clarifying the 1.5s idle timer choice. |
| src/CodeShellManager/Properties/launchSettings.json | Disables Hot Reload by default for Debug runs (--clean, hot reload off). |
| src/CodeShellManager/Models/ShellSession.cs | Adds SessionKind + WSL fields + shared display/accent helpers and WSL arg builder. |
| src/CodeShellManager/Models/RunCommandItem.cs | Adds RunMode and PostRunUrl fields with defaults and documentation. |
| src/CodeShellManager/Models/RecentlyClosedEntry.cs | Adds persisted snapshot model for reopening recently-closed sessions. |
| src/CodeShellManager/Models/AppState.cs | Persists RecentlyClosed ring buffer in state. |
| src/CodeShellManager/MainWindow.xaml.cs | Wires WSL sessions end-to-end, recently-closed reopen, new layout, and overlays. |
| src/CodeShellManager/MainWindow.xaml | Adds shutdown overlay and toolbar button for 3×3 layout. |
| src/CodeShellManager/CodeShellManager.csproj | Disables metadata updater in Debug and includes app.png content. |
| src/CodeShellManager/Assets/terminal.html | Adds per-session boot overlay markup/CSS. |
| src/CodeShellManager/Assets/terminal-transparent.html | Adds per-session boot overlay markup/CSS for transparent terminal variant. |
| src/CodeShellManager/Assets/terminal-init.js | Adds setBootState/bootDone handlers to drive boot overlay. |
| README.md | Updates feature list and shortcuts for WSL, recents, run-commands, and spinners. |
| installer/CodeShellManager.wxs | Adds app.png to installer assets. |
| docs/superpowers/specs/2026-05-16-session-spinners-design.md | Adds design spec for launch/shutdown spinners. |
| docs/superpowers/plans/2026-05-16-session-spinners.md | Adds implementation plan for spinner work. |
| CLAUDE.md | Updates contributor docs for hot reload workaround, PTY seam, recents, spinners, run-commands. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+358
to
+366
| // Duplicate _hProcess so Dispose() can close the original without racing the wait. | ||
| // Closing a handle that another thread is waiting on is Win32 UB — the wait may | ||
| // return prematurely and we'd fire Exited before the child actually exits. | ||
| if (!DuplicateHandle(GetCurrentProcess(), _hProcess, GetCurrentProcess(), | ||
| out IntPtr waitHandle, 0, false, DUPLICATE_SAME_ACCESS)) | ||
| { | ||
| Log($"DuplicateHandle failed: {Marshal.GetLastWin32Error()}"); | ||
| return; | ||
| } |
Comment on lines
92
to
100
| var list = _rows.Select(r => new RunCommandItem | ||
| { | ||
| Id = r.Id, | ||
| Label = r.Label.Trim(), | ||
| CommandLine = r.CommandLine.Trim(), | ||
| IsDefault = r.IsDefault, | ||
| Mode = (RunMode)r.ModeIndex, | ||
| PostRunUrl = string.IsNullOrWhiteSpace(r.PostRunUrl) ? null : r.PostRunUrl.Trim(), | ||
| }).ToList(); |
Comment on lines
+163
to
+167
| try { Process.Start(new ProcessStartInfo(PostRunUrl) { UseShellExecute = true }); } | ||
| catch (Exception ex) { LogPostRunUrlFailure(PostRunUrl, ex); } | ||
| } | ||
| } | ||
|
|
10 tasks
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.
Why this PR exists
Draft on the fork so we can request a fresh Copilot pass over the full
WSL feature set + the latest fixes before pushing them onto the upstream PR
(umage-ai#65). Once Copilot's clean, I'll squash/merge
back into `feat/wsl-sessions` and push to the upstream PR.
Scope shown to the reviewer
Everything on the WSL branch + the three new fixes on top:
(single quotes leaked through Win32 tokenization).
Self-review notes (issues I already flagged, leaving for the reviewer)
missing `git.exe` or `wsl.exe` would throw `Win32Exception` past
the caller's MessageBox. Pre-existing pattern, but the WSL routing
inherits it.
which would translate a URL's `://host` prefix in any future caller
that passes git stdout containing URLs. Today's callers don't, so it
doesn't bite — but it's a footgun.
backslash-doubling-before-quote rule. Matches the existing
ShellSession.BuildSshArgs simplification — symmetric, not a regression.
Looking for: anything else worth catching before this goes upstream.