feat: StepDetail accepts RuntimeStep, renders Custom Step badge for custom steps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-24 21:49:15 -05:00
parent e691c978f1
commit c2dbabc619

View File

@@ -1,6 +1,6 @@
import { useState } from 'react'
import { AlertTriangle, CheckCircle2, Info, Zap, Copy, Check, ExternalLink } from 'lucide-react'
import type { ProceduralStep, StepContentType, CommandBlock } from '@/types'
import type { RuntimeStep, StepContentType, CommandBlock } from '@/types'
import { resolveVariables } from '@/lib/variableResolver'
import { cn } from '@/lib/utils'
@@ -12,7 +12,7 @@ const contentTypeConfig: Record<StepContentType, { icon: typeof Zap; color: stri
}
interface StepDetailProps {
step: ProceduralStep
step: RuntimeStep
stepNumber: number
totalSteps: number
variables: Record<string, string>
@@ -39,13 +39,18 @@ export function StepDetail({
isLast,
}: StepDetailProps) {
const [copiedIndex, setCopiedIndex] = useState<number | null>(null)
const isCustom = 'isCustom' in step && step.isCustom
const contentType = step.content_type || 'action'
const config = contentTypeConfig[contentType]
const Icon = config.icon
// Derive verification from either flat fields or nested object
const verificationPrompt = step.verification_prompt || step.verification?.prompt
const verificationType = step.verification_type || step.verification?.type
const verificationPrompt = !isCustom && 'verification_prompt' in step
? step.verification_prompt || step.verification?.prompt
: undefined
const verificationType = !isCustom && 'verification_type' in step
? step.verification_type || step.verification?.type
: undefined
const resolve = (text: string | undefined) => {
if (!text) return ''
@@ -87,14 +92,20 @@ export function StepDetail({
<div className="min-w-0 flex-1">
<h2 className="text-lg font-semibold text-foreground">{step.title}</h2>
<div className="mt-1 flex items-center gap-2">
<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>
{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>
)}
<span className="text-xs text-muted-foreground">
Step {stepNumber} of {totalSteps}
</span>
{step.estimated_minutes && (
{'estimated_minutes' in step && step.estimated_minutes && (
<span className="text-xs text-muted-foreground">~{step.estimated_minutes} min</span>
)}
</div>
@@ -102,7 +113,7 @@ export function StepDetail({
</div>
{/* Warning banner */}
{step.warning_text && (
{'warning_text' in step && step.warning_text && (
<div className="flex items-start gap-2 rounded-lg border border-yellow-400/20 bg-yellow-400/5 px-3 py-2.5">
<AlertTriangle className="mt-0.5 h-4 w-4 shrink-0 text-yellow-400" />
<p className="text-sm text-yellow-200">{resolve(step.warning_text)}</p>
@@ -142,7 +153,7 @@ export function StepDetail({
)}
{/* Expected outcome */}
{step.expected_outcome && (
{'expected_outcome' in step && step.expected_outcome && (
<div className="rounded-lg border border-border bg-white/[0.02] p-3">
<h4 className="mb-1 text-xs font-medium text-muted-foreground">Expected Outcome</h4>
<p className="text-sm text-muted-foreground">{resolve(step.expected_outcome)}</p>
@@ -181,7 +192,7 @@ export function StepDetail({
)}
{/* Notes */}
{step.notes_enabled !== false && (
{(!('notes_enabled' in step) || step.notes_enabled !== false) && (
<div>
<label className="mb-1 block text-xs font-medium text-muted-foreground">Notes</label>
<textarea
@@ -195,7 +206,7 @@ export function StepDetail({
)}
{/* Reference link */}
{step.reference_url && (
{'reference_url' in step && step.reference_url && (
<a
href={resolve(step.reference_url)}
target="_blank"