nexus/docs/plans/2026-04-11-nexus-phase-11-projects-builder.md
Nexus Dev 3f0d3377e7 docs(nexus): wave 2 implementation plans (phases 9, 10, 11)
Three phase plans for Wave 2 of the Nexus layout overhaul. Each plan
is self-contained with ownership boundaries, scope fence, file inventory,
implementation notes, acceptance criteria, and commit scheme — designed
for parallel subagent dispatch per MIGRATION-PLAN.md section 11.

Phase 9 (Assistant mode):
  - Full-bleed chat at /assistant, no inner conversation list
  - History slide-over (left) + Memory slide-over (right)
  - Conversational home-state greeting replaces Dashboard
  - ActionStrip with Promote/Attach/Memory/History
  - New components: AssistantInputBar, ActionStrip, HistorySheet,
    MemorySheet, AssistantHomeGreeting + useAssistantHomeStatus hook
  - Owns: PersonalAssistant.tsx + components/assistant/**

Phase 10 (Studio mode):
  - 8-card workshop grid replaces 7-tab ContentStudio
  - ConvertPage folds in as 8th workshop (legacy /convert route kept)
  - StudioPromptBar freeform input with keyword-based classifier
  - Two-column workshop detail view (params left, preview right)
  - Owns: ContentStudio.tsx + pages/StudioWorkshopDetail.tsx
    + components/studio/**

Phase 11 (Projects + Builder mode):
  - ProjectCard with 72px Inter Black volt hero percentage
  - 7-tab BuilderTabStrip (Overview/Issues/Agents/Gates/Costs/Activity/Org)
  - Approvals -> Gates display-only rename
  - OverviewTab with milestone checklist, origin chat card, activity
  - Thin per-project wrappers reuse existing list components scoped
    by projectId (escalate if not supported)
  - useGateIndicator hook for the future Assistant dot notification
  - Owns: Projects.tsx + ProjectDetail.tsx + components/projects/**

Ownership boundaries prevent parallel-dispatch file conflicts:
- App.tsx routing changes are controller-owned (post-Wave wiring)
- Each phase declares its file ownership and must not cross into others
- Existing list components reused as-is; escalate if not scope-compatible
- IconRail dot wiring for phase 11's gate indicator deferred to
  post-Wave controller step

Dispatch pattern: three general-purpose subagents dispatched in parallel,
each handed the full plan text + the spec + ownership rules. Each
subagent implements its phase end-to-end with atomic commits per
logical unit. Controller reviews outputs after all three complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 12:09:34 +00:00

22 KiB
Raw Blame History

Nexus Phase 11 — Projects + Builder Mode Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:test-driven-development. Commit atomically per logical unit. This is the largest phase in Wave 2 by file count and integration surface.

Goal: Replace the existing Projects list page with hero-stat cards (72px Inter Black volt percentage), rebuild the Project Detail page with a 7-tab Builder mode strip (Overview · Issues · Agents · Gates · Costs · Activity · Org), demote the current global routes (Issues, Agents, Approvals→Gates, Costs, Activity, Org) into per-project tabs, rename Approvals to "Gates" in the UI display layer, and wire the Assistant dot indicator for pending gates.

Source of truth: docs/specs/2026-04-11-nexus-layout-overhaul.md §7 (Mode 3 — Projects + Builder mode). Read §7.1§7.3 thoroughly before starting. DESIGN.md governs visuals. Phase 8 components at ui/src/components/frame/* are the reference pattern.

Branch: nexus/design-system-migration. Commit directly.


Ownership boundaries

You may create or modify ONLY:

Path Action
ui/src/pages/Projects.tsx Modify (list page rewrite)
ui/src/pages/ProjectDetail.tsx Modify (add Builder tab strip, preserve existing sub-view logic)
ui/src/components/projects/** Create (new subdir for Phase 11 components)

You MUST NOT touch:

  • ui/src/App.tsx — routing is controller-owned. Per-project tab sub-routes (/projects/:id/agents, /projects/:id/gates, etc.) already partially exist in App.tsx from the Paperclip base. Report any routes you need added in your final report.
  • ui/src/pages/Issues.tsx, ui/src/pages/Agents.tsx, ui/src/pages/Approvals.tsx, ui/src/pages/Costs.tsx, ui/src/pages/Activity.tsx, ui/src/pages/OrgChart.tsx (or whatever the existing org page is called), ui/src/pages/Goals.tsx, ui/src/pages/Inbox.tsx, ui/src/pages/Routines.tsx. These pages stay on their existing global routes for backwards compat during Phase 11. Phase 16 deletes dead top-level pages if/when fully replaced.
  • Any existing list component that those pages use (IssuesList.tsx, AgentList.tsx, etc.). You MAY render these inside your new project tabs, scoped by project ID via props.
  • ui/src/components/Layout.tsx, ui/src/components/frame/* — Phase 8.
  • Any file outside ui/src/ except as noted.
  • Other phases' owned paths.

Existing code you may reuse (read-only):

  • IssuesList, AgentList, ApprovalsList (or whatever the current list components are named) — render inside your new project tabs with a project-id filter prop. If these components don't already accept a projectId filter prop, report BLOCKED rather than modifying them unilaterally. The controller will coordinate with a follow-up.
  • projectsApi, issuesApi, agentsApi, approvalsApi, costsApi, activityApi — existing backend clients. Reuse unchanged.
  • Existing milestone tracking data if any (the spec §7.2.1 shows a milestone checklist in the Overview tab — if no milestone data exists in the backend, render a placeholder with "No milestones defined" and note it as a Phase 11 gap).
  • ui/src/components/frame/* — Phase 8 patterns (test shape, tokens, focus-visible, etc.).

Scope (strictly)

In Phase 11:

1. Projects list page rewrite (ui/src/pages/Projects.tsx)

  • Full-width cards (or 2-up on >= 1024px), stacked vertically with 16px gap
  • Card: 1px charcoal border, 8px radius, transparent fill
  • Each card shows:
    • Title row: project slug in Inter 700 uppercase 16px white + status dot (forest = idle, volt pulsing = working, pale yellow = waiting on gate) at right
    • Hero stat: percentage in Inter Black 900, 72px, volt (text-primary) — the performance-stat pattern from DESIGN.md §4
    • Sub-line: Phase N of M · Next gate: {name} in Inter 500 14px silver
    • Progress bar: 8px tall, charcoal track (bg-border), volt fill (bg-primary), sharp 4px corners, no pill
    • Footer line: ${cost} burned · last activity ${when} in Inter 400 12px silver
  • Click navigates to /<prefix>/projects/{slug}/overview
  • Top-right: ⊕ NEW PROJECT button in Forest Green (bg-[#166534] text-white) — the secondary CTA per DESIGN.md. onClick opens the existing project-create dialog or brainstormer (use whatever the existing NewProjectDialog already wires up).
  • No table view, no filters, no sort dropdowns. ⌘K handles search in Phase 14; Phase 11 just renders the cards.
  • Empty state: if projects.length === 0, render a full-canvas message — NO PROJECTS YET in Inter Black 96px volt (font-black text-[96px] text-primary leading-none), below it a Forest Green ⊕ START YOUR FIRST PROJECT CTA that opens the same project-create flow

2. Project Detail rewrite (ui/src/pages/ProjectDetail.tsx)

  • Builder tab strip just below the TopStrip (note: TopStrip is Phase 8's top bar; the Builder tab strip is an ADDITIONAL 40px horizontal strip specific to Project Detail, rendered as the top of the page content — not in the global chrome).
  • Tabs, left to right: OVERVIEW · ISSUES · AGENTS · GATES · COSTS · ACTIVITY · ORG
    • Inter 600 14px uppercase, tracking-[0.1em]
    • Silver default (text-muted-foreground), volt active with 2px volt bottom border
    • Active tab also has text-primary
    • 24px gap between tabs, 24px left padding
    • Tab strip itself has border-b border-border
  • Each tab renders a different inner component (see §Tab components below)
  • Default tab is OVERVIEW (if the URL has no tab segment, redirect to …/overview)
  • Tab click navigates via React Router Link so the URL reflects the current tab

3. Tab components — each in ui/src/components/projects/tabs/

  • OverviewTab (OverviewTab.tsx) — renders:
    • Hero stat row: 47% in Inter Black 900 72px volt + 4 AGENTS ACTIVE in Inter 700 14px silver
    • Milestone checklist card (1px charcoal border, 8px radius, 24px padding, uppercase title)
      • Checkbox list of milestones: completed milestones get [✓] prefix and white text; pending get [○]/[ ] prefix and silver text; the "next gate" milestone gets a pale-yellow bullet and a ← NEXT GATE label
      • If no milestone data exists in the backend: render a small-muted placeholder "No milestones defined" and stop — do not fabricate data
    • Origin chat card (if the project has an originConversationId field on the project record): small card linking back to the conversation that birthed the project. Show the first line of the conversation as a quote. If no origin conversation, omit the card.
    • Activity card: rolling 24h activity for this project. Count commits, issues closed, gates awaiting, cost burned. Compose from existing activityApi with a project filter.
  • IssuesTab — renders <IssuesList projectId={projectId} /> if the existing list component supports that prop. If it doesn't, STOP and report BLOCKED.
  • AgentsTab — renders <AgentList projectId={projectId} /> scoped. Same rule.
  • GatesTab — renders the existing approvals list filtered by project. In the UI copy, label it "Gates" everywhere visible to the user. Do NOT rename the backend API, the route, or any code identifiers. "Approvals → Gates" is a display-only rename per the spec.
  • CostsTab — renders existing <CostsBreakdown projectId={projectId} /> or equivalent.
  • ActivityTab — renders existing activity feed scoped to project.
  • OrgTab — renders existing org chart scoped to project. Hide the tab entirely for projects with fewer than 2 agents (single-agent projects don't need an org chart). Implement this as a conditional tab — the tab strip shows 6 tabs for single-agent projects, 7 for multi-agent.

4. Assistant icon dot indicator for pending gates

The spec §10.4 says the single notification surface is the volt dot on the Assistant icon. Phase 11's scope for this is minimal: when there's at least one pending gate globally, the Assistant icon in the IconRail should show a small volt dot overlay.

Implementation strategy:

  • Create a new hook ui/src/hooks/useGateIndicator.ts that reads the existing approvals endpoint, filters by pending status, returns { hasPendingGates: boolean, count: number }.
  • You may NOT modify IconRail directly — that's Phase 8's file. Instead, report this to the controller: "Phase 11 needs the IconRail Assistant icon to show a volt dot when useGateIndicator().hasPendingGates is true. The controller will wire this up after Wave 2 by importing the hook in IconRail and conditionally rendering a dot element."

Alternative: if you're confident the dot wiring is trivial (a single line of conditional JSX in IconRail + importing the hook), you may create a PR-style note in your final report with the exact diff the controller should apply. Do NOT commit the IconRail change yourself.

5. "Approvals → Gates" display rename

  • All user-visible strings containing "Approval" or "Approvals" in the contexts affected by your changes become "Gate" / "Gates" in the UI copy.
  • Do NOT rename any symbol, file, function, API path, component name, CSS class, or test ID.
  • Scope of the rename: only strings that end up on screen inside your Phase 11 pages or components. If Phase 13 (Settings) or Phase 14 (⌘K) later references Approvals, they'll handle their own rename.

6. Goals fold-in and demotions

  • Goals collapses into the Overview tab's milestone checklist. For Phase 11, do NOT actually integrate Goals data — the milestone checklist reads from project.milestones (if exists) or shows a placeholder. Goals remains at /goals as a top-level page for backwards compat; Phase 16 cleanup decides whether to delete it.
  • Inbox has no direct replacement in Phase 11. It stays at /inbox for backwards compat; the spec §7.3 says it's replaced by ⌘K + Assistant dot, which Phase 14 handles.
  • Routines moves to Settings in Phase 13. Phase 11 does not touch Routines.

NOT in Phase 11:

  • Creation of Gates as a new API surface (it's a display rename only).
  • Modification of existing list components to add projectId props if they don't already have them (escalate instead).
  • Deletion of Goals.tsx, Inbox.tsx, Routines.tsx, or any of the existing global-list pages.
  • Modification of App.tsx (controller owns).
  • Modification of IconRail or any other frame component.
  • Any visual polish beyond DESIGN.md's rules.
  • Multi-select, batch operations, or table views on the Projects list.

File plan

Create

File Responsibility Est. lines
ui/src/components/projects/ProjectCard.tsx Hero-stat card for Projects list. Props: project record, onClick. Renders title, status dot, 72px percentage hero, progress bar, sub-line, footer. ~120
ui/src/components/projects/ProjectCard.test.tsx Tests: renders all fields, status dot color per status, hero stat font-black 72px, click fires callback ~140
ui/src/components/projects/BuilderTabStrip.tsx Horizontal tab strip for Project Detail. Props: projectId, activeTab, hasMultipleAgents (controls Org tab visibility). ~100
ui/src/components/projects/BuilderTabStrip.test.tsx Tests: renders 7 tabs by default, hides Org for single-agent, active tab styling, Link navigation ~130
ui/src/components/projects/tabs/OverviewTab.tsx The OVERVIEW tab content. Hero stat + milestone checklist + origin chat card + activity card. ~200
ui/src/components/projects/tabs/OverviewTab.test.tsx Tests: renders hero stat, milestone list, origin chat when present and omitted when absent, activity counts ~150
ui/src/components/projects/tabs/IssuesTab.tsx Thin wrapper that renders the existing IssuesList with projectId filter. ~30
ui/src/components/projects/tabs/AgentsTab.tsx Thin wrapper for agents list. ~30
ui/src/components/projects/tabs/GatesTab.tsx Thin wrapper for approvals list, with "Gates" display copy. ~40
ui/src/components/projects/tabs/CostsTab.tsx Thin wrapper for costs breakdown. ~30
ui/src/components/projects/tabs/ActivityTab.tsx Thin wrapper for activity feed. ~30
ui/src/components/projects/tabs/OrgTab.tsx Thin wrapper for org chart. ~30
ui/src/components/projects/tabs/tabs.test.tsx A single file covering all 6 thin wrappers (just verify they render and pass projectId through) ~120
ui/src/hooks/useGateIndicator.ts Hook: reads approvals endpoint, returns { hasPendingGates, count }. Used in post-Wave IconRail wiring. ~50
ui/src/hooks/useGateIndicator.test.ts Tests: returns correct count, loading state, error state ~80

Modify

File Change
ui/src/pages/Projects.tsx Rewrite the list page to use ProjectCard grid. Preserve any existing data-loading hooks, error boundaries, toast wiring.
ui/src/pages/ProjectDetail.tsx Add BuilderTabStrip at the top. Route tab content based on a tab segment from the URL (either use useParams if the App.tsx route has a :tab param, or parse from useLocation().pathname). Default to OVERVIEW. Render the appropriate tab component.

Do not create or modify any other files.


Implementation notes

ProjectCard hero-stat progress %

The percentage comes from the project's milestone progress. Check the existing projectsApi response shape. Common patterns:

  • project.milestoneProgress (a number 0100)
  • project.phasesCompleted / project.phasesTotal
  • project.completedMilestones.length / project.milestones.length

Pick the one that exists and compute. If none exist, render —% (em-dash percent) and note it as a gap for the backend team. Do NOT fabricate a value.

Status dot color mapping:

  • Forest #166534 = idle (no active agents, no pending gates)
  • Volt text-primary with pulse animation = working (at least one agent in "working" state)
  • Pale Yellow #f4f692 = waiting on user gate (pending approval exists)

Priority if multiple conditions hold: waiting > working > idle.

BuilderTabStrip URL structure

The existing App.tsx has these project routes (from my earlier read):

<Route path="projects/:projectId" element={<ProjectDetail />} />
<Route path="projects/:projectId/overview" element={<ProjectDetail />} />
<Route path="projects/:projectId/issues" element={<ProjectDetail />} />
<Route path="projects/:projectId/issues/:filter" element={<ProjectDetail />} />
<Route path="projects/:projectId/configuration" element={<ProjectDetail />} />
<Route path="projects/:projectId/budget" element={<ProjectDetail />} />

So overview, issues exist already. Missing: /agents, /gates, /costs, /activity, /org. Report these as routes the controller needs to add. For Phase 11 implementation, use path-matching inside ProjectDetail to infer the current tab:

const match = location.pathname.match(/\/projects\/[^/]+\/([^/?#]+)/);
const activeTab = (match?.[1] as ProjectTab) ?? "overview";

Tab Link to values can point at the routes that don't exist yet — React Router will render whatever catches them, which will likely fall back to ProjectDetail via the base /projects/:projectId route. Test that this works in practice.

Preserving ProjectDetail's existing behavior

The existing ProjectDetail.tsx probably has logic for the configuration and budget sub-routes (visible in the route table). You must preserve that behavior for backwards compat. Options:

  1. Union the old and new tab logic. In the new ProjectDetail, after deriving activeTab, first check if it's configuration or budget — if so, render the existing behavior unchanged. Otherwise dispatch to the new Builder tabs.
  2. Add Configuration and Budget as 8th/9th tabs. The spec says 7 tabs but also says "Configuration" isn't in the list — probably because it should be folded into OVERVIEW or dropped. For Phase 11, err on the side of preserving: keep them accessible but don't surface them in the BuilderTabStrip.

Decision: go with option 1 — Builder tabs for the new 6/7 tabs, existing behavior for configuration and budget (silent, not surfaced in the tab strip but still routable). Report the configuration/budget preservation decision to the controller.

"Approvals → Gates" display rename (scope)

Inside your Phase 11 files, any user-visible string that says "Approval" or "Approvals" should say "Gate" or "Gates":

  • Tab label: GATES (not APPROVALS)
  • Tab ARIA label: Gates (not Approvals)
  • Empty state: "No pending gates" (not "No pending approvals")

Do NOT rename:

  • The approvalsApi client
  • The /api/approvals endpoints
  • The Approval TypeScript type
  • The Approvals top-level page at /approvals (backwards compat)
  • Any component named ApprovalsList or similar
  • Any test ID or CSS class

The rename is display-only.

Hero stat typography

DESIGN.md specifies Inter Black 900 at 72px for hero stats. Implementation:

<div className="font-black text-[72px] leading-none text-primary">
  {Math.round(progress)}%
</div>

The font-weight 900 is Tailwind's font-black. The leading-none matches DESIGN.md §3 line-height 1.0 for display text.

For the empty state (96px), use font-black text-[96px] leading-none text-primary.

Status dot animations

// working state — pulsing volt dot
<span className="h-2 w-2 rounded-full bg-primary animate-pulse" aria-hidden="true" />

// idle — solid forest
<span className="h-2 w-2 rounded-full bg-[#166534]" aria-hidden="true" />

// waiting on gate — solid pale yellow
<span className="h-2 w-2 rounded-full bg-[#f4f692]" aria-hidden="true" />

Pale yellow and forest are literal hex for now (same reasoning as Phase 8's GlobalMicButton — MIGRATION-PLAN.md §3 proposes these as tokens but they haven't shipped).


Acceptance criteria

Phase 11 is complete when:

  1. Loading /NEX/projects renders project cards with 72px volt hero percentages, progress bars, status dots, and footer lines. No table, no filters.
  2. Clicking a project card navigates to /NEX/projects/{slug}/overview.
  3. Loading /NEX/projects/{slug}/overview renders the OverviewTab with hero row, milestone checklist (or placeholder), activity card.
  4. The BuilderTabStrip appears at the top of ProjectDetail with 6 or 7 tabs depending on agent count.
  5. Clicking each tab navigates to …/projects/{slug}/{tab} and renders the corresponding tab component.
  6. The ORG tab is hidden for projects with fewer than 2 agents.
  7. The GATES tab renders the existing approvals list with "Gates" display copy — underlying API calls still go to /api/approvals.
  8. Empty state (NO PROJECTS YET in 96px volt) renders when projects.length === 0.
  9. useGateIndicator hook returns correct pending-gate counts.
  10. All new components have tests using the Phase 8 manual createRoot + act pattern.
  11. npx vitest run src/components/projects/ src/hooks/useGateIndicator.test.ts passes.
  12. npx tsc --noEmit 2>&1 | grep -E "projects/|Projects\.tsx|ProjectDetail\.tsx|useGateIndicator" returns no errors.
  13. Existing /NEX/projects/{slug}/configuration and …/budget routes still render whatever they rendered before (backwards compat).
  14. /NEX/approvals still works (backwards compat).
  15. No file outside the declared ownership is modified.
  16. The IconRail volt-dot indicator is NOT wired (reported to controller for post-Wave wiring).

Commit scheme

Suggested atomic commits:

  1. feat(nexus): add useGateIndicator hook (phase 11) — hook + tests
  2. feat(nexus): add ProjectCard component (phase 11) — card + tests
  3. feat(nexus): add BuilderTabStrip component (phase 11) — strip + tests
  4. feat(nexus): add OverviewTab with hero stat + milestones (phase 11) — overview tab + tests
  5. feat(nexus): add thin wrappers for per-project tabs (phase 11) — Issues/Agents/Gates/Costs/Activity/Org wrappers + tests
  6. refactor(nexus): rewrite Projects list with hero-stat cards (phase 11) — Projects.tsx
  7. refactor(nexus): add BuilderTabStrip to ProjectDetail (phase 11) — ProjectDetail.tsx

Include Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> on every commit.


Before you begin

You MUST read the following files before starting to verify assumptions:

  1. ui/src/pages/Projects.tsx — existing list shape
  2. ui/src/pages/ProjectDetail.tsx — existing detail shape, configuration/budget handling
  3. ui/src/pages/Approvals.tsx and whatever list component it uses — to verify the list component can be scoped by projectId
  4. ui/src/pages/Issues.tsx, ui/src/pages/Agents.tsx, ui/src/pages/Costs.tsx, ui/src/pages/Activity.tsx — same verification
  5. Any existing projectsApi client to verify project record shape (does it have milestoneProgress, phasesCompleted, originConversationId, lastActivity, costBurned?)
  6. ui/src/App.tsxread only — to confirm the existing project sub-routes

If any reused list component does NOT accept a projectId filter prop, STOP and report BLOCKED. Do not modify that component unilaterally.

If the project record doesn't have fields for milestoneProgress / originConversationId / etc., render placeholders gracefully and note the gaps. Do not fabricate data.


When you're in over your head

Escalate early if:

  • Existing list components require modification to support projectId filtering (report BLOCKED)
  • Project record shape is incompatible with the hero-stat rendering (e.g. no progress data) → render —% and report
  • ProjectDetail's existing configuration/budget sub-route logic is tangled in a way that makes clean tab-stripping hard
  • The approvalsgates rename scope creeps into more files than expected → stop, report, and confirm scope

Report format (final)

  • Status: DONE / DONE_WITH_CONCERNS / BLOCKED / NEEDS_CONTEXT
  • Commits produced with SHAs
  • Files created / modified
  • Tests added and passing count
  • Typecheck result for Phase 11 files
  • Routing needs: exact list of App.tsx routes the controller needs to add (e.g. projects/:projectId/agents, projects/:projectId/gates, projects/:projectId/costs, projects/:projectId/activity, projects/:projectId/org)
  • IconRail dot wiring note: exact diff or description the controller should apply to IconRail after Wave 2
  • ProjectDetail preservation notes: how you handled configuration and budget sub-routes
  • Data gaps: fields the project record was missing (e.g. milestoneProgress, originConversationId) and what you rendered as placeholders
  • Approvals→Gates scope: exactly which strings you renamed
  • Open concerns
  • Deviations from the plan
  • Self-review findings