Files
resolutionflow/docs/plans/2026-02-24-procedural-custom-steps-design.md
2026-02-26 08:09:12 -05:00

6.4 KiB

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:

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:

{
  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:

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:

const [runtimeSteps, setRuntimeSteps] = useState<RuntimeStep[]>([])
const [showCustomStepModal, setShowCustomStepModal] = useState(false)
const [showPostStepModal, setShowPostStepModal] = useState(false)
const [pendingCustomStep, setPendingCustomStep] = useState<Step | CustomStepDraft | null>(null)
const [pendingIsFromLibrary, setPendingIsFromLibrary] = useState(false)
const [isSavingStep, setIsSavingStep] = useState(false)
const [sessionCustomSteps, setSessionCustomSteps] = useState<CustomStep[]>([])

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):

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.