579 lines
20 KiB
Markdown
579 lines
20 KiB
Markdown
# Procedural Custom Steps — Implementation Plan
|
|
|
|
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
|
|
**Goal:** Add "Add Custom Step" support to `ProceduralNavigationPage` so engineers can insert ad-hoc steps between existing checklist items during a session.
|
|
|
|
**Architecture:** Introduce a `RuntimeStep = ProceduralStep | CustomProceduralStep` discriminated union type. `ProceduralNavigationPage` grows new state + handlers for the custom-step modal flow (reusing `CustomStepModal` and `PostStepActionModal` as-is). `StepChecklist` and `StepDetail` accept `RuntimeStep` instead of `ProceduralStep`. No backend changes needed.
|
|
|
|
**Tech Stack:** React 19, TypeScript, Tailwind CSS, existing `sessionsApi.update`, `CustomStepModal`, `PostStepActionModal`
|
|
|
|
---
|
|
|
|
### Task 1: Add `RuntimeStep` union type
|
|
|
|
**Files:**
|
|
- Modify: `frontend/src/types/tree.ts`
|
|
- Modify: `frontend/src/types/index.ts` (re-export if needed)
|
|
|
|
**Context:** `ProceduralStep` is already in `tree.ts` along with `CommandBlock`. The new `CustomProceduralStep` interface lives in the same file. No hook needed — this is purely a type.
|
|
|
|
**Step 1: Add the types at the bottom of `frontend/src/types/tree.ts`**
|
|
|
|
After the existing `ProceduralStep` interface (line ~124), add:
|
|
|
|
```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
|
|
```
|
|
|
|
**Step 2: Export from `frontend/src/types/index.ts`**
|
|
|
|
Check what is currently re-exported from `tree.ts` in `index.ts` and add `CustomProceduralStep` and `RuntimeStep` to the export list.
|
|
|
|
Run: `cd frontend && npm run build 2>&1 | tail -20`
|
|
Expected: no new type errors
|
|
|
|
**Step 3: Commit**
|
|
|
|
```bash
|
|
git add frontend/src/types/tree.ts frontend/src/types/index.ts
|
|
git commit -m "feat: add RuntimeStep union type for procedural custom steps
|
|
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 2: Update `StepChecklist` to accept `RuntimeStep[]`
|
|
|
|
**Files:**
|
|
- Modify: `frontend/src/components/procedural/StepChecklist.tsx`
|
|
|
|
**Context:** Currently `StepChecklistProps.steps` is `ProceduralStep[]`. It filters internally for `type === 'procedure_step'`. Custom steps already have `type: 'procedure_step'` so the filter still works. We need to:
|
|
1. Change prop type to `RuntimeStep[]`
|
|
2. Render an amber "Custom" badge next to custom step titles
|
|
|
|
**Step 1: Update the import and prop type**
|
|
|
|
Change:
|
|
```tsx
|
|
import type { ProceduralStep } from '@/types'
|
|
```
|
|
To:
|
|
```tsx
|
|
import type { RuntimeStep } from '@/types'
|
|
```
|
|
|
|
Change `StepChecklistProps`:
|
|
```tsx
|
|
interface StepChecklistProps {
|
|
steps: RuntimeStep[]
|
|
currentStepIndex: number
|
|
completedStepIds: Set<string>
|
|
onStepClick: (index: number) => void
|
|
}
|
|
```
|
|
|
|
**Step 2: Add the "Custom" badge in the step row**
|
|
|
|
In the button's `<span className="min-w-0 flex-1 truncate">` row, add a badge after the title when `'isCustom' in step && step.isCustom`:
|
|
|
|
```tsx
|
|
<span className="min-w-0 flex-1 flex items-center gap-1.5 truncate">
|
|
<span className="truncate">{step.title || 'Untitled step'}</span>
|
|
{'isCustom' in step && step.isCustom && (
|
|
<span className="shrink-0 rounded-full bg-amber-400/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-amber-400">
|
|
Custom
|
|
</span>
|
|
)}
|
|
</span>
|
|
```
|
|
|
|
**Step 3: Build check**
|
|
|
|
Run: `cd frontend && npm run build 2>&1 | tail -20`
|
|
Expected: no new errors
|
|
|
|
**Step 4: Commit**
|
|
|
|
```bash
|
|
git add frontend/src/components/procedural/StepChecklist.tsx
|
|
git commit -m "feat: StepChecklist accepts RuntimeStep[], renders amber Custom badge
|
|
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 3: Update `StepDetail` to accept `RuntimeStep`
|
|
|
|
**Files:**
|
|
- Modify: `frontend/src/components/procedural/StepDetail.tsx`
|
|
|
|
**Context:** `StepDetail` currently only accepts `ProceduralStep`. For `CustomProceduralStep`, we skip the `content_type` badge (show "Custom Step" label instead), hide `warning_text`/`expected_outcome`/`verification`/`reference_url` sections (they don't exist on custom steps), and show description + commands normally. The `canComplete()` check and Mark Complete button stay the same.
|
|
|
|
**Step 1: Update import and prop type**
|
|
|
|
```tsx
|
|
import type { RuntimeStep, CommandBlock } from '@/types'
|
|
```
|
|
|
|
Change `StepDetailProps`:
|
|
```tsx
|
|
interface StepDetailProps {
|
|
step: RuntimeStep
|
|
// ... rest unchanged
|
|
}
|
|
```
|
|
|
|
**Step 2: Handle custom step header (replace content_type badge block)**
|
|
|
|
The existing header block uses `step.content_type` to pick a config. Add a guard:
|
|
|
|
```tsx
|
|
// At top of component body, after existing state:
|
|
const isCustom = 'isCustom' in step && step.isCustom
|
|
|
|
// In the header JSX, replace the content_type badge span:
|
|
{isCustom ? (
|
|
<span className="inline-flex items-center gap-1 rounded-full bg-amber-400/15 px-2 py-0.5 text-xs text-amber-400">
|
|
✦ Custom Step
|
|
</span>
|
|
) : (
|
|
<span className={cn('inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs', config.bg, config.color)}>
|
|
<Icon className="h-3 w-3" />
|
|
{config.label}
|
|
</span>
|
|
)}
|
|
```
|
|
|
|
Note: `contentType` and `config` are still computed but only used in the non-custom branch. TypeScript will be happy because `ProceduralStep` has `content_type` and custom step skips that branch.
|
|
|
|
**Step 3: Guard sections that don't exist on custom steps**
|
|
|
|
Sections that need guarding (wrap with `!isCustom &&`):
|
|
- `{step.warning_text && ...}` — already implicitly guarded since `CustomProceduralStep` has no `warning_text`, but TypeScript may complain → add `!isCustom &&` before the expression
|
|
- `{step.expected_outcome && ...}` — same
|
|
- `{verificationPrompt && ...}` — same (derive `verificationPrompt` with `!isCustom &&` check)
|
|
- `{step.reference_url && ...}` — same
|
|
|
|
The description block, commandBlocks block, and notes block all work fine as-is (custom steps have `description?` and `commands?` matching the normalized shapes).
|
|
|
|
Specifically, for `verificationPrompt` / `verificationType`:
|
|
```tsx
|
|
const verificationPrompt = isCustom ? undefined : (step.verification_prompt || step.verification?.prompt)
|
|
const verificationType = isCustom ? undefined : (step.verification_type || step.verification?.type)
|
|
```
|
|
|
|
For `commandBlocks` normalization — `CustomProceduralStep.commands` is `CommandBlock[]` already, so the existing ternary handles it fine.
|
|
|
|
**Step 4: Build check**
|
|
|
|
Run: `cd frontend && npm run build 2>&1 | tail -20`
|
|
Expected: no errors
|
|
|
|
**Step 5: Commit**
|
|
|
|
```bash
|
|
git add frontend/src/components/procedural/StepDetail.tsx
|
|
git commit -m "feat: StepDetail accepts RuntimeStep, renders Custom Step badge for custom steps
|
|
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 4: Wire custom step flow into `ProceduralNavigationPage`
|
|
|
|
**Files:**
|
|
- Modify: `frontend/src/pages/ProceduralNavigationPage.tsx`
|
|
|
|
**Context:** This is the main task. Current page has no custom step support. We add:
|
|
- `runtimeSteps: RuntimeStep[]` state (replaces `procedureSteps` derived value)
|
|
- `sessionCustomSteps: CustomStep[]` state
|
|
- `showCustomStepModal`, `showPostStepModal`, `pendingCustomStep`, `pendingIsFromLibrary`, `isSavingStep` state
|
|
- `handleStepCreated` — closes CustomStepModal, opens PostStepActionModal
|
|
- `handleInsertCustomStep` — builds CustomProceduralStep, inserts into runtimeSteps, persists
|
|
- `handleSaveForLater` / `handleUseNow` / `handleBoth` — wire PostStepActionModal buttons
|
|
- "Add Step" button below StepDetail, above StepFeedback
|
|
- Resume: inject custom steps at correct positions
|
|
|
|
**Step 1: Add new imports**
|
|
|
|
```tsx
|
|
import { CustomStepModal } from '@/components/step-library/CustomStepModal'
|
|
import { PostStepActionModal } from '@/components/session/PostStepActionModal'
|
|
import type { RuntimeStep, CustomProceduralStep } from '@/types'
|
|
import type { CustomStep } from '@/types/session'
|
|
import type { Step } from '@/types/step'
|
|
import type { CustomStepDraft } from '@/components/step-library/CustomStepModal'
|
|
import { Plus } from 'lucide-react'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
```
|
|
|
|
Check if `uuid` package is already available: `grep -r "from 'uuid'" frontend/src/`. If not, use `crypto.randomUUID()` instead (available in all modern browsers and Node 16+).
|
|
|
|
**Step 2: Add new state (after existing state declarations)**
|
|
|
|
```tsx
|
|
const [runtimeSteps, setRuntimeSteps] = useState<RuntimeStep[]>([])
|
|
const [sessionCustomSteps, setSessionCustomSteps] = useState<CustomStep[]>([])
|
|
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)
|
|
```
|
|
|
|
**Step 3: Replace `procedureSteps` derivation**
|
|
|
|
Currently:
|
|
```tsx
|
|
const procedureSteps = steps.filter((s) => s.type === 'procedure_step')
|
|
```
|
|
|
|
Replace with:
|
|
```tsx
|
|
// runtimeSteps is the authoritative list; filter for rendering (excludes section_headers)
|
|
const procedureSteps = runtimeSteps.filter((s) => s.type === 'procedure_step')
|
|
```
|
|
|
|
Also update `estimatedTotalMinutes` — it only sums `estimated_minutes` which only exists on `ProceduralStep`. Cast safely:
|
|
```tsx
|
|
const estimatedTotalMinutes = procedureSteps.reduce(
|
|
(sum, step) => sum + (('estimated_minutes' in step ? step.estimated_minutes : undefined) || 0),
|
|
0
|
|
)
|
|
```
|
|
|
|
**Step 4: Initialize `runtimeSteps` in `startSession`**
|
|
|
|
After `setSession(newSession)`, add:
|
|
```tsx
|
|
// Initialize runtimeSteps from tree steps
|
|
const allSteps = getStepsFromTree(tree!)
|
|
setRuntimeSteps(allSteps)
|
|
setSessionCustomSteps([])
|
|
```
|
|
|
|
The existing step state initialization loop stays the same — custom steps will add to it when inserted.
|
|
|
|
**Step 5: Initialize `runtimeSteps` in `resumeSession`**
|
|
|
|
After loading `sessionData`, before computing `pSteps`:
|
|
```tsx
|
|
// Build runtimeSteps: start with tree steps, then inject custom steps
|
|
const allSteps = getStepsFromTree(treeData)
|
|
const customSteps = sessionData.custom_steps || []
|
|
setSessionCustomSteps(customSteps)
|
|
|
|
// Inject custom steps at correct positions
|
|
const hydrated = buildRuntimeSteps(allSteps, customSteps)
|
|
setRuntimeSteps(hydrated)
|
|
```
|
|
|
|
And update the `pSteps` / `firstIncomplete` calculation to use `hydrated` (not `allSteps`):
|
|
```tsx
|
|
const pSteps = hydrated.filter((s) => s.type === 'procedure_step')
|
|
const firstIncomplete = pSteps.findIndex((s) => !initialStates.get(s.id)?.completedAt)
|
|
setCurrentStepIndex(firstIncomplete >= 0 ? firstIncomplete : pSteps.length - 1)
|
|
```
|
|
|
|
**Step 6: Add `buildRuntimeSteps` helper**
|
|
|
|
Add this function before the component (or as a module-level utility):
|
|
|
|
```ts
|
|
function buildRuntimeSteps(baseSteps: ProceduralStep[], customSteps: CustomStep[]): RuntimeStep[] {
|
|
const result: RuntimeStep[] = [...baseSteps]
|
|
// Sort custom steps by timestamp so earlier insertions come first if multiple
|
|
const sorted = [...customSteps].sort((a, b) => a.timestamp.localeCompare(b.timestamp))
|
|
for (const cs of sorted) {
|
|
// Find the index of the step this was inserted after
|
|
const afterIdx = result.findIndex((s) => s.id === cs.inserted_after_node_id)
|
|
const insertAt = afterIdx >= 0 ? afterIdx + 1 : result.length
|
|
const runtimeCustom: CustomProceduralStep = {
|
|
id: cs.id,
|
|
type: 'procedure_step',
|
|
title: cs.step_data.title,
|
|
description: cs.step_data.content?.instructions,
|
|
content_type: 'action',
|
|
commands: cs.step_data.content?.commands?.map((c) => ({
|
|
code: c.command,
|
|
label: c.label,
|
|
})),
|
|
isCustom: true,
|
|
}
|
|
result.splice(insertAt, 0, runtimeCustom)
|
|
}
|
|
return result
|
|
}
|
|
```
|
|
|
|
Note: `ProceduralStep` needs to be imported in scope here. Since it's used in `getStepsFromTree` already, it's available.
|
|
|
|
**Step 7: Add `handleStepCreated`**
|
|
|
|
```tsx
|
|
const handleStepCreated = (step: Step | CustomStepDraft, isFromLibrary: boolean) => {
|
|
setPendingCustomStep(step)
|
|
setPendingIsFromLibrary(isFromLibrary)
|
|
setShowCustomStepModal(false)
|
|
setShowPostStepModal(true)
|
|
}
|
|
```
|
|
|
|
**Step 8: Add `handleInsertCustomStep`**
|
|
|
|
```tsx
|
|
const handleInsertCustomStep = async (step: Step | CustomStepDraft) => {
|
|
if (!session) return
|
|
|
|
const id = crypto.randomUUID()
|
|
const currentStep = procedureSteps[currentStepIndex]
|
|
const insertedAfterId = currentStep?.id ?? ''
|
|
|
|
// Build the runtime representation
|
|
const runtimeCustom: CustomProceduralStep = {
|
|
id,
|
|
type: 'procedure_step',
|
|
title: step.title,
|
|
description: step.content?.instructions,
|
|
content_type: 'action',
|
|
commands: step.content?.commands?.map((c) => ({
|
|
code: c.command,
|
|
label: c.label,
|
|
})),
|
|
isCustom: true,
|
|
}
|
|
|
|
// Insert after currentStepIndex in runtimeSteps
|
|
setRuntimeSteps((prev) => {
|
|
const next = [...prev]
|
|
// Find the global index of the current procedureStep in runtimeSteps
|
|
const globalIdx = next.findIndex((s) => s.id === insertedAfterId)
|
|
const insertAt = globalIdx >= 0 ? globalIdx + 1 : next.length
|
|
next.splice(insertAt, 0, runtimeCustom)
|
|
return next
|
|
})
|
|
|
|
// Initialize step state for the new step
|
|
setStepStates((prev) => {
|
|
const next = new Map(prev)
|
|
next.set(id, { notes: '', verificationValue: '', completedAt: null })
|
|
return next
|
|
})
|
|
|
|
// Persist to session
|
|
const newCustomStep: CustomStep = {
|
|
id,
|
|
inserted_after_node_id: insertedAfterId,
|
|
step_data: step,
|
|
timestamp: new Date().toISOString(),
|
|
}
|
|
const newCustomSteps = [...sessionCustomSteps, newCustomStep]
|
|
setSessionCustomSteps(newCustomSteps)
|
|
|
|
try {
|
|
await sessionsApi.update(session.id, { custom_steps: newCustomSteps })
|
|
} catch {
|
|
toast.error('Failed to save custom step')
|
|
}
|
|
|
|
// Advance to the new step (it's now at currentStepIndex + 1)
|
|
setCurrentStepIndex(currentStepIndex + 1)
|
|
}
|
|
```
|
|
|
|
**Step 9: Add `handleSaveForLater`, `handleUseNow`, `handleBoth`**
|
|
|
|
```tsx
|
|
const handleSaveForLater = async () => {
|
|
if (!pendingCustomStep || pendingIsFromLibrary) return
|
|
setIsSavingStep(true)
|
|
try {
|
|
await stepsApi.create({
|
|
title: pendingCustomStep.title,
|
|
step_type: pendingCustomStep.step_type,
|
|
content: pendingCustomStep.content,
|
|
visibility: 'private',
|
|
})
|
|
toast.success('Step saved to library')
|
|
} catch {
|
|
toast.error('Failed to save step')
|
|
} finally {
|
|
setIsSavingStep(false)
|
|
setShowPostStepModal(false)
|
|
setPendingCustomStep(null)
|
|
}
|
|
}
|
|
|
|
const handleUseNow = async () => {
|
|
if (!pendingCustomStep) return
|
|
setShowPostStepModal(false)
|
|
await handleInsertCustomStep(pendingCustomStep)
|
|
setPendingCustomStep(null)
|
|
}
|
|
|
|
const handleBoth = async () => {
|
|
if (!pendingCustomStep || pendingIsFromLibrary) return
|
|
setIsSavingStep(true)
|
|
try {
|
|
await stepsApi.create({
|
|
title: pendingCustomStep.title,
|
|
step_type: pendingCustomStep.step_type,
|
|
content: pendingCustomStep.content,
|
|
visibility: 'private',
|
|
})
|
|
} catch {
|
|
toast.error('Failed to save step to library')
|
|
} finally {
|
|
setIsSavingStep(false)
|
|
}
|
|
setShowPostStepModal(false)
|
|
await handleInsertCustomStep(pendingCustomStep)
|
|
setPendingCustomStep(null)
|
|
}
|
|
```
|
|
|
|
Add `stepsApi` import at the top: `import { stepsApi } from '@/api/steps'`
|
|
|
|
**Step 10: Update `handleMarkComplete` to use `runtimeSteps`-based `procedureSteps`**
|
|
|
|
The existing `handleMarkComplete` reads `procedureSteps[currentStepIndex]` — since `procedureSteps` is now derived from `runtimeSteps`, this Just Works. But the completion check `currentStepIndex >= procedureSteps.length - 1` also works correctly.
|
|
|
|
No changes needed to `handleMarkComplete` itself.
|
|
|
|
**Step 11: Update `StepChecklist` call in JSX**
|
|
|
|
Change `steps={steps}` to `steps={runtimeSteps}`:
|
|
```tsx
|
|
<StepChecklist
|
|
steps={runtimeSteps}
|
|
currentStepIndex={currentStepIndex}
|
|
completedStepIds={completedStepIds}
|
|
onStepClick={setCurrentStepIndex}
|
|
/>
|
|
```
|
|
|
|
**Step 12: Update `StepDetail` call + add "Add Step" button**
|
|
|
|
Change `step={currentStep}` prop type is now `RuntimeStep` (TypeScript satisfied since `procedureSteps` is `RuntimeStep[]`).
|
|
|
|
Change `totalSteps={procedureSteps.length}` — still correct.
|
|
|
|
After the `StepDetail` closing tag and before `StepFeedback`, add the "Add Step" button:
|
|
|
|
```tsx
|
|
{/* Add Custom Step button — only on current active (incomplete) step, not on custom steps */}
|
|
{currentStep && !completedStepIds.has(currentStep.id) && !('isCustom' in currentStep && currentStep.isCustom) && (
|
|
<div className="mt-4">
|
|
<button
|
|
onClick={() => setShowCustomStepModal(true)}
|
|
className="flex w-full items-center justify-center gap-2 rounded-lg border border-dashed border-border px-4 py-2.5 text-sm text-muted-foreground transition-colors hover:border-primary/50 hover:text-foreground"
|
|
>
|
|
<Plus className="h-4 w-4" />
|
|
Add Step
|
|
</button>
|
|
</div>
|
|
)}
|
|
```
|
|
|
|
**Step 13: Add modals at the bottom of the JSX (before closing `</div>`)**
|
|
|
|
After the `ConfirmDialog` and parameters popover, add:
|
|
|
|
```tsx
|
|
{/* Custom Step Modal */}
|
|
<CustomStepModal
|
|
isOpen={showCustomStepModal}
|
|
onClose={() => setShowCustomStepModal(false)}
|
|
onInsertStep={handleStepCreated}
|
|
/>
|
|
|
|
{/* Post Step Action Modal */}
|
|
{pendingCustomStep && (
|
|
<PostStepActionModal
|
|
isOpen={showPostStepModal}
|
|
onClose={() => { setShowPostStepModal(false); setPendingCustomStep(null) }}
|
|
step={pendingCustomStep}
|
|
onSaveForLater={handleSaveForLater}
|
|
onUseNow={handleUseNow}
|
|
onBoth={handleBoth}
|
|
isFromLibrary={pendingIsFromLibrary}
|
|
isSaving={isSavingStep}
|
|
/>
|
|
)}
|
|
```
|
|
|
|
**Step 14: ProgressBar total count update**
|
|
|
|
The `ProgressBar` already uses `procedureSteps.length` as `totalSteps` which is derived from `runtimeSteps` — so the progress bar total automatically updates when custom steps are inserted.
|
|
|
|
**Step 15: Build check**
|
|
|
|
Run: `cd frontend && npm run build 2>&1 | tail -30`
|
|
Expected: no errors. Watch especially for:
|
|
- `CustomStep` import (it's in `@/types/session`, not `@/types` — import directly if needed)
|
|
- `buildRuntimeSteps` needing `ProceduralStep` type explicitly imported
|
|
|
|
**Step 16: Commit**
|
|
|
|
```bash
|
|
git add frontend/src/pages/ProceduralNavigationPage.tsx
|
|
git commit -m "feat: custom step insertion in procedural flow sessions
|
|
|
|
Engineers can add custom steps inline during execution. Steps are
|
|
persisted to session.custom_steps and restored on resume.
|
|
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
|
```
|
|
|
|
---
|
|
|
|
### Task 5: Manual verification checklist
|
|
|
|
**No code changes — verification only.**
|
|
|
|
Start a procedural session (or maintenance session). Verify:
|
|
|
|
1. **Add Step button appears** below the current step detail on an active, incomplete, non-custom step
|
|
2. **Add Step button is hidden** on completed steps
|
|
3. **Clicking Add Step** opens `CustomStepModal` with Create + Browse Library tabs
|
|
4. **Creating a step** → `PostStepActionModal` appears
|
|
5. **"Use Now"** → closes modal, custom step appears as next step in checklist with amber "Custom" badge, current view advances to it
|
|
6. **"Save for Later"** → closes modal, nothing inserted, no crash
|
|
7. **"Do Both"** → saves to library AND inserts
|
|
8. **Mark Complete on custom step** → advances to next step normally
|
|
9. **Custom step in StepDetail** → shows "Custom Step" amber badge instead of content_type badge
|
|
10. **Progress bar total** increments when custom step is inserted
|
|
11. **Resume session** (navigate away and back with `{ state: { sessionId } }`) → custom step reappears at correct position in checklist
|
|
|
|
---
|
|
|
|
### Task 6: Final build validation
|
|
|
|
Run: `cd frontend && npm run build`
|
|
Expected: build succeeds with zero errors (pre-existing chunk-size warnings are fine).
|
|
|
|
Commit build validation result if no issues:
|
|
|
|
```bash
|
|
git add -p # stage any incidental fixes
|
|
git commit -m "chore: final build validation for procedural custom steps
|
|
|
|
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>"
|
|
```
|
|
|
|
Only commit if there were actual changes to stage.
|