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>
184 lines
12 KiB
Markdown
184 lines
12 KiB
Markdown
# 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 component
|
||
- `Agent` type in `@paperclipai/shared` has **no `projectId` field** and no reverse index to projects
|
||
- `agentsApi.list(companyId)` returns all agents for a company, no filter options
|
||
|
||
**Scope:**
|
||
|
||
1. **Define the agent ↔ project relationship.** Two options:
|
||
- (a) Add `projectId: string | null` to the `Agent` type + 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_agents` join table for explicit assignment; requires migration and a separate assignment UI.
|
||
2. **Extract `AgentList` as a reusable component** from `Agents.tsx`, with a `projectId?: string | null` prop.
|
||
3. **Wire into `AgentsTab`** — replace the `TabPlaceholder`.
|
||
|
||
**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.payload` is `Record<string, unknown>`. Some approval types carry `payload.projectId` by convention; most don't.
|
||
- `ApprovalsList` or equivalent doesn't exist as a standalone component; `ui/src/pages/Approvals.tsx` is inline.
|
||
- `approvalsApi.list(companyId, status)` returns all approvals scoped by status, no project filter.
|
||
|
||
**Scope:**
|
||
|
||
1. **Audit approval types for `projectId` coverage.** Grep the server and UI for approval payload shapes; list which types carry `projectId` and which don't. Types that don't need either:
|
||
- (a) have a `projectId` added to their payload going forward (backward-compat default: `null`)
|
||
- (b) be filtered out of the per-project view and only surfaced in the global `/approvals` route
|
||
2. **Lift the frontend filter** already used by Projects.tsx (`buildProjectDerivatives` probes `payload.projectId`) into a shared `filterApprovalsByProject(approvals, projectId)` helper.
|
||
3. **Extract `ApprovalsList`** as a reusable component with a `projectId?: string | null` prop and a display-mode toggle for "Gates" vs "Approvals" copy.
|
||
4. **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.tsx` is ~1100 lines of inline content — aggregation, charting, filters, and layout all in one file
|
||
- No `CostsBreakdown` standalone component
|
||
- `costsApi` probably has per-company totals; per-project breakdown depends on the cost event records carrying `projectId` (likely yes because costs roll up to projects in the existing UI somewhere)
|
||
|
||
**Scope:**
|
||
|
||
1. **Read `Costs.tsx` and identify the breakdown section** (the chart/table that shows costs by project or by agent). If it exists, extract to `CostsBreakdown.tsx` with a `projectId?: string | null` prop.
|
||
2. **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).
|
||
3. **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 gaps
|
||
- `ui/src/pages/Activity.tsx` renders the unscoped feed inline, no standalone component
|
||
|
||
**Scope:**
|
||
|
||
1. **Extract `ActivityFeed` as a reusable component** from `Activity.tsx` with a `{ entityType: string; entityId: string | null; companyId: string }` prop shape.
|
||
2. **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 tree
|
||
- `ui/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:**
|
||
|
||
1. **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.
|
||
2. **If (a) or (b)**, extract `OrgChart` as a standalone component with a `highlightAgentIds?: string[]` or `agentIdFilter?: 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`, `phasesTotal` fields to the `Project` record. That's a Phase 11.6 backend phase if wanted, or accepted as permanent client-side proxy.
|
||
- Adding `originConversationId` to the `Project` record. Same disposition.
|
||
- Adding `costBurnedCents` aggregate to the `Project` record. Same.
|
||
- Adding explicit `project_agents` join 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:
|
||
|
||
1. **The `TabPlaceholder` UX 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.
|
||
2. **Wave 3 completes** and the team has bandwidth for backend-adjacent tickets.
|
||
3. **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.
|