* feat: Add TreeCanvasNode inline editor card component Replaces modal-based node editing with inline expand/collapse cards. Each card shows node type, title, and options in compact mode, then renders the full edit form inline on expand — no modal required. Local draft state with save/cancel prevents premature store writes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: Add TreeCanvas layout with visual branching and orchestration Replaces NodeList + TreePreviewPanel with a single full-width canvas. Decision nodes branch horizontally; action/solution nodes flow vertically. Inline type picker adds nodes without modal interruption. Handles pending link resolution, inbound reference cleanup on delete, and selection sync. CSS dot-grid background + connector lines for structure clarity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * refactor: Update forms for inline safety, add MetadataSidePanel, update layout - NodeFormDecision: option reorder via onUpdate (no premature store writes) - NodePicker: add allowCreate prop (default true) to hide Create New options during inline canvas editing, preventing side-effect node creation - MetadataSidePanel: 320px right slide-in overlay wrapping TreeMetadataForm, closes on backdrop click, close button, and Escape key - TreeEditorLayout: Flow mode now renders full-width TreeCanvas + MetadataSidePanel overlay; Code mode unchanged (Monaco + preview split) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: Wire toolbar metadata toggle and integrate canvas layout - Add isMetadataOpen state in TreeEditorPage - Add Metadata toolbar button (visible in Flow mode only) - Auto-close metadata panel when switching to Code mode - Pass isMetadataOpen/onCloseMetadata props through TreeEditorLayout - Update Flow mode toggle tooltip to reflect new canvas editing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: add canvas UX fixes design doc (scroll, tooltips, answer stubs) Captures approved design for three post-implementation UX improvements to the tree canvas editor: card scroll fix, info tooltip replacement for hint text, and the new 'answer' node type for sketching decision branches before assigning types. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: add implementation plan for canvas UX fixes 12-task plan covering scroll fix, info tooltips, and answer stub node type. Each task has exact file paths, code, and build verification steps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: make canvas card expanded area scrollable with sticky header Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add fullscreen toggle to Modal, enable in NodeEditorModal Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add reusable InfoTip component for field-level help Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: replace hint paragraphs with InfoTip tooltips in NodeFormDecision Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: replace hint paragraphs with InfoTip tooltips in NodeFormAction Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: replace hint paragraphs with InfoTip tooltips in NodeFormResolution Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add 'answer' to NodeType union for branch placeholder stubs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add AnswerStubCard component for unresolved branch placeholders Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: guard NODE_TYPE_CONFIG lookup against 'answer' type Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: redesign NodeFormDecision to label-only options, remove NodePicker Users now type answer labels only. Stub nodes are created automatically by TreeCanvas when the decision node is saved. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: auto-create answer stubs on decision save, render AnswerStubCard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: add answer type to all Record<NodeType> icon and color maps Fixes NodeList, ContinuationModal, NodePicker, and TreePreviewNode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: allow 'answer' type in tree drafts, block on publish Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: block publish if unresolved answer stub nodes exist Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: serialize 'answer' stub nodes in markdown output Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: add defensive guard for answer nodes in session navigation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: add delete button with confirmation to AnswerStubCard Adds an inline delete flow to answer stub placeholder cards: - Trash icon button (top-right, subtle) visible in idle state - Click reveals "Delete this stub?" confirmation with Delete/Cancel - Confirmed delete calls onDelete(nodeId) wired to handleDelete in TreeCanvas Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: prevent category Cancel overflow and add Tab/Enter to create options - TreeMetadataForm: add min-w-0 + shrink-0 to keep Cancel button in-panel - NodeFormDecision: Tab or Enter on the last non-empty option input adds a new option and auto-focuses it; empty last input lets Tab pass through normally Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: re-sync draft from store when canvas card is opened When a decision node is saved with new options, stub next_node_id values are written back to the store. But the local draft was initialized once at mount and never refreshed, so reopening the card gave a stale draft with empty next_node_ids — causing duplicate stubs on every subsequent save. Fix: reset draft from the live node whenever isExpanded transitions to true. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix+feat: blank options, stub card dismiss, collapsible subtrees - TreeCanvas: strip blank-label options on save so they don't generate stubs; also filter them from the unlinked-option add-button list - AnswerStubCard: collapse type-picker when clicking outside the card - TreeCanvasNode: add subtree collapse toggle button (ChevronsDownUp icon) visible in compact mode when the node has children - TreeCanvas: track collapsedNodeIds; hide subtree behind a clickable "N nodes hidden" pill when collapsed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: stop connector fork line from overlapping child cards Replace the two-element approach (separate fork line + child lanes div with mismatched maxWidth values) with a single relative-positioned container. The fork line is absolutely positioned and its left/right are calculated from the number of children so it spans exactly from the center of the first lane to the center of the last lane. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: replace Show Drafts checkbox with Drafts tab in Flow Library - Remove the out-of-place checkbox; add 'Drafts' as a tab alongside All | Troubleshooting | Projects | Maintenance - Drafts tab sets showDrafts=true + typeFilter='all' so the API filter still works correctly via include_drafts - Move SortDropdown to the right side next to ViewToggle, so both secondary controls are grouped together Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
11 KiB
Canvas UX Fixes — Design Document
Date: 2026-02-18
Branch: feature/tree-editor-canvas
Status: Approved, pending implementation
Context
The new TreeCanvas editor (Phase 1–4) was tested and three UX problems were identified:
- Scroll: Expanded card forms have no height limit — long forms are cut off and unreachable
- Busy forms: Inline hint text (
<p className="text-xs">) inside NodeForm components creates visual clutter - Answer stubs: When building a decision node, users must immediately pick a child node type — there's no way to sketch out answer options first and decide types later
All three fixes apply exclusively to the canvas editor. No session, navigation, backend session-saving, or procedural flow code is affected.
Fix 1: Card Scroll
Problem
TreeCanvasNode.tsx renders the expanded editing area as an unbounded <div>. On decision nodes with many options, or on any node when the browser viewport is short, the card overflows off-screen. There is no scrollbar — content is unreachable. Tab cycling doesn't scroll the canvas to bring hidden fields into view.
Design
Apply max-h-[70vh] overflow-y-auto to the expanded editing <div> inside TreeCanvasNode.tsx.
Make the save/cancel header row sticky (sticky top-0 z-10 bg-card) so the action buttons are always visible when the user scrolls the form content.
Files changed:
frontend/src/components/tree-editor/TreeCanvasNode.tsx- Add
max-h-[70vh] overflow-y-autoto the expanded area<div>(currentlyborder-t border-border px-3 pb-3 pt-3) - Add
sticky top-0 z-10 bg-cardto the card header<div>containing the save/cancel row when in expanded state
- Add
No other files affected.
Fix 2: Info Tooltips
Problem
NodeFormDecision.tsx, NodeFormAction.tsx, and NodeFormResolution.tsx each contain <p className="mb-1 text-xs text-muted-foreground"> hint paragraphs below field labels. These add vertical height and visual noise inside a card that's already compact.
Examples of the current hint text:
- "Supports markdown: bold, italic, - lists, 1. numbered lists, `code`"
- "PowerShell or CLI commands to execute"
- "Step-by-step instructions for resolving the issue"
Design
Replace each hint <p> with a small ⓘ icon placed inline next to the field label. The icon shows a tooltip on hover containing the same text.
Tooltip implementation:
Use title="" on the icon element for a native browser tooltip. No third-party tooltip library needed — keeps the implementation minimal and consistent with the existing codebase pattern (the validation badge already uses title={nodeErrors.map(...).join('\n')}).
// Before
<label className="block text-sm font-medium text-foreground">
Description
</label>
<p className="mb-1 text-xs text-muted-foreground">
Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`
</p>
// After
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
Description
<span
className="inline-flex items-center justify-center h-3.5 w-3.5 rounded-full border border-muted-foreground/40 text-[9px] text-muted-foreground cursor-help"
title="Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`"
>
i
</span>
</label>
Files changed:
frontend/src/components/tree-editor/NodeFormDecision.tsx— remove help_text hint<p>, replace withⓘonHelp Textlabelfrontend/src/components/tree-editor/NodeFormAction.tsx— remove description markdown hint<p>and commands hint<p>, addⓘon those labelsfrontend/src/components/tree-editor/NodeFormResolution.tsx— remove description markdown hint<p>and steps hint<p>, addⓘon those labels
Fix 3: Answer Stubs (New answer Node Type)
Problem
Decision nodes require the user to pick child node types at the same time they're creating the decision. This is backwards — you naturally know the answer options before you know what each one should do. The NodePicker in NodeFormDecision forces a concrete type selection (decision / action / solution) or leaves the option disconnected (next_node_id: null).
Users want to type answer labels first, see those answers appear as placeholder cards in the canvas, and then click each placeholder to assign a type and fill in details.
Design
Introduce 'answer' as a new internal NodeType that represents a typed-but-unresolved branch placeholder. Answer nodes are:
- Created when a user types an answer label in the decision node form
- Shown in the canvas as a dashed-border stub card with the answer label
- Clickable to open a type picker (decision / action / solution) and convert to a real node
- Not publishable — blocked by backend and frontend validation on publish
Answer nodes persist to draft saves so users don't lose their sketch when they navigate away.
Data Model
frontend/src/types/tree.ts
// Before
export type NodeType = 'decision' | 'action' | 'solution'
// After
export type NodeType = 'decision' | 'action' | 'solution' | 'answer'
TreeStructure interface: no new fields needed. Answer nodes use:
id: auto-generated UUID (same as other nodes)type:'answer'title: the answer label text (e.g. "Server", "Desktop")- No other fields required
NodeFormDecision Redesign
Replace the current options UI (NodePicker per option → picks existing or creates new) with a two-zone layout:
Zone 1 — Answer Labels
A simple list of text inputs, one per answer option. Each input edits options[i].label. Add/remove/reorder via DynamicArrayField (already available).
No next_node_id selection here. When the user saves, for each option that has a label but no next_node_id, a new answer-type stub node is created and linked automatically.
Options (answer labels):
[ Server ] [×]
[ Desktop ] [×]
[ + Add Answer ]
Zone 2 — (removed) NodePicker per option The per-option NodePicker is removed entirely. The canvas becomes the way to traverse to a child and set its type.
TreeCanvas Changes
Rendering answer nodes:
When a node has type === 'answer', render an AnswerStubCard instead of a full TreeCanvasNode. The stub card:
- Dashed border:
border-2 border-dashed border-border - Colored left accent: none (neutral/muted)
- Shows the answer label (
node.title) centered - Shows a "+ Choose Type" label below the title
- On click: opens an inline type picker (3 buttons: Decision / Action / Solution)
- On type selection: calls
updateNode(node.id, { type: selectedType })and immediately expands the node for editing
New component: frontend/src/components/tree-editor/AnswerStubCard.tsx
Props:
interface AnswerStubCardProps {
node: TreeStructure // type === 'answer'
fromOption?: string // the answer label (same as node.title)
onSelectType: (nodeId: string, type: 'decision' | 'action' | 'solution') => void
}
Stub Creation Logic (TreeCanvas)
When a decision node is saved (onSave), the canvas compares options before/after:
For each option in the saved node:
- If
option.next_node_idis null/undefined → create a new answer stub node withtitle = option.labeland linkoption.next_node_idto its ID. - If
option.next_node_idalready points to a node → leave it.
This creation logic lives in TreeCanvas.tsx's handleNodeSave() function, which already handles pending link resolution.
Backend Validation
backend/app/core/tree_validation.py
_validate_node() currently rejects unknown node types:
if node_type not in ('decision', 'action', 'solution'):
errors.append(...)
Changes:
- Allow
'answer'type through without structural validation (answer nodes have no required fields beyondidandtype). - Add a publish-time check in
can_publish_tree()(or invalidate_tree_structure()before publish): if any node hastype == 'answer', reject with a clear message:"Answer placeholders must be resolved to a node type before publishing." - The draft save endpoint (
PUT /trees/:id) does not callcan_publish_tree(), so draft saves continue to work with answer nodes present.
Frontend Publish Guard
In TreeEditorPage.tsx, before calling the publish API, add a check:
const hasAnswerNodes = findAllAnswerNodes(tree.tree_structure).length > 0
if (hasAnswerNodes) {
// Show toast or inline error: "Resolve all answer placeholders before publishing."
return
}
findAllAnswerNodes is a simple recursive traversal (can be a small utility function in TreeCanvas.tsx or a new file lib/treeUtils.ts).
Visual Design (AnswerStubCard)
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
Server
[ ? Decision ] [ ⚡ Action ] [ ✓ Solution ]
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘
- Card:
min-w-[180px] max-w-[280px] rounded-xl border-2 border-dashed border-border bg-card/50 - Title:
text-sm font-heading font-medium text-foreground text-center py-2 px-3 - Type picker row (default state — not yet clicked):
text-xs text-muted-foreground text-center pb-2 cursor-pointer hover:text-foreground- Clicking the card reveals three compact buttons for type selection
- Type picker (expanded): three small buttons side-by-side in the card footer
Files Changed Summary
| File | Change |
|---|---|
frontend/src/components/tree-editor/TreeCanvasNode.tsx |
Fix 1: max-h + overflow-y + sticky header |
frontend/src/components/tree-editor/NodeFormDecision.tsx |
Fix 2: ⓘ tooltip on help_text; Fix 3: replace NodePicker with answer label list |
frontend/src/components/tree-editor/NodeFormAction.tsx |
Fix 2: ⓘ tooltips on description + commands fields |
frontend/src/components/tree-editor/NodeFormResolution.tsx |
Fix 2: ⓘ tooltips on description + steps fields |
frontend/src/components/tree-editor/TreeCanvas.tsx |
Fix 3: stub creation in handleNodeSave; AnswerStubCard rendering |
frontend/src/components/tree-editor/AnswerStubCard.tsx |
Fix 3: NEW — dashed stub card with inline type picker |
frontend/src/types/tree.ts |
Fix 3: add 'answer' to NodeType union |
backend/app/core/tree_validation.py |
Fix 3: allow 'answer' in draft; block on publish |
frontend/src/pages/TreeEditorPage.tsx |
Fix 3: frontend publish guard |
Non-Goals
- No changes to session navigation, procedural flows, or maintenance flows
- No changes to the Code mode editor
- No changes to
treeEditorStore.tsstore actions (addNode, updateNode, deleteNode are used as-is) - No third-party tooltip library
- No new backend endpoints
Verification
- Open a troubleshooting tree in the canvas editor
- Click a decision node → card expands, form is scrollable with sticky save/cancel header
- Field labels show
ⓘicons; hovering reveals the hint text - Type answer labels in the Options section; click ✓ to save
- Answer stub cards appear as dashed cards below the decision node
- Click a stub card → type picker appears; select "Decision" → card converts and expands for editing
- Draft save works with answer nodes present (no backend error)
- Attempt to publish with unresolved answer nodes → blocked with a clear error message
npm run buildpasses with no TypeScript errors