feat: live phase completion in right panel + saved phase data in PRD page
Made-with: Cursor
This commit is contained in:
@@ -3,67 +3,110 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
|
|
||||||
|
// Maps each PRD section to the discovery phase that populates it
|
||||||
const PRD_SECTIONS = [
|
const PRD_SECTIONS = [
|
||||||
{ id: "executive_summary", label: "Executive Summary" },
|
{ id: "executive_summary", label: "Executive Summary", phaseId: "big_picture" },
|
||||||
{ id: "problem_statement", label: "Problem Statement" },
|
{ id: "problem_statement", label: "Problem Statement", phaseId: "big_picture" },
|
||||||
{ id: "vision_metrics", label: "Vision & Success Metrics" },
|
{ id: "vision_metrics", label: "Vision & Success Metrics", phaseId: "big_picture" },
|
||||||
{ id: "users_personas", label: "Users & Personas" },
|
{ id: "users_personas", label: "Users & Personas", phaseId: "users_personas" },
|
||||||
{ id: "user_flows", label: "User Flows" },
|
{ id: "user_flows", label: "User Flows", phaseId: "users_personas" },
|
||||||
{ id: "feature_requirements", label: "Feature Requirements" },
|
{ id: "feature_requirements", label: "Feature Requirements", phaseId: "features_scope" },
|
||||||
{ id: "screen_specs", label: "Screen Specs" },
|
{ id: "screen_specs", label: "Screen Specs", phaseId: "screens_data" },
|
||||||
{ id: "business_model", label: "Business Model" },
|
{ id: "business_model", label: "Business Model", phaseId: "business_model" },
|
||||||
{ id: "integrations", label: "Integrations & Dependencies" },
|
{ id: "integrations", label: "Integrations & Dependencies", phaseId: "features_scope" },
|
||||||
{ id: "non_functional", label: "Non-Functional Reqs" },
|
{ id: "non_functional", label: "Non-Functional Reqs", phaseId: null },
|
||||||
{ id: "risks", label: "Risks & Mitigations" },
|
{ id: "risks", label: "Risks & Mitigations", phaseId: "risks_questions" },
|
||||||
{ id: "open_questions", label: "Open Questions" },
|
{ id: "open_questions", label: "Open Questions", phaseId: "risks_questions" },
|
||||||
];
|
];
|
||||||
|
|
||||||
interface PRDSection {
|
interface SavedPhase {
|
||||||
id: string;
|
phase: string;
|
||||||
label: string;
|
title: string;
|
||||||
status: "done" | "active" | "pending";
|
summary: string;
|
||||||
pct: number;
|
data: Record<string, unknown>;
|
||||||
content?: string;
|
saved_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Project {
|
function formatValue(v: unknown): string {
|
||||||
id: string;
|
if (v === null || v === undefined) return "—";
|
||||||
prd?: string;
|
if (Array.isArray(v)) return v.map(item => typeof item === "object" ? JSON.stringify(item) : String(item)).join(", ");
|
||||||
prdSections?: Record<string, { status: string; pct: number; content?: string }>;
|
return String(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PhaseDataCard({ phase }: { phase: SavedPhase }) {
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
const entries = Object.entries(phase.data).filter(([, v]) => v !== null && v !== undefined && v !== "");
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
marginTop: 10, background: "#f6f4f0", borderRadius: 8,
|
||||||
|
border: "1px solid #e8e4dc", overflow: "hidden",
|
||||||
|
}}>
|
||||||
|
<button
|
||||||
|
onClick={() => setExpanded(e => !e)}
|
||||||
|
style={{
|
||||||
|
width: "100%", textAlign: "left", padding: "10px 14px",
|
||||||
|
background: "none", border: "none", cursor: "pointer",
|
||||||
|
display: "flex", alignItems: "center", justifyContent: "space-between",
|
||||||
|
fontFamily: "Outfit, sans-serif",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: "0.78rem", color: "#4a4640", lineHeight: 1.45 }}>
|
||||||
|
{phase.summary}
|
||||||
|
</span>
|
||||||
|
<span style={{ fontSize: "0.7rem", color: "#a09a90", marginLeft: 8, flexShrink: 0 }}>
|
||||||
|
{expanded ? "▲" : "▼"}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{expanded && entries.length > 0 && (
|
||||||
|
<div style={{ padding: "4px 14px 14px", borderTop: "1px solid #e8e4dc" }}>
|
||||||
|
{entries.map(([k, v]) => (
|
||||||
|
<div key={k} style={{ marginTop: 10 }}>
|
||||||
|
<div style={{
|
||||||
|
fontSize: "0.6rem", color: "#b5b0a6", textTransform: "uppercase",
|
||||||
|
letterSpacing: "0.06em", fontWeight: 600, marginBottom: 2,
|
||||||
|
}}>
|
||||||
|
{k.replace(/_/g, " ")}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: "0.78rem", color: "#2a2824", lineHeight: 1.5 }}>
|
||||||
|
{formatValue(v)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PRDPage() {
|
export default function PRDPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const projectId = params.projectId as string;
|
const projectId = params.projectId as string;
|
||||||
const [project, setProject] = useState<Project | null>(null);
|
const [prd, setPrd] = useState<string | null>(null);
|
||||||
|
const [savedPhases, setSavedPhases] = useState<SavedPhase[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`/api/projects/${projectId}`)
|
Promise.all([
|
||||||
.then((r) => r.json())
|
fetch(`/api/projects/${projectId}`).then(r => r.json()).catch(() => ({})),
|
||||||
.then((d) => {
|
fetch(`/api/projects/${projectId}/save-phase`).then(r => r.json()).catch(() => ({ phases: [] })),
|
||||||
setProject(d.project);
|
]).then(([projectData, phaseData]) => {
|
||||||
setLoading(false);
|
setPrd(projectData?.project?.prd ?? null);
|
||||||
})
|
setSavedPhases(phaseData?.phases ?? []);
|
||||||
.catch(() => setLoading(false));
|
setLoading(false);
|
||||||
|
});
|
||||||
}, [projectId]);
|
}, [projectId]);
|
||||||
|
|
||||||
// Build sections with real status if available
|
const phaseMap = new Map(savedPhases.map(p => [p.phase, p]));
|
||||||
const sections: PRDSection[] = PRD_SECTIONS.map((s) => {
|
const savedPhaseIds = new Set(savedPhases.map(p => p.phase));
|
||||||
const saved = project?.prdSections?.[s.id];
|
|
||||||
return {
|
|
||||||
...s,
|
|
||||||
status: (saved?.status as PRDSection["status"]) ?? "pending",
|
|
||||||
pct: saved?.pct ?? 0,
|
|
||||||
content: saved?.content,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// If we have a raw PRD markdown, show that instead of the section list
|
const sections = PRD_SECTIONS.map(s => ({
|
||||||
const hasPRD = Boolean(project?.prd);
|
...s,
|
||||||
|
savedPhase: s.phaseId ? phaseMap.get(s.phaseId) ?? null : null,
|
||||||
|
isDone: s.phaseId ? savedPhaseIds.has(s.phaseId) : false,
|
||||||
|
}));
|
||||||
|
|
||||||
const totalPct = Math.round(sections.reduce((a, s) => a + s.pct, 0) / sections.length);
|
const doneCount = sections.filter(s => s.isDone).length;
|
||||||
const doneCount = sections.filter((s) => s.status === "done").length;
|
const totalPct = Math.round((doneCount / sections.length) * 100);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
@@ -74,19 +117,16 @@ export default function PRDPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div style={{ padding: "28px 32px", flex: 1, overflow: "auto", fontFamily: "Outfit, sans-serif" }}>
|
||||||
className="vibn-enter"
|
{prd ? (
|
||||||
style={{ padding: "28px 32px", flex: 1, overflow: "auto", fontFamily: "Outfit, sans-serif" }}
|
/* ── Finalized PRD view ── */
|
||||||
>
|
|
||||||
{hasPRD ? (
|
|
||||||
/* ── Raw PRD view ── */
|
|
||||||
<div style={{ maxWidth: 760 }}>
|
<div style={{ maxWidth: 760 }}>
|
||||||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 20 }}>
|
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 20 }}>
|
||||||
<h3 style={{ fontFamily: "Newsreader, serif", fontSize: "1.2rem", fontWeight: 400, color: "#1a1a1a", margin: 0 }}>
|
<h3 style={{ fontFamily: "Newsreader, serif", fontSize: "1.2rem", fontWeight: 400, color: "#1a1a1a", margin: 0 }}>
|
||||||
Product Requirements
|
Product Requirements
|
||||||
</h3>
|
</h3>
|
||||||
<span style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: "0.72rem", color: "#6b6560", background: "#f0ece4", padding: "4px 10px", borderRadius: 5 }}>
|
<span style={{ fontFamily: "IBM Plex Mono, monospace", fontSize: "0.72rem", color: "#6b6560", background: "#f0ece4", padding: "4px 10px", borderRadius: 5 }}>
|
||||||
PRD approved
|
PRD complete
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
@@ -95,22 +135,22 @@ export default function PRDPage() {
|
|||||||
fontSize: "0.88rem", color: "#2a2824",
|
fontSize: "0.88rem", color: "#2a2824",
|
||||||
whiteSpace: "pre-wrap", fontFamily: "Outfit, sans-serif",
|
whiteSpace: "pre-wrap", fontFamily: "Outfit, sans-serif",
|
||||||
}}>
|
}}>
|
||||||
{project?.prd}
|
{prd}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
/* ── Section progress view ── */
|
/* ── Section progress view ── */
|
||||||
<div style={{ maxWidth: 640 }}>
|
<div style={{ maxWidth: 680 }}>
|
||||||
{/* Progress bar */}
|
{/* Progress bar */}
|
||||||
<div style={{
|
<div style={{
|
||||||
display: "flex", alignItems: "center", gap: 16,
|
display: "flex", alignItems: "center", gap: 16,
|
||||||
padding: "16px 20px", background: "#fff",
|
padding: "16px 20px", background: "#fff",
|
||||||
border: "1px solid #e8e4dc", borderRadius: 10,
|
border: "1px solid #e8e4dc", borderRadius: 10,
|
||||||
marginBottom: 20, boxShadow: "0 1px 2px #1a1a1a05",
|
marginBottom: 24, boxShadow: "0 1px 2px #1a1a1a05",
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontFamily: "IBM Plex Mono, monospace",
|
fontFamily: "IBM Plex Mono, monospace",
|
||||||
fontSize: "1.4rem", fontWeight: 500, color: "#1a1a1a", minWidth: 48,
|
fontSize: "1.4rem", fontWeight: 500, color: "#1a1a1a", minWidth: 52,
|
||||||
}}>
|
}}>
|
||||||
{totalPct}%
|
{totalPct}%
|
||||||
</div>
|
</div>
|
||||||
@@ -124,7 +164,7 @@ export default function PRDPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span style={{ fontSize: "0.75rem", color: "#a09a90" }}>
|
<span style={{ fontSize: "0.75rem", color: "#a09a90" }}>
|
||||||
{doneCount}/{sections.length} approved
|
{doneCount}/{sections.length} sections
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -133,60 +173,72 @@ export default function PRDPage() {
|
|||||||
<div
|
<div
|
||||||
key={s.id}
|
key={s.id}
|
||||||
style={{
|
style={{
|
||||||
display: "flex", alignItems: "center", gap: 12,
|
padding: "14px 18px", marginBottom: 6,
|
||||||
padding: "14px 18px", marginBottom: 4,
|
background: "#fff", borderRadius: 10,
|
||||||
background: "#fff", borderRadius: 8,
|
border: `1px solid ${s.isDone ? "#a5d6a740" : "#e8e4dc"}`,
|
||||||
border: "1px solid #e8e4dc",
|
|
||||||
cursor: "pointer", transition: "border-color 0.12s",
|
|
||||||
animationDelay: `${i * 0.04}s`,
|
animationDelay: `${i * 0.04}s`,
|
||||||
}}
|
}}
|
||||||
className="vibn-enter"
|
|
||||||
onMouseEnter={(e) => (e.currentTarget.style.borderColor = "#d0ccc4")}
|
|
||||||
onMouseLeave={(e) => (e.currentTarget.style.borderColor = "#e8e4dc")}
|
|
||||||
>
|
>
|
||||||
{/* Status icon */}
|
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
|
||||||
<div style={{
|
{/* Status icon */}
|
||||||
width: 24, height: 24, borderRadius: 6, flexShrink: 0,
|
|
||||||
background: s.status === "done" ? "#2e7d3210"
|
|
||||||
: s.status === "active" ? "#d4a04a12"
|
|
||||||
: "#f6f4f0",
|
|
||||||
display: "flex", alignItems: "center", justifyContent: "center",
|
|
||||||
fontSize: "0.65rem", fontWeight: 700,
|
|
||||||
color: s.status === "done" ? "#2e7d32"
|
|
||||||
: s.status === "active" ? "#9a7b3a"
|
|
||||||
: "#c5c0b8",
|
|
||||||
}}>
|
|
||||||
{s.status === "done" ? "✓" : s.status === "active" ? "◐" : "○"}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span style={{ flex: 1, fontSize: "0.84rem", color: "#1a1a1a", fontWeight: 450 }}>
|
|
||||||
{s.label}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{/* Mini progress bar */}
|
|
||||||
<div style={{ width: 60, height: 3, borderRadius: 2, background: "#eae6de" }}>
|
|
||||||
<div style={{
|
<div style={{
|
||||||
height: "100%", borderRadius: 2, width: `${s.pct}%`,
|
width: 24, height: 24, borderRadius: 6, flexShrink: 0,
|
||||||
background: s.status === "done" ? "#2e7d32"
|
background: s.isDone ? "#2e7d3210" : "#f6f4f0",
|
||||||
: s.status === "active" ? "#d4a04a"
|
display: "flex", alignItems: "center", justifyContent: "center",
|
||||||
: "#d0ccc4",
|
fontSize: "0.65rem", fontWeight: 700,
|
||||||
}} />
|
color: s.isDone ? "#2e7d32" : "#c5c0b8",
|
||||||
|
}}>
|
||||||
|
{s.isDone ? "✓" : "○"}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span style={{
|
||||||
|
flex: 1, fontSize: "0.84rem",
|
||||||
|
color: s.isDone ? "#1a1a1a" : "#a09a90",
|
||||||
|
fontWeight: s.isDone ? 500 : 400,
|
||||||
|
}}>
|
||||||
|
{s.label}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{s.isDone && s.savedPhase && (
|
||||||
|
<span style={{
|
||||||
|
fontSize: "0.65rem", fontFamily: "IBM Plex Mono, monospace",
|
||||||
|
color: "#2e7d32", background: "#2e7d3210",
|
||||||
|
padding: "2px 7px", borderRadius: 4, fontWeight: 500,
|
||||||
|
}}>
|
||||||
|
saved
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{!s.isDone && !s.phaseId && (
|
||||||
|
<span style={{
|
||||||
|
fontSize: "0.65rem", fontFamily: "IBM Plex Mono, monospace",
|
||||||
|
color: "#b5b0a6", padding: "2px 7px",
|
||||||
|
}}>
|
||||||
|
generated
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span style={{
|
{/* Expandable phase data */}
|
||||||
fontSize: "0.68rem", fontFamily: "IBM Plex Mono, monospace",
|
{s.isDone && s.savedPhase && (
|
||||||
color: s.status === "done" ? "#2e7d32" : "#a09a90",
|
<PhaseDataCard phase={s.savedPhase} />
|
||||||
fontWeight: 500, minWidth: 28, textAlign: "right",
|
)}
|
||||||
}}>
|
|
||||||
{s.pct}%
|
{/* Pending hint */}
|
||||||
</span>
|
{!s.isDone && (
|
||||||
|
<div style={{ marginTop: 6, marginLeft: 36, fontSize: "0.72rem", color: "#c5c0b8" }}>
|
||||||
|
{s.phaseId
|
||||||
|
? `Complete the ${s.savedPhase ? s.savedPhase.title : "discovery"} phase in Atlas`
|
||||||
|
: "Will be generated when PRD is finalized"}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Hint */}
|
{doneCount === 0 && (
|
||||||
<p style={{ fontSize: "0.78rem", color: "#b5b0a6", marginTop: 20, textAlign: "center" }}>
|
<p style={{ fontSize: "0.78rem", color: "#b5b0a6", marginTop: 20, textAlign: "center" }}>
|
||||||
Continue chatting with Atlas to complete your PRD
|
Continue chatting with Atlas — saved phases will appear here automatically.
|
||||||
</p>
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode, useEffect, useState } from "react";
|
||||||
import { VIBNSidebar } from "./vibn-sidebar";
|
import { VIBNSidebar } from "./vibn-sidebar";
|
||||||
import { Toaster } from "sonner";
|
import { Toaster } from "sonner";
|
||||||
|
|
||||||
@@ -33,14 +33,22 @@ const TABS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const DISCOVERY_PHASES = [
|
const DISCOVERY_PHASES = [
|
||||||
"Big Picture",
|
{ id: "big_picture", label: "Big Picture" },
|
||||||
"Users & Personas",
|
{ id: "users_personas", label: "Users & Personas" },
|
||||||
"Features",
|
{ id: "features_scope", label: "Features" },
|
||||||
"Business Model",
|
{ id: "business_model", label: "Business Model" },
|
||||||
"Screens",
|
{ id: "screens_data", label: "Screens" },
|
||||||
"Risks",
|
{ id: "risks_questions", label: "Risks" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
interface SavedPhase {
|
||||||
|
phase: string;
|
||||||
|
title: string;
|
||||||
|
summary: string;
|
||||||
|
data: Record<string, unknown>;
|
||||||
|
saved_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
function timeAgo(dateStr?: string): string {
|
function timeAgo(dateStr?: string): string {
|
||||||
if (!dateStr) return "—";
|
if (!dateStr) return "—";
|
||||||
const date = new Date(dateStr);
|
const date = new Date(dateStr);
|
||||||
@@ -90,8 +98,6 @@ export function ProjectShell({
|
|||||||
projectDescription,
|
projectDescription,
|
||||||
projectStatus,
|
projectStatus,
|
||||||
projectProgress,
|
projectProgress,
|
||||||
discoveryPhase = 0,
|
|
||||||
capturedData = {},
|
|
||||||
createdAt,
|
createdAt,
|
||||||
updatedAt,
|
updatedAt,
|
||||||
featureCount = 0,
|
featureCount = 0,
|
||||||
@@ -100,7 +106,26 @@ export function ProjectShell({
|
|||||||
const activeTab = TABS.find((t) => pathname?.includes(`/${t.path}`))?.id ?? "overview";
|
const activeTab = TABS.find((t) => pathname?.includes(`/${t.path}`))?.id ?? "overview";
|
||||||
const progress = projectProgress ?? 0;
|
const progress = projectProgress ?? 0;
|
||||||
|
|
||||||
const capturedEntries = Object.entries(capturedData);
|
const [savedPhases, setSavedPhases] = useState<SavedPhase[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetch(`/api/projects/${projectId}/save-phase`)
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(d => setSavedPhases(d.phases ?? []))
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
// Refresh every 10s while the user is chatting with Atlas
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
fetch(`/api/projects/${projectId}/save-phase`)
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(d => setSavedPhases(d.phases ?? []))
|
||||||
|
.catch(() => {});
|
||||||
|
}, 10_000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [projectId]);
|
||||||
|
|
||||||
|
const savedPhaseIds = new Set(savedPhases.map(p => p.phase));
|
||||||
|
const firstUnsavedIdx = DISCOVERY_PHASES.findIndex(p => !savedPhaseIds.has(p.id));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -212,11 +237,11 @@ export function ProjectShell({
|
|||||||
{/* Discovery phases */}
|
{/* Discovery phases */}
|
||||||
<SectionLabel>Discovery</SectionLabel>
|
<SectionLabel>Discovery</SectionLabel>
|
||||||
{DISCOVERY_PHASES.map((phase, i) => {
|
{DISCOVERY_PHASES.map((phase, i) => {
|
||||||
const isDone = i < discoveryPhase;
|
const isDone = savedPhaseIds.has(phase.id);
|
||||||
const isActive = i === discoveryPhase;
|
const isActive = !isDone && i === firstUnsavedIdx;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={phase.id}
|
||||||
style={{
|
style={{
|
||||||
display: "flex", alignItems: "center", gap: 10,
|
display: "flex", alignItems: "center", gap: 10,
|
||||||
padding: "9px 0",
|
padding: "9px 0",
|
||||||
@@ -237,7 +262,7 @@ export function ProjectShell({
|
|||||||
fontWeight: isActive ? 600 : 400,
|
fontWeight: isActive ? 600 : 400,
|
||||||
color: isDone ? "#6b6560" : isActive ? "#1a1a1a" : "#b5b0a6",
|
color: isDone ? "#6b6560" : isActive ? "#1a1a1a" : "#b5b0a6",
|
||||||
}}>
|
}}>
|
||||||
{phase}
|
{phase.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -245,20 +270,20 @@ export function ProjectShell({
|
|||||||
|
|
||||||
<div style={{ height: 1, background: "#f0ece4", margin: "16px 0" }} />
|
<div style={{ height: 1, background: "#f0ece4", margin: "16px 0" }} />
|
||||||
|
|
||||||
{/* Captured data */}
|
{/* Captured data — summaries from saved phases */}
|
||||||
<SectionLabel>Captured</SectionLabel>
|
<SectionLabel>Captured</SectionLabel>
|
||||||
{capturedEntries.length > 0 ? (
|
{savedPhases.length > 0 ? (
|
||||||
capturedEntries.map(([k, v], i) => (
|
savedPhases.map((p) => (
|
||||||
<div key={i} style={{ marginBottom: 14 }}>
|
<div key={p.phase} style={{ marginBottom: 14 }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: "0.62rem", color: "#b5b0a6",
|
fontSize: "0.62rem", color: "#2e7d32",
|
||||||
textTransform: "uppercase", letterSpacing: "0.05em",
|
textTransform: "uppercase", letterSpacing: "0.05em",
|
||||||
marginBottom: 3, fontWeight: 600,
|
marginBottom: 3, fontWeight: 600, display: "flex", alignItems: "center", gap: 4,
|
||||||
}}>
|
}}>
|
||||||
{k}
|
<span>✓</span><span>{p.title}</span>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ fontSize: "0.8rem", color: "#4a4640", lineHeight: 1.45 }}>
|
<div style={{ fontSize: "0.75rem", color: "#4a4640", lineHeight: 1.45 }}>
|
||||||
{v}
|
{p.summary}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|||||||
Reference in New Issue
Block a user