diff --git a/web/app/dashboard/ai-workflows/_components/workflow-config-sidebar.tsx b/web/app/dashboard/ai-workflows/_components/workflow-config-sidebar.tsx
index a9240cd..085d3f0 100644
--- a/web/app/dashboard/ai-workflows/_components/workflow-config-sidebar.tsx
+++ b/web/app/dashboard/ai-workflows/_components/workflow-config-sidebar.tsx
@@ -101,6 +101,7 @@ export function WorkflowConfigPanel({
return (
diff --git a/web/app/dashboard/ai-workflows/_components/workflow-editor.tsx b/web/app/dashboard/ai-workflows/_components/workflow-editor.tsx
index 402ea7d..1c8d9de 100644
--- a/web/app/dashboard/ai-workflows/_components/workflow-editor.tsx
+++ b/web/app/dashboard/ai-workflows/_components/workflow-editor.tsx
@@ -41,6 +41,7 @@ import {
import {
createWorkflowNodeFromSpec,
deleteWorkflowNode,
+ shouldClearWorkflowSelectionOnPointerDown,
updateWorkflowNodeData,
validateWorkflowDefinition,
type WorkflowNodeData,
@@ -319,13 +320,24 @@ function WorkflowEditorInner({
onSelectBranch(null)
}
+ const clearSelectionFromCanvas = () => {
+ setNodeMenu(null)
+ closeConfigPanel()
+ }
+
return (
{
+ if (shouldClearWorkflowSelectionOnPointerDown(event.target)) {
+ clearSelectionFromCanvas()
+ }
+ }}
>
-
+
playgroundTools.zoomin(true)}
@@ -360,7 +372,7 @@ function WorkflowEditorInner({
/>
-
+
{
},
}), false)
})
+
+ it("clears workflow selection only when clicking outside preserved regions", async () => {
+ const { shouldClearWorkflowSelectionOnPointerDown } = await loadModule()
+
+ assert.equal(shouldClearWorkflowSelectionOnPointerDown({
+ closest(selector) {
+ return selector === "[data-workflow-preserve-selection]" ? {} : null
+ },
+ }), false)
+ assert.equal(shouldClearWorkflowSelectionOnPointerDown({
+ closest() {
+ return null
+ },
+ }), true)
+ })
})
describe("workflow definition mutations", () => {
diff --git a/web/app/dashboard/ai-workflows/_components/workflow-utils.ts b/web/app/dashboard/ai-workflows/_components/workflow-utils.ts
index e567f34..6c83bd5 100644
--- a/web/app/dashboard/ai-workflows/_components/workflow-utils.ts
+++ b/web/app/dashboard/ai-workflows/_components/workflow-utils.ts
@@ -131,6 +131,14 @@ export function isBranchRowActionTarget(target: EventTarget | null): boolean {
return typeof maybeElement?.closest === "function" && maybeElement.closest("button") !== null
}
+export function shouldClearWorkflowSelectionOnPointerDown(target: EventTarget | null): boolean {
+ const maybeElement = target as { closest?: (selector: string) => Element | null } | null
+ if (typeof maybeElement?.closest !== "function") {
+ return false
+ }
+ return maybeElement.closest("[data-workflow-preserve-selection]") === null
+}
+
export function getNodeTitle(
node: AIWorkflowDefinition["nodes"][number] | undefined,
specs: AIWorkflowNodeSpec[] = []