Skip to content

feat: add tab close confirmation setting#1956

Merged
arnestrickmann merged 10 commits into
generalaction:mainfrom
janburzinski:emdash/confirm-tab-close-ga082
May 27, 2026
Merged

feat: add tab close confirmation setting#1956
arnestrickmann merged 10 commits into
generalaction:mainfrom
janburzinski:emdash/confirm-tab-close-ga082

Conversation

@janburzinski
Copy link
Copy Markdown
Collaborator

summary

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

image

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 10, 2026

Greptile Summary

This 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 gcTime: Infinity, so the synchronous getAppSettingValueSnapshot call inside closeTabWithConfirm is always reliable.

  • Schema + defaults: confirmTabClose: boolean added to interfaceSettingsSchema with a false default in SETTINGS_DEFAULTS.
  • Bootstrap prefetch: prefetchAppSettingsKey('interface') is awaited in the renderer's Promise.all bootstrap, ensuring the cache is populated before any tab close can fire.
  • Close path coverage: All three user-facing close buttons in unified-main-tab-bar.tsx and the keyboard shortcut in commands.ts route through the new closeTabWithConfirm / closeActiveTabWithConfirm helpers; automatic lifecycle closes in diff-tab-lifecycle-store.ts correctly bypass the modal.

Confidence Score: 5/5

Safe 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.

Important Files Changed

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
Loading

Reviews (3): Last reviewed commit: "fix: retain prefetched interface setting..." | Re-trigger Greptile

Comment on lines +13 to +17
export function closeTabWithConfirm(tabManager: TabManagerStore, tabId: string): void {
if (!getAppSettingValueSnapshot('interface')?.confirmTabClose) {
tabManager.closeTab(tabId);
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

@janburzinski
Copy link
Copy Markdown
Collaborator Author

@greptile

Comment thread src/renderer/features/tasks/tabs/close-tab-with-confirm.ts
@janburzinski
Copy link
Copy Markdown
Collaborator Author

@greptile

@janburzinski
Copy link
Copy Markdown
Collaborator Author

image

@janburzinski
Copy link
Copy Markdown
Collaborator Author

this shouold fix it :D @arnestrickmann

@janburzinski
Copy link
Copy Markdown
Collaborator Author

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
Copy link
Copy Markdown
Contributor

@arnestrickmann arnestrickmann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for addressing the feedback, @janburzinski - this works well!

@arnestrickmann arnestrickmann merged commit a96f254 into generalaction:main May 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants