Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 1.0.93

- Fix: **`⌃+⌘+R` no longer comes back to the Term tab.** Leave the app sitting on Term, switch to another app, press the Quick Switcher shortcut, and it reopened on Term — but Term has its own shortcut (`⌃+⌘+T`), and the switcher is what `⌃+⌘+R` is for. When the switcher is shown from the Term tab, it now moves to whatever **Settings → General → Default Tab** names; a window parked on Projects or Sessions keeps its place, so this is not a reset-to-default on every show. When Default Tab is itself *Terminal*, staying on Term is the same rule, not an exception. Two causes, neither of them a state bug: Default Tab was applied only when a window was created, and the Quick Switcher callback only showed, hid or focused the window without ever naming a tab
- Fix: a switcher window re-created after every window had closed — by the shortcut, or by clicking the Dock icon — briefly showed the Projects tab before jumping to Default Tab, because those paths built the window without the `#mode=` hash the startup path passes and relied on the renderer re-applying the default once it loaded. All three creation paths now pass it, read through one helper that also survives an unreadable settings file instead of leaving the shortcut dead

## 1.0.92

- Feat: **a non-anchor account can share the anchor's auto-memory, per project** (Settings → Accounts → Sharing → *Memory: share with the anchor, per project*; `codev account share-memory <name> on|off`). Auto-memory is stored per project under each account's own config dir, so two accounts on one machine kept two memories for the same repository. Turning this on points the other account at the anchor's copy of whichever repository a session starts in, computed at launch rather than baked in — a repository, its subdirectories and all of its linked worktrees resolve to one memory (the key is the git common directory's parent; outside a repository it is the directory itself, with symlinks resolved — Claude Code files a session under the physical path). Off by default, and the anchor is never offered it: its memory is the one the others share
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "CodeV",
"productName": "CodeV",
"version": "1.0.92",
"version": "1.0.93",
"description": "Quick switcher for VS Code, Cursor, and Claude Code sessions",
"repository": {
"type": "git",
Expand Down
2 changes: 2 additions & 0 deletions src/electron-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ interface IElectronAPI {
onWorkingFolderIterated: (callback: IpcCallback) => void;
onFocusWindow: (callback: IpcCallback) => void;
onSwitchToTerminal: (callback: IpcCallback) => void;
/** Quick Switcher was shown: if the window sits on Term, go to the default tab. */
onQuickSwitcherLeaveTerminal: (callback: IpcCallback) => void;
onCheckTerminalAndHide: (callback: IpcCallback) => void;
onXWinNotFound: (callback: IpcCallback) => void;

Expand Down
46 changes: 40 additions & 6 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -672,15 +672,35 @@ app.on('window-all-closed', () => {
}
});

/**
* Settings → General → Default Tab, read the one way every caller uses.
* Never throws: electron-settings rejects on an unreadable or malformed file,
* and a shortcut that awaits this must still open the window. Anything that is
* not a known tab normalises to 'projects', the renderer's own fallback.
*/
const readDefaultTab = async (): Promise<'projects' | 'sessions' | 'terminal'> => {
try {
const stored = await settings.get('default-switcher-mode');
if (stored === 'sessions' || stored === 'terminal') return stored;
} catch (err) {
console.error('[default-tab] could not read the setting:', err);
}
return 'projects';
};

/** not triggered yet */
app.on('activate', () => {
app.on('activate', async () => {
if (isDebug) {
console.log('activate');
}
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
// Read BEFORE the count check, so the check and the create run in one tick:
// with the await between them, two quick Dock clicks could both see zero
// windows and each create one, orphaning the first.
const defaultTab = await readDefaultTab();
if (BrowserWindow.getAllWindows().length === 0) {
switcherWindow = createSwitcherWindow();
switcherWindow = createSwitcherWindow(defaultTab);
}
// Normal mode: clicking Dock icon shows hidden window
const window = getSwitcherWindow();
Expand Down Expand Up @@ -1483,7 +1503,7 @@ const trayToggleEvtHandler = async () => {
}
}

const defaultSwitcherMode = ((await settings.get('default-switcher-mode')) as string) || 'projects';
const defaultSwitcherMode = await readDefaultTab();
switcherWindow = createSwitcherWindow(defaultSwitcherMode);
if (isDebug) {
console.log('when ready');
Expand Down Expand Up @@ -1728,16 +1748,28 @@ const trayToggleEvtHandler = async () => {
};

// Shortcut callback: Quick Switcher
const quickSwitcherCallback = () => {
const quickSwitcherCallback = async () => {
if (isDebug) {
console.log('Command+Control+R triggered - always shows Switcher window');
}

// "Default Tab" used to apply only when the window was CREATED, and nothing
// re-applied it afterwards — so a switcher left on the Term tab came back to
// Term. Term is not what this shortcut is for; it has its own (Ctrl+Cmd+T).
// Showing the switcher therefore leaves the Term tab for the default, while
// Projects and Sessions keep their place, which is the part worth keeping.
// Sent BEFORE the window is shown or focused in every branch: main-to-renderer
// messages arrive in send order, so the tab switches before the focus event
// that follows — which then refreshes Sessions once, so the listener does not.
const defaultTab = await readDefaultTab();
Comment thread
grimmerk marked this conversation as resolved.
const leaveTerminalTab = (w: BrowserWindow | null) =>
w?.webContents.send('quick-switcher-leave-terminal', defaultTab);

if (BrowserWindow.getAllWindows().length === 0) {
if (isDebug) {
console.log('No window, creating main window');
}
switcherWindow = createSwitcherWindow();
switcherWindow = createSwitcherWindow(defaultTab);
showSwitcherWindow();
} else {
const window = getSwitcherWindow();
Expand All @@ -1756,19 +1788,21 @@ const trayToggleEvtHandler = async () => {
if (appMode === 'normal') {
app.focus({ steal: true });
}
leaveTerminalTab(window);
window.show();
window.focus();
}
} else if (window) {
if (isDebug) {
console.log('Switcher window exists but hidden, showing it');
}
leaveTerminalTab(window);
showSwitcherWindow();
} else {
if (isDebug) {
console.log('Switcher window was destroyed, creating a new one');
}
switcherWindow = createSwitcherWindow();
switcherWindow = createSwitcherWindow(defaultTab);
showSwitcherWindow();
}
}
Expand Down
1 change: 1 addition & 0 deletions src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
openExternal: (url: string) => ipcRenderer.send('open-external', url),
onFocusWindow: (callback: any) => ipcRenderer.on('window-focus', callback),
onSwitchToTerminal: (callback: any) => ipcRenderer.on('switch-to-terminal', callback),
onQuickSwitcherLeaveTerminal: (callback: any) => ipcRenderer.on('quick-switcher-leave-terminal', callback),
onCheckTerminalAndHide: (callback: any) => ipcRenderer.on('check-terminal-and-hide', callback),
onXWinNotFound: (callback: any) => ipcRenderer.on('xwin-not-found', callback),

Expand Down
15 changes: 15 additions & 0 deletions src/switcher-ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2077,6 +2077,21 @@ function SwitcherApp() {
setMode('terminal');
});

// The Quick Switcher is being shown. Only the Term tab is left behind — a
// window parked on Projects or Sessions keeps its place, so this is not a
// "reset to the default every time". If the default IS terminal, staying put
// falls out of the same branch.
window.electronAPI.onQuickSwitcherLeaveTerminal((_e: unknown, target: unknown) => {
if (modeRef.current !== 'terminal') return;
// Anything that is not a switcher tab — 'terminal' itself, or a value an
// older settings file could hold — means stay put rather than guess.
if (target !== 'projects' && target !== 'sessions') return;
modeRef.current = target;
setMode(target);
// No fetch here: main sends this before showing/focusing the window, and
// the focus handler that follows refreshes Sessions once on its own.
});

// Account count decides whether the ⌥⌘+Enter picker hint is shown.
// Re-checked when the embedded Settings popup mutates accounts (same-window
// CustomEvent) and on window focus (covers CLI-side changes) — no restart
Expand Down
Loading