# Canvas UX Fixes Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Fix three UX problems in the TreeCanvas editor: card scroll, noisy hint text, and forced child-type selection when building decision nodes. **Architecture:** Three independent fixes applied to the canvas editor components only. Fix 1 is a pure CSS change. Fix 2 replaces `

` hint text with native `title` tooltips on ⓘ badges. Fix 3 introduces a new `'answer'` NodeType — a branch placeholder that the user converts to a real type by clicking it. **Tech Stack:** React 19, TypeScript, Tailwind CSS, Zustand (existing `treeEditorStore`), FastAPI backend (`tree_validation.py`) **Working directory:** `/home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas` --- ## Fix 1: Card Scroll ### Task 1: Make expanded card area scrollable with sticky header **Files:** - Modify: `frontend/src/components/tree-editor/TreeCanvasNode.tsx:164-321` **Step 1: Open the file and locate the card header div (expanded state)** The card header is the `

` at line 165. When expanded it shows the action buttons (save/cancel/etc). We need this row to be sticky. Find this block (around line 165): ```tsx
``` Change it to: ```tsx
``` **Step 2: Make the expanded editing area scrollable** Find the expanded editing area div (around line 324): ```tsx {isExpanded && (
``` Change it to: ```tsx {isExpanded && (
``` **Step 3: Build and verify no TypeScript errors** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/frontend npm run build 2>&1 | tail -20 ``` Expected: Build exits with code 0, no errors mentioning TreeCanvasNode. **Step 4: Commit** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas git add frontend/src/components/tree-editor/TreeCanvasNode.tsx git commit -m "fix: make canvas card expanded area scrollable with sticky header Co-Authored-By: Claude Sonnet 4.6 " ``` --- ## Fix 2: Info Tooltips ### Task 2: Replace hint text in NodeFormDecision **Files:** - Modify: `frontend/src/components/tree-editor/NodeFormDecision.tsx` The ⓘ badge pattern to use throughout Fix 2: ```tsx i ``` **Step 1: Find the root node hint paragraph inside the Question field** Around line 89–93: ```tsx {isRootNode && (

What's the main question to diagnose the issue?

)} ``` Remove this `

` block entirely. The input's placeholder already conveys the intent. **Step 2: Find the options hint paragraphs** Around lines 136–143: ```tsx {isRootNode ? (

Add as many options as needed (A, B, C, D...). Each option leads to a completely different troubleshooting path.

) : (

Each option can branch to a different next step.

)} ``` Replace both `

` tags with a ⓘ tooltip on the Options label. Change the label section (around line 133) from: ```tsx {isRootNode ? (

Add as many options as needed (A, B, C, D...). Each option leads to a completely different troubleshooting path.

) : (

Each option can branch to a different next step.

)} ``` To: ```tsx ``` **Step 3: Build to check for TS errors** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/frontend npm run build 2>&1 | tail -20 ``` Expected: Clean build. **Step 4: Commit** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas git add frontend/src/components/tree-editor/NodeFormDecision.tsx git commit -m "fix: replace hint paragraphs with info tooltips in NodeFormDecision Co-Authored-By: Claude Sonnet 4.6 " ``` --- ### Task 3: Replace hint text in NodeFormAction **Files:** - Modify: `frontend/src/components/tree-editor/NodeFormAction.tsx` **Step 1: Find the description hint paragraph** Around lines 91–93: ```tsx

Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`

``` Change the Description label + remove the hint: ```tsx // Before

Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`

// After ``` **Step 2: Find the commands hint paragraph** Around lines 124–126: ```tsx

PowerShell or CLI commands to execute

``` Change the Commands label + remove the hint: ```tsx // Before

PowerShell or CLI commands to execute

// After ``` **Step 3: Build to check for TS errors** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/frontend npm run build 2>&1 | tail -20 ``` Expected: Clean build. **Step 4: Commit** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas git add frontend/src/components/tree-editor/NodeFormAction.tsx git commit -m "fix: replace hint paragraphs with info tooltips in NodeFormAction Co-Authored-By: Claude Sonnet 4.6 " ``` --- ### Task 4: Replace hint text in NodeFormResolution **Files:** - Modify: `frontend/src/components/tree-editor/NodeFormResolution.tsx` **Step 1: Find the description hint paragraph** Around lines 86–88: ```tsx

Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`

``` Change the Description label + remove the hint: ```tsx // Before

Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`

// After ``` **Step 2: Find the resolution steps hint paragraph** Around lines 118–120: ```tsx

Step-by-step instructions for resolving the issue

``` Change the Resolution Steps label + remove the hint: ```tsx // Before

Step-by-step instructions for resolving the issue

// After ``` **Step 3: Build to check for TS errors** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/frontend npm run build 2>&1 | tail -20 ``` Expected: Clean build. **Step 4: Commit** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas git add frontend/src/components/tree-editor/NodeFormResolution.tsx git commit -m "fix: replace hint paragraphs with info tooltips in NodeFormResolution Co-Authored-By: Claude Sonnet 4.6 " ``` --- ## Fix 3: Answer Stubs ### Task 5: Add `'answer'` to the NodeType union **Files:** - Modify: `frontend/src/types/tree.ts:4` **Step 1: Add `'answer'` to NodeType** Current line 4: ```typescript export type NodeType = 'decision' | 'action' | 'solution' ``` Change to: ```typescript export type NodeType = 'decision' | 'action' | 'solution' | 'answer' ``` **Step 2: Check for TS exhaustiveness errors** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/frontend npm run build 2>&1 | grep -E "error TS|Type.*answer" ``` The build will likely show an error in `TreeCanvasNode.tsx` because `NODE_TYPE_CONFIG` only has keys for `decision`, `action`, `solution` — and `config = NODE_TYPE_CONFIG[node.type]` will fail when `node.type === 'answer'`. We fix that in Task 7. For now note the exact error and proceed. **Step 3: Commit the type change** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas git add frontend/src/types/tree.ts git commit -m "feat: add 'answer' to NodeType union for branch placeholder stubs Co-Authored-By: Claude Sonnet 4.6 " ``` --- ### Task 6: Create the AnswerStubCard component **Files:** - Create: `frontend/src/components/tree-editor/AnswerStubCard.tsx` **Step 1: Create the file with the following content** ```tsx import { useState } from 'react' import { HelpCircle, Zap, CheckCircle } from 'lucide-react' import { cn } from '@/lib/utils' import type { TreeStructure } from '@/types' interface AnswerStubCardProps { node: TreeStructure // type === 'answer' fromOption?: string onSelectType: (nodeId: string, type: 'decision' | 'action' | 'solution') => void } export function AnswerStubCard({ node, fromOption, onSelectType }: AnswerStubCardProps) { const [picking, setPicking] = useState(false) const label = fromOption || node.title || 'Answer' return (
!picking && setPicking(true)} > {/* Label */}
{label}
{/* Prompt / type picker */} {!picking ? (
+ Choose Type
) : (
)}
) } export default AnswerStubCard ``` **Step 2: Build to check for TS errors in the new file only** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/frontend npm run build 2>&1 | grep "AnswerStubCard" ``` Expected: No errors mentioning AnswerStubCard. **Step 3: Commit** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas git add frontend/src/components/tree-editor/AnswerStubCard.tsx git commit -m "feat: add AnswerStubCard component for unresolved branch placeholders Co-Authored-By: Claude Sonnet 4.6 " ``` --- ### Task 7: Update TreeCanvasNode to handle `'answer'` type **Files:** - Modify: `frontend/src/components/tree-editor/TreeCanvasNode.tsx` The `NODE_TYPE_CONFIG` object (line 47) only has entries for `decision`, `action`, `solution`. When `node.type === 'answer'`, calling `NODE_TYPE_CONFIG[node.type]` will cause a TypeScript error and runtime crash. The fix: guard `config` access so answer nodes get a safe fallback. However, answer nodes should **never** be rendered by `TreeCanvasNode` — `TreeCanvas` will render them as `AnswerStubCard` instead. We still need to fix the TypeScript error. **Step 1: Guard the config lookup** Find around line 135: ```tsx const config = NODE_TYPE_CONFIG[node.type] const TypeIcon = config.icon ``` Change to: ```tsx const config = node.type in NODE_TYPE_CONFIG ? NODE_TYPE_CONFIG[node.type as keyof typeof NODE_TYPE_CONFIG] : NODE_TYPE_CONFIG.decision // fallback for 'answer' type (should be rendered by AnswerStubCard instead) const TypeIcon = config.icon ``` **Step 2: Build to confirm the TS error from Task 5 is now resolved** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/frontend npm run build 2>&1 | tail -20 ``` Expected: Clean build (zero errors). **Step 3: Commit** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas git add frontend/src/components/tree-editor/TreeCanvasNode.tsx git commit -m "fix: guard NODE_TYPE_CONFIG lookup against 'answer' type Co-Authored-By: Claude Sonnet 4.6 " ``` --- ### Task 8: Redesign NodeFormDecision to use answer labels only (no NodePicker) **Files:** - Modify: `frontend/src/components/tree-editor/NodeFormDecision.tsx` This is the biggest change in the plan. We replace the per-option NodePicker with a simple label-only input. The `next_node_id` field on each option is **preserved** in the data model but no longer set via the form — it gets wired up automatically in TreeCanvas when the user saves (Task 9). **Step 1: Remove the NodePicker import** Current line 3: ```tsx import { NodePicker } from './NodePicker' ``` Remove this line entirely. **Step 2: Simplify handleAddOption — set next_node_id to empty string (not required by user)** The current `handleAddOption` (line 30–39) is fine as-is — it creates options with `next_node_id: ''`. Leave it unchanged. **Step 3: Replace the options renderItem to show only the label input** Find the `DynamicArrayField` renderItem (lines 156–209). Replace the entire `renderItem` prop with a simpler version: ```tsx renderItem={(option, index) => { const optionLabelError = validationErrors.find( e => e.nodeId === node.id && e.field === `options[${index}].label` ) const letter = indexToLetter(index) return (
{/* Letter badge */} {letter} handleUpdateOption(index, { label: e.target.value })} placeholder={isRootNode ? `Branch ${letter}: e.g., "Network Issues"...` : `Option ${letter} label`} className={cn( 'block flex-1 rounded-md border px-3 py-2 text-sm', 'bg-background text-foreground placeholder:text-muted-foreground', 'focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary', optionLabelError ? 'border-red-400' : 'border-border' )} /> {optionLabelError && (

{optionLabelError.message}

)}
) }} ``` Note: The surrounding `
` wrapper from the old renderItem should also be removed — the new renderItem renders a flat row. **Step 4: Remove the optionNextError validation lookup** (it's no longer displayed) Find and remove: ```tsx const optionNextError = validationErrors.find( e => e.nodeId === node.id && e.field === `options[${index}].next_node_id` ) ``` **Step 5: Build** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/frontend npm run build 2>&1 | tail -20 ``` Expected: Clean build. If there's an unused import warning for `NodePicker` even after removal, double-check Step 1. **Step 6: Commit** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas git add frontend/src/components/tree-editor/NodeFormDecision.tsx git commit -m "feat: redesign NodeFormDecision to use answer label list (no 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 " ``` --- ### Task 9: Wire up answer stub creation and AnswerStubCard rendering in TreeCanvas **Files:** - Modify: `frontend/src/components/tree-editor/TreeCanvas.tsx` Two changes: (1) when a decision node is saved, create answer stubs for any option without a `next_node_id`; (2) render `AnswerStubCard` for nodes with `type === 'answer'`. **Step 1: Import AnswerStubCard and add handleSelectAnswerType** At the top of the file, add the import after the existing `TreeCanvasNode` import: ```tsx import { AnswerStubCard } from './AnswerStubCard' ``` **Step 2: Add a `handleSelectAnswerType` callback to the TreeCanvas component** After the `handleDuplicate` callback (around line 278), add: ```tsx // ── 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] ) ``` **Step 3: Update handleSave to create answer stubs for unlinked options** Find `handleSave` (around line 202). After the existing `updateNode(nodeId, updates)` call but before the pending link resolution, add answer stub creation logic: The current `handleSave` starts: ```tsx const handleSave = useCallback( (nodeId: string, updates: Partial) => { updateNode(nodeId, updates) // Resolve pending link for new nodes const link = pendingLinks.get(nodeId) ``` Change to: ```tsx const handleSave = useCallback( (nodeId: string, updates: Partial) => { updateNode(nodeId, updates) // For decision nodes: create answer stubs for any option without a next_node_id if (updates.type === 'decision' || updates.options) { const options = updates.options || [] options.forEach((opt) => { if (!opt.next_node_id && opt.label.trim()) { // Create a new answer stub node under this decision node const stubId = addNode(nodeId, 'answer') // Give it the label as its title so AnswerStubCard can display it updateNode(stubId, { title: opt.label }) // Link the option to the stub const updatedOptions = options.map((o) => o.id === opt.id ? { ...o, next_node_id: stubId } : o ) updateNode(nodeId, { options: updatedOptions }) } }) } // Resolve pending link for new nodes const link = pendingLinks.get(nodeId) ``` **Step 4: Add `handleSelectAnswerType` to the renderNode dependency array** Find the `useCallback` dependency array at the end of `renderNode` (around line 580). Add `handleSelectAnswerType` to it: ```tsx [ expandedNodeId, newNodeIds, dragOverTarget, handleToggleExpand, handleSave, handleCancelNew, handleDelete, handleDuplicate, handleDragStart, handleDragOver, handleDrop, pendingAddKey, handleAddNodeSelect, handleSelectAnswerType, // ← add this ] ``` **Step 5: Render AnswerStubCard for answer-type nodes inside renderNode** Find the section in `renderNode` where `` is rendered (around line 468). Add a conditional before it: ```tsx {/* The node card — answer stubs get their own component */} {node.type === 'answer' ? ( ) : ( 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)} /> )} ``` **Step 6: Build** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/frontend npm run build 2>&1 | tail -30 ``` Expected: Clean build. **Step 7: Commit** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas git add frontend/src/components/tree-editor/TreeCanvas.tsx git commit -m "feat: auto-create answer stubs on decision save, render AnswerStubCard Co-Authored-By: Claude Sonnet 4.6 " ``` --- ### Task 10: Update backend to allow `'answer'` type in drafts and block on publish **Files:** - Modify: `backend/app/core/tree_validation.py` **Step 1: Allow `'answer'` type in `_validate_node` without structural validation** Find the `else` branch at the end of `_validate_node` (around line 92–96): ```python else: errors.append({ "field": f"{path}.type", "message": f"Unknown node type: {node_type}" }) ``` Change to: ```python elif node_type == "answer": # Answer nodes are draft-only placeholders — no structural validation needed pass else: errors.append({ "field": f"{path}.type", "message": f"Unknown node type: {node_type}" }) ``` **Step 2: Add publish-time answer node check in `validate_tree_structure`** After the root node is validated and before returning, add a recursive check for answer nodes. Find the end of `validate_tree_structure` (around line 53–56): ```python # Validate all child nodes recursively if "children" in tree_structure: _validate_children(tree_structure["children"], "root.children", errors) return len(errors) == 0, errors ``` Change to: ```python # Validate all child nodes recursively if "children" in tree_structure: _validate_children(tree_structure["children"], "root.children", errors) # Block publish if any answer placeholder nodes remain if _has_answer_nodes(tree_structure): errors.append({ "field": "tree_structure", "message": "Answer placeholders must be resolved to a node type before publishing." }) return len(errors) == 0, errors ``` **Step 3: Add the `_has_answer_nodes` helper function** Add this function after `_validate_children` (around line 115): ```python def _has_answer_nodes(node: dict[str, Any]) -> bool: """Recursively check if any node in the tree has type 'answer'.""" if node.get("type") == "answer": return True for child in node.get("children", []): if _has_answer_nodes(child): return True return False ``` **Step 4: Verify the backend tests still pass** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/backend source venv/bin/activate 2>/dev/null || true pytest tests/ -k "tree_valid" --override-ini="addopts=" -q 2>&1 | tail -20 ``` If no tests exist specifically for tree_validation, run the full suite: ```bash pytest --override-ini="addopts=" -q 2>&1 | tail -20 ``` Expected: All tests pass. **Step 5: Commit** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas git add backend/app/core/tree_validation.py git commit -m "feat: allow 'answer' type in tree drafts, block on publish Draft saves succeed with answer placeholder nodes. Publish is blocked with a clear message if any answer nodes remain unresolved. Co-Authored-By: Claude Sonnet 4.6 " ``` --- ### Task 11: Add frontend publish guard for answer nodes **Files:** - Modify: `frontend/src/pages/TreeEditorPage.tsx` **Step 1: Add a `hasAnswerNodes` utility** At the top of `TreeEditorPage.tsx`, after the imports, add a small utility function (before the component function): ```typescript /** Recursively check if any node in the tree has type 'answer' */ function hasAnswerNodes(node: TreeStructure): boolean { if (node.type === 'answer') return true return (node.children || []).some(hasAnswerNodes) } ``` You'll need to ensure `TreeStructure` is imported — it should already be imported via `@/types`. **Step 2: Add the guard in `handlePublish`** Find `handlePublish` (around line 269). After the name check (around line 293) and before `validate()`, add: ```typescript // Block publish if any answer placeholder nodes remain const currentStructure = useTreeEditorStore.getState().treeStructure if (currentStructure && hasAnswerNodes(currentStructure)) { toast.error('Resolve all answer placeholders before publishing. Click each dashed stub card to assign a type.') setSaving(false) return } ``` **Step 3: Build** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/frontend npm run build 2>&1 | tail -20 ``` Expected: Clean build. **Step 4: Commit** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas git add frontend/src/pages/TreeEditorPage.tsx git commit -m "feat: block publish if unresolved answer stub nodes exist Co-Authored-By: Claude Sonnet 4.6 " ``` --- ## Final Verification ### Task 12: Full build and manual test checklist **Step 1: Run the full frontend build** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/frontend npm run build 2>&1 | tail -10 ``` Expected: `✓ built in Xs` with zero errors. **Step 2: Run backend tests** ```bash cd /home/michaelchihlas/dev/patherly/.worktrees/tree-editor-canvas/backend pytest --override-ini="addopts=" -q 2>&1 | tail -10 ``` Expected: All tests pass. **Step 3: Manual test checklist (confirm with developer)** 1. Open a troubleshooting tree in the canvas editor 2. Click a decision node → card expands 3. Resize the browser to a short viewport — form should scroll, sticky header (save/cancel) stays visible 4. Hover over the `i` badge next to field labels — tooltip text appears 5. Type answer labels in the Options section (e.g. "Server", "Desktop") → click ✓ to save 6. Two dashed stub cards appear below the decision node labeled "Server" and "Desktop" 7. Click "Server" stub → three type buttons appear (Decision / Action / Solution) 8. Click "Decision" → stub converts to a full Decision card in expanded editing mode 9. Save draft → no backend error (answer nodes allowed in drafts) 10. Leave an unresolved stub and click Publish → blocked with: "Resolve all answer placeholders before publishing." 11. `npm run build` passes with no TypeScript errors **Step 4: Complete the development branch** Use `superpowers:finishing-a-development-branch` to present merge/PR options.