)
}
```
**Step 2: Verify it builds**
Run: `cd frontend && npm run build 2>&1 | tail -5`
Expected: `built in` success message (component is created but not imported anywhere yet)
**Step 3: Commit**
```bash
git add frontend/src/components/procedural-editor/CollapsibleEditorSection.tsx
git commit -m "feat: add CollapsibleEditorSection component for procedural editor"
```
---
## Phase 2: Layout Restructure
### Task 2: Convert ProceduralEditorPage to Fixed-Height Editor
**Files:**
- Modify: `frontend/src/pages/ProceduralEditorPage.tsx`
**Context:** The page currently uses `container mx-auto px-4 py-6` with vertical scrolling. We need to convert it to `flex flex-col h-full overflow-hidden` so the step list can scroll independently. The toolbar becomes a sticky header, and Details + Intake Form become collapsible sections.
Reference the existing troubleshooting editor pattern: `frontend/src/pages/TreeEditorPage.tsx` lines 409-410 for the `flex h-full flex-col overflow-hidden` pattern.
**Step 1: Read the current file**
Read: `frontend/src/pages/ProceduralEditorPage.tsx`
Understand the full structure before making changes.
**Step 2: Restructure the layout**
Replace the entire render return (from `return (` to the closing `)`) with the new fixed-height layout. The key changes:
1. Outer wrapper: `
` containing the back button, title, and save/publish buttons
3. Collapsible sections zone: `
` containing CollapsibleEditorSection wrappers for Details and IntakeFormBuilder
4. Step list zone: `
` containing StepList
Import `CollapsibleEditorSection` at the top:
```tsx
import { CollapsibleEditorSection } from '@/components/procedural-editor/CollapsibleEditorSection'
```
The Details collapsible section needs a summary string. Build it from store state:
```tsx
const detailsSummary = [
name ? `"${name}"` : '"Untitled"',
tags.length > 0 ? `${tags.length} tag${tags.length !== 1 ? 's' : ''}` : 'No tags',
isPublic ? 'Public' : 'Private',
].join(' · ')
```
The IntakeFormBuilder collapsible section needs a summary too. Read the `intakeForm` array from the store (add it to destructuring if not already there) and build:
```tsx
const intakeSummary = intakeForm.length === 0
? 'No fields defined'
: `${intakeForm.length} field${intakeForm.length !== 1 ? 's' : ''}: ${intakeForm.map(f => f.label || f.variable_name).slice(0, 4).join(', ')}${intakeForm.length > 4 ? ', ...' : ''}`
```
For the Details section content inside CollapsibleEditorSection, move the existing form fields (name input, description textarea, tags input, public checkbox) directly as children.
The Details section should `defaultExpanded={!isEditMode}` so new flows start with Details expanded (name is required), while existing flows start collapsed.
The Intake Form section should `defaultExpanded={false}` always.
**Step 3: Verify it builds**
Run: `cd frontend && npm run build 2>&1 | tail -5`
Expected: Clean build
**Step 4: Commit**
```bash
git add frontend/src/pages/ProceduralEditorPage.tsx
git commit -m "feat: restructure procedural editor to fixed-height layout with collapsible sections"
```
---
## Phase 3: Step List Improvements
### Task 3: Add Empty State and Step Count Header
**Files:**
- Modify: `frontend/src/components/procedural-editor/StepList.tsx`
**Context:** The step list needs: (1) a step count + total estimated time in the header, (2) auto-scroll to new steps. Note: the store enforces minimum one `procedure_step`, so remove any "0 steps" UI paths. The step list's outer card wrapper should be removed since ProceduralEditorPage now provides the scrolling container — StepList should just render its header and step items directly.
**Step 1: Read the current file**
Read: `frontend/src/components/procedural-editor/StepList.tsx`
**Step 2: Modify the header and add empty state**
Remove the outer `
` card wrapper. The step list now renders directly in the scrolling container.
Update the header to show step count + total estimated time:
```tsx
const totalMinutes = steps
.filter(s => s.type === 'procedure_step' && s.estimated_minutes)
.reduce((sum, s) => sum + (s.estimated_minutes || 0), 0)
// In the header:
({procedureSteps.length} step{procedureSteps.length !== 1 ? 's' : ''}
{totalMinutes > 0 ? ` · ~${totalMinutes} min` : ''})
```
Add empty state after the header, before the step map:
```tsx
{procedureSteps.length === 0 && (
Add your first step
Steps define the actions engineers follow during this procedure.
)}
```
**Step 3: Add auto-scroll to new step**
When a new step is added, the list should scroll to show it. Add a ref and useEffect:
```tsx
import { useRef, useEffect } from 'react'
const listEndRef = useRef(null)
const prevStepCount = useRef(steps.length)
useEffect(() => {
if (steps.length > prevStepCount.current) {
listEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}
prevStepCount.current = steps.length
}, [steps.length])
// At the bottom of the step list, before the Add Step button:
```
**Step 4: Verify it builds**
Run: `cd frontend && npm run build 2>&1 | tail -5`
Expected: Clean build
**Step 5: Commit**
```bash
git add frontend/src/components/procedural-editor/StepList.tsx
git commit -m "feat: add empty state, step count header, and auto-scroll to step list"
```
---
## Phase 4: Drag-to-Reorder Steps
### Task 4: Add @dnd-kit Drag-to-Reorder to StepList
**Files:**
- Modify: `frontend/src/components/procedural-editor/StepList.tsx`
**Context:** @dnd-kit is already installed (`@dnd-kit/core@^6.3.1`, `@dnd-kit/sortable@^10.0.0`). There's an existing pattern in `frontend/src/components/step-library/CategoryRow.tsx` using `useSortable` and in `frontend/src/pages/admin/AdminCategoriesPage.tsx` using `DndContext` + `SortableContext`. The store already has `moveStep(fromIndex, toIndex)`. Reorder is array-index based only — do NOT recalculate `display_order`. `procedure_end` must remain non-draggable and always last. Drag handles must have accessible labels and keyboard support (Enter/Space to pick up, arrow keys to move, Escape to cancel).
**Step 1: Read the existing @dnd-kit pattern**
Read: `frontend/src/components/step-library/CategoryRow.tsx` (lines 1-50 for the useSortable pattern)
Read: `frontend/src/pages/admin/AdminCategoriesPage.tsx` (lines 1-10 for imports, lines 110-140 for handleDragEnd)
**Step 2: Add DndContext to StepList**
Add imports at the top of StepList.tsx:
```tsx
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
type DragEndEvent,
} from '@dnd-kit/core'
import {
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
useSortable,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
```
Add sensors and drag handler before the return:
```tsx
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
)
const handleDragEnd = useCallback((event: DragEndEvent) => {
const { active, over } = event
if (!over || active.id === over.id) return
const oldIndex = steps.findIndex(s => s.id === active.id)
const newIndex = steps.findIndex(s => s.id === over.id)
if (oldIndex === -1 || newIndex === -1) return
// Don't allow moving past the procedure_end step
const endIndex = steps.findIndex(s => s.type === 'procedure_end')
if (newIndex >= endIndex) return
moveStep(oldIndex, newIndex)
}, [steps, moveStep])
```
Wrap the step list `
` with DndContext and SortableContext:
```tsx
s.type !== 'procedure_end').map(s => s.id)} strategy={verticalListSortingStrategy}>