From 1e85565765b3ed059c6661b82fa7fd662546c5c3 Mon Sep 17 00:00:00 2001 From: Nexus Dev Date: Sat, 11 Apr 2026 12:24:40 +0000 Subject: [PATCH] refactor(nexus): add BuilderTabStrip to ProjectDetail (phase 11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Integrates the Phase 11 Builder tab strip into ProjectDetail without disturbing any existing Paperclip sub-route behavior. The approach: 1. When location.pathname resolves to a Builder tab (overview / issues / agents / gates / costs / activity / org), render the BuilderTabStrip + the matching new tab component (OverviewTab / ProjectIssuesList / AgentsTab / etc.) and hide the legacy PageTabBar + legacy overview/list handlers. 2. When the pathname resolves to a legacy tab (configuration / budget / workspaces / plugin:*), the existing Tabs/PageTabBar and dispatch render exactly as before — backwards compat preserved. This is Option 1 from the Phase 11 plan: union the old and new tab logic, silent pass-through for configuration/budget, no surface in the new strip. OverviewTab currently receives null for every data slice (progress/milestones/originChat/activity24h) because the shared Project type + projectsApi don't carry those fields yet; the tab renders explicit em-dash / "No milestones defined" placeholders. See the Phase 11 report for the backend gap list. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/pages/ProjectDetail.tsx | 69 +++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/ui/src/pages/ProjectDetail.tsx b/ui/src/pages/ProjectDetail.tsx index 5c9b6f54..001e14cd 100644 --- a/ui/src/pages/ProjectDetail.tsx +++ b/ui/src/pages/ProjectDetail.tsx @@ -24,6 +24,13 @@ import { ExecutionWorkspaceCloseDialog } from "../components/ExecutionWorkspaceC import { IssuesList } from "../components/IssuesList"; import { PageSkeleton } from "../components/PageSkeleton"; import { PageTabBar } from "../components/PageTabBar"; +import { BuilderTabStrip, resolveBuilderTab } from "../components/projects/BuilderTabStrip"; +import { OverviewTab } from "../components/projects/tabs/OverviewTab"; +import { AgentsTab } from "../components/projects/tabs/AgentsTab"; +import { GatesTab } from "../components/projects/tabs/GatesTab"; +import { CostsTab } from "../components/projects/tabs/CostsTab"; +import { ActivityTab } from "../components/projects/tabs/ActivityTab"; +import { OrgTab } from "../components/projects/tabs/OrgTab"; import { buildProjectWorkspaceSummaries } from "../lib/project-workspaces-tab"; import { projectRouteRef, projectWorkspaceUrl } from "../lib/utils"; import { timeAgo } from "../lib/timeAgo"; @@ -847,6 +854,63 @@ export function ProjectDetail() { itemClassName="inline-flex" /> + {/* + Phase 11 Builder tab strip. Renders iff the current URL resolves + to one of the new Builder tabs (overview/issues/agents/gates/ + costs/activity/org). Legacy sub-routes (configuration/budget/ + workspaces/plugin) fall through to the existing PageTabBar + below for backwards compat — see plan §Preserving existing + behavior. + */} + {(() => { + const builderTab = resolveBuilderTab(location.pathname, canonicalProjectRef); + if (!builderTab) return null; + // Phase 11: no reliable per-project agent count source yet. Show + // the full (7-tab) strip by default; BuilderTabStrip hides ORG + // when hasMultipleAgents=false. Leaving this true preserves + // access to the Org placeholder tab until the data is wired. + const hasMultipleAgents = true; + return ( +
+ + {builderTab === "overview" && ( + + )} + {builderTab === "issues" && project?.id && resolvedCompanyId && ( + + )} + {builderTab === "agents" && } + {builderTab === "gates" && } + {builderTab === "costs" && } + {builderTab === "activity" && } + {builderTab === "org" && } +
+ ); + })()} + + {/* + Legacy Paperclip chrome — rendered only when the current tab is + NOT a Builder tab (configuration / budget / workspaces / plugin). + This preserves existing behavior for every pre-Phase-11 + sub-route while the Builder strip owns the new tabs. + */} + {resolveBuilderTab(location.pathname, canonicalProjectRef) === null && ( handleTabChange(value as ProjectTab)}> handleTabChange(value as ProjectTab)} /> + )} - {activeTab === "overview" && ( + {activeTab === "overview" && resolveBuilderTab(location.pathname, canonicalProjectRef) === null && ( updateProject.mutate(data)} @@ -877,7 +942,7 @@ export function ProjectDetail() { /> )} - {activeTab === "list" && project?.id && resolvedCompanyId && ( + {activeTab === "list" && project?.id && resolvedCompanyId && resolveBuilderTab(location.pathname, canonicalProjectRef) === null && ( )}