Skip to content

Commit 20908f9

Browse files
authored
Merge pull request #112 from grimmerk/feat/normal-app-mode
feat: Normal App mode, Settings UI tabs, Dev Hub sub-title
2 parents 4a00e9a + 5a16167 commit 20908f9

8 files changed

Lines changed: 412 additions & 237 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
# Changelog
22

3+
## 1.0.70
4+
5+
- Feat: Normal App mode — window stays visible, shows in Dock, draggable
6+
- Toggle in Settings: Normal App (default for new users) / Menu Bar
7+
- Instant switching, no restart needed
8+
- `Cmd+Ctrl+R` toggles show/hide in both modes
9+
- Title bar shows "Dev Hub" sub-title + mode indicator + shortcut key
10+
- Banner on first launch and mode switch (auto-dismiss)
11+
- Clicking Dock icon shows hidden window
12+
- Feat: Settings UI redesigned with tabs (General / Sessions / Shortcuts)
13+
- All settings visible without scrolling
14+
- No more content changing based on active main tab
15+
- Hints on context-specific settings (projects/sessions/tray)
16+
- Style: Terminal renamed to Terminal.app in Launch Terminal dropdown
17+
- Style: title bar padding reduced for tighter layout
18+
319
## 1.0.69
420

521
- Feat: adaptive VS Code resume via IDE lock file polling

README.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ Quick switcher for VS Code/Cursor projects, Claude Code session manager with liv
88

99
### Quick Switcher for VS Code / Cursor Projects
1010

11-
Spotlight-like quick open: press `⌃+⌘+R` or click the menu bar icon to launch the Quick Switcher. Search and select a project to open or switch to it in VS Code or Cursor — even if the IDE is not running yet.
11+
Press `⌃+⌘+R` or click the menu bar icon to launch the Quick Switcher. Search and select a project to open or switch to it in VS Code or Cursor — even if the IDE is not running yet. In Normal App mode, the window stays visible for monitoring; in Menu Bar mode, it works like Spotlight.
1212

1313
- **Recent projects** (white items): your latest VS Code/Cursor folders, workspaces, and recently opened files — read directly from IDE data, no extension required
14-
- **Working folder items** (green items): first-level subfolders found by scanning a folder you choose (Settings → Working Directory)
14+
- **Working folder items** (green items): first-level subfolders found by scanning a folder you choose (Settings → General → Working Dir)
1515
- **Git branch display**: shows the current branch for each recently opened project
1616
- Multi-word search across project names, paths, and branch names
17-
- Supports VS Code and Cursor — switch between them in Settings → IDE Preference
17+
- Supports VS Code and Cursor — switch between them in Settings → General → IDE
1818
- Remove items from the recent list by hovering and clicking "x"
1919
- **Quick-launch Claude session**: `⌘+Enter` to launch a new Claude Code session in the configured terminal, `⇧+Enter` to launch in CodeV's embedded terminal, `⌘+Click` as mouse alternative
2020

@@ -46,12 +46,28 @@ For the full same-cwd accuracy matrix (detection + switch by launch method and t
4646
CodeV includes a built-in terminal tab (powered by xterm.js + node-pty, same technology as VS Code's integrated terminal). Press `⌃+⌘+T` from anywhere (global shortcut) or `⌘+3` when CodeV is in foreground to open it.
4747

4848
- Pre-spawned on app start for instant access
49-
- Default working directory: Settings → Working Directory (fallback to home)
49+
- Default working directory: Settings → General → Working Dir (fallback to home)
5050
- Terminal state preserved when switching tabs
5151
- `⌘+K` clears screen, `Shift+Enter` for multi-line input (Claude Code compatible)
5252
- `Cmd+←/→` jumps to beginning/end of line
5353
- **"Claude in Terminal" button**: launches a new Claude Code session in the configured external terminal using the current working directory
5454

55+
### App Mode
56+
57+
CodeV supports two window modes, configurable in Settings → General → App Mode:
58+
59+
| | Normal App (default) | Menu Bar |
60+
|--|--|--|
61+
| **Dock** | Visible | Hidden |
62+
| **On blur** | Stays visible | Auto-hides |
63+
| **Window position** | Remembers last position, draggable | Centers on screen each time |
64+
| **On startup** | Shows window | Hidden until shortcut/tray click |
65+
| **`⌃+⌘+R`** | Toggle show/hide | Toggle show/hide |
66+
| **Click Dock icon** | Shows hidden window | N/A |
67+
| **Best for** | Dashboard / monitoring (keep in corner) | Quick access (spotlight-like) |
68+
69+
**Real-time updates when unfocused (Normal mode):** Status dots, final assistant/user messages, and session order update via fs.watch — no need to re-focus. New sessions and full list refresh only occur on re-focus.
70+
5571
### Tab Switching
5672

5773
| Shortcut | Action |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "CodeV",
33
"productName": "CodeV",
4-
"version": "1.0.69",
4+
"version": "1.0.70",
55
"description": "Quick switcher for VS Code, Cursor, and Claude Code sessions",
66
"repository": {
77
"type": "git",

src/electron-api.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ interface IElectronAPI {
2828
getUpdateStatus: () => Promise<{ status: string; releaseName?: string; error?: string } | null>;
2929
onUpdateStatus: (callback: IpcCallback) => void;
3030

31+
// App mode
32+
getAppMode: () => Promise<string>;
33+
setAppMode: (mode: string) => void;
34+
onAppModeChanged: (callback: IpcCallback) => void;
35+
onShortcutsUpdated: (callback: IpcCallback) => void;
36+
3137
// Session terminal settings
3238
getSessionTerminalApp: () => Promise<string>;
3339
setSessionTerminalApp: (app: string) => void;

src/main.ts

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ let serverProcess: any;
8181
const WIN_WIDTH = 800;
8282
const WIN_HEIGHT = 600;
8383

84+
// App mode: 'normal' (dock visible, no auto-hide) or 'menubar' (hidden dock, auto-hide on blur)
85+
let appMode: 'normal' | 'menubar' = 'normal'; // default to normal for new users
86+
8487
const getWindowPosition = () => {
8588
const primaryDisplay = screen.getPrimaryDisplay();
8689
const { width, height } = primaryDisplay.workAreaSize;
@@ -95,19 +98,23 @@ const getWindowPosition = () => {
9598
// NOTE: setVisibleOnAllWorkspaces is needed ?
9699
const showSwitcherWindow = () => {
97100
let window = getSwitcherWindow();
98-
101+
99102
if (!window) {
100103
// Recreate window if it has been destroyed
101104
switcherWindow = createSwitcherWindow();
102105
window = switcherWindow;
103106
}
104-
105-
const position = getWindowPosition();
106-
window.setPosition(position.x, position.y, false);
107+
108+
if (appMode === 'menubar') {
109+
// Menu bar mode: always center on screen
110+
const position = getWindowPosition();
111+
window.setPosition(position.x, position.y, false);
112+
}
113+
if (window.isMinimized()) {
114+
window.restore();
115+
}
107116
window.show();
108-
// mainWindow.setVisibleOnAllWorkspaces(true);
109117
window.focus();
110-
// mainWindow.setVisibleOnAllWorkspaces(false);
111118
};
112119

113120
const showAIAssistantWindow = () => {
@@ -258,6 +265,8 @@ const hideSwitcherWindow = () => {
258265
};
259266

260267
const onBlur = (event: any) => {
268+
// Normal mode: don't auto-hide on blur
269+
if (appMode === 'normal') return;
261270
hideSwitcherWindow();
262271
};
263272

@@ -506,6 +515,11 @@ app.on('activate', () => {
506515
if (BrowserWindow.getAllWindows().length === 0) {
507516
switcherWindow = createSwitcherWindow();
508517
}
518+
// Normal mode: clicking Dock icon shows hidden window
519+
const window = getSwitcherWindow();
520+
if (window && !window.isVisible()) {
521+
showSwitcherWindow();
522+
}
509523
});
510524

511525
/** https://www.electronjs.org/docs/latest/tutorial/ipc */
@@ -1016,6 +1030,12 @@ const trayToggleEvtHandler = async () => {
10161030
(async () => {
10171031
await app.whenReady();
10181032

1033+
// Load app mode setting early (before window creation)
1034+
appMode = ((await settings.get('app-mode')) as 'normal' | 'menubar') || 'normal';
1035+
if (appMode === 'menubar') {
1036+
app.dock.hide();
1037+
}
1038+
10191039
// Auto-update: check for updates via update.electronjs.org (non-MAS only)
10201040
if (!isMAS()) {
10211041
try {
@@ -1255,6 +1275,11 @@ const trayToggleEvtHandler = async () => {
12551275
// Load user settings
12561276
await loadUserSettings();
12571277

1278+
// Normal mode: show window after bootstrap + settings loaded (server ready for API calls)
1279+
if (appMode === 'normal') {
1280+
showSwitcherWindow();
1281+
}
1282+
12581283
let title = '';
12591284
if (!isDebug) {
12601285
title = ``;
@@ -1293,7 +1318,7 @@ const trayToggleEvtHandler = async () => {
12931318
} else {
12941319
const window = getSwitcherWindow();
12951320

1296-
if (window && window.isVisible()) {
1321+
if (window && window.isVisible() && !window.isMinimized()) {
12971322
if (isDebug) {
12981323
console.log('Switcher window visible, hiding it');
12991324
}
@@ -1679,6 +1704,8 @@ const trayToggleEvtHandler = async () => {
16791704
if (registered) {
16801705
await settings.set(`shortcut-${key}`, accelerator);
16811706
await syncTrayShortcuts();
1707+
// Notify switcher window to update shortcut display
1708+
switcherWindow?.webContents.send('shortcuts-updated', await getCurrentShortcuts());
16821709
return { success: true };
16831710
} else {
16841711
// Re-register the old shortcut since the new one failed
@@ -1694,6 +1721,8 @@ const trayToggleEvtHandler = async () => {
16941721
}
16951722
await registerAllShortcuts();
16961723
await syncTrayShortcuts();
1724+
// Notify switcher window to update shortcut display
1725+
switcherWindow?.webContents.send('shortcuts-updated', DEFAULT_SHORTCUTS);
16971726
return DEFAULT_SHORTCUTS;
16981727
});
16991728
})();
@@ -1934,6 +1963,31 @@ ipcMain.handle('get-session-statuses', async () => {
19341963
return obj;
19351964
});
19361965

1966+
ipcMain.handle('get-app-mode', async () => {
1967+
return appMode;
1968+
});
1969+
1970+
ipcMain.on('set-app-mode', async (_event, mode: string) => {
1971+
const newMode = mode === 'menubar' ? 'menubar' : 'normal';
1972+
await settings.set('app-mode', newMode);
1973+
appMode = newMode;
1974+
if (newMode === 'menubar') {
1975+
app.dock.hide();
1976+
} else {
1977+
await app.dock.show();
1978+
}
1979+
// Notify renderer to update drag region
1980+
const window = getSwitcherWindow();
1981+
if (window) {
1982+
window.webContents.send('app-mode-changed', newMode);
1983+
// Re-center when switching to menu bar mode
1984+
if (newMode === 'menubar') {
1985+
const position = getWindowPosition();
1986+
window.setPosition(position.x, position.y, false);
1987+
}
1988+
}
1989+
});
1990+
19371991
ipcMain.handle('get-session-terminal-app', async () => {
19381992
return (await settings.get('session-terminal-app')) || 'iterm2';
19391993
});
@@ -2077,4 +2131,4 @@ ipcMain.handle('detect-active-ide-projects', async () => {
20772131
return Array.from(folderNames);
20782132
});
20792133

2080-
app.dock.hide();
2134+
// app.dock.hide() moved to async init block (after settings loaded)

0 commit comments

Comments
 (0)