From a2aa158e72691e6e4df101da968a091049ce11f3 Mon Sep 17 00:00:00 2001 From: Steven Vo Date: Mon, 15 Dec 2025 11:03:55 +0700 Subject: [PATCH] Add confirmation dialog before closing tabs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevents accidental tab closures by showing a confirmation modal when clicking the X button or selecting 'Close Tab' from menu. - Created ConfirmCloseTabModal component - Registered modal in modal registry - Modified handleCloseTab to show confirmation - Modal has OK (closes tab) and Cancel (keeps tab) buttons Addresses common UX issue where users accidentally close tabs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- frontend/app/modals/confirmclosetab.tsx | 39 +++++++++++++++++++++++++ frontend/app/modals/modalregistry.tsx | 2 ++ frontend/app/tab/tabbar.tsx | 7 ++--- 3 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 frontend/app/modals/confirmclosetab.tsx diff --git a/frontend/app/modals/confirmclosetab.tsx b/frontend/app/modals/confirmclosetab.tsx new file mode 100644 index 000000000..a431fcb45 --- /dev/null +++ b/frontend/app/modals/confirmclosetab.tsx @@ -0,0 +1,39 @@ +// Copyright 2025, Command Line Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { Modal } from "@/app/modals/modal"; +import { deleteLayoutModelForTab } from "@/layout/index"; +import { atoms, getApi, globalStore } from "@/store/global"; +import { modalsModel } from "@/store/modalmodel"; + +interface ConfirmCloseTabModalProps { + tabId: string; +} + +const ConfirmCloseTabModal = ({ tabId }: ConfirmCloseTabModalProps) => { + const handleConfirmClose = () => { + const ws = globalStore.get(atoms.workspace); + getApi().closeTab(ws.oid, tabId); + deleteLayoutModelForTab(tabId); + modalsModel.popModal(); + }; + + const handleCancel = () => { + modalsModel.popModal(); + }; + + return ( + +
+
Close Tab?
+
+ Are you sure you want to close this tab? This action cannot be undone. +
+
+
+ ); +}; + +ConfirmCloseTabModal.displayName = "ConfirmCloseTabModal"; + +export { ConfirmCloseTabModal }; diff --git a/frontend/app/modals/modalregistry.tsx b/frontend/app/modals/modalregistry.tsx index 53fabde06..a19cb4704 100644 --- a/frontend/app/modals/modalregistry.tsx +++ b/frontend/app/modals/modalregistry.tsx @@ -7,6 +7,7 @@ import { UpgradeOnboardingModal } from "@/app/onboarding/onboarding-upgrade"; import { DeleteFileModal, PublishAppModal, RenameFileModal } from "@/builder/builder-apppanel"; import { SetSecretDialog } from "@/builder/tabs/builder-secrettab"; import { AboutModal } from "./about"; +import { ConfirmCloseTabModal } from "./confirmclosetab"; import { UserInputModal } from "./userinputmodal"; const modalRegistry: { [key: string]: React.ComponentType } = { @@ -15,6 +16,7 @@ const modalRegistry: { [key: string]: React.ComponentType } = { [UserInputModal.displayName || "UserInputModal"]: UserInputModal, [AboutModal.displayName || "AboutModal"]: AboutModal, [MessageModal.displayName || "MessageModal"]: MessageModal, + [ConfirmCloseTabModal.displayName || "ConfirmCloseTabModal"]: ConfirmCloseTabModal, [PublishAppModal.displayName || "PublishAppModal"]: PublishAppModal, [RenameFileModal.displayName || "RenameFileModal"]: RenameFileModal, [DeleteFileModal.displayName || "DeleteFileModal"]: DeleteFileModal, diff --git a/frontend/app/tab/tabbar.tsx b/frontend/app/tab/tabbar.tsx index 2a479aa63..7831fda1d 100644 --- a/frontend/app/tab/tabbar.tsx +++ b/frontend/app/tab/tabbar.tsx @@ -620,10 +620,9 @@ const TabBar = memo(({ workspace }: TabBarProps) => { const handleCloseTab = (event: React.MouseEvent | null, tabId: string) => { event?.stopPropagation(); - const ws = globalStore.get(atoms.workspace); - getApi().closeTab(ws.oid, tabId); - tabsWrapperRef.current.style.setProperty("--tabs-wrapper-transition", "width 0.3s ease"); - deleteLayoutModelForTab(tabId); + + // Show confirmation modal before closing + modalsModel.pushModal("ConfirmCloseTabModal", { tabId }); }; const handlePinChange = useCallback(