feat: Add custom step continuation flow with save/use/branch options

Custom steps during tree navigation now support a complete workflow:
- PostStepActionModal: Save for Later / Use Now / Both options
- ContinuationModal: Pick descendant nodes or build custom branch
- ForkTreeModal: Save modified tree as personal copy at completion
- Custom steps are recorded in decisions array for export
- Fix popular-tags API endpoint URL mismatch
- Add aria-labels for accessibility on select/button elements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-02-03 20:53:48 -05:00
parent 8498d25efb
commit 6bd21d7efc
9 changed files with 726 additions and 40 deletions

View File

@@ -0,0 +1,139 @@
import { Modal } from '@/components/common/Modal'
import { ArrowRight, GitBranch, AlertTriangle, HelpCircle, Zap, CheckCircle } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { NodeType } from '@/types/tree'
export interface DescendantNode {
id: string
label: string
type: NodeType
parentOptionLabel: string
}
interface ContinuationModalProps {
isOpen: boolean
onClose: () => void
descendantNodes: DescendantNode[]
onSelectNode: (nodeId: string) => void
onBuildCustomBranch: () => void
}
const nodeTypeIcons: Record<NodeType, React.ReactNode> = {
decision: <HelpCircle className="h-4 w-4 text-blue-500" />,
action: <Zap className="h-4 w-4 text-amber-500" />,
solution: <CheckCircle className="h-4 w-4 text-green-500" />
}
const nodeTypeLabels: Record<NodeType, string> = {
decision: 'Decision',
action: 'Action',
solution: 'Solution'
}
export function ContinuationModal({
isOpen,
onClose,
descendantNodes,
onSelectNode,
onBuildCustomBranch
}: ContinuationModalProps) {
// Group nodes by parent option
const groupedNodes = descendantNodes.reduce((acc, node) => {
if (!acc[node.parentOptionLabel]) {
acc[node.parentOptionLabel] = []
}
acc[node.parentOptionLabel].push(node)
return acc
}, {} as Record<string, DescendantNode[]>)
const hasDescendants = descendantNodes.length > 0
return (
<Modal isOpen={isOpen} onClose={onClose} title="Where does this lead?" size="lg">
<div className="space-y-6">
{/* Descendant Selection */}
{hasDescendants && (
<div>
<p className="mb-4 text-sm text-muted-foreground">
Select the next step in your troubleshooting path:
</p>
<div className="space-y-4">
{Object.entries(groupedNodes).map(([parentOption, nodes]) => (
<div key={parentOption}>
<p className="mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
From: {parentOption}
</p>
<div className="space-y-2">
{nodes.map(node => (
<button
key={node.id}
onClick={() => onSelectNode(node.id)}
className={cn(
'flex w-full items-center gap-3 rounded-lg border border-border p-3 text-left transition-colors',
'hover:border-primary hover:bg-accent'
)}
>
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-muted">
{nodeTypeIcons[node.type]}
</div>
<div className="min-w-0 flex-1">
<p className="truncate font-medium">{node.label}</p>
<p className="text-xs text-muted-foreground">
{nodeTypeLabels[node.type]}
</p>
</div>
<ArrowRight className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
</button>
))}
</div>
</div>
))}
</div>
</div>
)}
{/* Divider */}
{hasDescendants && (
<div className="flex items-center gap-4">
<div className="h-px flex-1 bg-border" />
<span className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
Or
</span>
<div className="h-px flex-1 bg-border" />
</div>
)}
{/* Build Custom Branch */}
<div>
<button
onClick={onBuildCustomBranch}
className={cn(
'flex w-full items-center gap-3 rounded-lg border-2 border-dashed border-amber-500/50 bg-amber-500/5 p-4 text-left transition-colors',
'hover:border-amber-500 hover:bg-amber-500/10'
)}
>
<div className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-amber-500/10">
<GitBranch className="h-5 w-5 text-amber-500" />
</div>
<div className="flex-1">
<p className="font-medium">Build Custom Branch</p>
<p className="text-sm text-muted-foreground">
Create your own troubleshooting path with custom steps
</p>
</div>
</button>
{/* Warning */}
<div className="mt-3 flex items-start gap-2 rounded-md bg-amber-500/10 p-3">
<AlertTriangle className="mt-0.5 h-4 w-4 flex-shrink-0 text-amber-500" />
<p className="text-sm text-amber-700 dark:text-amber-400">
You'll need to complete this branch manually or mark the issue as resolved.
Custom branches can be saved as a personal tree when your session ends.
</p>
</div>
</div>
</div>
</Modal>
)
}