diff --git a/src/routes/v2/pages/Editor/nodes/TaskNode/context/TaskDetails/TaskDetails.tsx b/src/routes/v2/pages/Editor/nodes/TaskNode/context/TaskDetails/TaskDetails.tsx
index 9dcdd8704..f6cacf383 100644
--- a/src/routes/v2/pages/Editor/nodes/TaskNode/context/TaskDetails/TaskDetails.tsx
+++ b/src/routes/v2/pages/Editor/nodes/TaskNode/context/TaskDetails/TaskDetails.tsx
@@ -1,6 +1,8 @@
import { observer } from "mobx-react-lite";
import { useEffect, useRef, useState } from "react";
+import type { SaveAction } from "@/components/shared/ComponentEditor/saveAction";
+import { SaveActionsView } from "@/components/shared/ComponentEditor/SaveActionsView";
import { StackingControls } from "@/components/shared/ReactFlow/FlowControls/StackingControls";
import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";
@@ -18,6 +20,7 @@ import { useSpec } from "@/routes/v2/shared/providers/SpecContext";
import { useSharedStores } from "@/routes/v2/shared/store/SharedStoreContext";
import { SYSTEM_ANNOTATIONS, ZINDEX_ANNOTATION } from "@/utils/annotations";
import type { HydratedComponentReference } from "@/utils/componentSpec";
+import { diffComponentIO } from "@/utils/componentSpecDiff";
import { tracking } from "@/utils/tracking";
import { getTaskYamlText } from "./components/actions/getTaskYamlText";
@@ -75,9 +78,37 @@ export const TaskDetails = observer(function TaskDetails({
const isSubgraphTask = task.subgraphSpec !== undefined;
+ const renderSaveActions = ({
+ hydratedComponent,
+ onChoose,
+ }: {
+ hydratedComponent: HydratedComponentReference;
+ onChoose: (action: "update" | "import" | "place") => void;
+ }) => {
+ const { inputDiff, outputDiff } = diffComponentIO<
+ { name: string; type?: unknown },
+ { name: string; type?: unknown }
+ >(task.resolvedComponentSpec, hydratedComponent.spec);
+
+ return (
+
+ );
+ };
+
const handleComponentSaved = (
hydratedComponent: HydratedComponentReference,
+ action: SaveAction,
) => {
+ if (action !== "update") {
+ // "place" arrives once placement ships; nothing else applies in place.
+ return;
+ }
+
const result = replaceTask(spec, task.$id, hydratedComponent);
const lostInputs = result.inputDiff?.lostEntities ?? [];
@@ -182,6 +213,7 @@ export const TaskDetails = observer(function TaskDetails({
taskName={task.name}
pythonCode={pythonCode}
onComponentSaved={handleComponentSaved}
+ renderSaveActions={renderSaveActions}
/>
diff --git a/src/routes/v2/pages/Editor/nodes/TaskNode/context/TaskDetails/components/ComponentRefBar.tsx b/src/routes/v2/pages/Editor/nodes/TaskNode/context/TaskDetails/components/ComponentRefBar.tsx
index 3dffbab27..6978324d9 100644
--- a/src/routes/v2/pages/Editor/nodes/TaskNode/context/TaskDetails/components/ComponentRefBar.tsx
+++ b/src/routes/v2/pages/Editor/nodes/TaskNode/context/TaskDetails/components/ComponentRefBar.tsx
@@ -1,7 +1,8 @@
-import { useState } from "react";
+import { type ReactNode, useState } from "react";
import { CodeViewer } from "@/components/shared/CodeViewer";
import { ComponentEditorDialog } from "@/components/shared/ComponentEditor/ComponentEditorDialog";
+import type { SaveAction } from "@/components/shared/ComponentEditor/saveAction";
import ComponentDetailsDialog from "@/components/shared/Dialogs/ComponentDetailsDialog";
import { TrimmedDigest } from "@/components/shared/ManageComponent/TrimmedDigest";
import { Button } from "@/components/ui/button";
@@ -38,7 +39,12 @@ interface ComponentRefBarProps {
pythonCode: string | undefined;
onComponentSaved?: (
hydratedComponent: HydratedComponentReference,
+ action: SaveAction,
) => void | Promise;
+ renderSaveActions?: (args: {
+ hydratedComponent: HydratedComponentReference;
+ onChoose: (action: "update" | "import" | "place") => void;
+ }) => ReactNode;
}
export function ComponentRefBar({
@@ -47,6 +53,7 @@ export function ComponentRefBar({
taskName,
pythonCode,
onComponentSaved,
+ renderSaveActions,
}: ComponentRefBarProps) {
const { track } = useAnalytics();
const notify = useToastNotification();
@@ -201,6 +208,7 @@ export function ComponentRefBar({
text={yamlText}
onClose={() => setIsEditDialogOpen(false)}
onComponentSaved={onComponentSaved}
+ renderSaveActions={renderSaveActions}
/>
)}