feat(wizard): per-phase preview — graph in Analyze, form in Generate
The Wizard already has the progress stepper with checkmarks, the preview pane, and the review summary card before Publish. The remaining gap from the UX overhaul was that the preview pane showed the same flat list regardless of phase. Specialise it: - Analyze + onward: graph-style vertical chain with dispatch-kind bubbles (manual / assistant / system) and connector edges, so the operator sees the flow as a graph not a list. - Generate + onward: form preview that renders disabled input/select controls for each named field, so the operator sees what end users will fill in. - Validate + onward: rules list (unchanged). - Draft saved: all three render together so the review card has the full picture beside it. EA2 write contract is unchanged — createDraft / applyBatch / publishFlow still fire at every phase transition. 31/31 vitest pass.
This commit is contained in:
+60
-44
@@ -263,59 +263,75 @@ export default function Wizard() {
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderGraphPreview = () => (
|
||||
<div className="wizard-preview-section">
|
||||
<div className="wizard-preview-label">Flow · {draft.nodes.length} step{draft.nodes.length === 1 ? "" : "s"}</div>
|
||||
<div className="wizard-preview-graph">
|
||||
{draft.nodes.map((n, i) => (
|
||||
<div key={n._key || i} className="wizard-graph-node">
|
||||
<div className={`wizard-graph-bubble kind-${n.dispatch_kind || "human"}`}>
|
||||
<span className="wizard-graph-idx">{i + 1}</span>
|
||||
<span className="wizard-graph-name">{n.display_name || "Untitled"}</span>
|
||||
</div>
|
||||
<div className="wizard-graph-meta">
|
||||
{({ human: "Manual", agent: "Assistant", system: "System" } as Record<string, string>)[n.dispatch_kind || "human"]}
|
||||
</div>
|
||||
{i < draft.nodes.length - 1 && <div className="wizard-graph-edge" aria-hidden />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderFormPreview = () => (
|
||||
<div className="wizard-preview-section">
|
||||
<div className="wizard-preview-label">Form preview · what users will fill in</div>
|
||||
<div className="wizard-form-preview">
|
||||
{draft.fields.filter((f) => f.name).length === 0 ? (
|
||||
<div className="wizard-preview-empty">Add fields on the left to see the form here.</div>
|
||||
) : (
|
||||
draft.fields.filter((f) => f.name).map((f, i) => (
|
||||
<div key={i} className="wizard-form-field">
|
||||
<label className="wizard-form-label">{f.name}</label>
|
||||
{f.type === "boolean" ? (
|
||||
<select className="wizard-form-input" disabled defaultValue=""><option value="">Yes / No</option></select>
|
||||
) : f.type === "number" ? (
|
||||
<input className="wizard-form-input" type="number" placeholder="0" disabled />
|
||||
) : (
|
||||
<input className="wizard-form-input" type="text" placeholder={`Enter ${f.name.toLowerCase()}`} disabled />
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderRulesPreview = () => (
|
||||
<div className="wizard-preview-section">
|
||||
<div className="wizard-preview-label">Rules · {draft.rules.filter((r) => r.trim()).length}</div>
|
||||
<ul className="wizard-preview-rules">
|
||||
{draft.rules.filter((r) => r.trim()).map((r, i) => <li key={i}>{r}</li>)}
|
||||
{draft.rules.filter((r) => r.trim()).length === 0 && (
|
||||
<li className="wizard-preview-empty">No rules typed yet</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderPreviewPane = () => {
|
||||
if (draft.nodes.length === 0 && draft.fields.length === 0 && draft.rules.length === 0) return null;
|
||||
return (
|
||||
<aside className="wizard-preview">
|
||||
<div className="wizard-preview-head">
|
||||
<span className="mc-hero-eyebrow">Preview</span>
|
||||
<span className="mc-hero-eyebrow">Live preview</span>
|
||||
<div className="wizard-preview-title">{draft.name || "Untitled process"}</div>
|
||||
{draft.description && <div className="wizard-preview-desc">{draft.description.slice(0, 140)}</div>}
|
||||
</div>
|
||||
|
||||
{draft.nodes.length > 0 && (
|
||||
<div className="wizard-preview-section">
|
||||
<div className="wizard-preview-label">Steps · {draft.nodes.length}</div>
|
||||
<ol className="wizard-preview-chain">
|
||||
{draft.nodes.map((n, i) => (
|
||||
<li key={n._key || i} className="wizard-preview-chain-item">
|
||||
<span className="wizard-preview-chain-idx">{i + 1}</span>
|
||||
<span className="wizard-preview-chain-name">{n.display_name || "Untitled step"}</span>
|
||||
<span className={`wizard-preview-chain-kind kind-${n.dispatch_kind || "human"}`}>{({ human: "Manual review", agent: "Assistant", system: "System" } as Record<string, string>)[n.dispatch_kind || "human"] || "Manual review"}</span>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{draft.fields.length > 0 && (
|
||||
<div className="wizard-preview-section">
|
||||
<div className="wizard-preview-label">Form · {draft.fields.length} field{draft.fields.length === 1 ? "" : "s"}</div>
|
||||
<ul className="wizard-preview-fields">
|
||||
{draft.fields.filter((f) => f.name).map((f, i) => (
|
||||
<li key={i}>
|
||||
<span className="wizard-preview-field-name">{f.name}</span>
|
||||
<span className="wizard-preview-field-type">· {f.type}</span>
|
||||
</li>
|
||||
))}
|
||||
{draft.fields.filter((f) => f.name).length === 0 && (
|
||||
<li className="wizard-preview-empty">No fields named yet</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{draft.rules.length > 0 && (
|
||||
<div className="wizard-preview-section">
|
||||
<div className="wizard-preview-label">Rules · {draft.rules.length}</div>
|
||||
<ul className="wizard-preview-rules">
|
||||
{draft.rules.filter((r) => r.trim()).map((r, i) => <li key={i}>{r}</li>)}
|
||||
{draft.rules.filter((r) => r.trim()).length === 0 && (
|
||||
<li className="wizard-preview-empty">No rules typed yet</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{(draft.step === "Analyze" || draft.step === "Generate" || draft.step === "Validate" || draft.step === "Draft saved") && draft.nodes.length > 0 && renderGraphPreview()}
|
||||
{(draft.step === "Generate" || draft.step === "Validate" || draft.step === "Draft saved") && renderFormPreview()}
|
||||
{(draft.step === "Validate" || draft.step === "Draft saved") && renderRulesPreview()}
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user