From 1af5595e3529da8169d86e302286237fdc696e5d Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Mon, 9 Mar 2026 22:02:01 -0700 Subject: [PATCH] feat(tasks): move Tasks first in toolbar, add Tasks+PRD left nav and content Made-with: Cursor --- .../project/[projectId]/build/page.tsx | 159 ++++++++++++++++++ components/layout/project-shell.tsx | 2 +- 2 files changed, 160 insertions(+), 1 deletion(-) diff --git a/app/[workspace]/project/[projectId]/build/page.tsx b/app/[workspace]/project/[projectId]/build/page.tsx index e0b6f1f..507f812 100644 --- a/app/[workspace]/project/[projectId]/build/page.tsx +++ b/app/[workspace]/project/[projectId]/build/page.tsx @@ -921,6 +921,122 @@ function FileViewer({ selectedPath, fileContent, fileLoading, fileName, rootPath interface PreviewApp { name: string; url: string | null; status: string; } +// ── PRD Content ─────────────────────────────────────────────────────────────── + +const PRD_SECTIONS = [ + { id: "executive_summary", label: "Executive Summary", phaseId: "big_picture" }, + { id: "problem_statement", label: "Problem Statement", phaseId: "big_picture" }, + { id: "vision_metrics", label: "Vision & Success Metrics", phaseId: "big_picture" }, + { id: "users_personas", label: "Users & Personas", phaseId: "users_personas" }, + { id: "user_flows", label: "User Flows", phaseId: "users_personas" }, + { id: "feature_requirements", label: "Feature Requirements", phaseId: "features_scope" }, + { id: "screen_specs", label: "Screen Specs", phaseId: "screens_data" }, + { id: "business_model", label: "Business Model", phaseId: "business_model" }, + { id: "integrations", label: "Integrations & Dependencies", phaseId: "features_scope" }, + { id: "non_functional", label: "Non-Functional Reqs", phaseId: null }, + { id: "risks", label: "Risks & Mitigations", phaseId: "risks_questions" }, + { id: "open_questions", label: "Open Questions", phaseId: "risks_questions" }, +]; + +interface SavedPhase { phase: string; title: string; summary: string; data: Record; saved_at: string; } + +function PrdContent({ projectId }: { projectId: string }) { + const [prd, setPrd] = useState(null); + const [savedPhases, setSavedPhases] = useState([]); + const [loading, setLoading] = useState(true); + const [expandedPhase, setExpandedPhase] = useState(null); + + useEffect(() => { + Promise.all([ + fetch(`/api/projects/${projectId}`).then(r => r.json()).catch(() => ({})), + fetch(`/api/projects/${projectId}/save-phase`).then(r => r.json()).catch(() => ({ phases: [] })), + ]).then(([projectData, phaseData]) => { + setPrd(projectData?.project?.prd ?? null); + setSavedPhases(phaseData?.phases ?? []); + setLoading(false); + }); + }, [projectId]); + + if (loading) return ( +
Loading…
+ ); + + const phaseMap = new Map(savedPhases.map(p => [p.phase, p])); + const savedPhaseIds = new Set(savedPhases.map(p => p.phase)); + const sections = PRD_SECTIONS.map(s => ({ + ...s, + savedPhase: s.phaseId ? phaseMap.get(s.phaseId) ?? null : null, + isDone: s.phaseId ? savedPhaseIds.has(s.phaseId) : false, + })); + const doneCount = sections.filter(s => s.isDone).length; + const totalPct = Math.round((doneCount / sections.length) * 100); + + return ( +
+ {prd ? ( +
+
+

Product Requirements

+ PRD complete +
+
+ {prd} +
+
+ ) : ( +
+ {/* Progress bar */} +
+
{totalPct}%
+
+
+
+
+
+ {doneCount}/{sections.length} sections +
+ + {sections.map((s, i) => ( +
+
+
+ {s.isDone ? "✓" : "○"} +
+ {s.label} + {s.isDone && s.savedPhase && ( + + )} +
+ {s.isDone && s.savedPhase && expandedPhase === s.id && ( +
+
{s.savedPhase.summary}
+ {Object.entries(s.savedPhase.data).filter(([, v]) => v !== null && v !== undefined && v !== "").map(([k, v]) => ( +
+
{k.replace(/_/g, " ")}
+
{Array.isArray(v) ? v.join(", ") : String(v)}
+
+ ))} +
+ )} + {!s.isDone && ( +
+ {s.phaseId ? "Complete this phase in Atlas" : "Generated when PRD is finalized"} +
+ )} +
+ ))} + + {doneCount === 0 && ( +

Continue chatting with Atlas — saved phases appear here automatically.

+ )} +
+ )} +
+ ); +} + function PreviewContent({ projectId, apps, activePreviewApp, onSelectApp }: { projectId: string; apps: PreviewApp[]; @@ -1160,6 +1276,43 @@ function BuildHubInner() {
)} + {/* Tasks: sub-nav + app list */} + {section === "tasks" && ( +
+ {/* Tasks | PRD sub-nav */} +
+ {[{ id: "tasks", label: "Tasks" }, { id: "prd", label: "PRD" }].map(item => { + const isActive = (searchParams.get("tab") ?? "tasks") === item.id; + return ( + + ); + })} +
+ {/* App list (only in tasks tab) */} + {(searchParams.get("tab") ?? "tasks") !== "prd" && ( + <> +
Apps
+ {apps.length > 0 ? apps.map(app => ( + navigate({ section: "tasks", tab: "tasks", app: app.name, root: app.path })} + /> + )) : ( +
No apps yet
+ )} + + )} +
+ )} + {/* Preview: deployed apps list */} {section === "preview" && (
@@ -1195,6 +1348,12 @@ function BuildHubInner() { {section === "infrastructure" && ( )} + {section === "tasks" && (searchParams.get("tab") ?? "tasks") !== "prd" && ( + + )} + {section === "tasks" && searchParams.get("tab") === "prd" && ( + + )} {section === "preview" && (