From 2b8325146d916df5e45d7503c6aeff42f6ab5bd2 Mon Sep 17 00:00:00 2001 From: shrimbly Date: Tue, 27 Jan 2026 21:14:56 +1300 Subject: [PATCH] feat(33-02): implement workflow edit safety - snapshot capture and revert UI Tasks 1 & 2: Complete AI workflow revert feature Changes: - WorkflowCanvas: Call captureSnapshot() before AI loadWorkflow - Header: Add "Revert AI Changes" button with confirmation dialog - Button visible only when previousWorkflowSnapshot exists - Amber styling to distinguish safety feature from destructive actions Snapshot capture ONLY in AI generation path (not file loads/templates). Button auto-hides after revert or 3 manual changes (store handles this). Co-Authored-By: Claude Opus 4.5 --- .planning/ROADMAP.md | 9 ++++++--- src/components/Header.tsx | 20 ++++++++++++++++++++ src/components/WorkflowCanvas.tsx | 5 +++-- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 321a25bf..5fe519ed 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -467,7 +467,7 @@ Plans: Plans: - [x] 32-01: ChatPanel component with useChat hook, WorkflowCanvas integration -- [ ] 32-02: Chat context and workflow generation integration +- [x] 32-02: Chat context and workflow generation integration #### Phase 33: Workflow Edit Safety @@ -477,8 +477,11 @@ Plans: **Research topics**: Shadow copy vs diff-based vs undo stack approaches, JSON diff visualization **Plans**: TBD +**Plans**: 2 plans + Plans: -- [ ] 33-01: TBD (run /gsd:plan-phase 33 to break down) +- [ ] 33-01-PLAN.md — Store snapshot state, capture/revert/clear actions, manual change tracking +- [ ] 33-02-PLAN.md — Wire snapshot capture in AI flow, Revert AI Changes button in Header #### Phase 34: Agentic Workflow Editing @@ -651,7 +654,7 @@ Phases execute in numeric order: 1 → 2 → ... → 35 → 36 → 37 → 38 → | 29. Canvas Performance | v1.3 | 0/0 | Deferred | - | | 30. Small Fixes | v1.3 | 0/0 | Deferred | - | | 31. Workflow Proposal System | v1.4 | 2/2 | Complete | 2026-01-26 | -| 32. Chat UI Foundation | v1.4 | 1/2 | In progress | - | +| 32. Chat UI Foundation | v1.4 | 2/2 | Complete | 2026-01-27 | | 33. Workflow Edit Safety | v1.4 | 0/? | Not started | - | | 34. Agentic Workflow Editing | v1.4 | 0/? | Not started | - | | 35. Large Workflow Handling | v1.4 | 0/? | Not started | - | diff --git a/src/components/Header.tsx b/src/components/Header.tsx index fe6cbbcb..9fcb5cc9 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -67,6 +67,8 @@ export function Header() { setWorkflowMetadata, saveToFile, loadWorkflow, + previousWorkflowSnapshot, + revertToSnapshot, } = useWorkflowStore(); const [showProjectModal, setShowProjectModal] = useState(false); @@ -157,6 +159,15 @@ export function Header() { } }; + const handleRevertAIChanges = useCallback(() => { + const confirmed = window.confirm( + "Are you sure? This will restore your previous workflow." + ); + if (confirmed) { + revertToSnapshot(); + } + }, [revertToSnapshot]); + return ( <>
+ {previousWorkflowSnapshot && ( + + )} {isProjectConfigured ? ( diff --git a/src/components/WorkflowCanvas.tsx b/src/components/WorkflowCanvas.tsx index 916fb70b..67bbc547 100644 --- a/src/components/WorkflowCanvas.tsx +++ b/src/components/WorkflowCanvas.tsx @@ -179,7 +179,7 @@ const findScrollableAncestor = (target: HTMLElement, deltaX: number, deltaY: num }; export function WorkflowCanvas() { - const { nodes, edges, groups, onNodesChange, onEdgesChange, onConnect, addNode, updateNodeData, loadWorkflow, getNodeById, addToGlobalHistory, setNodeGroupId, executeWorkflow, isModalOpen, showQuickstart, setShowQuickstart, navigationTarget, setNavigationTarget } = + const { nodes, edges, groups, onNodesChange, onEdgesChange, onConnect, addNode, updateNodeData, loadWorkflow, getNodeById, addToGlobalHistory, setNodeGroupId, executeWorkflow, isModalOpen, showQuickstart, setShowQuickstart, navigationTarget, setNavigationTarget, captureSnapshot } = useWorkflowStore(); const { screenToFlowPosition, getViewport, zoomIn, zoomOut, setViewport, setCenter } = useReactFlow(); const { show: showToast } = useToast(); @@ -579,6 +579,7 @@ export function WorkflowCanvas() { const data = await response.json(); if (data.success && data.workflow) { + captureSnapshot(); // Capture BEFORE loading new workflow await loadWorkflow(data.workflow); setIsChatOpen(false); showToast("Workflow generated successfully", "success"); @@ -591,7 +592,7 @@ export function WorkflowCanvas() { } finally { setIsBuildingWorkflow(false); } - }, [loadWorkflow, showToast]); + }, [loadWorkflow, showToast, captureSnapshot]); // Handle node selection from drop menu const handleMenuSelect = useCallback(