Files
resolutionflow/docs/plans/archive/2026-02-24-procedural-custom-steps-design.md
chihlasm 932927b9df chore: archive old plan docs + add survey foundation files
Move completed plan docs to docs/plans/archive/. Add survey migration 046
and reference HTML/plan files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 02:03:38 -05:00

179 lines
6.4 KiB
Markdown

# 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<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`):
```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.