docs: add Step Library and Procedural Custom Steps design/plan docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
178
docs/plans/2026-02-24-procedural-custom-steps-design.md
Normal file
178
docs/plans/2026-02-24-procedural-custom-steps-design.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user