feat: auto-create answer stubs on decision save, render AnswerStubCard

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-18 01:33:08 -05:00
parent 10238f85e2
commit e19c740603

View File

@@ -2,6 +2,7 @@ import { useState, useCallback, useRef, useEffect } from 'react'
import { HelpCircle, Zap, CheckCircle, Plus, X } from 'lucide-react' import { HelpCircle, Zap, CheckCircle, Plus, X } from 'lucide-react'
import { useTreeEditorStore, findNodeInTree } from '@/store/treeEditorStore' import { useTreeEditorStore, findNodeInTree } from '@/store/treeEditorStore'
import { TreeCanvasNode } from './TreeCanvasNode' import { TreeCanvasNode } from './TreeCanvasNode'
import { AnswerStubCard } from './AnswerStubCard'
import type { TreeStructure, NodeType } from '@/types' import type { TreeStructure, NodeType } from '@/types'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
@@ -203,6 +204,28 @@ export function TreeCanvas() {
(nodeId: string, updates: Partial<TreeStructure>) => { (nodeId: string, updates: Partial<TreeStructure>) => {
updateNode(nodeId, updates) updateNode(nodeId, updates)
// For decision nodes: create answer stubs for any option without a next_node_id
if (updates.options) {
const options = updates.options
const stubsToCreate: Array<{ opt: typeof options[number]; stubId: string }> = []
options.forEach((opt) => {
if (!opt.next_node_id && opt.label.trim()) {
const stubId = addNode(nodeId, 'answer')
updateNode(stubId, { title: opt.label })
stubsToCreate.push({ opt, stubId })
}
})
if (stubsToCreate.length > 0) {
const updatedOptions = options.map((o) => {
const stub = stubsToCreate.find((s) => s.opt.id === o.id)
return stub ? { ...o, next_node_id: stub.stubId } : o
})
updateNode(nodeId, { options: updatedOptions })
}
}
// Resolve pending link for new nodes // Resolve pending link for new nodes
const link = pendingLinks.get(nodeId) const link = pendingLinks.get(nodeId)
if (link) { if (link) {
@@ -278,6 +301,16 @@ export function TreeCanvas() {
[duplicateNode] [duplicateNode]
) )
// ── Convert answer stub to a real node type ──
const handleSelectAnswerType = useCallback(
(nodeId: string, type: 'decision' | 'action' | 'solution') => {
updateNode(nodeId, { type })
setExpandedNodeId(nodeId)
selectNode(nodeId)
},
[updateNode, selectNode]
)
// ── Add node flow ── // ── Add node flow ──
const handleAddNodeSelect = useCallback( const handleAddNodeSelect = useCallback(
(type: NodeType, parentId: string, optionId?: string) => { (type: NodeType, parentId: string, optionId?: string) => {
@@ -464,22 +497,30 @@ export function TreeCanvas() {
</div> </div>
)} )}
{/* The node card itself */} {/* The node card — answer stubs get their own component */}
<TreeCanvasNode {node.type === 'answer' ? (
node={node} <AnswerStubCard
depth={0} node={node}
fromOption={optionLabel} fromOption={optionLabel}
isExpanded={isExpanded} onSelectType={handleSelectAnswerType}
isNew={isNew} />
onToggleExpand={() => handleToggleExpand(node.id)} ) : (
onSave={handleSave} <TreeCanvasNode
onCancelNew={handleCancelNew} node={node}
onDelete={handleDelete} depth={0}
onDuplicate={handleDuplicate} fromOption={optionLabel}
onDragStart={handleDragStart} isExpanded={isExpanded}
onDragOver={(e) => handleDragOver(e, parentId, index)} isNew={isNew}
onDrop={(e) => handleDrop(e, parentId, index)} onToggleExpand={() => handleToggleExpand(node.id)}
/> onSave={handleSave}
onCancelNew={handleCancelNew}
onDelete={handleDelete}
onDuplicate={handleDuplicate}
onDragStart={handleDragStart}
onDragOver={(e) => handleDragOver(e, parentId, index)}
onDrop={(e) => handleDrop(e, parentId, index)}
/>
)}
{/* Unlinked option add buttons (decision nodes with unlinked options) */} {/* Unlinked option add buttons (decision nodes with unlinked options) */}
{!isExpanded && unlinkedOptions.length > 0 && ( {!isExpanded && unlinkedOptions.length > 0 && (
@@ -586,6 +627,7 @@ export function TreeCanvas() {
handleCancelNew, handleCancelNew,
handleDelete, handleDelete,
handleDuplicate, handleDuplicate,
handleSelectAnswerType,
handleDragStart, handleDragStart,
handleDragOver, handleDragOver,
handleDrop, handleDrop,