@@ -63,6 +63,12 @@ public partial class MainWindow : Window
6363 WpfButton drawerCopyBtn ,
6464 WpfButton drawerSendBtn ) > _runControls = new ( ) ;
6565 private readonly Dictionary < string , string > _drawerItemBySession = new ( ) ;
66+ // Per-session sidebar action button panels — kept so SettingsButton_Click
67+ // can flip every row to a new SidebarActionIconsMode without rebuilding sidebar items.
68+ private readonly Dictionary < string , StackPanel > _sidebarActionPanels = new ( ) ;
69+ // Per-session rename trigger — captured from BuildSidebarItem so the context menu's
70+ // Rename action can invoke the same in-place editor as the double-click handler.
71+ private readonly Dictionary < string , Action > _sidebarRenameActions = new ( ) ;
6672 // Anchor for shift-click range selection in the sidebar.
6773 private string ? _selectionAnchorId ;
6874 // Group-tab notification indicators (badge + text), keyed by group id (or "__ALL__"
@@ -1184,29 +1190,30 @@ static void UpdateGitText(TextBlock tb, SessionViewModel svm)
11841190 } ;
11851191 Grid . SetColumn ( statusDot , 2 ) ;
11861192
1187- // Action buttons
1193+ // Action buttons — reduced set (#29). Secondary actions (Open in Explorer, PowerShell,
1194+ // Rename) live on the right-click context menu instead. The icons are surfaced based
1195+ // on AppSettings.SidebarActionIconsMode (default OnHover) so the sidebar stays calm
1196+ // unless the user is actively reaching for an action.
11881197 var btnPanel = new StackPanel
11891198 {
11901199 Orientation = Orientation . Vertical ,
11911200 Margin = new Thickness ( 0 , 4 , 4 , 4 ) ,
11921201 VerticalAlignment = VerticalAlignment . Center
11931202 } ;
11941203
1195- var exploreBtn = MakeMiniButton ( "📁" , "Open in Explorer" , ( ) => vm . OpenInExplorerCommand . Execute ( null ) ) ;
1196- var psBtn = MakeMiniButton ( ">_" , "Open PowerShell here" , ( ) => LaunchPowerShellInFolder ( vm . WorkingFolder , vm . GroupId ) ) ;
1197- var spawnBtn = MakeMiniButton ( "➕" , "New session here (inherits group + profile)" ,
1204+ var spawnBtn = MakeMiniButton ( "➕" , "New session here (inherits group + profile)" ,
11981205 ( ) => OpenNewSessionDialogFromParent ( vm ) ) ;
1199- var renameBtn = MakeMiniButton ( "✏" , "Rename session" , StartRename ) ;
1200- var sleepBtn = MakeMiniButton ( "💤" , "Sleep session (keep it but stop the terminal)" , ( ) => SleepSession ( vm ) ) ;
1201- var closeBtn = MakeMiniButton ( "✕" , "Close session" , ( ) => vm . CloseCommand . Execute ( null ) ) ;
1206+ var sleepBtn = MakeMiniButton ( "💤" , "Sleep session (keep it but stop the terminal)" , ( ) => SleepSession ( vm ) ) ;
1207+ var closeBtn = MakeMiniButton ( "✕" , "Close session" , ( ) => vm . CloseCommand . Execute ( null ) ) ;
12021208
1203- btnPanel . Children . Add ( exploreBtn ) ;
1204- btnPanel . Children . Add ( psBtn ) ;
12051209 btnPanel . Children . Add ( spawnBtn ) ;
1206- btnPanel . Children . Add ( renameBtn ) ;
12071210 btnPanel . Children . Add ( sleepBtn ) ;
12081211 btnPanel . Children . Add ( closeBtn ) ;
12091212
1213+ _sidebarActionPanels [ vm . Id ] = btnPanel ;
1214+ _sidebarRenameActions [ vm . Id ] = StartRename ;
1215+ ApplyActionIconsMode ( btnPanel , container , _vm . Settings . SidebarActionIconsMode , isHovered : false ) ;
1216+
12101217 Grid . SetColumn ( textPanel , 1 ) ;
12111218 Grid . SetColumn ( btnPanel , 3 ) ;
12121219 inner . Children . Add ( stripe ) ;
@@ -1283,14 +1290,18 @@ static void UpdateGitText(TextBlock tb, SessionViewModel svm)
12831290 // Hover effect — must not clobber multi-select tint. Selected-but-not-active items
12841291 // keep their blue background on hover and on mouse leave; only plain, unselected,
12851292 // non-active items show the muted hover background and clear to transparent.
1293+ // Also drives the OnHover SidebarActionIconsMode: action buttons fade in on enter,
1294+ // out on leave. Hidden and Always modes ignore the hover transition.
12861295 container . MouseEnter += ( _ , _ ) =>
12871296 {
1297+ ApplyActionIconsMode ( btnPanel , container , _vm . Settings . SidebarActionIconsMode , isHovered : true ) ;
12881298 if ( vm . Id == _vm . ActiveSession ? . Id ) return ;
12891299 if ( _vm . IsSelected ( vm . Id ) ) return ;
12901300 container . Background = new SolidColorBrush ( Color . FromRgb ( 0x31 , 0x32 , 0x44 ) ) ;
12911301 } ;
12921302 container . MouseLeave += ( _ , _ ) =>
12931303 {
1304+ ApplyActionIconsMode ( btnPanel , container , _vm . Settings . SidebarActionIconsMode , isHovered : false ) ;
12941305 if ( vm . Id == _vm . ActiveSession ? . Id ) return ;
12951306 container . Background = _vm . IsSelected ( vm . Id )
12961307 ? new SolidColorBrush ( Color . FromArgb ( 0x55 , 0x89 , 0xb4 , 0xfa ) )
@@ -1391,6 +1402,37 @@ private static WpfButton MakeMiniButton(string icon, string tooltip, Action onCl
13911402 return btn ;
13921403 }
13931404
1405+ /// <summary>
1406+ /// Applies the current <see cref="Models.SidebarActionIconsMode"/> to a single sidebar
1407+ /// row's action-button stack. Hidden collapses the panel entirely (the row reclaims the
1408+ /// horizontal space). OnHover keeps the panel laid out but transparent + non-interactive
1409+ /// until the row is hovered. Always shows it at full opacity. Called from BuildSidebarItem
1410+ /// at construction, from the row's MouseEnter/Leave handlers, and after a settings save.
1411+ /// </summary>
1412+ private static void ApplyActionIconsMode ( StackPanel btnPanel , Border container ,
1413+ Models . SidebarActionIconsMode mode , bool isHovered )
1414+ {
1415+ switch ( mode )
1416+ {
1417+ case Models . SidebarActionIconsMode . Hidden :
1418+ btnPanel . Visibility = Visibility . Collapsed ;
1419+ btnPanel . IsHitTestVisible = false ;
1420+ btnPanel . Opacity = 0 ;
1421+ break ;
1422+ case Models . SidebarActionIconsMode . Always :
1423+ btnPanel . Visibility = Visibility . Visible ;
1424+ btnPanel . IsHitTestVisible = true ;
1425+ btnPanel . Opacity = 1 ;
1426+ break ;
1427+ case Models . SidebarActionIconsMode . OnHover :
1428+ default :
1429+ btnPanel . Visibility = Visibility . Visible ; // reserves layout space
1430+ btnPanel . Opacity = isHovered ? 1 : 0 ;
1431+ btnPanel . IsHitTestVisible = isHovered ;
1432+ break ;
1433+ }
1434+ }
1435+
13941436 private void UpdateSidebarActiveState ( )
13951437 {
13961438 foreach ( Border item in SidebarSessionList . Children )
@@ -2124,6 +2166,29 @@ private System.Windows.Controls.ContextMenu BuildSessionContextMenu(SessionViewM
21242166 // Spawn-near-parent + worktree actions — single-target only
21252167 if ( ! isMulti )
21262168 {
2169+ // Rename — in-place editor inside the sidebar row (same path as double-click).
2170+ // Stored in _sidebarRenameActions when the sidebar row is built.
2171+ if ( _sidebarRenameActions . TryGetValue ( vm . Id , out var renameAction ) )
2172+ {
2173+ var renameItem = new System . Windows . Controls . MenuItem { Header = "Rename session" } ;
2174+ renameItem . Click += ( _ , _ ) => renameAction ( ) ;
2175+ menu . Items . Add ( renameItem ) ;
2176+ }
2177+
2178+ // Folder actions — only when there's a local working folder to open.
2179+ if ( ! vm . Session . IsRemote && ! string . IsNullOrEmpty ( vm . Session . WorkingFolder ) )
2180+ {
2181+ var explorerItem = new System . Windows . Controls . MenuItem { Header = "Open in Explorer" } ;
2182+ explorerItem . Click += ( _ , _ ) => vm . OpenInExplorerCommand . Execute ( null ) ;
2183+ menu . Items . Add ( explorerItem ) ;
2184+
2185+ var psItem = new System . Windows . Controls . MenuItem { Header = "Open PowerShell here" } ;
2186+ psItem . Click += ( _ , _ ) => LaunchPowerShellInFolder ( vm . WorkingFolder , vm . GroupId ) ;
2187+ menu . Items . Add ( psItem ) ;
2188+ }
2189+
2190+ menu . Items . Add ( new System . Windows . Controls . Separator ( ) ) ;
2191+
21272192 var dupItem = new System . Windows . Controls . MenuItem { Header = "Duplicate session" } ;
21282193 dupItem . Click += async ( _ , _ ) => await DuplicateSessionAsync ( vm ) ;
21292194 menu . Items . Add ( dupItem ) ;
@@ -3050,6 +3115,25 @@ private Border BuildTerminalWrapper(SessionViewModel vm, WebView2 webView)
30503115 } ;
30513116 sleepBtn . Click += ( _ , _ ) => SleepSession ( vm ) ;
30523117
3118+ // Close button — tied to the same path as the sidebar ✕ (vm.CloseCommand).
3119+ // Sits at the far right of the toolbar so the terminal toolbar is a complete,
3120+ // canonical home for per-session actions (#29). Right margin nudges it slightly
3121+ // inside the toolbar padding so the ✕ doesn't kiss the toolbar edge.
3122+ var closeBtn = new WpfButton
3123+ {
3124+ Content = "✕" ,
3125+ ToolTip = "Close session" ,
3126+ Background = Brushes . Transparent ,
3127+ BorderThickness = new Thickness ( 0 ) ,
3128+ Foreground = new SolidColorBrush ( Color . FromRgb ( 0xa6 , 0xad , 0xc8 ) ) ,
3129+ FontSize = 12 ,
3130+ Cursor = System . Windows . Input . Cursors . Hand ,
3131+ Padding = new Thickness ( 4 , 2 , 4 , 2 ) ,
3132+ Margin = new Thickness ( 0 )
3133+ } ;
3134+ closeBtn . Click += ( _ , _ ) => vm . CloseCommand . Execute ( null ) ;
3135+
3136+ DockPanel . SetDock ( closeBtn , Dock . Right ) ;
30533137 DockPanel . SetDock ( termStatusDot , Dock . Right ) ;
30543138 DockPanel . SetDock ( explorerBtn , Dock . Right ) ;
30553139 DockPanel . SetDock ( toolbarPsBtn , Dock . Right ) ;
@@ -3059,6 +3143,9 @@ private Border BuildTerminalWrapper(SessionViewModel vm, WebView2 webView)
30593143 DockPanel . SetDock ( playBtn , Dock . Right ) ;
30603144 DockPanel . SetDock ( claudeBadge , Dock . Left ) ;
30613145 DockPanel . SetDock ( titleBlock , Dock . Left ) ;
3146+ // Dock.Right children are stacked right-to-left in declaration order, so this
3147+ // puts closeBtn at the far right edge, then termStatusDot to its left, etc.
3148+ toolbarContent . Children . Add ( closeBtn ) ;
30623149 toolbarContent . Children . Add ( termStatusDot ) ;
30633150 toolbarContent . Children . Add ( explorerBtn ) ;
30643151 toolbarContent . Children . Add ( toolbarPsBtn ) ;
@@ -3295,6 +3382,8 @@ private void OnSessionVmClosed(SessionViewModel vm)
32953382 }
32963383 _runControls . Remove ( vm . Id ) ;
32973384 _drawerItemBySession . Remove ( vm . Id ) ;
3385+ _sidebarActionPanels . Remove ( vm . Id ) ;
3386+ _sidebarRenameActions . Remove ( vm . Id ) ;
32983387 if ( _selectionAnchorId == vm . Id ) _selectionAnchorId = null ;
32993388 _sessionManager . RemoveSession ( vm . Id ) ;
33003389 RefreshTerminalLayout ( ) ;
@@ -3331,6 +3420,8 @@ private void SleepSession(SessionViewModel vm)
33313420 }
33323421 _runControls . Remove ( vm . Id ) ;
33333422 _drawerItemBySession . Remove ( vm . Id ) ;
3423+ _sidebarActionPanels . Remove ( vm . Id ) ;
3424+ _sidebarRenameActions . Remove ( vm . Id ) ;
33343425
33353426 // Remove the VM directly — bypass CloseRequested so the ShellSession is
33363427 // NOT removed from the SessionManager (we want to keep it for wake-up).
@@ -3754,6 +3845,7 @@ private void SettingsButton_Click(object sender, RoutedEventArgs e)
37543845 _vm . Settings . ShowGitBranch = edited . ShowGitBranch ;
37553846 _vm . Settings . ShowGroupsTab = edited . ShowGroupsTab ;
37563847 _vm . Settings . GroupDisplayMode = edited . GroupDisplayMode ;
3848+ _vm . Settings . SidebarActionIconsMode = edited . SidebarActionIconsMode ;
37573849 _vm . Settings . ShowWorktreeClusters = edited . ShowWorktreeClusters ;
37583850 _vm . Settings . SearchCollapseAfterNavigate = edited . SearchCollapseAfterNavigate ;
37593851
@@ -3777,6 +3869,17 @@ private void SettingsButton_Click(object sender, RoutedEventArgs e)
37773869 foreach ( var vm in _vm . Sessions )
37783870 vm . Bridge ? . ApplyFontSettings ( _vm . Settings ) ;
37793871
3872+ // Push the action-icons mode to every live sidebar row. Hovered state is
3873+ // recomputed via IsMouseOver so the change is visible immediately without
3874+ // requiring the cursor to leave + re-enter the row.
3875+ foreach ( var ( id , panel ) in _sidebarActionPanels )
3876+ {
3877+ if ( _sessionUi . TryGetValue ( id , out var ui ) )
3878+ ApplyActionIconsMode ( panel , ui . sidebarItem ,
3879+ _vm . Settings . SidebarActionIconsMode ,
3880+ isHovered : ui . sidebarItem . IsMouseOver ) ;
3881+ }
3882+
37803883 UpdateGroupStripVisibility ( ) ;
37813884 RebuildSidebarOrder ( ) ;
37823885 }
0 commit comments