feat(wizard): per-phase preview — graph in Analyze, form in Generate #8

Merged
shad merged 1 commits from feat/wizard-per-phase-preview into main 2026-06-15 00:18:25 +00:00
2 changed files with 128 additions and 44 deletions
Showing only changes of commit 0a57bbc464 - Show all commits
+68
View File
@@ -2260,3 +2260,71 @@ select.studio-input { background: var(--bp-paper); }
} }
.global-banner-warn { background: #fff6e0; color: #5a3a00; border-color: #b08a4a; } .global-banner-warn { background: #fff6e0; color: #5a3a00; border-color: #b08a4a; }
[data-theme="dark"] .global-banner-warn { background: #3a2a0a; color: #ffd690; border-color: #6b4a14; } [data-theme="dark"] .global-banner-warn { background: #3a2a0a; color: #ffd690; border-color: #6b4a14; }
/* Wizard per-phase preview: graph + form */
.wizard-preview-graph {
display: flex;
flex-direction: column;
gap: 0;
}
.wizard-graph-node {
display: flex;
flex-direction: column;
align-items: stretch;
position: relative;
padding-bottom: 4px;
}
.wizard-graph-bubble {
display: grid;
grid-template-columns: 22px 1fr;
align-items: center;
gap: 8px;
padding: 8px 10px;
border: 1px solid color-mix(in srgb, var(--bp-navy) 25%, transparent);
background: var(--bp-paper);
font-size: 12px;
}
.wizard-graph-bubble.kind-agent { background: color-mix(in srgb, var(--bp-amber) 18%, var(--bp-paper)); border-color: color-mix(in srgb, var(--bp-amber) 60%, var(--bp-navy)); }
.wizard-graph-bubble.kind-system { background: color-mix(in srgb, var(--bp-navy) 10%, var(--bp-paper)); }
.wizard-graph-idx { font-family: var(--bp-mono); font-size: 10px; color: var(--bp-muted); text-align: right; }
.wizard-graph-name { color: var(--bp-navy); font-weight: 600; line-height: 1.3; }
.wizard-graph-meta {
font-family: var(--bp-mono);
font-size: 9.5px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--bp-muted);
padding: 2px 0 0 30px;
}
.wizard-graph-edge {
width: 1px;
height: 12px;
background: color-mix(in srgb, var(--bp-navy) 35%, transparent);
margin: 4px 0 4px 10px;
}
.wizard-form-preview {
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px;
border: 1px dashed color-mix(in srgb, var(--bp-navy) 25%, transparent);
background: color-mix(in srgb, var(--bp-paper) 70%, var(--bp-canvas));
}
.wizard-form-field { display: flex; flex-direction: column; gap: 3px; }
.wizard-form-label {
font-family: var(--bp-mono);
font-size: 10px;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--bp-muted);
}
.wizard-form-input {
padding: 6px 8px;
font-size: 12px;
border: 1px solid color-mix(in srgb, var(--bp-navy) 25%, transparent);
background: var(--bp-paper);
color: var(--bp-navy);
font-family: inherit;
}
.wizard-form-input:disabled { opacity: 0.7; }
+56 -40
View File
@@ -263,51 +263,53 @@ export default function Wizard() {
</div> </div>
); );
const renderPreviewPane = () => { const renderGraphPreview = () => (
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>
<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-section">
<div className="wizard-preview-label">Steps · {draft.nodes.length}</div> <div className="wizard-preview-label">Flow · {draft.nodes.length} step{draft.nodes.length === 1 ? "" : "s"}</div>
<ol className="wizard-preview-chain"> <div className="wizard-preview-graph">
{draft.nodes.map((n, i) => ( {draft.nodes.map((n, i) => (
<li key={n._key || i} className="wizard-preview-chain-item"> <div key={n._key || i} className="wizard-graph-node">
<span className="wizard-preview-chain-idx">{i + 1}</span> <div className={`wizard-graph-bubble kind-${n.dispatch_kind || "human"}`}>
<span className="wizard-preview-chain-name">{n.display_name || "Untitled step"}</span> <span className="wizard-graph-idx">{i + 1}</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> <span className="wizard-graph-name">{n.display_name || "Untitled"}</span>
</li>
))}
</ol>
</div> </div>
)} <div className="wizard-graph-meta">
{({ human: "Manual", agent: "Assistant", system: "System" } as Record<string, string>)[n.dispatch_kind || "human"]}
{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> </div>
)} {i < draft.nodes.length - 1 && <div className="wizard-graph-edge" aria-hidden />}
</div>
))}
</div>
</div>
);
{draft.rules.length > 0 && ( const renderFormPreview = () => (
<div className="wizard-preview-section"> <div className="wizard-preview-section">
<div className="wizard-preview-label">Rules · {draft.rules.length}</div> <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"> <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()).map((r, i) => <li key={i}>{r}</li>)}
{draft.rules.filter((r) => r.trim()).length === 0 && ( {draft.rules.filter((r) => r.trim()).length === 0 && (
@@ -315,7 +317,21 @@ export default function Wizard() {
)} )}
</ul> </ul>
</div> </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">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.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> </aside>
); );
}; };