refactor(nexus): add BuilderTabStrip to ProjectDetail (phase 11)

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) <noreply@anthropic.com>
This commit is contained in:
Nexus Dev 2026-04-11 12:24:40 +00:00
parent e43d5ec220
commit 1e85565765

View file

@ -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 (
<div data-testid="project-detail-builder-chrome" className="space-y-6">
<BuilderTabStrip
projectRef={canonicalProjectRef}
activeTab={builderTab}
hasMultipleAgents={hasMultipleAgents}
/>
{builderTab === "overview" && (
<OverviewTab
project={{ name: project.name, status: project.status }}
progress={null}
activeAgentsCount={0}
milestones={null}
originChat={null}
activity24h={{
commits: null,
issuesClosed: null,
gatesAwaiting: null,
costBurnedCents: null,
}}
/>
)}
{builderTab === "issues" && project?.id && resolvedCompanyId && (
<ProjectIssuesList projectId={project.id} companyId={resolvedCompanyId} />
)}
{builderTab === "agents" && <AgentsTab projectId={project.id} />}
{builderTab === "gates" && <GatesTab projectId={project.id} />}
{builderTab === "costs" && <CostsTab projectId={project.id} />}
{builderTab === "activity" && <ActivityTab projectId={project.id} />}
{builderTab === "org" && <OrgTab projectId={project.id} />}
</div>
);
})()}
{/*
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 && (
<Tabs value={activeTab ?? "list"} onValueChange={(value) => handleTabChange(value as ProjectTab)}>
<PageTabBar
items={[
@ -865,8 +929,9 @@ export function ProjectDetail() {
onValueChange={(value) => handleTabChange(value as ProjectTab)}
/>
</Tabs>
)}
{activeTab === "overview" && (
{activeTab === "overview" && resolveBuilderTab(location.pathname, canonicalProjectRef) === null && (
<OverviewContent
project={project}
onUpdate={(data) => 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 && (
<ProjectIssuesList projectId={project.id} companyId={resolvedCompanyId} />
)}