Skip to content

Pre-flight: WSL session support + Copilot-feedback fixes#1

Closed
Bitblade wants to merge 44 commits into
mainfrom
review/wsl-quoting-and-paths
Closed

Pre-flight: WSL session support + Copilot-feedback fixes#1
Bitblade wants to merge 44 commits into
mainfrom
review/wsl-quoting-and-paths

Conversation

@Bitblade

Copy link
Copy Markdown
Owner

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:

  • `fix(run-commands)`: WSL run-args use Windows-style double quotes
    (single quotes leaked through Win32 tokenization).
  • `fix(git)`: TranslateLinuxPathsToUnc allows spaces in path tails.
  • `refactor(paths)`: replace hand-rolled LeafName with Path.GetFileName.

Self-review notes (issues I already flagged, leaving for the reviewer)

  • `CreateWorktreeAsync` doesn't wrap `Process.Start` in try/catch — a
    missing `git.exe` or `wsl.exe` would throw `Win32Exception` past
    the caller's MessageBox. Pre-existing pattern, but the WSL routing
    inherits it.
  • `TranslateLinuxPathsToUnc`'s anchor `(^|[\s=:])` includes `:`,
    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.
  • Win32 quoting in `ShellSession.QuoteForCmd` doesn't apply the CRT's
    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.

AThraen and others added 30 commits May 16, 2026 11:44
…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.
Bitblade added 14 commits May 17, 2026 15:18
…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.
@Bitblade Bitblade marked this pull request as ready for review May 17, 2026 18:16
Copilot AI review requested due to automatic review settings May 17, 2026 18:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 optional PostRunUrl on 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 IsRemoteKind 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); }
}
}

@Bitblade Bitblade closed this May 17, 2026
@Bitblade Bitblade deleted the review/wsl-quoting-and-paths branch May 17, 2026 18:27
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.

3 participants