fix: tree editor authoring blockers - scroll trap, form density, branching hint
- Replace fixed viewport height with flex layout in NodeEditorPanel - Make footer sticky so Save/Cancel always reachable - Compact root node banner to single-line with InfoTip tooltip - Reduce resolution note from callout box to inline text - Add answer-first branching hint below options label Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -162,7 +162,7 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
|||||||
const isRoot = treeStructure?.id === nodeId
|
const isRoot = treeStructure?.id === nodeId
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={panelRef} className="flex h-[calc(100vh-105px)] w-[400px] shrink-0 flex-col border-l border-border bg-card">
|
<div ref={panelRef} className="flex h-full min-h-0 w-[400px] shrink-0 flex-col border-l border-border bg-card">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center gap-2 border-b border-border px-4 py-3 shrink-0">
|
<div className="flex items-center gap-2 border-b border-border px-4 py-3 shrink-0">
|
||||||
<span className={cn('flex h-5 w-5 shrink-0 items-center justify-center rounded', config.badgeClass)}>
|
<span className={cn('flex h-5 w-5 shrink-0 items-center justify-center rounded', config.badgeClass)}>
|
||||||
@@ -175,14 +175,14 @@ export function NodeEditorPanel({ nodeId, onClose, onSelectType }: NodeEditorPan
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Body — scrollable form area */}
|
{/* Body — scrollable form area */}
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto px-4 py-3">
|
<div className="min-h-0 flex-1 overflow-y-auto px-4 py-3 scroll-pb-24">
|
||||||
{draft.type === 'decision' && <NodeFormDecision node={draft} onUpdate={handleDraftUpdate} />}
|
{draft.type === 'decision' && <NodeFormDecision node={draft} onUpdate={handleDraftUpdate} />}
|
||||||
{draft.type === 'action' && <NodeFormAction node={draft} onUpdate={handleDraftUpdate} />}
|
{draft.type === 'action' && <NodeFormAction node={draft} onUpdate={handleDraftUpdate} />}
|
||||||
{draft.type === 'solution' && <NodeFormResolution node={draft} onUpdate={handleDraftUpdate} />}
|
{draft.type === 'solution' && <NodeFormResolution node={draft} onUpdate={handleDraftUpdate} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex items-center gap-2 border-t border-border px-4 py-3 shrink-0">
|
<div className="sticky bottom-0 flex items-center gap-2 border-t border-border bg-card px-4 py-3 shrink-0">
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={!isDirty}
|
disabled={!isDirty}
|
||||||
|
|||||||
@@ -82,21 +82,10 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Root node banner */}
|
{/* Root node banner */}
|
||||||
{isRootNode && (
|
{isRootNode && (
|
||||||
<div className="rounded-lg border-2 border-blue-500/30 bg-blue-500/10 p-4">
|
<div className="flex items-center gap-2 rounded-lg border border-blue-500/30 bg-blue-500/10 px-3 py-2">
|
||||||
<div className="flex items-start gap-3">
|
<Play className="h-4 w-4 text-blue-500 shrink-0" />
|
||||||
<div className="rounded-full bg-blue-500/20 p-2">
|
<span className="text-sm font-medium text-blue-400">Starting Question</span>
|
||||||
<Play className="h-5 w-5 text-blue-500" />
|
<InfoTip text="This is the first question users will see. Each option creates a different troubleshooting path." />
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3 className="font-semibold text-blue-400">
|
|
||||||
Starting Question
|
|
||||||
</h3>
|
|
||||||
<p className="mt-1 text-sm text-muted-foreground">
|
|
||||||
This is the first question users will see when they start this troubleshooting tree.
|
|
||||||
Each option below creates a different troubleshooting path.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -150,6 +139,7 @@ export function NodeFormDecision({ node, onUpdate }: NodeFormDecisionProps) {
|
|||||||
? "Add as many options as needed (A, B, C, D...). Each option leads to a different troubleshooting path."
|
? "Add as many options as needed (A, B, C, D...). Each option leads to a different troubleshooting path."
|
||||||
: "Each option can branch to a different next step."} />
|
: "Each option can branch to a different next step."} />
|
||||||
</label>
|
</label>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">Options become answer placeholders you can fill in later.</p>
|
||||||
{optionsError && (
|
{optionsError && (
|
||||||
<p className="mt-1 text-xs text-red-400">{optionsError.message}</p>
|
<p className="mt-1 text-xs text-red-400">{optionsError.message}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -143,10 +143,9 @@ Document what was done and the outcome.
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Note about terminal node */}
|
{/* Note about terminal node */}
|
||||||
<div className="rounded-md bg-emerald-400/10 p-3 text-sm text-emerald-400">
|
<p className="text-xs text-emerald-400/70">
|
||||||
<strong>Note:</strong> Solution nodes are terminal - they end the troubleshooting flow.
|
Solution nodes are terminal — the session completes when users reach this node.
|
||||||
The session will be marked complete when the user reaches this node.
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user