From fc1d83e7c604dfef74ee3803bc51ebe08b8bd142 Mon Sep 17 00:00:00 2001 From: Steven Vo <875426+stevenvo@users.noreply.github.com> Date: Mon, 15 Dec 2025 11:03:55 +0700 Subject: [PATCH 1/3] 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) --- 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 0000000000..a431fcb455 --- /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 53fabde064..a19cb4704a 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 acd23ad3a9..f92f040949 100644 --- a/frontend/app/tab/tabbar.tsx +++ b/frontend/app/tab/tabbar.tsx @@ -586,10 +586,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 handleTabLoaded = useCallback((tabId: string) => { From 029892ad3a39af0f9b4f8e5decc60c1b8b3d8eca Mon Sep 17 00:00:00 2001 From: Steven Vo <875426+stevenvo@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:46:30 -0800 Subject: [PATCH 2/3] Address CodeRabbit feedback: add null check for workspace --- frontend/app/modals/confirmclosetab.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/app/modals/confirmclosetab.tsx b/frontend/app/modals/confirmclosetab.tsx index a431fcb455..9fdac66b44 100644 --- a/frontend/app/modals/confirmclosetab.tsx +++ b/frontend/app/modals/confirmclosetab.tsx @@ -13,6 +13,10 @@ interface ConfirmCloseTabModalProps { const ConfirmCloseTabModal = ({ tabId }: ConfirmCloseTabModalProps) => { const handleConfirmClose = () => { const ws = globalStore.get(atoms.workspace); + if (!ws) { + modalsModel.popModal(); + return; + } getApi().closeTab(ws.oid, tabId); deleteLayoutModelForTab(tabId); modalsModel.popModal(); From e64f9bf166bb704e46f2d3ba1def5b4b57713920 Mon Sep 17 00:00:00 2001 From: Steven Vo <875426+stevenvo@users.noreply.github.com> Date: Sat, 3 Jan 2026 00:50:15 -0800 Subject: [PATCH 3/3] Route Cmd+Shift+W keyboard shortcut through confirmation modal --- frontend/app/store/keymodel.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/app/store/keymodel.ts b/frontend/app/store/keymodel.ts index 318d1d775a..ae6d196356 100644 --- a/frontend/app/store/keymodel.ts +++ b/frontend/app/store/keymodel.ts @@ -516,7 +516,8 @@ function registerGlobalKeys() { return true; }); globalKeyMap.set("Cmd:Shift:w", () => { - simpleCloseStaticTab(); + const tabId = globalStore.get(atoms.staticTabId); + modalsModel.pushModal("ConfirmCloseTabModal", { tabId }); return true; }); globalKeyMap.set("Cmd:m", () => {