From 94720c9133c5c8a570708eefe64509953de050ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Aslo-=C3=98stergaard?= Date: Wed, 13 May 2026 22:36:24 +0200 Subject: [PATCH 1/3] feat(groups): bulk actions in group right-click menu Adds Remote control all, Sleep all, Start all, and Close all entries to both the group-strip tab and the inline group-header context menus. Items enable/disable based on live vs dormant member counts. Co-Authored-By: Claude Opus 4.7 --- src/CodeShellManager/MainWindow.xaml.cs | 70 ++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/src/CodeShellManager/MainWindow.xaml.cs b/src/CodeShellManager/MainWindow.xaml.cs index 93685ca..c64888c 100644 --- a/src/CodeShellManager/MainWindow.xaml.cs +++ b/src/CodeShellManager/MainWindow.xaml.cs @@ -1739,7 +1739,7 @@ private Border BuildGroupTab(string? groupId, string fullName, string label) _vm.MoveGroup(groupId, idx + 1); }; menu.Items.Add(moveDown); - menu.Items.Add(new System.Windows.Controls.Separator()); + AddGroupBulkActionItems(menu, groupId, fullName); var rename = new System.Windows.Controls.MenuItem { Header = "Rename group…" }; rename.Click += (_, _) => PromptRenameGroup(groupId, fullName); @@ -1966,7 +1966,7 @@ private Border BuildInlineGroupHeader(Models.SessionGroup? group, int count, boo _vm.MoveGroup(group.Id, idx + 1); }; menu.Items.Add(moveDown); - menu.Items.Add(new System.Windows.Controls.Separator()); + AddGroupBulkActionItems(menu, group.Id, group.Name); var rename = new System.Windows.Controls.MenuItem { Header = "Rename group…" }; rename.Click += (_, _) => PromptRenameGroup(group.Id, group.Name); menu.Items.Add(rename); @@ -2036,6 +2036,72 @@ private Border BuildInlineGroupHeader(Models.SessionGroup? group, int count, boo return border; } + /// + /// Appends bulk-action items (Remote control, Sleep all, Start all, Close all) for + /// , wrapped in leading + trailing separators. Items are + /// enabled/disabled in menu.Opened based on live vs. dormant counts in the group. + /// + private void AddGroupBulkActionItems(System.Windows.Controls.ContextMenu menu, string groupId, string groupName) + { + menu.Items.Add(new System.Windows.Controls.Separator()); + + var remoteControl = new System.Windows.Controls.MenuItem { Header = "Remote control all in group" }; + remoteControl.Click += (_, _) => + { + foreach (var vm in _vm.Sessions.Where(v => v.Session.GroupId == groupId).ToList()) + { + vm.Bridge?.SendToTerminal("/remote-control\r"); + vm.AlertDetector?.NotifyUserInteracted(); + } + }; + menu.Items.Add(remoteControl); + + var sleepAll = new System.Windows.Controls.MenuItem { Header = "Sleep all" }; + sleepAll.Click += (_, _) => + { + foreach (var vm in _vm.Sessions.Where(v => v.Session.GroupId == groupId).ToList()) + SleepSession(vm); + }; + menu.Items.Add(sleepAll); + + var startAll = new System.Windows.Controls.MenuItem { Header = "Start all" }; + startAll.Click += async (_, _) => + { + var dormant = _sessionManager.Sessions + .Where(s => s.GroupId == groupId && s.IsDormant) + .ToList(); + foreach (var session in dormant) + await WakeSessionAsync(session); + }; + menu.Items.Add(startAll); + + var closeAll = new System.Windows.Controls.MenuItem { Header = "Close all…" }; + closeAll.Click += (_, _) => + { + var targets = _vm.Sessions.Where(v => v.Session.GroupId == groupId).ToList(); + if (targets.Count == 0) return; + var r = MessageBox.Show( + $"Close {targets.Count} session(s) in group '{groupName}'? They will be removed permanently.", + "Close all", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No); + if (r != MessageBoxResult.Yes) return; + foreach (var vm in targets) + vm.CloseCommand.Execute(null); + }; + menu.Items.Add(closeAll); + + menu.Items.Add(new System.Windows.Controls.Separator()); + + menu.Opened += (_, _) => + { + int liveCount = _vm.Sessions.Count(v => v.Session.GroupId == groupId); + int dormantCount = _sessionManager.Sessions.Count(s => s.GroupId == groupId && s.IsDormant); + remoteControl.IsEnabled = liveCount > 0; + sleepAll.IsEnabled = liveCount > 0; + startAll.IsEnabled = dormantCount > 0; + closeAll.IsEnabled = liveCount > 0; + }; + } + /// True when the drag payload is "group:" and (if specified) not the excepted id. private static bool IsGroupDragPayload(System.Windows.IDataObject data, string? exceptGroupId = null) { From dd4ebd47937e549802a7d69726492bca21a57bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Aslo-=C3=98stergaard?= Date: Wed, 13 May 2026 23:10:51 +0200 Subject: [PATCH 2/3] feat(sidebar): empty-area quick menu, bulk actions, sort sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sidebar right-click on empty space (header, session list, group strip, empty terminal grid) now shows a shared quick menu with: - New session / New group - Bulk actions submenu — Wake all dormant, Sleep all, Close all - Group display mode toggle (None / FilterStrip / InlineHeaders) - Expand / collapse all group sections - Session row icons mode (OnHover / Always / Hidden) - Show git branch / worktree clusters toggles - All settings… The legacy + group footer button is removed; group creation flows through "New group…" in the same menu. New sort button in the SESSIONS header (left of +). Opens a direction-aware menu: Name, Folder, Last active, Git ▸ (Branch, Dirty, Repo). Re-clicking the active field flips direction; switching fields picks the field's natural starting direction. Direction glyph lives in the MenuItem icon slot so it column-aligns. Sessions without live git info sink to the bottom of git-based orderings. ShellSession gains a LastActivityAt timestamp updated when a session becomes the active one, persisted via the existing state.json path. SessionManager.SortSessions exposes a one-shot Sort(Comparison). Visual polish: vertical filter strip now has a hairline divider between All/Ungrouped and the user groups; Ungrouped glyph changed from "∅" to "□" so it reads as the hollow companion to the "▦" All glyph. Co-Authored-By: Claude Opus 4.7 --- src/CodeShellManager/MainWindow.xaml | 14 +- src/CodeShellManager/MainWindow.xaml.cs | 412 ++++++++++++++++-- src/CodeShellManager/Models/ShellSession.cs | 8 + .../Services/SessionManager.cs | 10 + 4 files changed, 408 insertions(+), 36 deletions(-) diff --git a/src/CodeShellManager/MainWindow.xaml b/src/CodeShellManager/MainWindow.xaml index 2fa5cce..7c3ee6b 100644 --- a/src/CodeShellManager/MainWindow.xaml +++ b/src/CodeShellManager/MainWindow.xaml @@ -250,16 +250,22 @@ - + + -