diff --git a/frontend/src/components/tree-editor/MetadataSidePanel.tsx b/frontend/src/components/tree-editor/MetadataSidePanel.tsx
new file mode 100644
index 00000000..25ebafb3
--- /dev/null
+++ b/frontend/src/components/tree-editor/MetadataSidePanel.tsx
@@ -0,0 +1,66 @@
+import { useEffect } from 'react'
+import { X } from 'lucide-react'
+import { TreeMetadataForm } from './TreeMetadataForm'
+
+interface MetadataSidePanelProps {
+ isOpen: boolean
+ onClose: () => void
+}
+
+export function MetadataSidePanel({ isOpen, onClose }: MetadataSidePanelProps) {
+ // Close on Escape key
+ useEffect(() => {
+ if (!isOpen) return
+
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') {
+ onClose()
+ }
+ }
+
+ document.addEventListener('keydown', handleKeyDown)
+ return () => document.removeEventListener('keydown', handleKeyDown)
+ }, [isOpen, onClose])
+
+ if (!isOpen) return null
+
+ return (
+ <>
+ {/* Backdrop — click to close */}
+
+
+ {/* Side panel — slides in from right */}
+
+ {/* Panel header */}
+
+
+ Flow Details
+
+
+
+
+ {/* Scrollable metadata form */}
+
+
+
+
+ >
+ )
+}
+
+export default MetadataSidePanel
diff --git a/frontend/src/components/tree-editor/NodeFormDecision.tsx b/frontend/src/components/tree-editor/NodeFormDecision.tsx
index b2af10dc..d7bbd0fd 100644
--- a/frontend/src/components/tree-editor/NodeFormDecision.tsx
+++ b/frontend/src/components/tree-editor/NodeFormDecision.tsx
@@ -16,7 +16,7 @@ const indexToLetter = (index: number): string => {
}
export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
- const { reorderOptions, validationErrors } = useTreeEditorStore()
+ const { validationErrors } = useTreeEditorStore()
const isRootNode = node.id === 'root'
const questionError = validationErrors.find(
@@ -51,7 +51,12 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
}
const handleReorderOptions = (fromIndex: number, toIndex: number) => {
- reorderOptions(node.id, fromIndex, toIndex)
+ // Mutate local draft via onUpdate (backward-compatible: modal path relays to store,
+ // canvas path updates local draft without writing to store early)
+ const newOptions = [...(node.options || [])]
+ const [moved] = newOptions.splice(fromIndex, 1)
+ newOptions.splice(toIndex, 0, moved)
+ onUpdate({ options: newOptions })
}
return (
diff --git a/frontend/src/components/tree-editor/NodePicker.tsx b/frontend/src/components/tree-editor/NodePicker.tsx
index 0ea8bd7a..00d081c2 100644
--- a/frontend/src/components/tree-editor/NodePicker.tsx
+++ b/frontend/src/components/tree-editor/NodePicker.tsx
@@ -35,6 +35,9 @@ interface NodePickerProps {
error?: string
/** Callback when a new node is created (receives the new node ID) */
onNodeCreated?: (nodeId: string) => void
+ /** Whether to show the "Create New Node" options. Default: true.
+ * Set to false in inline canvas editing to prevent premature store writes. */
+ allowCreate?: boolean
}
export function NodePicker({
@@ -46,7 +49,8 @@ export function NodePicker({
className,
label,
error,
- onNodeCreated
+ onNodeCreated,
+ allowCreate = true
}: NodePickerProps) {
const { getAvailableTargetNodes, addNode, updateNode } = useTreeEditorStore()
const availableNodes = getAvailableTargetNodes(excludeNodeId)
@@ -201,12 +205,14 @@ export function NodePicker({
>
- {/* Create new options */}
-
+ {/* Create new options — hidden when allowCreate=false (e.g. canvas inline editing) */}
+ {allowCreate && (
+
+ )}
{/* Existing nodes grouped by type */}
{groupedNodes.decisions.length > 0 && (
diff --git a/frontend/src/components/tree-editor/TreeEditorLayout.tsx b/frontend/src/components/tree-editor/TreeEditorLayout.tsx
index cb2f9510..7b87ef56 100644
--- a/frontend/src/components/tree-editor/TreeEditorLayout.tsx
+++ b/frontend/src/components/tree-editor/TreeEditorLayout.tsx
@@ -1,7 +1,7 @@
import { lazy, Suspense } from 'react'
-import { TreeMetadataForm } from './TreeMetadataForm'
-import { NodeList } from './NodeList'
import { TreePreviewPanel } from '@/components/tree-preview/TreePreviewPanel'
+import { TreeCanvas } from './TreeCanvas'
+import { MetadataSidePanel } from './MetadataSidePanel'
import { useTreeEditorStore } from '@/store/treeEditorStore'
import { cn } from '@/lib/utils'
@@ -12,9 +12,15 @@ const CodeModeEditor = lazy(() =>
interface TreeEditorLayoutProps {
isMobile?: boolean
+ isMetadataOpen?: boolean
+ onCloseMetadata?: () => void
}
-export function TreeEditorLayout({ isMobile = false }: TreeEditorLayoutProps) {
+export function TreeEditorLayout({
+ isMobile = false,
+ isMetadataOpen = false,
+ onCloseMetadata = () => {},
+}: TreeEditorLayoutProps) {
const editorMode = useTreeEditorStore(s => s.editorMode)
return (
@@ -26,7 +32,7 @@ export function TreeEditorLayout({ isMobile = false }: TreeEditorLayoutProps) {
>
{editorMode === 'code' ? (
<>
- {/* Code Mode: Monaco editor (60%) + Preview (40%) */}
+ {/* Code Mode: Monaco editor (60%) + Preview (40%) — unchanged */}
) : (
<>
- {/* Flow Mode: Form editor (60%) + Preview (40%) */}
-
-
-
-
-
+ {/* Flow Mode: Full-width visual canvas */}
+
+
- {/* Right Panel - Preview */}
-
-
-
+ {/* Metadata side panel — overlays the canvas from the right */}
+
>
)}