import { useCallback, useState } from "react"; import type { WizardStateData, WizardStepInfo } from "../api/client"; const API_BASE = "/api"; interface SetupWizardProps { wizardState: WizardStateData; onWizardUpdate: (state: WizardStateData) => void; sendMessage: (message: string) => void; } /** Style constants for the wizard UI. */ const STEP_BG_PENDING = "#1a1f2e"; const STEP_BG_ACTIVE = "#1c2a1c"; const STEP_BG_DONE = "#1a2a1a"; const STEP_BORDER_PENDING = "#2a2f3e"; const STEP_BORDER_ACTIVE = "#2d4a2d"; const STEP_BORDER_DONE = "#2d4a2d"; const COLOR_LABEL = "#ccc"; const COLOR_LABEL_DONE = "#a0d4a0"; const COLOR_ACCENT = "#a0d4a0"; function statusIcon(status: string): string { switch (status) { case "confirmed": return "\u2713"; case "skipped": return "\u2013"; case "generating": return "\u2026"; case "awaiting_confirmation": return "?"; default: return "\u00B7"; } } function stepBackground(status: string, isActive: boolean): string { if (status === "confirmed" || status === "skipped") return STEP_BG_DONE; if (isActive) return STEP_BG_ACTIVE; return STEP_BG_PENDING; } function stepBorder(status: string, isActive: boolean): string { if (status === "confirmed" || status === "skipped") return STEP_BORDER_DONE; if (isActive) return STEP_BORDER_ACTIVE; return STEP_BORDER_PENDING; } /** Messages sent to the chat to trigger agent generation for each step. */ const STEP_PROMPTS: Record = { context: "Read the codebase and generate .huskies/specs/00_CONTEXT.md with a project context spec. Include High-Level Goal, Core Features, Domain Definition, and Glossary sections. Then call the wizard API to store the content: PUT /api/wizard/step/context/content", stack: "Read the tech stack and generate .huskies/specs/tech/STACK.md with a tech stack spec. Include Core Stack, Coding Standards, Quality Gates, and Libraries sections. Then call the wizard API to store the content: PUT /api/wizard/step/stack/content", test_script: "Read the project structure and create script/test — a bash script that runs the project's actual test suite. Then call the wizard API: PUT /api/wizard/step/test_script/content", release_script: "Read the project's deployment setup and create script/release tailored to the project. Then call the wizard API: PUT /api/wizard/step/release_script/content", test_coverage: "If the stack supports coverage reporting, create script/test_coverage. Then call the wizard API: PUT /api/wizard/step/test_coverage/content", }; async function apiPost(path: string): Promise { try { const resp = await fetch(`${API_BASE}${path}`, { method: "POST" }); if (!resp.ok) return null; return (await resp.json()) as WizardStateData; } catch { return null; } } function StepCard({ step, isActive, onGenerate, onConfirm, onSkip, }: { step: WizardStepInfo; isActive: boolean; onGenerate: () => void; onConfirm: () => void; onSkip: () => void; }) { const isDone = step.status === "confirmed" || step.status === "skipped"; return (
{statusIcon(step.status)} {step.label} {isActive && step.status === "pending" && ( )} {isActive && step.status === "generating" && ( Generating... )}
{step.content && step.status === "awaiting_confirmation" && (
						{step.content}
					
)} {isActive && step.status === "pending" && !step.content && (
)}
); } export default function SetupWizard({ wizardState, onWizardUpdate, sendMessage, }: SetupWizardProps) { const [, setRefreshKey] = useState(0); const handleGenerate = useCallback( (step: WizardStepInfo) => { const prompt = STEP_PROMPTS[step.step]; if (prompt) { sendMessage(prompt); } }, [sendMessage], ); const handleConfirm = useCallback( async (step: WizardStepInfo) => { const result = await apiPost(`/wizard/step/${step.step}/confirm`); if (result) { onWizardUpdate(result); setRefreshKey((k) => k + 1); } }, [onWizardUpdate], ); const handleSkip = useCallback( async (step: WizardStepInfo) => { const result = await apiPost(`/wizard/step/${step.step}/skip`); if (result) { onWizardUpdate(result); setRefreshKey((k) => k + 1); } }, [onWizardUpdate], ); if (wizardState.completed) { return (

Setup Complete

Your project is configured. You can start writing stories.

); } return (

Project Setup Wizard

Step {wizardState.current_step_index + 1} of{" "} {wizardState.steps.length}

{wizardState.steps.map((step, idx) => ( handleGenerate(step)} onConfirm={() => handleConfirm(step)} onSkip={() => handleSkip(step)} /> ))}
); }