Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2b2d7ad
feat(sessions): first-class WSL session type alongside Local and SSH
Bitblade May 17, 2026
60de1ab
feat(new-session): folder picker for WSL working folder
Bitblade May 17, 2026
4a49953
feat(sessions): add IsWsl convenience predicate on ShellSession
Bitblade May 17, 2026
bc4b3e6
fix(git): route GitService through wsl.exe for WSL UNC working folders
Bitblade May 17, 2026
fe2b3d0
feat(new-session): WSL browse picker opens at the distro's home folder
Bitblade May 17, 2026
ad657d1
fix(new-session): re-fire name suggestion when distro / folder changes
Bitblade May 17, 2026
e82f6ae
feat(new-session): inherit WSL distro/user/folder from the parent ses…
Bitblade May 17, 2026
736dca4
feat(menu): "Open WSL console here" for WSL sessions
Bitblade May 17, 2026
96003de
fix(worktree): new worktree sessions inherit kind from the parent
Bitblade May 17, 2026
86b73ff
fix(new-session): drop SelectedPath so the WSL picker's Folder field …
Bitblade May 17, 2026
bf8011f
fix(wsl): drain stderr in GetDistroHomeAsync to avoid pipe-buffer dea…
Bitblade May 17, 2026
6c93b67
docs(session-vm): update stale comment on WSL git refresh
Bitblade May 17, 2026
b26accd
feat(run-commands): allow template seeding for WSL sessions
Bitblade May 17, 2026
ef4c73e
fix(recents): persist Kind + WSL fields so reopened WSL sessions stay…
Bitblade May 17, 2026
ae34fbb
fix(git): TranslateUncArgsToLinux handles quoted UNC paths with spaces
Bitblade May 17, 2026
9c2ac28
fix(args): quote distro/user/cwd values in WSL arg builders
Bitblade May 17, 2026
201d61e
fix(new-session): reject non-WSL paths from the WSL folder picker
Bitblade May 17, 2026
dfc0293
fix(new-session): resolve \$HOME eagerly when WslWorkingFolder is blank
Bitblade May 17, 2026
73694d0
fix(wsl): honor "Never throws" contract on discovery helpers
Bitblade May 17, 2026
08140e3
fix(wsl): Parse handles distro names with spaces
Bitblade May 17, 2026
564a773
fix(wsl): QuoteForCmd the distro/user in GetDistroHomeAsync too
Bitblade May 17, 2026
c7ef555
fix(run-commands): switch WSL run-args to Windows-style double quotes
Bitblade May 17, 2026
e0044f0
fix(git): TranslateLinuxPathsToUnc allows spaces in path tail
Bitblade May 17, 2026
a376a7a
refactor(paths): use Path.GetFileName instead of hand-rolled LeafName
Bitblade May 17, 2026
d4e6fd4
merge origin/main into pr-65 (trial)
AThraen Sep 6, 2026
79028c1
docs(plan): WSL sessions hardening plan for landing PR #65
AThraen Sep 6, 2026
ba67e8a
refactor(sessions): migrate legacy IsRemote in the state loader, not …
AThraen Sep 6, 2026
75ba120
feat(edit-session): carry Kind and WSL fields through SessionConfigDraft
AThraen Sep 6, 2026
fb04ff0
feat(edit-session): edit WSL sessions in the shared New Session form
AThraen Sep 6, 2026
9856cb4
fix(new-session): keep the "(not installed)" distro fallback edit-mod…
AThraen Sep 6, 2026
0501e46
fix(wsl): MSVCRT-correct quoting, one BuildWslArgs for sessions and runs
AThraen Sep 6, 2026
cc3cb27
test(run): cover RunInstance graceful start failure; restore printf t…
AThraen Sep 6, 2026
c14494b
fix(launch): refuse unlaunchable sessions before creating a pane
AThraen Sep 6, 2026
3435ba5
fix(launch): leave the dormant fallback to the caller on a refused la…
AThraen Sep 6, 2026
55609f0
perf(git): probe off the UI thread; slower cadence and negative cache…
AThraen Sep 6, 2026
56a6e6b
fix(git): don't touch a disposed session VM when the probe finishes late
AThraen Sep 6, 2026
d05d619
fix(wsl): one UNC parser, and a distro-name boundary in the arg trans…
AThraen Sep 6, 2026
b7ccb20
fix(search): relaunching a closed session from a search hit keeps its…
AThraen Sep 6, 2026
b4e5537
fix(search): build the session-history snapshot on the UI thread
AThraen Sep 6, 2026
db8014f
fix(new-session): no double submit while the WSL home probe runs
AThraen Sep 6, 2026
fd1c0c9
docs: WSL sessions section; Kind replaces IsRemote throughout CLAUDE.md
AThraen Sep 6, 2026
e850cfc
fix: hot-path buffer copy, blank-name dialog, WSL UNC resync helper, …
AThraen Sep 7, 2026
897ee82
style(new-session): reindent the Start_Click try body
AThraen Sep 7, 2026
739d4dd
fix(wsl): -e not --, bash-to-sh fallback, filter docker-desktop distros
AThraen Sep 7, 2026
2ca903d
fix(wsl): blank Linux folder means $HOME; unclip the dialog buttons
AThraen Sep 7, 2026
dbcb05a
fix(wsl): overlap the launch probes, expand a typed ~/ folder
AThraen Sep 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 45 additions & 18 deletions CLAUDE.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Built with WPF + [xterm.js](https://xtermjs.org/) + Windows ConPTY for full pseu
- **SSH remote sessions** — connect to remote hosts using your existing SSH config; sessions persist across restarts
- **Windows Terminal profile import** — opt-in import of profiles from Windows Terminal's `settings.json`; pick a profile in the New Session dialog to stamp its font, color scheme, cursor and padding onto the new terminal
- **Launch & shutdown spinners** — every starting session shows a brief overlay (`Starting <cmd>…` or `Connecting to <host>…`) until the first PTY byte arrives; closing the window shows a "Shutting down…" overlay during session disposal
- **WSL sessions** — first-class session type for any installed WSL distro: distro picker (auto-detected via `wsl -l -v`), Linux working folder, optional `-u` user override; git status works via the `\\wsl$\<distro>` UNC view
- **Session history** — clicking a search result from a closed session offers to relaunch it
- **Configurable launch commands** — customise the commands available in the New Session dialog
- **Claude badge** — sessions running `claude` commands get a visual indicator
Expand Down
1,465 changes: 1,465 additions & 0 deletions docs/superpowers/plans/2026-09-06-wsl-sessions-hardening.md

Large diffs are not rendered by default.

256 changes: 216 additions & 40 deletions src/CodeShellManager/MainWindow.xaml.cs

Large diffs are not rendered by default.

57 changes: 51 additions & 6 deletions src/CodeShellManager/Models/RecentlyClosedEntry.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;

namespace CodeShellManager.Models;

Expand All @@ -23,12 +24,47 @@ public class RecentlyClosedEntry
public string GroupId { get; set; } = "";
public string? ColorOverride { get; set; }

public bool IsRemote { get; set; }
/// <summary>
/// Kind of the closed session, so a reopened WSL or SSH session comes back as the same
/// kind instead of Local at a UNC. Legacy entries carried only <c>IsRemote</c>; see
/// <see cref="LegacyIsRemote"/>.
/// </summary>
public SessionKind Kind { get; set; } = SessionKind.Local;

[JsonIgnore]
public bool IsRemote => Kind == SessionKind.Ssh;

/// <summary>
/// Legacy <c>"IsRemote"</c> JSON slot — same computed-getter / backing-field-setter
/// split as <see cref="ShellSession.LegacyIsRemote"/> (see there for the full rationale):
/// written as <c>true</c> for an SSH entry, omitted for Local/Wsl, so a rollback reading
/// this file still sees SSH entries as remote.
/// </summary>
[JsonPropertyName("IsRemote")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? LegacyIsRemote
{
get => Kind == SessionKind.Ssh ? true : (bool?)null;
set => _legacyIsRemoteIncoming = value;
}

private bool? _legacyIsRemoteIncoming;

public void MigrateLegacyFields()
{
if (_legacyIsRemoteIncoming == true && Kind == SessionKind.Local) Kind = SessionKind.Ssh;
_legacyIsRemoteIncoming = null;
}

public string SshUser { get; set; } = "";
public string SshHost { get; set; } = "";
public int SshPort { get; set; } = 22;
public string SshRemoteFolder { get; set; } = "";

public string WslDistro { get; set; } = "";
public string WslUser { get; set; } = "";
public string WslWorkingFolder { get; set; } = "";

public string? ProfileFontFamily { get; set; }
public int? ProfileFontSize { get; set; }
public string? ProfileFontWeight { get; set; }
Expand Down Expand Up @@ -57,11 +93,14 @@ public class RecentlyClosedEntry
Args = s.Args,
GroupId = s.GroupId,
ColorOverride = s.ColorOverride,
IsRemote = s.IsRemote,
Kind = s.Kind,
SshUser = s.SshUser,
SshHost = s.SshHost,
SshPort = s.SshPort,
SshRemoteFolder = s.SshRemoteFolder,
WslDistro = s.WslDistro,
WslUser = s.WslUser,
WslWorkingFolder = s.WslWorkingFolder,
ProfileFontFamily = s.ProfileFontFamily,
ProfileFontSize = s.ProfileFontSize,
ProfileFontWeight = s.ProfileFontWeight,
Expand All @@ -84,8 +123,14 @@ public class RecentlyClosedEntry
ClosedAt = DateTime.UtcNow,
};

/// <summary>Friendly subtitle for the recents UI — folder or user@host.</summary>
public string Subtitle => IsRemote
? (string.IsNullOrWhiteSpace(SshUser) ? SshHost : $"{SshUser}@{SshHost}")
: WorkingFolder;
/// <summary>Friendly subtitle for the recents UI — kind-specific locator.</summary>
[JsonIgnore]
public string Subtitle => Kind switch
{
SessionKind.Ssh => string.IsNullOrWhiteSpace(SshUser) ? SshHost : $"{SshUser}@{SshHost}",
SessionKind.Wsl => string.IsNullOrEmpty(WslWorkingFolder)
? WslDistro
: $"{WslDistro}: {WslWorkingFolder}",
_ => WorkingFolder,
};
}
13 changes: 10 additions & 3 deletions src/CodeShellManager/Models/SessionConfigDraft.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ public sealed class SessionConfigDraft
public string Command { get; set; } = "";
public string Args { get; set; } = "";

// Remote
public bool IsRemote { get; set; }
// Kind + kind-specific fields. Kind is authoritative; IsRemote is a read-only view.
public SessionKind Kind { get; set; } = SessionKind.Local;
public bool IsRemote => Kind == SessionKind.Ssh;
public string SshUser { get; set; } = "";
public string SshHost { get; set; } = "";
public int SshPort { get; set; } = 22;
public string SshRemoteFolder { get; set; } = "";
public string WslDistro { get; set; } = "";
public string WslUser { get; set; } = "";
public string WslWorkingFolder { get; set; } = "";

// Appearance overrides — null means "use the global terminal settings"
public string? ProfileFontFamily { get; set; }
Expand All @@ -46,7 +50,10 @@ public sealed class SessionConfigDraft
WorkingFolder = s.WorkingFolder,
Command = s.Command,
Args = s.Args,
IsRemote = s.IsRemote,
Kind = s.Kind,
WslDistro = s.WslDistro,
WslUser = s.WslUser,
WslWorkingFolder = s.WslWorkingFolder,
SshUser = s.SshUser,
SshHost = s.SshHost,
SshPort = s.SshPort,
Expand Down
Loading