# Implementation Plan: Step Library Frontend + Tree Editor Validation

> **Date:** February 3, 2026 (Updated after refinement session)
> **Scope:** Issues #1, #8, #9, #10
> **Estimated Components:** 10 new files, 8 modified files, 1 migration
> **Parallel Workstreams:** 2
> **Related Planning:** Issue #25 (Draft Trees - Phase 3)

---

## Plan Refinements (Feb 3, 2026)

This plan was refined through collaborative design discussion. Key changes from initial draft:

1. **Custom Steps Persistence** - Changed from storing in `decisions` array to separate `custom_steps` JSONB field
   - Added: Backend migration task (B.10)
   - Added: Session schema updates
   - Enables: Session resuming with custom steps

2. **Custom Step Navigation** - Expanded to support all step types (decision/action/solution)
   - Added: Detailed rendering logic for each type in B.11
   - Enables: Custom branching decisions during troubleshooting

3. **Validation Warnings** - Clarified as inline/dismissible (no confirmation modal)
   - Updated: Acceptance criteria to specify warning behavior
   - Deferred: "Save as Draft" feature to Issue #25 (Phase 3)

---

## Overview

This plan covers two parallel workstreams:

| Workstream | Issues | Description |
|------------|--------|-------------|
| **A** | #1 | Tree Editor Validation UI |
| **B** | #10 → #9 → #8 | Step Library Frontend (sequential) |

Both workstreams are independent and can be developed simultaneously.

---

## Workstream A: Tree Editor Validation (Issue #1)

### Current State

The validation logic **already exists** in `treeEditorStore.ts:519-658`. It checks:
- ✅ Tree name required
- ✅ Decision nodes need question + options
- ✅ Option labels required
- ✅ Action/Solution nodes need titles
- ✅ At least one solution node
- ✅ Orphan node detection
- ✅ Invalid node reference detection

### What's Missing

1. **UI to display validation errors** - No visual feedback shown to user
2. **Prevent save on errors** - Save button should be disabled or show confirmation
3. **Circular reference detection** - Not implemented in current validation
4. **Real-time validation** - Currently only validates on explicit call

### Tasks

#### A.1: Add Circular Reference Detection
**File:** `frontend/src/store/treeEditorStore.ts`

Add to the `validate()` function after line 624:

```typescript
// Check for circular references in next_node_id chains
const detectCircularRefs = (startId: string, visited: Set<string> = new Set()): boolean => {
  if (visited.has(startId)) return true
  visited.add(startId)

  const node = findNodeInTree(startId, state.treeStructure)
  if (!node) return false

  // Check options
  if (node.options) {
    for (const opt of node.options) {
      if (opt.next_node_id && detectCircularRefs(opt.next_node_id, new Set(visited))) {
        errors.push({
          nodeId: node.id,
          message: `Circular reference detected: "${opt.label}" creates a loop`,
          severity: 'error'
        })
        return true
      }
    }
  }

  // Check next_node_id
  if (node.next_node_id && detectCircularRefs(node.next_node_id, new Set(visited))) {
    errors.push({
      nodeId: node.id,
      message: `Circular reference detected in node "${node.title || node.id}"`,
      severity: 'error'
    })
    return true
  }

  return false
}

// Run from root
detectCircularRefs('root')
```

#### A.2: Create Validation Summary Component
**File:** `frontend/src/components/tree-editor/ValidationSummary.tsx` (NEW)

```typescript
interface ValidationSummaryProps {
  errors: ValidationError[]
  onSelectNode: (nodeId: string) => void
}
```

Features:
- Collapsible panel showing error/warning count
- Click error to select the problematic node
- Color-coded: red for errors, yellow for warnings
- Icon indicators (AlertCircle, AlertTriangle from lucide-react)

#### A.3: Integrate Validation UI into TreeEditorPage
**File:** `frontend/src/pages/TreeEditorPage.tsx`

Modifications:
1. Call `validate()` before save attempt
2. Show ValidationSummary when errors or warnings exist
3. Block save if any severity='error' exists (disable save button)
4. Warnings are **informational only** - do not block save, no confirmation modal
5. Add "Validate" button in toolbar for manual check
6. Auto-validate on blur from form fields (debounced)

#### A.4: Visual Node Error Indicators
**File:** `frontend/src/components/tree-editor/NodeList.tsx`

Modifications:
- Add red border/highlight to nodes with validation errors
- Show error icon badge on problem nodes
- Tooltip on hover showing the specific error

### Acceptance Criteria - Workstream A

- [ ] Cannot save tree without a name (error blocks save)
- [ ] Cannot save tree without at least one solution node (error blocks save)
- [ ] Validation errors display in a clear, clickable list
- [ ] Clicking an error selects the problematic node
- [ ] Circular references are detected and blocked (error)
- [ ] Orphan nodes show as warnings (informational, do not block save)
- [ ] Save button disabled when errors exist
- [ ] Warnings are dismissible/ignorable - user can save with warnings present

---

## Workstream B: Step Library Frontend (Issues #10, #9, #8)

### Dependencies
- Backend API complete ✅ (`/api/v1/steps/*`, `/api/v1/step-categories/*`)
- No frontend API client exists yet

### Execution Order
```
#10 Step Library Browser Component
         ↓
#9 Custom Step Creation Modal (embeds browser)
         ↓
#8 Add Custom Step Button (triggers modal)
```

---

### Issue #10: Step Library Browser Component

#### B.1: Create Steps API Client
**File:** `frontend/src/api/steps.ts` (NEW)

```typescript
// API client for step library endpoints
export const stepsApi = {
  list: (params?: StepListParams) => Promise<StepListItem[]>
  get: (id: string) => Promise<Step>
  create: (data: StepCreate) => Promise<Step>
  update: (id: string, data: StepUpdate) => Promise<Step>
  delete: (id: string) => Promise<void>
  search: (query: string) => Promise<StepListItem[]>
  getPopularTags: () => Promise<PopularTag[]>
  rate: (id: string, data: RatingCreate) => Promise<Rating>
  updateRating: (id: string, data: RatingUpdate) => Promise<Rating>
  deleteRating: (id: string) => Promise<void>
  getReviews: (id: string) => Promise<Review[]>
}
```

#### B.2: Create Step Categories API Client
**File:** `frontend/src/api/stepCategories.ts` (NEW)

```typescript
export const stepCategoriesApi = {
  list: () => Promise<StepCategory[]>
  get: (id: string) => Promise<StepCategory>
}
```

#### B.3: Add TypeScript Types
**File:** `frontend/src/types/step.ts` (NEW)

```typescript
export interface StepCommand {
  label: string
  command: string
  command_type?: string
}

export interface StepContent {
  instructions: string
  help_text?: string
  commands?: StepCommand[]
}

export interface Step {
  id: string
  title: string
  step_type: 'decision' | 'action' | 'solution'
  content: StepContent
  visibility: 'private' | 'team' | 'public'
  category_id?: string
  category_name?: string
  tags: string[]
  usage_count: number
  rating_average: number
  rating_count: number
  helpful_yes: number
  helpful_no: number
  is_featured: boolean
  is_verified: boolean
  created_by: string
  author_name?: string
  created_at: string
  updated_at: string
}

export interface StepListItem {
  id: string
  title: string
  step_type: string
  visibility: string
  category_id?: string
  category_name?: string
  tags: string[]
  usage_count: number
  rating_average: number
  rating_count: number
  is_featured: boolean
  created_by: string
  author_name?: string
  created_at: string
}

export interface StepCategory {
  id: string
  name: string
  description?: string
  display_order: number
  team_id?: string
  is_active: boolean
}

export interface StepListParams {
  visibility?: 'private' | 'team' | 'public'
  category_id?: string
  tags?: string[]
  min_rating?: number
  step_type?: 'decision' | 'action' | 'solution'
  sort_by?: 'recent' | 'popular' | 'highest_rated' | 'most_used'
  limit?: number
  offset?: number
}

export interface PopularTag {
  tag: string
  count: number
}
```

#### B.4: Create Step Card Component
**File:** `frontend/src/components/step-library/StepCard.tsx` (NEW)

A card displaying:
- Step title and type badge (decision/action/solution)
- Category name
- Star rating (if rated) or "Not rated" placeholder
- Tags as chips (max 3 visible, "+N more" overflow)
- Author name and created date
- Usage count
- [Preview] and [Insert] buttons

#### B.5: Create Step Detail Modal
**File:** `frontend/src/components/step-library/StepDetailModal.tsx` (NEW)

Full step preview showing:
- Title, type, category, tags
- Full rating breakdown (star distribution chart)
- Instructions (markdown rendered)
- Commands (copyable code blocks)
- Help text
- Top reviews (2-3 shown, "See all" link)
- [Cancel] and [Insert Into Session] buttons

#### B.6: Create Step Library Browser Component
**File:** `frontend/src/components/step-library/StepLibraryBrowser.tsx` (NEW)

Main browser component with:

**Header:**
- Search input (full-text, debounced 300ms)
- Filter dropdowns: Category, Type, Min Rating, Sort By
- Popular tags as clickable chips

**Body:**
- Grouped sections: "My Steps", "Team Steps", "Community"
- Each section collapsible
- Virtualized list for performance (if >50 items)
- Loading skeletons while fetching
- Empty state: "No steps found. Create your first step!"

**Footer:**
- [+ Create New Step] button (optional, for standalone use)

**Props:**
```typescript
interface StepLibraryBrowserProps {
  onInsert: (step: Step) => void
  onCreateNew?: () => void
  showCreateButton?: boolean
}
```

#### B.7: Update API Index
**File:** `frontend/src/api/index.ts`

Add exports for `stepsApi` and `stepCategoriesApi`.

### Acceptance Criteria - Issue #10

- [ ] Can search steps with full-text query
- [ ] Can filter by category, type, minimum rating
- [ ] Can sort by recent, popular, highest rated, most used
- [ ] Steps grouped by visibility (My Steps, Team, Community)
- [ ] Can click step to see full preview modal
- [ ] Can copy commands from preview
- [ ] Can click "Insert" to select a step
- [ ] Popular tags shown and clickable as quick filters

---

### Issue #9: Custom Step Creation Modal

#### B.8: Create Step Form Component
**File:** `frontend/src/components/step-library/StepForm.tsx` (NEW)

Form fields:
- Step Type: Radio group (Decision / Action / Solution)
- Title: Text input (required)
- Instructions: Textarea with markdown support (required)
- Help Text: Textarea (optional)
- Commands: Dynamic array field (optional)
  - Each command: label + command + type dropdown
- Category: Dropdown (optional)
- Tags: Tag input with autocomplete (optional)
- Visibility: Dropdown (Private / Team / Public)
- Checkbox: "Save to My Step Library for reuse"

#### B.9: Create Custom Step Modal
**File:** `frontend/src/components/step-library/CustomStepModal.tsx` (NEW)

Tabbed modal with:
- **Tab 1: "Type My Own"** - StepForm component
- **Tab 2: "Browse Library"** - StepLibraryBrowser component

**Props:**
```typescript
interface CustomStepModalProps {
  isOpen: boolean
  onClose: () => void
  onInsertStep: (step: Step | CustomStepDraft) => void
}
```

**Behavior:**
- Tab 1: User fills form, clicks [Insert Step]
  - If "Save to library" checked, POST to `/api/v1/steps` first
  - Returns step data to parent
- Tab 2: User browses, clicks Insert on a step
  - Returns selected step to parent

### Acceptance Criteria - Issue #9

- [ ] Modal has two tabs: "Type My Own" and "Browse Library"
- [ ] Can create custom step with type, title, instructions
- [ ] Can optionally add commands, help text, category, tags
- [ ] Can optionally save step to personal library
- [ ] Can switch to Browse tab and select existing step
- [ ] Insert button returns step data to parent component

---

### Issue #8: Add Custom Step Button in Tree Navigation

#### B.10: Add Custom Steps Backend Support
**Files:**
- `backend/alembic/versions/XXXX_add_custom_steps_to_sessions.py` (NEW - migration)
- `backend/app/schemas/session.py` (MODIFIED)
- `backend/app/api/endpoints/sessions.py` (MODIFIED)

**Migration:**
```python
def upgrade():
    # Add custom_steps JSONB column to sessions table
    op.add_column('sessions',
        sa.Column('custom_steps', JSONB, nullable=False, server_default='[]')
    )

def downgrade():
    op.drop_column('sessions', 'custom_steps')
```

**Schema Updates:**
```python
# In DecisionRecord - no changes needed (still uses node_id)

# In SessionResponse - add custom_steps field
class SessionResponse(BaseModel):
    # ... existing fields
    custom_steps: list[dict[str, Any]] = Field(default_factory=list)  # NEW

# In SessionUpdate - add custom_steps field
class SessionUpdate(BaseModel):
    # ... existing fields
    custom_steps: Optional[list[dict[str, Any]]] = None  # NEW
```

**Custom Step Structure in Database:**
```python
# Each item in custom_steps array:
{
    "id": "uuid-string",
    "inserted_after_node_id": "parent-node-id",
    "step_data": {
        "title": "Check Additional Logs",
        "step_type": "action",  # decision | action | solution
        "content": {
            "instructions": "Check /var/log/...",
            "help_text": "Optional help",
            "commands": [...]  # optional
        },
        "category_id": "optional-uuid",
        "tags": ["optional"]
    },
    "timestamp": "2026-02-03T10:30:00Z"
}
```

**API Logic:**
- `PUT /api/v1/sessions/{id}` - Accept `custom_steps` in request body
- Validation: Ensure `step_data.step_type` is valid enum value
- No cascade delete needed - custom steps are session-scoped

#### B.11: Modify TreeNavigationPage
**File:** `frontend/src/pages/TreeNavigationPage.tsx`

Add state:
```typescript
const [showCustomStepModal, setShowCustomStepModal] = useState(false)
const [customSteps, setCustomSteps] = useState<CustomStep[]>([])
```

**UI Changes:**

1. After each decision node's options, add:
```tsx
<button
  onClick={() => setShowCustomStepModal(true)}
  className="mt-2 text-sm text-primary hover:underline"
>
  + Add Custom Step
</button>
```

2. Add CustomStepModal at bottom of component

3. Handle insert:
```typescript
const handleInsertCustomStep = (step: Step | CustomStepDraft) => {
  // Create custom step object
  const customStep: CustomStep = {
    id: crypto.randomUUID(),
    inserted_after_node_id: currentNodeId,
    step_data: step,
    timestamp: new Date().toISOString()
  }

  // Add to local state
  const newCustomSteps = [...customSteps, customStep]
  setCustomSteps(newCustomSteps)

  // Navigate to custom step (becomes current)
  setCurrentNodeId(customStep.id)
  setShowCustomStepModal(false)

  // Save to backend
  if (session) {
    sessionsApi.update(session.id, {
      custom_steps: newCustomSteps
    }).catch(err => console.error('Failed to save custom step:', err))
  }
}
```

4. Render custom steps based on type:
```tsx
{/* Render custom step in navigation flow */}
{currentNode?.id.startsWith('custom-') && (
  <div className="rounded-lg border border-purple-200 bg-purple-50 p-4 dark:border-purple-800 dark:bg-purple-900/20">
    {/* Custom Step Badge */}
    <span className="mb-2 inline-block rounded-full bg-purple-100 px-2 py-1 text-xs font-medium text-purple-800 dark:bg-purple-900 dark:text-purple-100">
      Custom Step
    </span>

    {/* Render based on step type */}
    {customStepNode.step_data.step_type === 'decision' && (
      <>
        <h2>{customStepNode.step_data.title}</h2>
        <MarkdownContent content={customStepNode.step_data.content.instructions} />
        {/* Render decision options - user creates them inline or uses predefined */}
        <div className="mt-4 space-y-2">
          <button onClick={() => handleCustomDecisionOption('yes')}>Yes</button>
          <button onClick={() => handleCustomDecisionOption('no')}>No</button>
        </div>
      </>
    )}

    {customStepNode.step_data.step_type === 'action' && (
      <>
        <h2>{customStepNode.step_data.title}</h2>
        <MarkdownContent content={customStepNode.step_data.content.instructions} />
        {customStepNode.step_data.content.commands?.map(cmd => (
          <pre key={cmd.label}>{cmd.command}</pre>
        ))}
        <button onClick={handleContinueFromCustomAction}>Continue</button>
      </>
    )}

    {customStepNode.step_data.step_type === 'solution' && (
      <>
        <h2>{customStepNode.step_data.title}</h2>
        <MarkdownContent content={customStepNode.step_data.content.instructions} />
        <button onClick={handleComplete}>Complete Session</button>
      </>
    )}
  </div>
)}
```

**Navigation Logic:**
- Custom decision: User selects option → continues to next tree node or another custom step
- Custom action: User clicks Continue → returns to tree flow (next_node_id of original tree node)
- Custom solution: User clicks Complete → ends session

#### B.12: Update Session Export
**File:** `frontend/src/components/session/ExportPreviewModal.tsx` or `backend/app/api/endpoints/sessions.py`

Ensure custom steps are included in export with clear marking:

```markdown
## Step 5: [CUSTOM] Check Additional Logs
*Custom step added by user*

Instructions: ...
```

#### B.13: Update Session Types
**File:** `frontend/src/types/index.ts`

Add:
```typescript
export interface CustomStep {
  id: string
  inserted_after_node_id: string
  step_data: Step | CustomStepDraft
  timestamp: string
}

export interface CustomStepDraft {
  title: string
  step_type: 'decision' | 'action' | 'solution'
  content: StepContent
  category_id?: string
  tags?: string[]
}
```

### Acceptance Criteria - Issue #8

- [ ] "+ Add Custom Step" button visible at each decision point
- [ ] Button opens CustomStepModal
- [ ] Can insert step from "Type My Own" tab
- [ ] Can insert step from "Browse Library" tab
- [ ] Custom steps appear in session flow with visual indicator
- [ ] Custom steps included in session export
- [ ] Session continues after custom step

---

## File Summary

### New Files (10)

| File | Issue | Description |
|------|-------|-------------|
| `frontend/components/tree-editor/ValidationSummary.tsx` | #1 | Error/warning display |
| `frontend/api/steps.ts` | #10 | Steps API client |
| `frontend/api/stepCategories.ts` | #10 | Categories API client |
| `frontend/types/step.ts` | #10 | Step TypeScript types |
| `frontend/components/step-library/StepCard.tsx` | #10 | Step list item card |
| `frontend/components/step-library/StepDetailModal.tsx` | #10 | Step preview modal |
| `frontend/components/step-library/StepLibraryBrowser.tsx` | #10 | Main browser component |
| `frontend/components/step-library/StepForm.tsx` | #9 | Step creation form |
| `frontend/components/step-library/CustomStepModal.tsx` | #9 | Tabbed modal wrapper |
| `backend/alembic/versions/XXXX_add_custom_steps.py` | #8 | Add custom_steps to sessions |

### Modified Files (6)

| File | Issue | Changes |
|------|-------|---------|
| `frontend/store/treeEditorStore.ts` | #1 | Add circular reference detection |
| `frontend/pages/TreeEditorPage.tsx` | #1 | Integrate validation UI |
| `frontend/components/tree-editor/NodeList.tsx` | #1 | Node error indicators |
| `frontend/pages/TreeNavigationPage.tsx` | #8 | Add custom step button + rendering |
| `frontend/api/index.ts` | #10 | Export new API clients |
| `frontend/types/index.ts` | #8 | Add CustomStep types |
| `backend/app/schemas/session.py` | #8 | Add custom_steps field |
| `backend/app/api/endpoints/sessions.py` | #8 | Handle custom_steps in update |

---

## Execution Plan

### Phase 1: Foundation (Do First)
```
Workstream A                    Workstream B
─────────────                   ─────────────
A.1 Circular ref detection      B.1 Steps API client
                                B.2 Categories API client
                                B.3 TypeScript types
```

### Phase 2: Core Components
```
Workstream A                    Workstream B
─────────────                   ─────────────
A.2 ValidationSummary           B.4 StepCard
A.3 TreeEditorPage integration  B.5 StepDetailModal
A.4 NodeList indicators         B.6 StepLibraryBrowser
```

### Phase 3: Integration
```
Workstream A                    Workstream B
─────────────                   ─────────────
Testing & polish                B.8 StepForm
                                B.9 CustomStepModal
                                B.10 Backend custom steps support (migration + schemas)
                                B.11 TreeNavigationPage integration
                                B.12-13 Export & types updates
```

---

## Testing Checklist

### Workstream A - Validation
- [ ] Create tree without name → Error shown, save blocked
- [ ] Create tree without solution → Error shown, save blocked
- [ ] Create decision without options → Error shown
- [ ] Create circular reference (A → B → A) → Error detected
- [ ] Create orphan node → Warning shown (can still save)
- [ ] Click error → Node selected
- [ ] Fix all errors → Save enabled

### Workstream B - Step Library
- [ ] Open browser → Steps load, grouped by visibility
- [ ] Search "citrix" → Filtered results appear
- [ ] Filter by category → Results filtered
- [ ] Click step → Preview modal opens
- [ ] Click copy command → Copied to clipboard
- [ ] Click Insert → Modal closes, step returned
- [ ] Create custom step → Form validates, step inserted
- [ ] Save to library checked → Step saved to API
- [ ] Add custom step in navigation → Step appears with badge
- [ ] Complete session → Export includes custom steps

---

## Notes for Implementation

1. **Use existing patterns**: Follow Modal.tsx, TagInput.tsx, and existing page structures
2. **Dark mode**: All components must support light/dark themes via Tailwind classes
3. **Keyboard navigation**: Support Escape to close modals, Enter to submit
4. **Loading states**: Show skeletons or spinners during API calls
5. **Error handling**: Display user-friendly error messages, log details to console
6. **Accessibility**: Use proper ARIA labels, maintain focus management in modals

---

## Design Decisions (Resolved)

### 1. Custom Steps Persistence ✅
**Decision:** Separate `custom_steps` JSONB field in sessions table

**Rationale:**
- Clean separation of concerns (decisions track actions taken, custom_steps store step content)
- Enables session resuming with custom steps intact
- Easier to query and export custom steps separately
- Follows normalized data structure patterns

**Implementation:** Task B.10 adds migration + schema updates

### 2. Custom Step Navigation Flow ✅
**Decision:** Full step type support (decision/action/solution)

**Rationale:**
- Real-world troubleshooting requires branching after custom actions
- Example: "Try clearing cache" (action) → "Did it work?" (decision)
- Consistent with tree structure mental model
- Maximum flexibility for engineers

**Implementation:** Task B.11 renders custom steps based on `step_type`

### 3. Validation Warning UX ✅
**Decision:** Inline dismissible warnings, no confirmation modal

**Rationale:**
- Warnings are informational, not critical
- Orphan nodes might be intentional (work in progress)
- Trust users to manage their own tree quality
- Simpler UX, fewer clicks
- Draft feature (Issue #25) handles "save incomplete work" use case

**Implementation:** Task A.2-A.3 show warnings in ValidationSummary, don't block save

---

## Future Enhancements (Out of Scope)

1. **Draft Trees Feature** - Documented in Issue #25
   - Save incomplete trees without validation errors blocking
   - Planned for Phase 3 after Step Library is complete

2. **Standalone Step Library Page** - `/steps` route
   - Browse/manage personal step library outside of active sessions
   - Add after Issue #10 is complete and usage patterns are understood

3. **Rate Limiting** - Backend concern
   - Prevent step library spam
   - Add if abuse is observed in production
