fix: Custom step navigation bugs - go-back, descendants, redundant checkbox
- Show previously-created custom steps as clickable options on decision nodes so they remain accessible after going back - Fix breadcrumb to show custom step titles instead of raw UUIDs - Fix ContinuationModal to show grandchildren (two levels deep) instead of immediate children that duplicate option labels - Remove redundant "Save to Library" checkbox from StepForm since PostStepActionModal now handles that decision Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -123,7 +123,9 @@ export function TreeNavigationPage() {
|
||||
return customSteps.find(cs => cs.id === nodeId) || null
|
||||
}
|
||||
|
||||
// Get descendant nodes (grandchildren) from a decision node's options
|
||||
// Get descendant nodes two levels deep (grandchildren) from a decision node's options.
|
||||
// This skips the immediate children (which often mirror the option labels) and shows
|
||||
// the actual next-level nodes the user would encounter further down each path.
|
||||
const getDescendantNodes = (decisionNodeId: string): DescendantNode[] => {
|
||||
const decisionNode = findNode(decisionNodeId, tree?.tree_structure)
|
||||
if (!decisionNode || decisionNode.type !== 'decision' || !decisionNode.options) {
|
||||
@@ -134,15 +136,35 @@ export function TreeNavigationPage() {
|
||||
|
||||
for (const option of decisionNode.options) {
|
||||
if (!option.next_node_id) continue
|
||||
const nextNode = findNode(option.next_node_id, tree?.tree_structure)
|
||||
if (!nextNode) continue
|
||||
const childNode = findNode(option.next_node_id, tree?.tree_structure)
|
||||
if (!childNode) continue
|
||||
|
||||
descendants.push({
|
||||
id: nextNode.id,
|
||||
label: nextNode.question || nextNode.title || 'Untitled',
|
||||
type: nextNode.type,
|
||||
parentOptionLabel: option.label
|
||||
})
|
||||
// Go one level deeper: get the grandchildren
|
||||
if (childNode.type === 'decision' && childNode.options) {
|
||||
for (const childOption of childNode.options) {
|
||||
if (!childOption.next_node_id) continue
|
||||
const grandchild = findNode(childOption.next_node_id, tree?.tree_structure)
|
||||
if (!grandchild) continue
|
||||
|
||||
descendants.push({
|
||||
id: grandchild.id,
|
||||
label: grandchild.question || grandchild.title || 'Untitled',
|
||||
type: grandchild.type,
|
||||
parentOptionLabel: `${option.label} \u2192 ${childOption.label}`
|
||||
})
|
||||
}
|
||||
} else if (childNode.type === 'action' && childNode.next_node_id) {
|
||||
const grandchild = findNode(childNode.next_node_id, tree?.tree_structure)
|
||||
if (grandchild) {
|
||||
descendants.push({
|
||||
id: grandchild.id,
|
||||
label: grandchild.question || grandchild.title || 'Untitled',
|
||||
type: grandchild.type,
|
||||
parentOptionLabel: `${option.label} \u2192 ${childNode.title || 'Action'}`
|
||||
})
|
||||
}
|
||||
}
|
||||
// Solution children have no further descendants - skip them
|
||||
}
|
||||
|
||||
return descendants
|
||||
@@ -262,6 +284,13 @@ export function TreeNavigationPage() {
|
||||
setCurrentNodeId(newPath[newPath.length - 1])
|
||||
}
|
||||
|
||||
// Navigate back to a previously-created custom step from the decision node
|
||||
const handleNavigateToCustomStep = (customStep: CustomStep) => {
|
||||
const newPath = [...pathTaken, customStep.id]
|
||||
setPathTaken(newPath)
|
||||
setCurrentNodeId(customStep.id)
|
||||
}
|
||||
|
||||
// Called when CustomStepModal submits - show action modal instead of inserting directly
|
||||
const handleStepCreated = (step: Step | CustomStepDraft, isFromLibrary: boolean) => {
|
||||
setPendingStep(step)
|
||||
@@ -616,7 +645,9 @@ export function TreeNavigationPage() {
|
||||
{/* Breadcrumb */}
|
||||
<div className="mb-6 flex items-center gap-2 overflow-x-auto text-sm">
|
||||
{pathTaken.map((nodeId, index) => {
|
||||
const node = findNode(nodeId)
|
||||
const node = findNode(nodeId, tree?.tree_structure)
|
||||
const customStep = findCustomStep(nodeId)
|
||||
const label = node?.question || node?.title || customStep?.step_data.title || nodeId
|
||||
return (
|
||||
<span key={nodeId} className="flex items-center gap-2 whitespace-nowrap">
|
||||
{index > 0 && <span className="text-muted-foreground">→</span>}
|
||||
@@ -627,8 +658,7 @@ export function TreeNavigationPage() {
|
||||
: 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{node?.question?.slice(0, 30) || node?.title?.slice(0, 30) || nodeId}
|
||||
{((node?.question?.length || 0) > 30 || (node?.title?.length || 0) > 30) && '...'}
|
||||
{label.length > 30 ? `${label.slice(0, 30)}...` : label}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
@@ -668,6 +698,35 @@ export function TreeNavigationPage() {
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{/* Previously-created custom steps at this node */}
|
||||
{customSteps.filter(cs => cs.inserted_after_node_id === currentNodeId).length > 0 && (
|
||||
<div className="mt-2 space-y-2">
|
||||
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
||||
Your Custom Steps
|
||||
</p>
|
||||
{customSteps
|
||||
.filter(cs => cs.inserted_after_node_id === currentNodeId)
|
||||
.map(cs => (
|
||||
<button
|
||||
type="button"
|
||||
key={cs.id}
|
||||
onClick={() => handleNavigateToCustomStep(cs)}
|
||||
className={cn(
|
||||
'w-full rounded-md border border-purple-300 bg-purple-50 p-3 text-left transition-colors',
|
||||
'hover:border-purple-500 hover:bg-purple-100',
|
||||
'dark:border-purple-700 dark:bg-purple-900/20 dark:hover:border-purple-500 dark:hover:bg-purple-900/40',
|
||||
'flex items-center gap-3'
|
||||
)}
|
||||
>
|
||||
<span className="flex-shrink-0 rounded-full bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-800 dark:bg-purple-900 dark:text-purple-100">
|
||||
Custom
|
||||
</span>
|
||||
<span>{cs.step_data.title}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add Custom Step Button */}
|
||||
<button
|
||||
onClick={() => setShowCustomStepModal(true)}
|
||||
|
||||
Reference in New Issue
Block a user