From 94bb9dbeb44fdc15be79745ee073b6c8ea4e880c Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Mon, 2 Mar 2026 16:11:58 -0800 Subject: [PATCH] Add Stackless right panel to project shell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Shows Discovery phase tracker (Big Picture → Risks), Captured data from Atlas, and Project Info (created, last active, features). Data flows from DB via layout server component. Made-with: Cursor --- .../project/[projectId]/layout.tsx | 21 +- components/layout/project-shell.tsx | 180 +++++++++++++++--- 2 files changed, 169 insertions(+), 32 deletions(-) diff --git a/app/[workspace]/project/[projectId]/layout.tsx b/app/[workspace]/project/[projectId]/layout.tsx index 77d4065..73e728d 100644 --- a/app/[workspace]/project/[projectId]/layout.tsx +++ b/app/[workspace]/project/[projectId]/layout.tsx @@ -5,20 +5,30 @@ interface ProjectData { name: string; status?: string; progress?: number; + discoveryPhase?: number; + capturedData?: Record; + createdAt?: string; + updatedAt?: string; + featureCount?: number; } async function getProjectData(projectId: string): Promise { try { - const rows = await query<{ data: any }>( - `SELECT data FROM fs_projects WHERE id = $1 LIMIT 1`, + const rows = await query<{ data: any; created_at?: string; updated_at?: string }>( + `SELECT data, created_at, updated_at FROM fs_projects WHERE id = $1 LIMIT 1`, [projectId] ); if (rows.length > 0) { - const data = rows[0].data; + const { data, created_at, updated_at } = rows[0]; return { name: data?.productName || data?.name || "Project", status: data?.status, progress: data?.progress ?? 0, + discoveryPhase: data?.discoveryPhase ?? 0, + capturedData: data?.capturedData ?? {}, + createdAt: created_at, + updatedAt: updated_at, + featureCount: Array.isArray(data?.features) ? data.features.length : (data?.featureCount ?? 0), }; } } catch (error) { @@ -44,6 +54,11 @@ export default async function ProjectLayout({ projectName={project.name} projectStatus={project.status} projectProgress={project.progress} + discoveryPhase={project.discoveryPhase} + capturedData={project.capturedData} + createdAt={project.createdAt} + updatedAt={project.updatedAt} + featureCount={project.featureCount} > {children} diff --git a/components/layout/project-shell.tsx b/components/layout/project-shell.tsx index 7da00fe..3a87721 100644 --- a/components/layout/project-shell.tsx +++ b/components/layout/project-shell.tsx @@ -13,27 +13,60 @@ interface ProjectShellProps { projectName: string; projectStatus?: string; projectProgress?: number; + discoveryPhase?: number; + capturedData?: Record; + createdAt?: string; + updatedAt?: string; + featureCount?: number; } const TABS = [ - { id: "overview", label: "Atlas", path: "overview" }, - { id: "prd", label: "PRD", path: "prd" }, - { id: "design", label: "Design", path: "design" }, - { id: "build", label: "Build", path: "build" }, - { id: "deployment", label: "Deploy", path: "deployment" }, - { id: "settings", label: "Settings", path: "settings" }, + { id: "overview", label: "Atlas", path: "overview" }, + { id: "prd", label: "PRD", path: "prd" }, + { id: "design", label: "Design", path: "design" }, + { id: "build", label: "Build", path: "build" }, + { id: "deployment", label: "Deploy", path: "deployment" }, + { id: "settings", label: "Settings", path: "settings" }, ]; +const DISCOVERY_PHASES = [ + "Big Picture", + "Users & Personas", + "Features", + "Business Model", + "Screens", + "Risks", +]; + +function timeAgo(dateStr?: string): string { + if (!dateStr) return "—"; + const date = new Date(dateStr); + if (isNaN(date.getTime())) return "—"; + const diff = (Date.now() - date.getTime()) / 1000; + if (diff < 60) return "just now"; + if (diff < 3600) return `${Math.floor(diff / 60)}m ago`; + if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`; + const days = Math.floor(diff / 86400); + if (days === 1) return "Yesterday"; + if (days < 7) return `${days}d ago`; + return new Date(dateStr).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); +} + +function SectionLabel({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + function StatusTag({ status }: { status?: string }) { - const label = status === "live" ? "Live" - : status === "building" ? "Building" - : "Defining"; - const color = status === "live" ? "#2e7d32" - : status === "building" ? "#3d5afe" - : "#9a7b3a"; - const bg = status === "live" ? "#2e7d3210" - : status === "building" ? "#3d5afe10" - : "#d4a04a12"; + const label = status === "live" ? "Live" : status === "building" ? "Building" : "Defining"; + const color = status === "live" ? "#2e7d32" : status === "building" ? "#3d5afe" : "#9a7b3a"; + const bg = status === "live" ? "#2e7d3210" : status === "building" ? "#3d5afe10" : "#d4a04a12"; return ( pathname?.includes(`/${t.path}`))?.id ?? "overview"; - const progress = projectProgress ?? 0; + const capturedEntries = Object.entries(capturedData); + return ( <>
- {/* Sidebar */} + {/* Left sidebar */} - {/* Main content */} + {/* Main column */}
+ {/* Project header */}
- + {projectName[0]?.toUpperCase() ?? "P"}
@@ -96,8 +131,7 @@ export function ProjectShell({

{projectName}

@@ -120,7 +154,6 @@ export function ProjectShell({ padding: "0 32px", borderBottom: "1px solid #e8e4dc", display: "flex", - gap: 0, background: "#fff", flexShrink: 0, }}> @@ -130,8 +163,6 @@ export function ProjectShell({ href={`/${workspace}/project/${projectId}/${t.path}`} style={{ padding: "12px 18px", - border: "none", - background: "none", fontSize: "0.8rem", fontWeight: 500, color: activeTab === t.id ? "#1a1a1a" : "#a09a90", @@ -153,6 +184,97 @@ export function ProjectShell({ {children}
+ + {/* Right panel */} +
+ {/* Discovery phases */} + Discovery + {DISCOVERY_PHASES.map((phase, i) => { + const isDone = i < discoveryPhase; + const isActive = i === discoveryPhase; + return ( +
+
+ {isDone ? "✓" : isActive ? "→" : i + 1} +
+ + {phase} + +
+ ); + })} + +
+ + {/* Captured data */} + Captured + {capturedEntries.length > 0 ? ( + capturedEntries.map(([k, v], i) => ( +
+
+ {k} +
+
+ {v} +
+
+ )) + ) : ( +

+ Atlas will capture key details here as you chat. +

+ )} + +
+ + {/* Project info */} + Project Info + {[ + { k: "Created", v: timeAgo(createdAt) }, + { k: "Last active", v: timeAgo(updatedAt) }, + { k: "Features", v: featureCount > 0 ? `${featureCount} defined` : "None yet" }, + ].map((item, i) => ( +
+
+ {item.k} +
+
{item.v}
+
+ ))} +