feat: add tab close confirmation setting#1956
Conversation
Greptile SummaryThis PR adds an opt-in "Confirm tab close" setting (off by default) that shows a destructive confirmation modal before any user-initiated tab close. The implementation correctly warms the React Query cache at bootstrap time and pins it with
Confidence Score: 5/5Safe to merge — the feature is off by default and all user-facing close paths are covered. The bootstrap prefetch ensures the cache is always warm before any tab close fires, and gcTime: Infinity on the 'interface' key prevents the data from being evicted. All user-facing close paths (three tab-bar buttons and the keyboard shortcut) route through the confirmation helper, while intentional programmatic closes correctly bypass it. No breaking changes to existing behavior. No files require special attention.
|
| Filename | Overview |
|---|---|
| src/renderer/features/tasks/tabs/close-tab-with-confirm.ts | New file implementing closeTabWithConfirm and closeActiveTabWithConfirm; reads setting synchronously from cache, shows a confirmation modal when enabled, and falls back to a direct close otherwise. |
| src/renderer/features/settings/use-app-settings-key.tsx | Adds settingsMetaQueryKey helper, getAppSettingValueSnapshot for synchronous cache reads, prefetchAppSettingsKey, and appSettingsGcTime that pins the 'interface' key to gcTime: Infinity; correctly updates useQuery to use the new helper. |
| src/renderer/main.tsx | Adds prefetchAppSettingsKey('interface') to the bootstrap Promise.all, ensuring the cache is warmed before React renders so getAppSettingValueSnapshot is never cold on first tab close. |
| src/renderer/features/tasks/view/unified-main-tab-bar.tsx | Replaces all three onClose lambdas from direct tabManager.closeTab to closeTabWithConfirm; all user-facing close paths covered. |
| src/renderer/features/settings/components/InterfaceSettingsCard.tsx | New settings card with toggle for confirmTabClose; correctly uses useAppSettingsKey, ResetToDefaultButton, and Switch, no issues. |
Sequence Diagram
sequenceDiagram
participant Bootstrap as main.tsx bootstrap
participant QC as QueryClient (cache)
participant RPC as rpc.appSettings
participant UI as Tab Bar / Command
participant Helper as closeTabWithConfirm
participant Modal as confirmActionModal
participant TM as TabManagerStore
Bootstrap->>QC: prefetchAppSettingsKey('interface') [gcTime: Infinity]
QC->>RPC: getWithMeta('interface')
RPC-->>QC: "{ value: { confirmTabClose: false/true }, ... }"
UI->>Helper: closeTabWithConfirm(tabManager, tabId)
Helper->>QC: getAppSettingValueSnapshot('interface')
QC-->>Helper: cached value (never undefined)
alt "confirmTabClose === false"
Helper->>TM: closeTab(tabId)
else "confirmTabClose === true"
Helper->>Modal: "showModal('confirmActionModal', { onSuccess })"
Modal-->>Helper: user confirms
Helper->>TM: closeTab(tabId)
end
Reviews (3): Last reviewed commit: "fix: retain prefetched interface setting..." | Re-trigger Greptile
| export function closeTabWithConfirm(tabManager: TabManagerStore, tabId: string): void { | ||
| if (!getAppSettingValueSnapshot('interface')?.confirmTabClose) { | ||
| tabManager.closeTab(tabId); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Cold-cache bypass silently skips confirmation
getAppSettingValueSnapshot returns undefined when the React Query cache for the interface key hasn't been populated yet. Since useAppSettingsKey('interface') is only mounted inside InterfaceSettingsCard (which renders only when the settings page is open), the cache is cold on every fresh session. The expression !undefined?.confirmTabClose evaluates to true, so the early-return fires and tabs close without a dialog — even when the user has confirmTabClose: true persisted on disk.
A safe fix is to prefetch the interface key at app initialization (e.g. in the renderer bootstrap) so the cache is always warm, or to fall back to the schema default (false) rather than treating a cold cache the same as an explicit false.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/renderer/features/tasks/tabs/close-tab-with-confirm.ts
Line: 13-17
Comment:
**Cold-cache bypass silently skips confirmation**
`getAppSettingValueSnapshot` returns `undefined` when the React Query cache for the `interface` key hasn't been populated yet. Since `useAppSettingsKey('interface')` is only mounted inside `InterfaceSettingsCard` (which renders only when the settings page is open), the cache is cold on every fresh session. The expression `!undefined?.confirmTabClose` evaluates to `true`, so the early-return fires and tabs close without a dialog — even when the user has `confirmTabClose: true` persisted on disk.
A safe fix is to prefetch the `interface` key at app initialization (e.g. in the renderer bootstrap) so the cache is always warm, or to fall back to the schema default (`false`) rather than treating a cold cache the same as an explicit `false`.
How can I resolve this? If you propose a fix, please make it concise.|
@greptile |
|
@greptile |
|
this shouold fix it :D @arnestrickmann |
|
fixing conflicts rq |
# Conflicts: # src/renderer/features/settings/use-app-settings-key.tsx # src/renderer/features/tasks/commands.ts # src/renderer/features/tasks/view/tab-bar.tsx # src/renderer/main.tsx
arnestrickmann
left a comment
There was a problem hiding this comment.
thanks for addressing the feedback, @janburzinski - this works well!

summary
confirm modal on tab close (setting; off by default)