diff --git a/CHANGELOG.md b/CHANGELOG.md index 31b5a75..77b5287 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 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 diff --git a/package.json b/package.json index 83a65c7..e115246 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/electron-api.d.ts b/src/electron-api.d.ts index 27ee30c..aec6d95 100644 --- a/src/electron-api.d.ts +++ b/src/electron-api.d.ts @@ -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; diff --git a/src/main.ts b/src/main.ts index 922221a..c5b9383 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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(); @@ -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'); @@ -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(); + 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(); @@ -1756,6 +1788,7 @@ const trayToggleEvtHandler = async () => { if (appMode === 'normal') { app.focus({ steal: true }); } + leaveTerminalTab(window); window.show(); window.focus(); } @@ -1763,12 +1796,13 @@ const trayToggleEvtHandler = async () => { 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(); } } diff --git a/src/preload.ts b/src/preload.ts index dc34333..f0a6605 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -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), diff --git a/src/switcher-ui.tsx b/src/switcher-ui.tsx index b6e2866..6d2c0f1 100644 --- a/src/switcher-ui.tsx +++ b/src/switcher-ui.tsx @@ -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