Browse Source

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 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 6 months ago
parent
commit
2b8325146d
  1. 9
      .planning/ROADMAP.md
  2. 20
      src/components/Header.tsx
  3. 5
      src/components/WorkflowCanvas.tsx

9
.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 | - |

20
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 (
<>
<ProjectSetupModal
@ -357,6 +368,15 @@ export function Header() {
</div>
<div className="flex items-center gap-3 text-xs">
{previousWorkflowSnapshot && (
<button
onClick={handleRevertAIChanges}
className="px-2.5 py-1 text-xs font-medium text-amber-300 bg-amber-500/15 hover:bg-amber-500/25 border border-amber-500/30 rounded transition-colors"
title="Restore workflow from before AI changes"
>
Revert AI Changes
</button>
)}
<CommentsNavigationIcon />
<span className="text-neutral-400">
{isProjectConfigured ? (

5
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(

Loading…
Cancel
Save