Six plans drafted after Wave 2 completed:
Phase 11.5 — per-project scoping follow-up (BACKLOG)
The 5 BLOCKED tabs from Phase 11 (Agents, Gates, Costs, Activity,
Org) each need projectId scoping in the corresponding backend types
and list components. Not time-critical — shipped as TabPlaceholder
components in Phase 11 with honest data-gap badges. Each of the 5
tickets is self-contained and can run independently when the
placeholder UX hurts or when Wave 3 completes. Recommends dropping
the Org tab entirely rather than building per-project scoping for it.
Phase 12 — Promote-to-project transition (WAVE 3)
Replaces Phase 9's synchronous assistantHandoff() with the animated
700ms compress-and-rise from spec 5.6. New files: PromoteTransition,
BrainstormerPanel, usePromoteToProject state machine. CSS-first
animation, not Motion library. Respects prefers-reduced-motion.
Depends on Phase 9 (Assistant) and Phase 11 (Projects).
Phase 13 — Settings consolidation (WAVE 3)
Collapses the nested instance-settings tree into a single-column
scroll with 8 section cards (Workspace, LocalAI, Cloud, Skills,
Routines, Telegram, About, Danger). Phase 13 is the one phase
where routing changes ARE phase-owned: it strips nested
/instance/settings sub-routes and replaces with Navigate redirects.
Phase 14 — Voice + CmdK globalization (WAVE 3)
Lifts voice state out of ChatInput's internal VoiceMicButton into
a shared VoiceContext so the top-strip GlobalMicButton becomes
functional from any route, with speech queued to the Assistant
inbox per spec 5.5. Replaces the CmdKButton synthetic-keydown shim
with a real CommandPaletteContext. Extends CommandPalette search
to cover conversations, projects, issues, agents, settings,
workshops. Also fixes the pre-existing useKeyboardShortcuts.ts
destructure bug caught during Phase 6/11 reviews.
Phase 15 — Mobile parity (WAVE 3)
4-destination MobileTabBar replacing MobileBottomNav. History +
Memory sheets become full-screen on mobile. Promote transition
uses a full-screen takeover instead of split layout. BuilderTabStrip
scrolls horizontally with edge fades. Single 768px breakpoint.
Phase 16 — Cleanup pass (WAVE 4, sequential)
Deletes dead chrome files (ChatPanel, ChatPanelContext, Sidebar,
InstanceSidebar, BreadcrumbBar, PropertiesPanel, MobileBottomNav).
Migrates ChatMessageList off ChatPanelContext first (flagged
hazard from Phase 9). Vocabulary sweep company to workspace in
UI strings only (backend keeps company identifiers per upstream
sync constraint). Fixes the minor issues accumulated across
Phase 8-15 reviews (useKeyboardShortcuts destructure bug,
GlobalMicButton test double-render, Projects umbrella regex
duplication, PersonalAssistant brittle fixed height). Visual
QA pass.
Wave structure for Wave 3 dispatch:
Wave 3A: Phase 12, Phase 13, Phase 14, Phase 15 in parallel
(4 subagents, disjoint file ownership)
Wave 3B: Phase 16 sequential (intentionally touches everything)
Phase 11.5 stays in backlog — not in any wave. It activates when
the TabPlaceholder UX becomes a friction point or when a milestone
explicitly pulls one of its tickets into scope.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
12 KiB
Nexus Phase 11.5 — Per-Project Scoping Follow-Up
Status: Drafted 2026-04-11. Not yet started. Queued after Wave 3 completes, unless the TabPlaceholder UX pain surfaces earlier.
Why this exists: Phase 11's subagent discovered that 5 of the 6 new Project Detail "Builder" tabs have no existing projectId-aware list component to reuse, and the underlying type system doesn't carry project association on the relevant entities. Rather than halt Phase 11, the subagent shipped visible TabPlaceholder components that render a "PHASE 11 DATA GAP" badge with a precise description of what each tab needs. Those placeholders work as honest diagnostic UI but are not the final product.
Source of truth: docs/plans/2026-04-11-nexus-phase-11-projects-builder.md (original phase 11 plan), Phase 11's final subagent report, and docs/specs/2026-04-11-nexus-layout-overhaul.md §7.2 for the target UX.
1. The five gaps
Each gap is a self-contained ticket that can be worked independently. Ordering is flexible but the agents/gates/costs trio share some backend patterns and are best done together; activity and org are their own work.
1.1 AGENTS tab — per-project agent list
Goal: Render the agents assigned to or actively working on a specific project, in the AgentsTab component currently showing a TabPlaceholder.
Current state:
ui/src/pages/Agents.tsx— 100+ lines of inline list + filter UI, not extracted as a reusable componentAgenttype in@paperclipai/sharedhas noprojectIdfield and no reverse index to projectsagentsApi.list(companyId)returns all agents for a company, no filter options
Scope:
- Define the agent ↔ project relationship. Two options:
- (a) Add
projectId: string | nullto theAgenttype + DB column, backfilled by looking at the agents' currently-assigned issues. Simplest, requires a migration. - (b) Derive the relationship from assigned issues: an agent is "on project X" if it has any open issue in X. No schema change; frontend join; more expensive to query.
- (c) Add a
project_agentsjoin table for explicit assignment; requires migration and a separate assignment UI.
- (a) Add
- Extract
AgentListas a reusable component fromAgents.tsx, with aprojectId?: string | nullprop. - Wire into
AgentsTab— replace theTabPlaceholder.
Recommendation: start with (b) — pure frontend derivation from the issue query already in memory (Phase 11.5 already fetches all issues for ProjectCard derivatives; piggyback on that cache). If the UX turns out to need explicit assignment semantics (e.g., "assign this agent to project X even though they have no issues yet"), escalate to (a) or (c) later.
Files to touch:
ui/src/components/agents/AgentList.tsx(new — extract from Agents.tsx)ui/src/components/projects/tabs/AgentsTab.tsx(update — remove TabPlaceholder, render<AgentList projectId={projectId} />)ui/src/pages/Agents.tsx(small refactor — consume the new AgentList for the unscoped case)
Acceptance: AgentsTab renders the subset of company agents that have any open issue whose projectId matches the current project. Hover/click behavior matches the existing Agents page.
1.2 GATES tab — per-project pending approvals
Goal: Render the approvals (display: "gates") for a specific project in the GatesTab.
Current state:
Approval.payloadisRecord<string, unknown>. Some approval types carrypayload.projectIdby convention; most don't.ApprovalsListor equivalent doesn't exist as a standalone component;ui/src/pages/Approvals.tsxis inline.approvalsApi.list(companyId, status)returns all approvals scoped by status, no project filter.
Scope:
- Audit approval types for
projectIdcoverage. Grep the server and UI for approval payload shapes; list which types carryprojectIdand which don't. Types that don't need either:- (a) have a
projectIdadded to their payload going forward (backward-compat default:null) - (b) be filtered out of the per-project view and only surfaced in the global
/approvalsroute
- (a) have a
- Lift the frontend filter already used by Projects.tsx (
buildProjectDerivativesprobespayload.projectId) into a sharedfilterApprovalsByProject(approvals, projectId)helper. - Extract
ApprovalsListas a reusable component with aprojectId?: string | nullprop and a display-mode toggle for "Gates" vs "Approvals" copy. - Wire into
GatesTab— replace TabPlaceholder, use "Gates" copy.
Files to touch:
ui/src/lib/approval-filters.ts(new — shared filter helper)ui/src/components/approvals/ApprovalsList.tsx(new — extract from Approvals.tsx)ui/src/components/projects/tabs/GatesTab.tsx(update)ui/src/pages/Approvals.tsx(refactor — consume new component)
Acceptance: GatesTab renders pending+historical approvals whose payload.projectId matches. Copy uses "Gate" / "Gates" everywhere. Underlying API calls remain on /api/approvals.
1.3 COSTS tab — per-project cost burn
Goal: Render cost breakdown for a specific project in CostsTab.
Current state:
ui/src/pages/Costs.tsxis ~1100 lines of inline content — aggregation, charting, filters, and layout all in one file- No
CostsBreakdownstandalone component costsApiprobably has per-company totals; per-project breakdown depends on the cost event records carryingprojectId(likely yes because costs roll up to projects in the existing UI somewhere)
Scope:
- Read
Costs.tsxand identify the breakdown section (the chart/table that shows costs by project or by agent). If it exists, extract toCostsBreakdown.tsxwith aprojectId?: string | nullprop. - If the existing costs API doesn't support per-project filtering, add a server-side filter to the endpoint (small backend change) or filter on the client (acceptable if cost events are bounded).
- Wire into
CostsTab— replace TabPlaceholder.
Files to touch:
ui/src/components/costs/CostsBreakdown.tsx(new — extract)ui/src/components/projects/tabs/CostsTab.tsx(update)ui/src/pages/Costs.tsx(refactor)- Possibly
server/src/routes/costs.ts(add projectId filter param)
Acceptance: CostsTab renders cost events for the current project with an agent-by-agent breakdown. The global /costs page still works.
1.4 ACTIVITY tab — per-project activity feed
Goal: Render an activity feed scoped to a single project in ActivityTab.
Current state:
activityApi.list(entityType, entityId)already supports filtering by entity — this is the easiest of the five gapsui/src/pages/Activity.tsxrenders the unscoped feed inline, no standalone component
Scope:
- Extract
ActivityFeedas a reusable component fromActivity.tsxwith a{ entityType: string; entityId: string | null; companyId: string }prop shape. - Wire into
ActivityTab— call<ActivityFeed entityType="project" entityId={projectId} companyId={companyId} />.
Files to touch:
ui/src/components/activity/ActivityFeed.tsx(new — extract)ui/src/components/projects/tabs/ActivityTab.tsx(update)ui/src/pages/Activity.tsx(refactor)
Acceptance: ActivityTab renders a filtered feed for the current project. Global /activity still works. The proxy in Wave 2.5's ProjectCard (newest issue updatedAt) can optionally be replaced with this feed's latest event.
This is the lowest-effort ticket of the five. Start here if you want a quick win.
1.5 ORG tab — per-project org chart
Goal: Render the subset of the org chart relevant to a specific project in OrgTab, hidden entirely for single-agent projects.
Current state:
agentsApi.org(companyId)returns a company-wide treeui/src/pages/Org.tsx(or similarly named) renders the full tree inline- No way to query "which agents and what reporting structure are relevant to project X"
Scope:
- Define what "project-scoped org chart" means. Options:
- (a) Show the subset of agents assigned to the project (from ticket 1.1) with their existing reporting relationships preserved.
- (b) Show the full company org with non-project agents visually dimmed.
- (c) Drop the tab entirely for Phase 11.5 — the ORG tab is already hidden for single-agent projects, and multi-agent projects might be better served by the global /org page than a per-project subview.
- If (a) or (b), extract
OrgChartas a standalone component with ahighlightAgentIds?: string[]oragentIdFilter?: string[]prop.
Recommendation: (c) — drop the ORG tab entirely for Phase 11.5 unless you actively miss it. The single-agent auto-hide already reduces the surface; multi-agent projects are rare enough that the global org page suffices. If the tab is dropped, delete OrgTab.tsx and update BuilderTabStrip.tsx to unconditionally hide it.
Files to touch (if not dropped):
ui/src/components/agents/OrgChart.tsx(new — extract)ui/src/components/projects/tabs/OrgTab.tsx(update)ui/src/pages/Org.tsx(refactor)
Acceptance (if not dropped): OrgTab renders a subtree of agents relevant to the project. Still hidden for single-agent projects.
2. Dependency order and parallelism
Ticket 1.4 (Activity) ← lowest effort, start here
Ticket 1.1 (Agents) ← unblocks Ticket 1.5 if we keep Org
Ticket 1.2 (Gates) ← independent
Ticket 1.3 (Costs) ← independent, backend work TBD
Ticket 1.5 (Org) ← drop it, or wait on Ticket 1.1
Tickets 1.2, 1.3, 1.4 are pure-frontend extractions and can run in parallel. Ticket 1.1 may involve a schema decision that blocks parallelization. Ticket 1.5 is suggested to drop.
3. Non-scope (things Phase 11.5 does NOT do)
- Adding real
milestoneProgress,phasesCompleted,phasesTotalfields to theProjectrecord. That's a Phase 11.6 backend phase if wanted, or accepted as permanent client-side proxy. - Adding
originConversationIdto theProjectrecord. Same disposition. - Adding
costBurnedCentsaggregate to theProjectrecord. Same. - Adding explicit
project_agentsjoin table. That's (c) in Ticket 1.1 and deferred unless issue-derivation proves insufficient. - Phase 14's ⌘K globalization (that's its own phase in Wave 3).
- Phase 16 cleanup of dead top-level routes like
/approvals,/agents,/activity,/org,/costs. Those pages stay for backwards compat until Phase 16 decides their fate.
4. Triggering condition
This plan sits in the backlog until either:
- The
TabPlaceholderUX hurts — user hits one of the 5 blocked tabs frequently and the "data gap" badge becomes a friction point. At that moment, promote whichever tab is hurting most to active work. - Wave 3 completes and the team has bandwidth for backend-adjacent tickets.
- A v1.8+ milestone explicitly pulls one of these scoping stories into scope (e.g., "project-level cost budgeting" would pull in Ticket 1.3).
None of these tickets is time-critical. The placeholders are honest and diagnosable — users can see "PHASE 11 DATA GAP: project-scoped costs not yet implemented" and navigate to the global /costs page if they need the data.
5. Execution
When ready to start, write a narrow implementation plan per ticket (one plan per ticket), using the same format as Phase 8's task-by-task TDD plan. Dispatch via subagent-driven-development for tickets 1.2–1.5 and in-session for ticket 1.1 (because it requires an architectural decision).
Do not dispatch all five tickets as one subagent — they touch disjoint subsystems and deserve independent review cycles.