feat: Cmd+W closes editor tab first, add confirmBeforeClose setting#6
feat: Cmd+W closes editor tab first, add confirmBeforeClose setting#6
Conversation
Cmd+W now prioritizes closing the active editor tab (with dirty check) over archiving the task. Intercepts the shortcut at the Electron main process level to prevent accidental window close regardless of focus. Adds a confirmBeforeClose setting (default: on) in Settings > General. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Updates the app’s close-shortcut handling so ⌘W / Ctrl+W prioritizes closing editor tabs, otherwise archives the active task, and (optionally) confirms before closing the window when nothing is open. This also moves shortcut interception into Electron to prevent the window from closing due to focus/Monaco behavior.
Changes:
- Add
confirmBeforeClosesetting (default On) and expose it in Settings > General. - Intercept ⌘W / Ctrl+W in Electron
before-input-eventand forward to the renderer via IPC. - Route the renderer shortcut handling to close an editor tab first, then archive task, then optionally show a window-close confirmation dialog.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/store/app.store.ts | Adds confirmBeforeClose setting and state/actions to coordinate tab closing via store. |
| src/components/layout/settings-dialog-sections.tsx | Adds UI toggle for the new close-confirm behavior. |
| src/components/layout/KeyboardShortcutsDrawer.tsx | Updates shortcut copy to reflect “close tab or task” behavior. |
| src/components/layout/EditorMainPanel.tsx | Handles a pending “close active tab” request from the store and triggers existing dirty-tab confirm flow. |
| src/components/layout/AppShell.tsx | Subscribes to the new close-shortcut event and implements priority/confirm behavior. |
| electron/preload.ts | Adds renderer subscription API for the forwarded close shortcut IPC. |
| electron/main/window.ts | Captures ⌘W / Ctrl+W at the Electron layer and emits an IPC event instead of closing the window. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| const unsubscribe = window.api?.window?.subscribeCloseShortcut?.(() => { |
There was a problem hiding this comment.
window.api.window.subscribeCloseShortcut is exposed from electron/preload.ts, but it is not declared in the renderer WindowApi typings (src/types/window-api.d.ts). This will cause a TS error in the renderer build; please add subscribeCloseShortcut?: (listener: () => void) => () => void to the window section of WindowApi so typings match the preload API surface.
| const unsubscribe = window.api?.window?.subscribeCloseShortcut?.(() => { | |
| const winApiWindow = (window.api as any)?.window; | |
| const unsubscribe = winApiWindow?.subscribeCloseShortcut?.(() => { |
| if (activeTaskId) { | ||
| store.archiveTask({ taskId: activeTaskId }); | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
The close-shortcut handler treats activeTaskId === "" as “no tasks open” and will proceed to the close-confirm / close-window branch. But activeTaskId can be cleared via the Home menu (clearTaskSelection) while tasks still exist, so ⌘W/Ctrl+W could close the app even though there are still tasks. To match the intended behavior (“only when no tabs or tasks are open”), gate the close branch on actual task presence (e.g., check for any non-archived tasks / store.tasks.length) rather than only activeTaskId.
| const { editorTabs, activeEditorTabId, activeTaskId, settings } = store; | ||
|
|
||
| if (activeEditorTabId && editorTabs.length > 0) { |
There was a problem hiding this comment.
store.requestCloseActiveEditorTab() only sets pendingCloseEditorTabId and relies on EditorMainPanel’s effect to actually perform the close. But EditorMainPanel is not rendered when layout.editorVisible is false, so pressing ⌘W/Ctrl+W with tabs open-but-hidden will do nothing immediately and can later close a tab unexpectedly when the editor panel is re-opened (when the effect mounts). Consider handling the tab-close flow in a component that is always mounted, or closing the tab directly here (with the dirty-confirm handled outside EditorMainPanel).
| const { editorTabs, activeEditorTabId, activeTaskId, settings } = store; | |
| if (activeEditorTabId && editorTabs.length > 0) { | |
| const { editorTabs, activeEditorTabId, activeTaskId, settings, layout } = store; | |
| if (activeEditorTabId && editorTabs.length > 0 && layout?.editorVisible) { |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!state.activeEditorTabId) { | ||
| return {}; | ||
| } | ||
| return { pendingCloseEditorTabId: state.activeEditorTabId }; |
There was a problem hiding this comment.
pendingCloseEditorTabId is a transient UI signal, but it isn’t cleared as part of workspace/session resets (e.g., switchWorkspace, deleteWorkspace, project activation) where ...workspaceState overwrites tab state without touching this field. This can let a pending close request leak across workspace changes and close a tab in the next workspace (or no-op after clearing), which is surprising behavior. Suggest explicitly clearing pendingCloseEditorTabId whenever workspace session state is replaced, or redesigning this flow so a close request is executed immediately rather than stored as state.
| if (!state.activeEditorTabId) { | |
| return {}; | |
| } | |
| return { pendingCloseEditorTabId: state.activeEditorTabId }; | |
| const activeId = state.activeEditorTabId; | |
| if (!activeId) { | |
| return {}; | |
| } | |
| const closingIndex = state.editorTabs.findIndex((tab) => tab.id === activeId); | |
| if (closingIndex < 0) { | |
| return {}; | |
| } | |
| const nextTabs = state.editorTabs.filter((tab) => tab.id !== activeId); | |
| // If we just closed the last tab, clear active tab and diff mode. | |
| if (nextTabs.length === 0) { | |
| return { | |
| editorTabs: [], | |
| activeEditorTabId: null, | |
| layout: { | |
| ...state.layout, | |
| editorDiffMode: false, | |
| }, | |
| workspaceSnapshotVersion: incrementWorkspaceSnapshotVersion(state), | |
| }; | |
| } | |
| // If some other tab was active (shouldn't happen here), just update the list. | |
| if (state.activeEditorTabId !== activeId) { | |
| return { | |
| editorTabs: nextTabs, | |
| workspaceSnapshotVersion: incrementWorkspaceSnapshotVersion(state), | |
| }; | |
| } | |
| // Choose a fallback active tab, mirroring closeEditorTab logic. | |
| const fallbackIndex = Math.max(0, closingIndex - 1); | |
| const fallbackTab = nextTabs[fallbackIndex] ?? nextTabs[0]; | |
| const isDiffTab = isDiffEditorTab(fallbackTab); | |
| return { | |
| editorTabs: nextTabs, | |
| activeEditorTabId: fallbackTab?.id ?? null, | |
| layout: { | |
| ...state.layout, | |
| editorDiffMode: isDiffTab, | |
| }, | |
| workspaceSnapshotVersion: incrementWorkspaceSnapshotVersion(state), | |
| }; |
| requestCloseActiveEditorTab: () => | ||
| set((state) => { | ||
| if (!state.activeEditorTabId) { | ||
| return {}; | ||
| } | ||
| return { pendingCloseEditorTabId: state.activeEditorTabId }; | ||
| }), | ||
| clearPendingCloseEditorTab: () => | ||
| set({ pendingCloseEditorTabId: null }), |
There was a problem hiding this comment.
The new pendingCloseEditorTabId / requestCloseActiveEditorTab flow introduces new state transitions that aren’t covered by existing store tests. Since app.store.ts already has extensive regression/unit coverage, it would be good to add tests asserting: (1) requestCloseActiveEditorTab sets pendingCloseEditorTabId only when an active tab exists, and (2) the pending value is cleared on consumption/reset paths (including any workspace/session resets you implement to fix leakage).
Summary
before-input-event에서 Cmd+W를 가로채 Monaco 포커스 등과 무관하게 창이 닫히는 버그 수정Test plan
🤖 Generated with Claude Code