diff --git a/app/[workspace]/project/[projectId]/prd/page.tsx b/app/[workspace]/project/[projectId]/prd/page.tsx index 0a2fc2a..58673c6 100644 --- a/app/[workspace]/project/[projectId]/prd/page.tsx +++ b/app/[workspace]/project/[projectId]/prd/page.tsx @@ -78,12 +78,117 @@ function PhaseDataCard({ phase }: { phase: SavedPhase }) { ); } +interface ArchApp { name: string; type: string; description: string; tech?: string[]; screens?: string[] } +interface ArchInfra { name: string; reason: string } +interface ArchPackage { name: string; description: string } +interface ArchIntegration { name: string; required?: boolean; notes?: string } +interface Architecture { + productName?: string; + productType?: string; + summary?: string; + apps?: ArchApp[]; + packages?: ArchPackage[]; + infrastructure?: ArchInfra[]; + integrations?: ArchIntegration[]; + designSurfaces?: string[]; + riskNotes?: string[]; +} + +function ArchitectureView({ arch }: { arch: Architecture }) { + const Section = ({ title, children }: { title: string; children: React.ReactNode }) => ( +
+
{title}
+ {children} +
+ ); + const Card = ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ); + const Tag = ({ label }: { label: string }) => ( + {label} + ); + + return ( +
+ {arch.summary && ( +
+ {arch.summary} +
+ )} + {(arch.apps ?? []).length > 0 && ( +
+ {arch.apps!.map(a => ( + +
+ {a.name} + {a.type} +
+
{a.description}
+ {a.tech?.map(t => )} + {a.screens && a.screens.length > 0 && ( +
Screens: {a.screens.join(", ")}
+ )} +
+ ))} +
+ )} + {(arch.packages ?? []).length > 0 && ( +
+ {arch.packages!.map(p => ( + +
+ {p.name} + {p.description} +
+
+ ))} +
+ )} + {(arch.infrastructure ?? []).length > 0 && ( +
+ {arch.infrastructure!.map(i => ( + +
{i.name}
+
{i.reason}
+
+ ))} +
+ )} + {(arch.integrations ?? []).length > 0 && ( +
+ {arch.integrations!.map(i => ( + +
+ {i.name} + {i.required && required} +
+ {i.notes &&
{i.notes}
} +
+ ))} +
+ )} + {(arch.riskNotes ?? []).length > 0 && ( +
+ {arch.riskNotes!.map((r, i) => ( +
+ + {r} +
+ ))} +
+ )} +
+ ); +} + export default function PRDPage() { const params = useParams(); const projectId = params.projectId as string; const [prd, setPrd] = useState(null); + const [architecture, setArchitecture] = useState(null); const [savedPhases, setSavedPhases] = useState([]); const [loading, setLoading] = useState(true); + const [activeTab, setActiveTab] = useState<"prd" | "architecture">("prd"); useEffect(() => { Promise.all([ @@ -91,6 +196,7 @@ export default function PRDPage() { fetch(`/api/projects/${projectId}/save-phase`).then(r => r.json()).catch(() => ({ phases: [] })), ]).then(([projectData, phaseData]) => { setPrd(projectData?.project?.prd ?? null); + setArchitecture(projectData?.project?.architecture ?? null); setSavedPhases(phaseData?.phases ?? []); setLoading(false); }); @@ -116,9 +222,48 @@ export default function PRDPage() { ); } + const tabs = [ + { id: "prd" as const, label: "PRD", available: true }, + { id: "architecture" as const, label: "Architecture", available: !!architecture }, + ]; + return (
- {prd ? ( + + {/* Tab bar — only when at least one doc exists */} + {(prd || architecture) && ( +
+ {tabs.map(t => { + const isActive = activeTab === t.id; + return ( + + ); + })} +
+ )} + + {/* Architecture tab */} + {activeTab === "architecture" && architecture && ( + + )} + + {/* PRD tab */} + {activeTab === "prd" && prd ? ( /* ── Finalized PRD view ── */
@@ -138,7 +283,7 @@ export default function PRDPage() { {prd}
- ) : ( + ) : activeTab === "prd" ? ( /* ── Section progress view ── */
{/* Progress bar */} diff --git a/app/api/projects/[projectId]/advisor/route.ts b/app/api/projects/[projectId]/advisor/route.ts index 6b1e9ce..35710fa 100644 --- a/app/api/projects/[projectId]/advisor/route.ts +++ b/app/api/projects/[projectId]/advisor/route.ts @@ -46,6 +46,7 @@ async function buildKnowledgeContext(projectId: string, email: string): Promise< const vision = (d.productVision as string) ?? (d.vision as string) ?? ''; const giteaRepo = (d.giteaRepo as string) ?? ''; const prd = (d.prd as string) ?? ''; + const architecture = d.architecture as Record | null ?? null; const apps = (d.apps as Array<{ name: string; domain?: string; coolifyServiceUuid?: string }>) ?? []; const coolifyProjectUuid = (d.coolifyProjectUuid as string) ?? ''; const theiaUrl = (d.theiaWorkspaceUrl as string) ?? ''; @@ -73,6 +74,15 @@ Operating principles: if (coolifyProjectUuid) lines.push(`Coolify project UUID: ${coolifyProjectUuid} — use coolify_list_applications to find its apps`); if (theiaUrl) lines.push(`Theia IDE: ${theiaUrl}`); + // Architecture document + if (architecture) { + const archApps = (architecture.apps as Array<{ name: string; type: string; description: string }> ?? []) + .map(a => ` - ${a.name} (${a.type}): ${a.description}`).join('\n'); + const archInfra = (architecture.infrastructure as Array<{ name: string; reason: string }> ?? []) + .map(i => ` - ${i.name}: ${i.reason}`).join('\n'); + lines.push(`\n## Technical Architecture\nSummary: ${architecture.summary ?? ''}\n\nApps:\n${archApps}\n\nInfrastructure:\n${archInfra}`); + } + // PRD or discovery phases if (prd) { // Claude Sonnet has a 200k token context — pass the full PRD, no truncation needed