# 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 `<p>` 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 `<div>` 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
<div
  className={cn(
    'flex items-center gap-2 px-3 py-2.5',
    !isExpanded && 'cursor-pointer hover:bg-accent/50 rounded-t-xl',
    !isExpanded && 'rounded-xl'
  )}
  onClick={!isExpanded ? handleCardClick : undefined}
>
```

Change it to:
```tsx
<div
  className={cn(
    'flex items-center gap-2 px-3 py-2.5',
    !isExpanded && 'cursor-pointer hover:bg-accent/50 rounded-t-xl',
    !isExpanded && 'rounded-xl',
    isExpanded && 'sticky top-0 z-10 bg-card rounded-t-xl'
  )}
  onClick={!isExpanded ? handleCardClick : undefined}
>
```

**Step 2: Make the expanded editing area scrollable**

Find the expanded editing area div (around line 324):
```tsx
{isExpanded && (
  <div className="border-t border-border px-3 pb-3 pt-3">
```

Change it to:
```tsx
{isExpanded && (
  <div className="border-t border-border px-3 pb-3 pt-3 max-h-[70vh] overflow-y-auto">
```

**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 <noreply@anthropic.com>"
```

---

## 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
<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 shrink-0"
  title="YOUR TOOLTIP TEXT HERE"
>
  i
</span>
```

**Step 1: Find the root node hint paragraph inside the Question field**

Around line 89–93:
```tsx
{isRootNode && (
  <p className="mt-0.5 text-xs text-muted-foreground">
    What's the main question to diagnose the issue?
  </p>
)}
```

Remove this `<p>` block entirely. The input's placeholder already conveys the intent.

**Step 2: Find the options hint paragraphs**

Around lines 136–143:
```tsx
{isRootNode ? (
  <p className="mt-0.5 text-xs text-muted-foreground">
    Add as many options as needed (A, B, C, D...). Each option leads to a completely different troubleshooting path.
  </p>
) : (
  <p className="mt-0.5 text-xs text-muted-foreground">
    Each option can branch to a different next step.
  </p>
)}
```

Replace both `<p>` tags with a ⓘ tooltip on the Options label. Change the label section (around line 133) from:
```tsx
<label className="block text-sm font-medium text-foreground">
  {isRootNode ? 'Answer Options (Branches)' : 'Options'} <span className="text-red-400">*</span>
</label>
{isRootNode ? (
  <p className="mt-0.5 text-xs text-muted-foreground">
    Add as many options as needed (A, B, C, D...). Each option leads to a completely different troubleshooting path.
  </p>
) : (
  <p className="mt-0.5 text-xs text-muted-foreground">
    Each option can branch to a different next step.
  </p>
)}
```

To:
```tsx
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
  {isRootNode ? 'Answer Options (Branches)' : 'Options'} <span className="text-red-400">*</span>
  <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 shrink-0"
    title={isRootNode
      ? "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."}
  >
    i
  </span>
</label>
```

**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 <noreply@anthropic.com>"
```

---

### 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
<p className="mb-1 text-xs text-muted-foreground">
  Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`
</p>
```

Change the Description label + remove the hint:
```tsx
// 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 shrink-0"
    title="Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`"
  >
    i
  </span>
</label>
```

**Step 2: Find the commands hint paragraph**

Around lines 124–126:
```tsx
<p className="mb-2 text-xs text-muted-foreground">
  PowerShell or CLI commands to execute
</p>
```

Change the Commands label + remove the hint:
```tsx
// Before
<label className="block text-sm font-medium text-foreground">
  Commands
</label>
<p className="mb-2 text-xs text-muted-foreground">
  PowerShell or CLI commands to execute
</p>

// After
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
  Commands
  <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 shrink-0"
    title="PowerShell or CLI commands to execute"
  >
    i
  </span>
</label>
```

**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 <noreply@anthropic.com>"
```

---

### 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
<p className="mb-1 text-xs text-muted-foreground">
  Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`
</p>
```

Change the Description label + remove the hint:
```tsx
// 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 shrink-0"
    title="Supports markdown: **bold**, *italic*, - lists, 1. numbered lists, `code`"
  >
    i
  </span>
</label>
```

**Step 2: Find the resolution steps hint paragraph**

Around lines 118–120:
```tsx
<p className="mb-2 text-xs text-muted-foreground">
  Step-by-step instructions for resolving the issue
</p>
```

Change the Resolution Steps label + remove the hint:
```tsx
// Before
<label className="block text-sm font-medium text-foreground">
  Resolution Steps
</label>
<p className="mb-2 text-xs text-muted-foreground">
  Step-by-step instructions for resolving the issue
</p>

// After
<label className="flex items-center gap-1.5 text-sm font-medium text-foreground">
  Resolution Steps
  <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 shrink-0"
    title="Step-by-step instructions for resolving the issue"
  >
    i
  </span>
</label>
```

**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 <noreply@anthropic.com>"
```

---

## 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 <noreply@anthropic.com>"
```

---

### 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 (
    <div
      className={cn(
        'min-w-[180px] max-w-[280px] rounded-xl border-2 border-dashed border-border bg-card/50',
        'transition-all duration-150',
        !picking && 'cursor-pointer hover:border-primary/40 hover:bg-accent/30'
      )}
      onClick={() => !picking && setPicking(true)}
    >
      {/* Label */}
      <div className="px-3 pt-2.5 pb-1 text-sm font-heading font-medium text-foreground text-center">
        {label}
      </div>

      {/* Prompt / type picker */}
      {!picking ? (
        <div className="pb-2.5 text-center text-[10px] text-muted-foreground font-label">
          + Choose Type
        </div>
      ) : (
        <div className="flex items-center justify-center gap-1.5 pb-2.5 px-2">
          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation()
              onSelectType(node.id, 'decision')
            }}
            className={cn(
              'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label',
              'border border-blue-500/30 bg-blue-500/10 text-blue-400 hover:bg-blue-500/20'
            )}
          >
            <HelpCircle className="h-2.5 w-2.5" />
            Decision
          </button>

          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation()
              onSelectType(node.id, 'action')
            }}
            className={cn(
              'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label',
              'border border-yellow-500/30 bg-yellow-500/10 text-yellow-400 hover:bg-yellow-500/20'
            )}
          >
            <Zap className="h-2.5 w-2.5" />
            Action
          </button>

          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation()
              onSelectType(node.id, 'solution')
            }}
            className={cn(
              'flex items-center gap-1 rounded-md px-2 py-1 text-[10px] font-label',
              'border border-green-500/30 bg-green-500/10 text-green-400 hover:bg-green-500/20'
            )}
          >
            <CheckCircle className="h-2.5 w-2.5" />
            Solution
          </button>
        </div>
      )}
    </div>
  )
}

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 <noreply@anthropic.com>"
```

---

### 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 <noreply@anthropic.com>"
```

---

### 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 (
    <div className="flex items-center gap-2">
      {/* Letter badge */}
      <span className={cn(
        'flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-xs font-bold',
        isRootNode
          ? 'bg-blue-500/20 text-blue-400'
          : 'bg-accent text-muted-foreground'
      )}>
        {letter}
      </span>
      <input
        type="text"
        value={option.label}
        onChange={(e) => 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 && (
        <p className="mt-1 text-xs text-red-400">{optionLabelError.message}</p>
      )}
    </div>
  )
}}
```

Note: The surrounding `<div className="rounded-md border border-border bg-accent/50 p-3">` 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 <noreply@anthropic.com>"
```

---

### 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<TreeStructure>) => {
    updateNode(nodeId, updates)

    // Resolve pending link for new nodes
    const link = pendingLinks.get(nodeId)
```

Change to:
```tsx
const handleSave = useCallback(
  (nodeId: string, updates: Partial<TreeStructure>) => {
    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 `<TreeCanvasNode>` is rendered (around line 468). Add a conditional before it:

```tsx
{/* The node card — answer stubs get their own component */}
{node.type === 'answer' ? (
  <AnswerStubCard
    node={node}
    fromOption={optionLabel}
    onSelectType={handleSelectAnswerType}
  />
) : (
  <TreeCanvasNode
    node={node}
    depth={0}
    fromOption={optionLabel}
    isExpanded={isExpanded}
    isNew={isNew}
    onToggleExpand={() => 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 <noreply@anthropic.com>"
```

---

### 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 <noreply@anthropic.com>"
```

---

### 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 <noreply@anthropic.com>"
```

---

## 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.
