import { useEffect, useState } from 'react'; import { Loader2, Sparkles } from 'lucide-react'; import { Badge } from './ui/badge'; import { Button } from './ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './ui/card'; import { Input } from './ui/input'; import { Label } from './ui/label'; import { Textarea } from './ui/textarea'; import { cn } from '@/lib/utils'; import { api } from '@/lib/api'; import type { TemplateMeta } from '@shared/scenario'; interface Props { /** A generate (LLM) call is in flight — disables the form's controls. */ busy?: boolean; onBack: () => void; /** * Step 1: design the draft world + checkpoint list. The form hands over the * selected template's full metadata (the review screen shows its name/year) * plus the description and the effective start year. */ onGenerate: (template: TemplateMeta, description: string, year: number) => void; } /** The year input is an empty string ("use the template's default") or a number. */ const YEAR_MIN = 1000; const YEAR_MAX = 3000; /** * "Create scenario" — step 1 of the dynamic path (v0.31, #52). The user picks * one of the light era templates (a card each: name, default year, blurb), * writes a free-text description of the world they want, and may override the * start year (the field is pre-filled from the selected template; leaving it * empty keeps the template's default). "Generate world" posts the draft * request; on success the app moves to the checkpoint review screen, on * failure it toasts and stays here (the form's local state is preserved). */ export default function ScenarioForm({ busy = false, onBack, onGenerate }: Props) { const [templates, setTemplates] = useState([]); const [templatesError, setTemplatesError] = useState(null); const [selectedId, setSelectedId] = useState(null); const [description, setDescription] = useState(''); // Pre-filled from the selected template's default year (the user can edit). const [yearText, setYearText] = useState(''); // Load the era-template manifest on mount (the form is the only consumer). useEffect(() => { let alive = true; api .listScenarioTemplates() .then((r) => alive && setTemplates(r.templates)) .catch((e: unknown) => alive && setTemplatesError(e instanceof Error ? e.message : String(e))); return () => { alive = false; }; }, []); const selected = templates.find((t) => t.id === selectedId) ?? null; const effectiveYear = yearText.trim() === '' ? NaN : Number.parseInt(yearText.trim(), 10); const yearValid = Number.isInteger(effectiveYear) && effectiveYear >= YEAR_MIN && effectiveYear <= YEAR_MAX; const canGenerate = !busy && selected !== null && description.trim() !== '' && yearValid; const selectTemplate = (t: TemplateMeta) => { setSelectedId(t.id); // Selecting an era pre-fills the year with that era's default (the user // can still edit it afterwards). setYearText(String(t.year)); }; const generate = () => { if (!canGenerate || !selected) return; onGenerate(selected, description.trim(), effectiveYear); }; return (

Create a scenario

Design your own world: pick an era, describe what should be true, and the AI will draft the world and an opening set of advisor checkpoints for your review. Nothing is saved until you finalize.

{/* 1 — the era template (a card each). */}

1 · Pick an era (required)

{templatesError ? (

Could not load the era templates: {templatesError}

) : templates.length === 0 ? (

Loading era templates…

) : (
{templates.map((t) => { const isSel = t.id === selectedId; return ( selectTemplate(t)} >
{t.name} {t.year}
{t.eraDescription}
{isSel ? 'Selected' : 'Start from this era'}
); })}
)} {/* 2 — the description (+ optional year override). */}

2 · Describe your world (required)