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