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 { useTreeEditorStore, findNodeInTree } from '@/store/treeEditorStore'
import { TreeCanvasNode } from './TreeCanvasNode'
import { AnswerStubCard } from './AnswerStubCard'
import type { TreeStructure, NodeType } from '@/types'
import { cn } from '@/lib/utils'
@@ -203,6 +204,28 @@ export function TreeCanvas() {
(nodeId: string, updates: Partial<TreeStructure>) => {
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
const link = pendingLinks.get(nodeId)
if (link) {
@@ -278,6 +301,16 @@ export function TreeCanvas() {
[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 ──
const handleAddNodeSelect = useCallback(
(type: NodeType, parentId: string, optionId?: string) => {
@@ -464,22 +497,30 @@ export function TreeCanvas() {
</div>
)}
{/* The node card itself */}
<TreeCanvasNode
node={node}
depth={0}
fromOption={optionLabel}
isExpanded={isExpanded}
isNew={isNew}
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)}
/>
{/* The node card — answer stubs get their own component */}
{node.type === 'answer' ? (
<AnswerStubCard
node={node}
fromOption={optionLabel}
onSelectType={handleSelectAnswerType}
/>
) : (
<TreeCanvasNode
node={node}
depth={0}
fromOption={optionLabel}
isExpanded={isExpanded}
isNew={isNew}
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) */}
{!isExpanded && unlinkedOptions.length > 0 && (
@@ -586,6 +627,7 @@ export function TreeCanvas() {
handleCancelNew,
handleDelete,
handleDuplicate,
handleSelectAnswerType,
handleDragStart,
handleDragOver,
handleDrop,