fix(canvas): cascade-delete UX — warn before deleting workspace with children (PR #1252)
- Store: pendingDelete now carries `hasChildren: boolean` (computed from nodes.some(parentId === nodeId)) - ContextMenu: passes hasChildren into setPendingDelete - Canvas: dialog title changes to "Delete Workspace and Children" with ⚠️ message when hasChildren; confirms with "Delete All" Refs: #1137 Co-authored-by: Molecule AI Fullstack (floater) <fullstack-floater@agents.moleculesai.app>
This commit is contained in:
parent
221d8b2384
commit
04c3bc6eb1
@ -117,6 +117,12 @@ function CanvasInner() {
|
||||
}
|
||||
}, [pendingDelete, setPendingDelete, removeNode]);
|
||||
|
||||
// Cascade guard: include child count in the warning message when the workspace
|
||||
// has children, so the user understands the blast radius before clicking Delete All.
|
||||
const cascadeMessage = pendingDelete?.hasChildren
|
||||
? `⚠️ Deleting "${pendingDelete.name}" will permanently delete all child workspaces and their data. This cannot be undone.`
|
||||
: null;
|
||||
|
||||
const onNodeDragStop: OnNodeDrag<Node<WorkspaceNodeData>> = useCallback(
|
||||
(_event, node) => {
|
||||
const { dragOverNodeId, nodes: allNodes } = useCanvasStore.getState();
|
||||
@ -381,9 +387,11 @@ function CanvasInner() {
|
||||
{/* Confirmation dialog for workspace delete — driven by store */}
|
||||
<ConfirmDialog
|
||||
open={!!pendingDelete}
|
||||
title="Delete Workspace"
|
||||
message={`Permanently delete "${pendingDelete?.name}"? This will stop the container and remove all configuration. This action cannot be undone.`}
|
||||
confirmLabel="Delete"
|
||||
title={pendingDelete?.hasChildren ? "Delete Workspace and Children" : "Delete Workspace"}
|
||||
message={pendingDelete?.hasChildren
|
||||
? `⚠️ Deleting "${pendingDelete?.name}" will permanently delete all of its child workspaces and their data. This cannot be undone.`
|
||||
: `Permanently delete "${pendingDelete?.name}"? This will stop the container and remove all configuration. This action cannot be undone.`}
|
||||
confirmLabel={pendingDelete?.hasChildren ? "Delete All" : "Delete"}
|
||||
confirmVariant="danger"
|
||||
onConfirm={confirmDelete}
|
||||
onCancel={() => setPendingDelete(null)}
|
||||
|
||||
@ -164,7 +164,7 @@ export function ContextMenu() {
|
||||
// it survives ContextMenu unmount. Closing the menu here avoids the
|
||||
// prior race where the portal dialog's Confirm click was treated as
|
||||
// "outside" by the menu's outside-click handler.
|
||||
setPendingDelete({ id: contextMenu.nodeId, name: contextMenu.nodeData.name });
|
||||
setPendingDelete({ id: contextMenu.nodeId, name: contextMenu.nodeData.name, hasChildren });
|
||||
closeContextMenu();
|
||||
}, [contextMenu, setPendingDelete, closeContextMenu]);
|
||||
|
||||
|
||||
@ -72,8 +72,8 @@ interface CanvasState {
|
||||
// handler: clicking Confirm registered as "outside", closed the menu, and
|
||||
// unmounted the dialog before its onClick fired. Hoisting the state fixes
|
||||
// that — see fix/context-menu-delete-race.
|
||||
pendingDelete: { id: string; name: string } | null;
|
||||
setPendingDelete: (v: { id: string; name: string } | null) => void;
|
||||
pendingDelete: { id: string; name: string; hasChildren: boolean } | null;
|
||||
setPendingDelete: (v: { id: string; name: string; hasChildren: boolean } | null) => void;
|
||||
searchOpen: boolean;
|
||||
setSearchOpen: (open: boolean) => void;
|
||||
viewport: { x: number; y: number; zoom: number };
|
||||
|
||||
Loading…
Reference in New Issue
Block a user