# Procedural Custom Steps — Design > **Date:** February 24, 2026 > **Status:** Approved > **Phase:** 2.5 — Feature 2 of 3 --- ## What We're Building Add "Add Custom Step" support to `ProceduralNavigationPage` so engineers can insert ad-hoc steps between any existing checklist items during execution. The inserted step appears inline in the checklist and detail panel — worked through just like a regular step before proceeding. --- ## Key Difference from Troubleshooting Custom Steps `useCustomStepFlow` is built for tree-graph navigation: `currentNodeId`, `findNode`, `setCurrentNodeId`, path traversal, continuation modal for picking descendants, custom branch mode. **None of that applies here.** Procedural flows are linear arrays. A custom step is just a new `ProceduralStep`-shaped object injected at a position in the array. No new hook needed — all state lives in `ProceduralNavigationPage`. --- ## Data Model Custom steps are stored in session `custom_steps` (already a JSONB array on `Session`). The existing `CustomStep` type is: ```ts interface CustomStep { id: string inserted_after_node_id: string // step ID it was inserted after step_data: Step | CustomStepDraft timestamp: string } ``` For procedural flows, `inserted_after_node_id` is the `ProceduralStep.id` of the step it follows. A custom step is represented in the runtime `ProceduralStep[]` as: ```ts { id: customStep.id, // the CustomStep UUID type: 'procedure_step', title: step_data.title, description: step_data.content.instructions, content_type: 'action', commands: step_data.content.commands (mapped), // marker for custom steps: _isCustom: true // not in ProceduralStep type — use a local union } ``` Rather than mutating `ProceduralStep`, we use a local discriminated union: ```ts type RuntimeStep = ProceduralStep | CustomProceduralStep interface CustomProceduralStep { id: string type: 'procedure_step' title: string description?: string content_type: 'action' commands?: CommandBlock[] isCustom: true // discriminant } ``` The `procedureSteps` array used for rendering becomes `RuntimeStep[]` instead of `ProceduralStep[]`. --- ## User Flow 1. Engineer is on any step in a procedural flow 2. Clicks **"+ Add Step"** button below the current step detail 3. `CustomStepModal` opens (existing component — Create tab + Browse Library tab) 4. Engineer creates or selects a step → `PostStepActionModal` appears 5. **"Use Now"**: inserts after current step, closes modals, advances to the new step 6. **"Save for Later"**: saves to library only, no insertion 7. **"Do Both"**: saves to library + inserts The inserted step renders in `StepDetail` and `StepChecklist` with a visual "Custom" badge. The engineer marks it complete the same way as any other step (the "Mark Complete" button). No continuation modal. No custom branch mode. No fork flow. --- ## What Changes ### ProceduralNavigationPage.tsx **New state:** ```ts const [runtimeSteps, setRuntimeSteps] = useState([]) const [showCustomStepModal, setShowCustomStepModal] = useState(false) const [showPostStepModal, setShowPostStepModal] = useState(false) const [pendingCustomStep, setPendingCustomStep] = useState(null) const [pendingIsFromLibrary, setPendingIsFromLibrary] = useState(false) const [isSavingStep, setIsSavingStep] = useState(false) const [sessionCustomSteps, setSessionCustomSteps] = useState([]) ``` **`runtimeSteps`** is initialized from `tree.tree_structure.steps` and updated when custom steps are inserted. `procedureSteps` (currently `steps.filter(s => s.type === 'procedure_step')`) becomes `runtimeSteps.filter(...)`. **`handleInsertCustomStep(step, isFromLibrary)`:** - Builds a `CustomProceduralStep` from the draft/step - Inserts it into `runtimeSteps` after `currentStepIndex` - Adds a `CustomStep` entry to `sessionCustomSteps` - Calls `sessionsApi.update(session.id, { custom_steps: newCustomSteps })` - Advances `currentStepIndex` by 1 (focus moves to the new step) **`handleStepCreated(step, isFromLibrary)`:** — same pattern as troubleshooting - Sets `pendingCustomStep`, `pendingIsFromLibrary` - Closes `CustomStepModal`, opens `PostStepActionModal` **Add `handleSaveForLater`, `handleUseNow`, `handleBoth`** — same logic as `useCustomStepFlow` but without path/node navigation. **"+ Add Step" button:** Renders in the right panel below `StepDetail`, above `StepFeedback`. Only shown on the current active (incomplete) step. Not shown on already-completed steps or on custom steps (no nesting). **Session resume:** On `resumeSession`, initialize `runtimeSteps` from the tree steps then inject custom steps from `sessionData.custom_steps` at the correct positions. ### StepChecklist.tsx Accept `RuntimeStep[]` instead of `ProceduralStep[]`. Render a small "Custom" badge (amber dot or label) next to custom step titles. ### StepDetail.tsx Accept `RuntimeStep` instead of `ProceduralStep`. When `step.isCustom === true`, render slightly differently: no `content_type` badge (use a plain "Custom Step" label instead), show instructions as description, render commands if present using the existing command block renderer. ### New type: `RuntimeStep` Add to `frontend/src/types/tree.ts` (or a separate `procedural.ts`): ```ts export interface CustomProceduralStep { id: string type: 'procedure_step' title: string description?: string content_type: 'action' commands?: CommandBlock[] isCustom: true } export type RuntimeStep = ProceduralStep | CustomProceduralStep ``` --- ## What Does NOT Change - `useCustomStepFlow` — not used at all in procedural flows - `ContinuationModal` — not used (no branching) - Fork flow — not used - `CustomStepModal` — reused as-is (already works for both create and browse) - `PostStepActionModal` — reused as-is - `sessionsApi.update` with `custom_steps` — already supports this - Backend — no changes needed --- ## Checklist render (sidebar) Custom steps show in the checklist with an amber `✦` or small "Custom" chip: ``` ✅ 1. Check service status ✅ 2. Restart broker agent 3. [✦ Custom] Verify VDA re-registration ← custom step 4. Check event log ``` --- ## Completion behavior When the engineer marks the last custom step complete and it was inserted before the last regular step, `currentStepIndex` increments normally — they continue with the remaining regular steps. The total step count in the progress bar updates when custom steps are inserted.