Skip to content

Commit fed9391

Browse files
author
Mohammod Al Amin Ashik
committed
feat: Simplify system tray and fix startup settings UI
- Simplify tray menu to only: Active Space, Open, Quit - Remove unsupported features: export config, refresh servers, create space - Fix Switch component to use correct CSS variables (--primary) - Add comprehensive test coverage for startup settings - Update tray documentation This addresses user feedback about cluttered tray menu and non-working UI elements.
1 parent 7a1ee9a commit fed9391

4 files changed

Lines changed: 97 additions & 92 deletions

File tree

apps/desktop/src-tauri/src/services/file_watcher.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,6 @@ impl SpaceFileWatcherBuilder {
223223

224224
#[cfg(test)]
225225
mod tests {
226-
use super::*;
227226

228227
#[test]
229228
fn test_builder_default_space_id() {

apps/desktop/src-tauri/src/tray.rs

Lines changed: 10 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
//!
33
//! Provides a system tray icon with quick access to:
44
//! - Space switching
5-
//! - Config export
6-
//! - Server status
75
//! - Open main window
6+
//! - Quit application
87
98
use tauri::{
109
menu::{Menu, MenuBuilder, MenuItemBuilder, PredefinedMenuItem, SubmenuBuilder},
@@ -65,36 +64,18 @@ pub fn setup_tray<R: Runtime>(app: &AppHandle<R>) -> tauri::Result<()> {
6564

6665
/// Build the tray menu
6766
fn build_tray_menu<R: Runtime>(app: &AppHandle<R>) -> tauri::Result<Menu<R>> {
68-
// Space submenu
67+
// Space submenu (will be populated dynamically)
6968
let space_submenu = SubmenuBuilder::new(app, "Active Space")
7069
.text("space_default", "🌐 Default")
71-
.separator()
72-
.text("create_space", "➕ Create Space...")
73-
.build()?;
74-
75-
// Export submenu
76-
let export_submenu = SubmenuBuilder::new(app, "📋 Export Config")
77-
.text("export_cursor", "Cursor")
78-
.text("export_vscode", "VS Code")
79-
.text("export_claude", "Claude Desktop")
8070
.build()?;
8171

82-
// Build main menu
72+
// Build simplified main menu
8373
let menu = MenuBuilder::new(app)
84-
.item(
85-
&MenuItemBuilder::with_id("status", "McpMux 🟢")
86-
.enabled(false)
87-
.build(app)?,
88-
)
89-
.separator()
9074
.item(&space_submenu)
9175
.separator()
92-
.text("refresh", "🔄 Refresh All Servers")
93-
.item(&export_submenu)
76+
.text("open", "Open McpMux")
9477
.separator()
95-
.text("open", "⚙️ Open McpMux")
96-
.item(&PredefinedMenuItem::separator(app)?)
97-
.text("quit", "❌ Quit")
78+
.text("quit", "Quit")
9879
.build()?;
9980

10081
Ok(menu)
@@ -110,25 +91,6 @@ fn handle_menu_event<R: Runtime>(app: &AppHandle<R>, event_id: &str) {
11091
let space_id = id.strip_prefix("space_").unwrap_or("default");
11192
handle_switch_space(app, space_id);
11293
}
113-
"create_space" => {
114-
open_main_window_at(app, "/spaces/new");
115-
}
116-
117-
// Export actions
118-
"export_cursor" => {
119-
handle_export(app, "cursor");
120-
}
121-
"export_vscode" => {
122-
handle_export(app, "vscode");
123-
}
124-
"export_claude" => {
125-
handle_export(app, "claude");
126-
}
127-
128-
// General actions
129-
"refresh" => {
130-
handle_refresh_servers(app);
131-
}
13294
"open" => {
13395
if let Some(window) = app.get_webview_window("main") {
13496
let _ = window.show();
@@ -149,34 +111,12 @@ fn handle_menu_event<R: Runtime>(app: &AppHandle<R>, event_id: &str) {
149111
fn handle_switch_space<R: Runtime>(app: &AppHandle<R>, space_id: &str) {
150112
info!("Switching to space: {}", space_id);
151113

152-
// Emit event to frontend
153-
let _ = app.emit("tray:switch-space", space_id);
154-
}
155-
156-
/// Open main window at a specific route
157-
fn open_main_window_at<R: Runtime>(app: &AppHandle<R>, route: &str) {
114+
// Show window and emit event to frontend
158115
if let Some(window) = app.get_webview_window("main") {
159116
let _ = window.show();
160117
let _ = window.set_focus();
161-
// Emit navigation event
162-
let _ = app.emit("tray:navigate", route);
163118
}
164-
}
165-
166-
/// Handle export request
167-
fn handle_export<R: Runtime>(app: &AppHandle<R>, client_type: &str) {
168-
info!("Export config requested for: {}", client_type);
169-
170-
// Emit event to frontend to handle export
171-
let _ = app.emit("tray:export-config", client_type);
172-
}
173-
174-
/// Handle refresh all servers
175-
fn handle_refresh_servers<R: Runtime>(app: &AppHandle<R>) {
176-
info!("Refresh all servers requested");
177-
178-
// Emit event to frontend
179-
let _ = app.emit("tray:refresh-servers", ());
119+
let _ = app.emit("tray:switch-space", space_id);
180120
}
181121

182122
/// Update tray menu with current spaces
@@ -205,34 +145,15 @@ pub async fn update_tray_spaces<R: Runtime>(
205145
space_menu = space_menu.text(id, label);
206146
}
207147

208-
space_menu = space_menu
209-
.separator()
210-
.text("create_space", "➕ Create Space...");
211-
212148
let space_submenu = space_menu.build()?;
213149

214-
// Rebuild full menu
215-
let export_submenu = SubmenuBuilder::new(app, "📋 Export Config")
216-
.text("export_cursor", "Cursor")
217-
.text("export_vscode", "VS Code")
218-
.text("export_claude", "Claude Desktop")
219-
.build()?;
220-
150+
// Rebuild simplified menu
221151
let menu = MenuBuilder::new(app)
222-
.item(
223-
&MenuItemBuilder::with_id("status", "McpMux 🟢")
224-
.enabled(false)
225-
.build(app)?,
226-
)
227-
.separator()
228152
.item(&space_submenu)
229153
.separator()
230-
.text("refresh", "🔄 Refresh All Servers")
231-
.item(&export_submenu)
154+
.text("open", "Open McpMux")
232155
.separator()
233-
.text("open", "⚙️ Open McpMux")
234-
.item(&PredefinedMenuItem::separator(app)?)
235-
.text("quit", "❌ Quit")
156+
.text("quit", "Quit")
236157
.build()?;
237158

238159
tray.set_menu(Some(menu))?;

packages/ui/src/components/common/Switch.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ export function Switch({
2929
onClick={() => !disabled && onCheckedChange(!checked)}
3030
data-testid={testId}
3131
className={cn(
32-
'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
33-
checked ? 'bg-primary-500' : 'bg-surface-secondary',
32+
'relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-[rgb(var(--primary))] focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
33+
checked ? 'bg-[rgb(var(--primary))]' : 'bg-surface-secondary',
3434
className
3535
)}
3636
>

tests/e2e/specs/settings.spec.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,89 @@ test.describe('Settings', () => {
208208
await expect(mainContent).toBeVisible();
209209
});
210210
});
211+
212+
test.describe('Startup & System Tray Settings', () => {
213+
test('should display startup settings section', async ({ page }) => {
214+
const dashboard = new DashboardPage(page);
215+
await dashboard.navigate();
216+
217+
await page.locator('nav button:has-text("Settings")').click();
218+
219+
// Check for startup settings card
220+
await expect(page.getByText('Startup & System Tray')).toBeVisible();
221+
await expect(page.getByText(/Control how McpMux starts/)).toBeVisible();
222+
});
223+
224+
test('should display all three startup toggles', async ({ page }) => {
225+
const dashboard = new DashboardPage(page);
226+
await dashboard.navigate();
227+
228+
await page.locator('nav button:has-text("Settings")').click();
229+
230+
// Check all three settings exist
231+
await expect(page.getByText('Launch at Startup')).toBeVisible();
232+
await expect(page.getByText('Start Minimized')).toBeVisible();
233+
await expect(page.getByText('Close to Tray')).toBeVisible();
234+
});
235+
236+
test('should have functional toggle switches', async ({ page }) => {
237+
const dashboard = new DashboardPage(page);
238+
await dashboard.navigate();
239+
240+
await page.locator('nav button:has-text("Settings")').click();
241+
242+
// Check switches are interactive
243+
const autoLaunchSwitch = page.getByTestId('auto-launch-switch');
244+
const startMinimizedSwitch = page.getByTestId('start-minimized-switch');
245+
const closeToTraySwitch = page.getByTestId('close-to-tray-switch');
246+
247+
await expect(autoLaunchSwitch).toBeVisible();
248+
await expect(startMinimizedSwitch).toBeVisible();
249+
await expect(closeToTraySwitch).toBeVisible();
250+
251+
// All switches should be enabled (except start-minimized might be disabled if auto-launch is off)
252+
await expect(autoLaunchSwitch).toBeEnabled();
253+
await expect(closeToTraySwitch).toBeEnabled();
254+
});
255+
256+
test('should toggle close to tray setting', async ({ page }) => {
257+
const dashboard = new DashboardPage(page);
258+
await dashboard.navigate();
259+
260+
await page.locator('nav button:has-text("Settings")').click();
261+
262+
const closeToTraySwitch = page.getByTestId('close-to-tray-switch');
263+
264+
// Get initial state
265+
const initialState = await closeToTraySwitch.getAttribute('aria-checked');
266+
267+
// Toggle the switch
268+
await closeToTraySwitch.click();
269+
await page.waitForTimeout(500); // Wait for state to update
270+
271+
// Verify state changed
272+
const newState = await closeToTraySwitch.getAttribute('aria-checked');
273+
expect(newState).not.toBe(initialState);
274+
});
275+
276+
test('start minimized should be disabled when auto-launch is off', async ({ page }) => {
277+
const dashboard = new DashboardPage(page);
278+
await dashboard.navigate();
279+
280+
await page.locator('nav button:has-text("Settings")').click();
281+
282+
const autoLaunchSwitch = page.getByTestId('auto-launch-switch');
283+
const startMinimizedSwitch = page.getByTestId('start-minimized-switch');
284+
285+
// Ensure auto-launch is off
286+
const autoLaunchState = await autoLaunchSwitch.getAttribute('aria-checked');
287+
if (autoLaunchState === 'true') {
288+
await autoLaunchSwitch.click();
289+
await page.waitForTimeout(500);
290+
}
291+
292+
// Start minimized should be disabled
293+
await expect(startMinimizedSwitch).toBeDisabled();
294+
});
295+
});
211296
});

0 commit comments

Comments
 (0)