feat: restore PRD section tracker on the right side of Atlas chat

Two-column layout on the Atlas tab:
- Left: Atlas discovery chat (full height, flex 1)
- Right: 240px PRD section panel showing all 12 sections with
  live status dots (filled = phase saved, empty = pending)
  plus a progress bar showing phases complete out of 6
- Discovery banner (all 6 done) now lives inside the left column
- "Generate PRD" footer CTA appears in right panel when all done

Made-with: Cursor
This commit is contained in:
2026-03-17 16:15:06 -07:00
parent 9e20125938
commit 2e3b405893

View File

@@ -14,6 +14,22 @@ const DISCOVERY_PHASES = [
"risks_questions", "risks_questions",
]; ];
// Maps discovery phases → the PRD sections they populate
const PRD_SECTIONS: { label: string; phase: string | null }[] = [
{ label: "Executive Summary", phase: "big_picture" },
{ label: "Problem Statement", phase: "big_picture" },
{ label: "Vision & Success Metrics", phase: "big_picture" },
{ label: "Users & Personas", phase: "users_personas" },
{ label: "User Flows", phase: "users_personas" },
{ label: "Feature Requirements", phase: "features_scope" },
{ label: "Screen Specs", phase: "features_scope" },
{ label: "Business Model", phase: "business_model" },
{ label: "Integrations & Dependencies", phase: "screens_data" },
{ label: "Non-Functional Reqs", phase: null }, // generated at PRD finalization
{ label: "Risks & Mitigations", phase: "risks_questions" },
{ label: "Open Questions", phase: "risks_questions" },
];
interface FreshIdeaMainProps { interface FreshIdeaMainProps {
projectId: string; projectId: string;
projectName: string; projectName: string;
@@ -123,33 +139,39 @@ export function FreshIdeaMain({ projectId, projectName }: FreshIdeaMainProps) {
); );
} }
const completedCount = savedPhaseIds.size;
const totalPhases = DISCOVERY_PHASES.length;
return ( return (
<div style={{ height: "100%", display: "flex", flexDirection: "column", position: "relative" }}> <div style={{ height: "100%", display: "flex", flexDirection: "row", overflow: "hidden" }}>
{/* ── Left: Atlas chat ── */}
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden", minWidth: 0 }}>
{/* Decision banner — shown when all 6 phases are saved */} {/* Decision banner — shown when all 6 phases are saved */}
{allDone && !dismissed && ( {allDone && !dismissed && (
<div style={{ <div style={{
background: "linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%)", background: "linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%)",
padding: "18px 28px", padding: "14px 20px",
display: "flex", alignItems: "center", justifyContent: "space-between", display: "flex", alignItems: "center", justifyContent: "space-between",
gap: 16, flexShrink: 0, flexWrap: "wrap", gap: 16, flexShrink: 0, flexWrap: "wrap",
borderBottom: "1px solid #333", borderBottom: "1px solid #333",
}}> }}>
<div> <div>
<div style={{ fontSize: "0.88rem", fontWeight: 700, color: "#fff", fontFamily: "Outfit, sans-serif", marginBottom: 3 }}> <div style={{ fontSize: "0.84rem", fontWeight: 700, color: "#fff", fontFamily: "Outfit, sans-serif", marginBottom: 2 }}>
Discovery complete what's next? Discovery complete what's next?
</div> </div>
<div style={{ fontSize: "0.75rem", color: "#a09a90", fontFamily: "Outfit, sans-serif" }}> <div style={{ fontSize: "0.72rem", color: "#a09a90", fontFamily: "Outfit, sans-serif" }}>
Atlas has captured all 6 discovery phases. Choose your next step. All 6 phases captured. Generate your PRD or jump into Build.
</div> </div>
</div> </div>
<div style={{ display: "flex", gap: 10, flexShrink: 0 }}> <div style={{ display: "flex", gap: 8, flexShrink: 0 }}>
<button <button
onClick={handleGeneratePRD} onClick={handleGeneratePRD}
disabled={prdLoading} disabled={prdLoading}
style={{ style={{
padding: "10px 20px", borderRadius: 8, border: "none", padding: "8px 16px", borderRadius: 7, border: "none",
background: "#fff", color: "#1a1a1a", background: "#fff", color: "#1a1a1a",
fontSize: "0.84rem", fontWeight: 700, fontSize: "0.8rem", fontWeight: 700,
fontFamily: "Outfit, sans-serif", cursor: "pointer", fontFamily: "Outfit, sans-serif", cursor: "pointer",
transition: "opacity 0.12s", transition: "opacity 0.12s",
}} }}
@@ -161,37 +183,122 @@ export function FreshIdeaMain({ projectId, projectName }: FreshIdeaMainProps) {
<button <button
onClick={handleMVP} onClick={handleMVP}
style={{ style={{
padding: "10px 20px", borderRadius: 8, padding: "8px 16px", borderRadius: 7,
border: "1px solid rgba(255,255,255,0.2)", border: "1px solid rgba(255,255,255,0.2)",
background: "transparent", color: "#fff", background: "transparent", color: "#fff",
fontSize: "0.84rem", fontWeight: 600, fontSize: "0.8rem", fontWeight: 600,
fontFamily: "Outfit, sans-serif", cursor: "pointer", fontFamily: "Outfit, sans-serif", cursor: "pointer",
transition: "background 0.12s",
}} }}
onMouseEnter={e => (e.currentTarget.style.background = "rgba(255,255,255,0.08)")}
onMouseLeave={e => (e.currentTarget.style.background = "transparent")}
> >
Plan MVP Test Plan MVP
</button> </button>
<button <button
onClick={() => setDismissed(true)} onClick={() => setDismissed(true)}
style={{ style={{
background: "none", border: "none", cursor: "pointer", background: "none", border: "none", cursor: "pointer",
color: "#666", fontSize: "1rem", padding: "4px 6px", color: "#888", fontSize: "1rem", padding: "4px 6px",
fontFamily: "Outfit, sans-serif",
}} }}
title="Dismiss" title="Dismiss"
> >×</button>
×
</button>
</div> </div>
</div> </div>
)} )}
<AtlasChat <AtlasChat projectId={projectId} projectName={projectName} />
projectId={projectId} </div>
projectName={projectName}
/> {/* ── Right: PRD section tracker ── */}
<div style={{
width: 240, flexShrink: 0,
background: "#faf8f5",
borderLeft: "1px solid #e8e4dc",
display: "flex", flexDirection: "column",
overflow: "hidden",
}}>
{/* Header */}
<div style={{
padding: "14px 16px 10px",
borderBottom: "1px solid #e8e4dc",
flexShrink: 0,
}}>
<div style={{ fontSize: "0.72rem", fontWeight: 700, color: "#1a1a1a", letterSpacing: "0.06em", textTransform: "uppercase", marginBottom: 6 }}>
PRD Sections
</div>
{/* Progress bar */}
<div style={{ height: 3, background: "#e8e4dc", borderRadius: 99, overflow: "hidden" }}>
<div style={{
height: "100%", borderRadius: 99,
background: "#1a1a1a",
width: `${Math.round((completedCount / totalPhases) * 100)}%`,
transition: "width 0.4s ease",
}} />
</div>
<div style={{ fontSize: "0.68rem", color: "#a09a90", marginTop: 5 }}>
{completedCount} of {totalPhases} phases complete
</div>
</div>
{/* Section list */}
<div style={{ flex: 1, overflowY: "auto", padding: "8px 0" }}>
{PRD_SECTIONS.map(({ label, phase }) => {
const isDone = phase === null
? allDone // non-functional reqs generated when all done
: savedPhaseIds.has(phase);
return (
<div
key={label}
style={{
padding: "8px 16px",
display: "flex", alignItems: "flex-start", gap: 10,
}}
>
{/* Status dot */}
<div style={{
width: 8, height: 8, borderRadius: "50%", flexShrink: 0, marginTop: 4,
background: isDone ? "#1a1a1a" : "transparent",
border: isDone ? "none" : "1.5px solid #c8c4bc",
transition: "all 0.3s",
}} />
<div style={{ minWidth: 0 }}>
<div style={{
fontSize: "0.78rem", fontWeight: isDone ? 600 : 400,
color: isDone ? "#1a1a1a" : "#6b6560",
lineHeight: 1.3,
}}>
{label}
</div>
{!isDone && (
<div style={{ fontSize: "0.65rem", color: "#a09a90", marginTop: 2, lineHeight: 1.3 }}>
{phase === null
? "Generated when PRD is finalized"
: "Complete this phase in Atlas"
}
</div>
)}
</div>
</div>
);
})}
</div>
{/* Footer CTA */}
{allDone && (
<div style={{ padding: "12px 16px", borderTop: "1px solid #e8e4dc", flexShrink: 0 }}>
<Link
href={`/${workspace}/project/${projectId}/prd`}
style={{
display: "block", textAlign: "center",
padding: "9px 0", borderRadius: 7,
background: "#1a1a1a", color: "#fff",
fontSize: "0.78rem", fontWeight: 600,
textDecoration: "none",
}}
>
Generate PRD
</Link>
</div>
)}
</div>
</div> </div>
); );
} }