diff --git a/docs/plans/2026-04-11-nexus-phase-11-5-per-project-scoping.md b/docs/plans/2026-04-11-nexus-phase-11-5-per-project-scoping.md
new file mode 100644
index 00000000..301936f5
--- /dev/null
+++ b/docs/plans/2026-04-11-nexus-phase-11-5-per-project-scoping.md
@@ -0,0 +1,184 @@
+# 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 ``)
+- `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`. 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 ``.
+
+**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.
diff --git a/docs/plans/2026-04-11-nexus-phase-12-promote-transition.md b/docs/plans/2026-04-11-nexus-phase-12-promote-transition.md
new file mode 100644
index 00000000..144f430c
--- /dev/null
+++ b/docs/plans/2026-04-11-nexus-phase-12-promote-transition.md
@@ -0,0 +1,230 @@
+# Nexus Phase 12 — Promote-to-Project Transition
+
+> **For agentic workers:** Use `superpowers:test-driven-development` per component. Commit atomically per logical unit.
+
+**Goal:** Replace the current synchronous `assistantHandoff()` call on the ActionStrip's "Promote to project" button with the signature animated transition from `docs/specs/2026-04-11-nexus-layout-overhaul.md` §5.6 — the 700ms compress-and-rise where the chat thread compresses to a 30% ribbon at the top while the brainstormer panel rises into the bottom 70%, with an inset shadow ripple along the boundary.
+
+**Source of truth:** spec §5.6 (promote-to-project transition) and §5.7 (origin chat preservation). Phase 9 components under `ui/src/components/assistant/` are the consumption context.
+
+**Branch:** `nexus/design-system-migration`. Commit directly.
+
+---
+
+## Ownership boundaries
+
+**You may create or modify ONLY:**
+
+| Path | Action |
+|---|---|
+| `ui/src/components/assistant/PromoteTransition.tsx` | Create — the animation-owning component |
+| `ui/src/components/assistant/PromoteTransition.test.tsx` | Create |
+| `ui/src/components/assistant/BrainstormerPanel.tsx` | Create — the rising brainstormer form (goal, acceptance, gates, agents) |
+| `ui/src/components/assistant/BrainstormerPanel.test.tsx` | Create |
+| `ui/src/components/assistant/ActionStrip.tsx` | Modify — replace synchronous handoff with a `promoteState` flip that opens the PromoteTransition |
+| `ui/src/pages/PersonalAssistant.tsx` | Modify minimally — render the PromoteTransition overlay when active |
+| `ui/src/hooks/usePromoteToProject.ts` | Create — state machine + create-project mutation |
+| `ui/src/hooks/usePromoteToProject.test.ts` | Create |
+
+**You MUST NOT touch:**
+
+- Any other `ui/src/components/assistant/**` file besides the two listed above
+- `ui/src/components/frame/**`, `ui/src/components/Layout.tsx`
+- `ui/src/pages/Projects.tsx`, `ui/src/pages/ProjectDetail.tsx`, `ui/src/components/projects/**`
+- `ui/src/pages/ContentStudio.tsx`, `ui/src/pages/StudioWorkshopDetail.tsx`, `ui/src/components/studio/**`
+- `ui/src/App.tsx`
+- Any backend code
+
+---
+
+## Scope (strictly)
+
+**In Phase 12:**
+
+1. **Replace the existing synchronous promote handler** on `ActionStrip`'s "Promote to project" button with a state-machine-backed handler that transitions:
+ - `idle` → (click) → `prompting` (the transition animation running, brainstormer rising)
+ - `prompting` → (user confirms) → `creating` (API call to create project)
+ - `creating` → `done` (project created, banner appears linking to new project, transition collapses, chat restores)
+ - Any error → `error` (transition collapses, error toast, state resets to `idle`)
+2. **Animate the transition** per spec §5.6:
+ - 0–200ms: chat thread height compresses from 100% to 30% via CSS transform/grid or max-height + cubic-bezier(0.22, 1, 0.36, 1). Inset shadow (DESIGN.md Level 4) fades in along the bottom edge of the ribbon.
+ - 200–500ms: BrainstormerPanel slides up from the bottom into the lower 70%
+ - 500–700ms: small `SOURCE CONVERSATION` uppercase 1.4px-tracked silver label fades in above the compressed thread
+3. **BrainstormerPanel form** — spec §5.6:
+ - Goal (textarea)
+ - Acceptance criteria (textarea, one per line)
+ - Gates (list of checkboxes with default gate suggestions)
+ - Agents (PM auto-assigned; Engineer template picker)
+ - `[ Create project ]` volt-outline submit button at the bottom
+ - Cancel button (returns to `idle` state)
+4. **On successful create:**
+ - Animate the collapse: brainstormer slides down, chat restores to full height (reverse of steps 2.1–2.2 but faster, ~300ms)
+ - A persistent banner at the top of the chat reads `→ Project: ` with a link to `//projects//overview`
+ - The user stays in Assistant — does NOT auto-navigate
+5. **Implementation must be CSS-first, not a motion library.** Use CSS keyframes + transitions, not `motion/react`. Reasoning: one-off transition, no need for a new dependency. If you genuinely can't hand-roll the timing, report BLOCKED and the controller will reconsider adding Motion.
+
+**NOT in Phase 12:**
+
+- Server-side brainstormer changes. Reuse existing create-project endpoint.
+- Changes to the project create flow semantics.
+- Any changes to the Projects list or Project Detail pages — they just receive the new project normally.
+- Mobile full-screen takeover for the transition — Phase 15 handles mobile variant.
+- Reduced-motion fallback more elaborate than `prefers-reduced-motion: no animation`.
+
+---
+
+## File plan
+
+| File | Responsibility | Est. lines |
+|---|---|---|
+| `ui/src/hooks/usePromoteToProject.ts` | State machine: `{state, startPrompting, confirm, cancel, error}`. `confirm(form)` calls `projectsApi.create` and transitions through states. | ~120 |
+| `ui/src/hooks/usePromoteToProject.test.ts` | Tests each state transition, mocks `projectsApi.create` | ~140 |
+| `ui/src/components/assistant/BrainstormerPanel.tsx` | The form that appears in the bottom 70% during `prompting`. Controlled form, `onConfirm(payload)` → calls hook. | ~180 |
+| `ui/src/components/assistant/BrainstormerPanel.test.tsx` | Tests: renders fields, validation, submit payload | ~160 |
+| `ui/src/components/assistant/PromoteTransition.tsx` | The animation container. Props: `state`, `children` (the chat thread ribbon), `panelChildren` (BrainstormerPanel). CSS-driven. | ~150 |
+| `ui/src/components/assistant/PromoteTransition.test.tsx` | Tests: renders in idle/prompting/creating/done states, aria-live polite for status | ~140 |
+
+Modifications:
+- `ui/src/components/assistant/ActionStrip.tsx`: swap sync `onPromote` for `usePromoteToProject().startPrompting`, pass the hook's state down
+- `ui/src/pages/PersonalAssistant.tsx`: mount `` as an overlay that wraps the chat thread when `promoteState !== "idle"`, render `` inside it
+
+---
+
+## Implementation notes
+
+### State machine shape
+
+```ts
+type PromoteState =
+ | { kind: "idle" }
+ | { kind: "prompting"; conversationId: string }
+ | { kind: "creating"; conversationId: string; payload: BrainstormerPayload }
+ | { kind: "done"; projectSlug: string; projectName: string }
+ | { kind: "error"; message: string };
+
+interface BrainstormerPayload {
+ goal: string;
+ acceptanceCriteria: string[];
+ gates: string[];
+ engineerTemplateId?: string;
+}
+```
+
+### Animation — target keyframes
+
+```css
+/* Spec §5.6 — 700ms total. Prefers-reduced-motion: instant swap. */
+
+.promote-chat-ribbon {
+ /* idle → prompting: collapses from 100% to 30% over 200ms */
+ transition: max-height 200ms cubic-bezier(0.22, 1, 0.36, 1);
+ max-height: 100%;
+ overflow: hidden;
+}
+.promote-chat-ribbon[data-state="prompting"],
+.promote-chat-ribbon[data-state="creating"] {
+ max-height: 30vh;
+ box-shadow: inset 0 -16px 24px -12px rgba(0, 0, 0, 0.55);
+}
+
+.promote-panel {
+ /* 200ms delay, 300ms rise */
+ transition: transform 300ms cubic-bezier(0.22, 1, 0.36, 1),
+ opacity 300ms ease-out;
+ transform: translateY(100%);
+ opacity: 0;
+}
+.promote-panel[data-state="prompting"],
+.promote-panel[data-state="creating"] {
+ transform: translateY(0);
+ opacity: 1;
+ transition-delay: 200ms;
+}
+
+.source-conversation-label {
+ /* 500ms delay, 200ms fade */
+ transition: opacity 200ms ease-out;
+ opacity: 0;
+}
+.source-conversation-label[data-state="prompting"],
+.source-conversation-label[data-state="creating"] {
+ opacity: 1;
+ transition-delay: 500ms;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .promote-chat-ribbon,
+ .promote-panel,
+ .source-conversation-label {
+ transition: none !important;
+ }
+}
+```
+
+Inline these via Tailwind arbitrary values where possible. If keyframes need to live in a global stylesheet, add them to `ui/src/index.css` under a clearly-scoped block — but prefer inline Tailwind.
+
+### Integration with Phase 9's ActionStrip
+
+Phase 9 wired `onPromote` to call `assistantHandoff()` synchronously. Phase 12 replaces that:
+
+```tsx
+// ActionStrip.tsx — change
+const promote = usePromoteToProject(activeConversationId);
+// ...
+
+```
+
+PersonalAssistant.tsx renders the transition overlay:
+
+```tsx
+{promote.state.kind !== "idle" && promote.state.kind !== "done" && (
+
+
+
+)}
+{promote.state.kind === "done" && (
+
+ → Project: {promote.state.projectName}
+
+)}
+```
+
+### Accessibility
+
+- `aria-live="polite"` on the transition container so screen readers announce state changes
+- The BrainstormerPanel form fields have labels
+- ESC while in `prompting` or `creating` triggers `promote.cancel()`
+- Focus traps inside the BrainstormerPanel while active
+
+---
+
+## Acceptance criteria
+
+1. Clicking `⊕ Promote to project` on an active Assistant conversation triggers the 700ms animation per spec §5.6
+2. The chat thread compresses to 30%, brainstormer rises into 70%, source-conversation label fades in — in that order
+3. The BrainstormerPanel accepts Goal / Acceptance Criteria / Gates / Engineer Template, validates the Goal field as non-empty, submits via the existing project-create endpoint
+4. On success: transition collapses, chat restores, a `→ Project: ` banner appears at the top of the chat, user stays in Assistant
+5. On error: transition collapses, error toast shown, chat restores, promote button becomes clickable again
+6. ESC cancels the prompting state and restores chat
+7. `prefers-reduced-motion: reduce` disables the transition — brainstormer snaps in and out instead of animating
+8. Tests pass: `npx vitest run src/components/assistant/PromoteTransition.test.tsx src/components/assistant/BrainstormerPanel.test.tsx src/hooks/usePromoteToProject.test.ts`
+9. Typecheck clean on Phase 12 files
+10. No file outside declared ownership modified
+
+---
+
+## Report format
+
+- Status
+- Commit SHAs
+- Files created / modified
+- Tests added / passing
+- Typecheck result
+- Animation approach (CSS-only vs keyframes-file)
+- Concerns, deviations, self-review findings
diff --git a/docs/plans/2026-04-11-nexus-phase-13-settings-consolidation.md b/docs/plans/2026-04-11-nexus-phase-13-settings-consolidation.md
new file mode 100644
index 00000000..75243cc5
--- /dev/null
+++ b/docs/plans/2026-04-11-nexus-phase-13-settings-consolidation.md
@@ -0,0 +1,181 @@
+# Nexus Phase 13 — Settings Consolidation
+
+> Use `superpowers:test-driven-development`. Commit atomically per section.
+
+**Goal:** Collapse the current Paperclip nested instance-settings tree (`/instance/settings/general`, `/instance/settings/integrations`, …) into a **single-column scroll page** at `/instance/settings/general` (the URL stays so existing bookmarks still work) with ~8 section cards per `docs/specs/2026-04-11-nexus-layout-overhaul.md` §8. Move the Skill Aggregator, Routines, Telegram bridge, and re-run onboarding link into section cards. Drop the nested sub-routes from App.tsx (they redirect to the single page).
+
+**Source of truth:** spec §8 (Mode 4 — Settings). Phase 8 established the frame; the Settings destination in the IconRail already points at `/instance/settings/general`.
+
+**Branch:** `nexus/design-system-migration`.
+
+---
+
+## Ownership boundaries
+
+**You may create or modify ONLY:**
+
+| Path | Action |
+|---|---|
+| `ui/src/pages/InstanceSettings.tsx` (or equivalent single page) | Major rewrite into single-column scroll |
+| `ui/src/pages/instance/settings/**` or sub-pages | Modify — kill the child sub-pages, fold into sections |
+| `ui/src/components/settings/**` | Create (new subdir for section cards) |
+| `ui/src/App.tsx` | Routes may be removed (nested settings sub-routes) — see note |
+
+**Exception on App.tsx:** Phase 13 is the one phase where routing changes ARE owned by the phase, because the instance-settings tree is being collapsed. You may remove nested `/instance/settings/integrations`, `/instance/settings/adapters`, etc. routes and replace them with `` redirects so existing bookmarks still work. Do not touch any other App.tsx route.
+
+**You MUST NOT touch:**
+
+- `ui/src/pages/PersonalAssistant.tsx`, `ui/src/components/assistant/**` (Phase 9/12)
+- `ui/src/pages/Projects.tsx`, `ui/src/pages/ProjectDetail.tsx`, `ui/src/components/projects/**` (Phase 11)
+- `ui/src/pages/ContentStudio.tsx`, `ui/src/pages/StudioWorkshopDetail.tsx`, `ui/src/components/studio/**` (Phase 10)
+- `ui/src/components/Layout.tsx`, `ui/src/components/frame/**` (Phase 8)
+- Any backend code
+
+---
+
+## Scope (strictly)
+
+### Section cards per spec §8.1
+
+1. **WORKSPACE** — root directory, theme toggle (dark/light), re-run onboarding link
+2. **LOCAL AI** — Hermes provider, Whisper STT model, Piper TTS voice
+3. **CLOUD PROVIDERS** — Anthropic / OpenAI API keys (masked), Puter.js status
+4. **SKILLS** — Skill Aggregator browse/install/assign (currently wherever it lives; Phase 13 folds it in)
+5. **ROUTINES** — list of workspace routines with edit/pause (currently the top-level `/routines` route — folded in; that route stays for backwards compat)
+6. **TELEGRAM BRIDGE** — bot token, allowed chat IDs
+7. **ABOUT** — Nexus version, fork info, MIT license
+8. **DANGER ZONE** — reset workspace, delete all conversations
+
+Each card: 1px charcoal border, 8px radius, 24px padding, transparent fill. Uppercase 1.4px-tracked title in silver + hairline rule. Sections listed vertically with 24px gap.
+
+### What to preserve from the existing nested pages
+
+- All existing form state, field validation, save handlers
+- The API endpoints each section uses (likely `instanceSettingsApi`, `skillsApi`, `routinesApi`, etc.)
+- Access control checks (though single-user self-hosted, some checks may still exist)
+
+### What to kill from the chrome
+
+- The old left-rail `InstanceSidebar` that showed nested settings sub-links — already unmounted by Phase 8 but the component file may linger; delete it in Phase 13 along with its test file
+- Any `/instance/settings/*` sub-routes other than `/general`
+
+---
+
+## File plan
+
+| File | Action |
+|---|---|
+| `ui/src/pages/InstanceSettings.tsx` (or the existing main settings page file) | Rewrite into single-column scroll that renders 8 section cards |
+| `ui/src/components/settings/WorkspaceSection.tsx` | Create |
+| `ui/src/components/settings/LocalAISection.tsx` | Create |
+| `ui/src/components/settings/CloudProvidersSection.tsx` | Create |
+| `ui/src/components/settings/SkillsSection.tsx` | Create (embeds Skill Aggregator) |
+| `ui/src/components/settings/RoutinesSection.tsx` | Create (reuses routines list components) |
+| `ui/src/components/settings/TelegramSection.tsx` | Create |
+| `ui/src/components/settings/AboutSection.tsx` | Create |
+| `ui/src/components/settings/DangerZoneSection.tsx` | Create |
+| Each section + `.test.tsx` for tests | Create tests |
+| `ui/src/components/InstanceSidebar.tsx` | Delete (was a Phase 8 legacy candidate) |
+| `ui/src/App.tsx` | Strip nested `/instance/settings/*` sub-routes except `/general`; add redirect stubs |
+
+---
+
+## Implementation notes
+
+### Single-page layout
+
+```tsx
+export function InstanceSettings() {
+ return (
+
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+ );
+}
+```
+
+### Section card skeleton
+
+Every section uses the same shell:
+
+```tsx
+export function SettingsSection({ title, children }: { title: string; children: ReactNode }) {
+ return (
+
+
+
+ {title}
+
+
+
+
{children}
+
+ );
+}
+```
+
+Consider extracting `SettingsSection` as a shared primitive in `components/settings/SettingsSection.tsx` and importing it from all 8 section files — that's 9 files total but keeps each section shell 1-line and eliminates drift.
+
+### Routines fold-in strategy
+
+The existing `/routines` top-level route and its page stay. The Routines section in Settings either:
+- (a) renders a compact read-only list of routines with an "open full routines page" link that navigates to `/routines`, OR
+- (b) renders the full interactive routines UI inline
+
+**Recommendation (a)** — single responsibility per page. Phase 16 decides whether to delete `/routines`.
+
+### Skills fold-in
+
+Same pattern: the Skills section embeds the Skill Aggregator UI inline (browse + installed count + assignments) OR links out to a Skill Aggregator page if one exists. Read the codebase to find where Skills currently live. If there's an existing route, link to it from the section; if it's unrouted, embed the component.
+
+---
+
+## Acceptance criteria
+
+1. `/instance/settings/general` renders a single-column scroll page with 8 section cards
+2. Any previous `/instance/settings/` URL (e.g., `/instance/settings/integrations`) redirects to `/instance/settings/general`
+3. The theme toggle in the WORKSPACE section switches between light/dark and persists
+4. API key fields mask their values and never log them
+5. All new components have tests using the Phase 8 pattern
+6. `npx vitest run src/components/settings/` passes
+7. `npx tsc --noEmit 2>&1 | grep -E "(settings/|InstanceSettings\.tsx)"` clean
+8. `InstanceSidebar.tsx` deleted (and any other dead settings chrome files)
+9. No file outside declared ownership touched
+10. Existing settings functionality preserved — nothing breaks
+
+---
+
+## Before you begin
+
+Read:
+- `ui/src/pages/InstanceSettings.tsx` (or whatever the main settings page is named)
+- Any nested settings sub-pages (grep for `InstanceSettings*.tsx` or look under `ui/src/pages/instance/settings/`)
+- `ui/src/api/instanceSettings.ts` for the API shape
+- The current routines list component and skills aggregator UI locations
+
+---
+
+## Report format
+
+- Status
+- Commit SHAs
+- Files created / modified / deleted
+- Tests added / passing
+- Typecheck result
+- Nested sub-routes removed and their redirect targets
+- Skill Aggregator fold-in strategy (inline vs link-out)
+- Routines section strategy (compact list vs full inline)
+- Concerns, deviations, self-review
diff --git a/docs/plans/2026-04-11-nexus-phase-14-voice-cmdk-globalization.md b/docs/plans/2026-04-11-nexus-phase-14-voice-cmdk-globalization.md
new file mode 100644
index 00000000..71e89b29
--- /dev/null
+++ b/docs/plans/2026-04-11-nexus-phase-14-voice-cmdk-globalization.md
@@ -0,0 +1,179 @@
+# Nexus Phase 14 — Voice + ⌘K Globalization
+
+> Use `superpowers:test-driven-development`. Commit atomically.
+
+**Goal:** Lift the voice capture state out of `ChatInput`'s internal `VoiceMicButton` into a shared context so the top-strip `GlobalMicButton` becomes functional from any route, with all speech queued to the Assistant inbox per spec §5.5. Replace the `CmdKButton` shim (which dispatches synthetic Meta+K keydowns) with a real `CommandPaletteContext` that exposes an imperative open method, and expand the command palette's search index to cover conversations, projects, issues, agents, recipes, settings, and workshops per spec §10.1.
+
+**Source of truth:** spec §4.2 (GlobalMicButton states), §5.5 (voice routing from non-Assistant modes), §10.1 (⌘K palette), §10.3 (voice as global affordance), §10.4 (single notification surface).
+
+**Branch:** `nexus/design-system-migration`.
+
+---
+
+## Ownership boundaries
+
+**You may create or modify ONLY:**
+
+| Path | Action |
+|---|---|
+| `ui/src/context/VoiceContext.tsx` | Create — new context |
+| `ui/src/context/VoiceContext.test.tsx` | Create |
+| `ui/src/context/CommandPaletteContext.tsx` | Create — new context |
+| `ui/src/context/CommandPaletteContext.test.tsx` | Create |
+| `ui/src/components/frame/GlobalMicButton.tsx` | Modify — wire to VoiceContext |
+| `ui/src/components/frame/GlobalMicButton.test.tsx` | Modify — add functional tests |
+| `ui/src/components/frame/CmdKButton.tsx` | Modify — wire to CommandPaletteContext, remove synthetic keydown |
+| `ui/src/components/frame/CmdKButton.test.tsx` | Modify |
+| `ui/src/components/CommandPalette.tsx` | Modify — consume CommandPaletteContext, extend search index |
+| `ui/src/components/ChatInput.tsx` | Modify — lift voice state to VoiceContext |
+| `ui/src/pages/PersonalAssistant.tsx` | Modify — read voice queue from VoiceContext when navigating to assistant |
+| `ui/src/main.tsx` or equivalent | Modify — mount the new providers in the provider stack |
+| `ui/src/hooks/useKeyboardShortcuts.ts` | Modify — fix the pre-existing destructure bug (onSearch missing from destructure) AND wire onSearch to CommandPaletteContext |
+
+**You MUST NOT touch:**
+
+- Any other Phase 8–13 owned files
+- `ui/src/App.tsx` routes
+- Backend voice pipeline or STT/TTS endpoints (v1.6 already ships them — consume as-is)
+- `@/lib/router`
+
+---
+
+## Scope
+
+### 1. VoiceContext
+
+**Responsibilities:**
+- Ownership of the current `MediaStream | null` for the microphone
+- Recording state: `idle` / `listening` / `speaking` (matching the GlobalMicButton states)
+- Transcription buffer (latest transcript from Whisper STT)
+- Queue: when voice is captured from a non-Assistant route, append to a pending queue; when the user navigates to `/assistant`, drain the queue into a new user message
+- Emits a `hasQueuedVoice` boolean that other components (e.g., the Assistant icon volt dot) can read
+
+**Consumers:**
+- `GlobalMicButton` — renders idle/listening/speaking per context state; tap cycles through record → stop → queue
+- `ChatInput` inside PersonalAssistant — subscribes to the VoiceContext stream instead of owning its own
+- `PersonalAssistant` — on mount, drains `VoiceContext.queue`
+
+**What lifts out of ChatInput:**
+Currently `VoiceMicButton` inside `ChatInput` owns:
+- `navigator.mediaDevices.getUserMedia` call
+- MediaStream state
+- MediaRecorder and audio chunk buffer
+- Whisper transcription invocation
+- Silence-detection / auto-send behavior
+
+All of this moves to VoiceContext. ChatInput's internal mic button becomes a thin consumer that reads `VoiceContext.state` and calls `VoiceContext.startListening()` / `stopListening()`.
+
+### 2. CommandPaletteContext
+
+**Responsibilities:**
+- `open: boolean`, `setOpen(next: boolean)`, `toggle()`
+- Keyboard listener for Cmd+K / Ctrl+K registered once at the provider level (replaces the existing one inside `CommandPalette.tsx`'s `useEffect`)
+
+**Consumers:**
+- `CmdKButton` — calls `setOpen(true)` on click. No more synthetic keydown dispatch.
+- `CommandPalette` — reads `open` from context, `setOpen(false)` to close
+- `useKeyboardShortcuts.onSearch` — calls `setOpen(true)` instead of synthetic keydown
+
+**Extending the search index (spec §10.1):**
+- Conversations (by title, by recent message text snippet)
+- Projects (by name)
+- Issues (by title, by project)
+- Agents (by name, by role)
+- Recipes (by name, by tag — **stubbed if recipe API doesn't exist yet; that's a v1.8 feature**)
+- Settings (by section name — map the 8 Phase 13 section titles to `/instance/settings/general#` anchors)
+- Studio workshops (by name — map the 9 WorkshopSlugs to `/content-studio/`)
+- Commands: `New project`, `New conversation`, `Re-run onboarding`, etc.
+
+Grouped with uppercase 1.4px-tracked category headers. Selected result has a 2px volt left border + pale-yellow text. Keyboard nav: arrow keys + enter + escape.
+
+### 3. Fix `useKeyboardShortcuts.ts:12-17` destructure bug
+
+Phase 6/11 reviews flagged that `onSearch` is referenced at line 25 but never destructured at lines 12–17. Fix: add `onSearch` to the destructure. Once fixed, wire it to `commandPalette.setOpen(true)` through a context consumer (the shortcut hook itself may not consume contexts directly — escalate and resolve).
+
+### 4. IconRail dot wiring — keep Phase 11's integration
+
+The volt dot on the Assistant icon (from Phase 11's `useGateIndicator`) already exists and works. Phase 14 optionally extends it to also light up when there's queued voice (`VoiceContext.hasQueuedVoice`). If you add this, make the aria-label more precise: "Assistant (pending gates and queued voice)" or split into two overlays.
+
+---
+
+## Implementation notes
+
+### Provider stack order
+
+```
+
+
+
+
+ <-- NEW (Phase 14)
+ <-- NEW (Phase 14)
+
+
+```
+
+VoiceContext must be above CompanyContext because voice state is user-level, not company-level. CommandPaletteContext is above Router because the shortcut handler is global.
+
+### CmdKButton shim removal
+
+Before:
+```tsx
+const handleClick = () => {
+ const event = new KeyboardEvent("keydown", { key: "k", metaKey: true, bubbles: true });
+ document.dispatchEvent(event);
+};
+```
+
+After:
+```tsx
+const { setOpen } = useCommandPalette();
+const handleClick = () => setOpen(true);
+```
+
+Delete the shim comment block at the top of the file; replace with a note that Phase 14 landed the real wiring.
+
+### GlobalMicButton real wiring
+
+Before (Phase 8 scaffold):
+```tsx
+export function GlobalMicButton({ state = "idle", onClick }: GlobalMicButtonProps) {
+```
+
+After:
+```tsx
+export function GlobalMicButton() {
+ const { state, toggleListening } = useVoice();
+ // render idle/listening/speaking based on state
+}
+```
+
+Remove the `state` prop and `onClick` prop — they were Phase 8 scaffolding for when no real voice pipeline was available. Tests need updating.
+
+---
+
+## Acceptance criteria
+
+1. Tapping the `GlobalMicButton` from ANY route starts listening and transitions through states
+2. Speech captured on non-Assistant routes queues to `VoiceContext.queue` and surfaces as queued-voice indicator
+3. Navigating to `/assistant` drains the queue into a new user message that streams through the existing chat pipeline
+4. `Cmd+K` / `Ctrl+K` from anywhere opens the command palette via context, not synthetic keydown
+5. Clicking the `CmdKButton` opens the palette via context
+6. The palette searches across conversations, projects, issues, agents, settings sections, and workshops (recipes stubbed if no API)
+7. `useKeyboardShortcuts.onSearch` is wired correctly (destructure bug fixed)
+8. `ChatInput`'s internal voice button consumes `VoiceContext` instead of owning its own state
+9. All new tests pass; all existing frame tests still pass
+10. Typecheck clean on Phase 14 files
+
+---
+
+## Report format
+
+- Status
+- Commit SHAs
+- Files created / modified
+- Tests added / passing
+- Voice pipeline integration notes — where did the `getUserMedia` and MediaRecorder calls end up? What backend endpoints are consumed?
+- Command palette search extensions — which sources are live and which are stubbed?
+- IconRail dot extension — did you extend it for queued voice, or only gates?
+- Concerns, deviations, self-review
diff --git a/docs/plans/2026-04-11-nexus-phase-15-mobile.md b/docs/plans/2026-04-11-nexus-phase-15-mobile.md
new file mode 100644
index 00000000..9e33a9a7
--- /dev/null
+++ b/docs/plans/2026-04-11-nexus-phase-15-mobile.md
@@ -0,0 +1,131 @@
+# Nexus Phase 15 — Mobile Parity
+
+> Use `superpowers:test-driven-development`. Commit atomically.
+
+**Goal:** Bring the new frame to mobile breakpoint `< 768px`. Replace the legacy `MobileBottomNav` with a 4-destination bottom tab bar matching the desktop IconRail destinations (Assistant, Studio, Projects, Settings). Collapse the desktop slide-overs (History, Memory) to full-screen sheets on mobile. Make the project tab strip scroll horizontally without shrinking. Adapt the promote-to-project transition to a full-screen takeover on mobile instead of the split layout.
+
+**Source of truth:** spec §9 (Mobile).
+
+**Branch:** `nexus/design-system-migration`.
+
+---
+
+## Ownership boundaries
+
+**You may create or modify ONLY:**
+
+| Path | Action |
+|---|---|
+| `ui/src/components/frame/MobileTabBar.tsx` | Create |
+| `ui/src/components/frame/MobileTabBar.test.tsx` | Create |
+| `ui/src/components/Layout.tsx` | Modify — swap `MobileBottomNav` for `MobileTabBar` |
+| `ui/src/components/assistant/HistorySheet.tsx` | Modify — add mobile full-screen variant |
+| `ui/src/components/assistant/MemorySheet.tsx` | Modify — add mobile full-screen variant |
+| `ui/src/components/assistant/PromoteTransition.tsx` | Modify — mobile full-screen takeover variant |
+| `ui/src/components/projects/BuilderTabStrip.tsx` | Modify — horizontal scroll on mobile |
+| `ui/src/components/frame/TopStrip.tsx` | Modify if needed — mobile tweaks to cluster spacing |
+
+**You MUST NOT touch:**
+
+- Phase 8 icon rail semantics (desktop behavior stays)
+- Phase 9 Assistant thread layout (desktop stays)
+- Phase 10 Studio grid (mobile already collapses to 1-col)
+- Phase 11 Project card layout (desktop stays)
+- `ui/src/App.tsx`
+
+---
+
+## Scope
+
+### 1. MobileTabBar component
+
+Replacement for `MobileBottomNav`. Renders 4 tab links at the bottom of the viewport:
+- Assistant (MessageCircle)
+- Studio (Sparkles)
+- Projects (FolderKanban)
+- Settings (Settings)
+
+Specs:
+- `fixed bottom-0 left-0 right-0 h-14` (56px tall)
+- `border-t border-border bg-background` — charcoal top border, pure black fill
+- Each tab is a flex column: icon on top (20×20), label below (10px uppercase 1.4px tracking)
+- Silver default, volt active with a 2px volt bar above the icon
+- Focus-visible styles per DESIGN.md
+- Safe-area aware: `pb-[env(safe-area-inset-bottom)]`
+- Active state derivation: same regex patterns as IconRail's `isActive` functions — factor out if possible
+
+Delete the existing `MobileBottomNav.tsx` after confirming the new bar covers its behavior. If `MobileBottomNav` has non-destination features (e.g., a "new issue" FAB), report them and the controller will decide whether to keep or drop.
+
+### 2. Mobile slide-over → full-screen sheet
+
+Phase 9's `HistorySheet` and `MemorySheet` are desktop slide-overs at 320px / 340px. On mobile:
+- Take the full viewport width
+- Full viewport height below the top strip (48px)
+- Close button in the top-right of the sheet
+- Swipe-down-to-close gesture (optional — acceptable if CSS-only)
+
+Implementation: add a `useMediaQuery("(min-width: 768px)")` check; when mobile, render different class names that go full-screen. Do NOT duplicate the component — same file, conditional rendering.
+
+### 3. Promote-to-project mobile takeover
+
+Phase 12's `PromoteTransition` split-screen layout (30% chat ribbon / 70% brainstormer) doesn't fit on mobile. On mobile, the brainstormer **completely covers the chat** — slides up to 100% viewport height instead of 70%.
+
+Implementation: same `PromoteTransition.tsx` file, conditional max-height and translate values based on `useMediaQuery`. Tests need a mobile case.
+
+### 4. BuilderTabStrip horizontal scroll
+
+Phase 11's Project tab strip has 7 tabs that don't fit on narrow mobile. Spec §9.1 says "Project sub-tabs become a horizontally-scrolling strip under the header, no shrinking."
+
+Implementation: on mobile, wrap the tab list in a `flex overflow-x-auto` container with `scroll-snap-type: x mandatory` for tab-sized snapping. Add edge fades (`mask-image` or left/right gradient overlays) so users see there's more content. Tests need a mobile case.
+
+### 5. TopStrip mobile polish
+
+On mobile, the top strip should:
+- Keep the 48px height
+- Drop the `⌘K` text label and show only the kbd glyph (already minimal — verify)
+- Keep the mic button
+- Keep the mode breadcrumb but allow it to truncate if too long
+- Consider whether to hide the breadcrumb entirely on very narrow screens
+
+---
+
+## Implementation notes
+
+### `useMediaQuery("(min-width: 768px)")` already exists
+
+`ui/src/hooks/useMediaQuery.ts` (or similar) is used throughout the codebase. Consume it rather than adding a new media-query hook.
+
+### Mobile breakpoint is single — 768px
+
+Per spec §9.2: "Single breakpoint: `>= 768px` is desktop frame, `< 768px` is mobile frame." Don't introduce intermediate breakpoints; everything is binary.
+
+### Tailwind responsive classes vs runtime media query
+
+For simple show/hide behavior, use Tailwind's `hidden md:block` / `md:hidden`. For layout variants that need JS logic (like the promote transition's translate values), use `useMediaQuery`. Prefer CSS when possible.
+
+---
+
+## Acceptance criteria
+
+1. On a viewport `< 768px`, the new MobileTabBar renders with 4 destinations matching the IconRail; clicking each navigates correctly
+2. The 56px desktop IconRail is hidden on mobile (already `hidden md:flex` from Phase 8 — verify)
+3. HistorySheet and MemorySheet render full-screen on mobile
+4. The promote-to-project transition completely covers the chat on mobile
+5. BuilderTabStrip scrolls horizontally on mobile with no tabs cropped
+6. TopStrip remains 48px and functional on mobile
+7. Safe-area insets (notch, home indicator) respected
+8. `MobileBottomNav` file deleted or clearly documented as dead
+9. All existing frame + assistant + projects tests pass
+10. Mobile-specific tests added
+
+---
+
+## Report format
+
+- Status
+- Commit SHAs
+- Files created / modified / deleted
+- Mobile tests added
+- Features carried over from MobileBottomNav (if any)
+- Gesture support (swipe-down-to-close etc.)
+- Concerns, deviations, self-review
diff --git a/docs/plans/2026-04-11-nexus-phase-16-cleanup.md b/docs/plans/2026-04-11-nexus-phase-16-cleanup.md
new file mode 100644
index 00000000..226d124a
--- /dev/null
+++ b/docs/plans/2026-04-11-nexus-phase-16-cleanup.md
@@ -0,0 +1,161 @@
+# Nexus Phase 16 — Cleanup Pass
+
+> **Sequential, not parallel.** This phase touches everything. Use `superpowers:test-driven-development` only for any non-trivial logic change; most of Phase 16 is deletion and find-replace.
+
+**Goal:** Delete the dead chrome code that Phase 8 unmounted, sweep the UI copy for "company" → "workspace" (or deletion), fix accumulated minor issues surfaced by review cycles across Phases 8–15, and perform a final visual QA pass.
+
+**Source of truth:** spec §2 "Killed list" and the list of known minor issues logged across Phase 8–15 reviews.
+
+**Branch:** `nexus/design-system-migration`. This is the LAST phase of the structural overhaul.
+
+---
+
+## Ownership boundaries
+
+**Phase 16 is intentionally broad.** You may touch any file in `ui/src/` that contains dead chrome code, legacy vocabulary, or accumulated cruft. However:
+
+- Do NOT touch `server/`, `packages/`, or `cli/` — the backend vocabulary stays per PROJECT.md's upstream-sync constraint
+- Do NOT touch any test file unless its assertions reference dead code you're deleting
+- Do NOT introduce new features — this is strictly subtraction
+- Do NOT touch the `.planning/` directory
+
+If in doubt whether a file is dead, run `grep -r "" ui/src/` — if there are zero remaining consumers, it's dead. If there's any consumer, escalate before deleting.
+
+---
+
+## Scope
+
+### 1. Delete dead chrome files (spec §2 "Killed list")
+
+Files Phase 8 unmounted but didn't delete. Verify each has zero consumers, then delete:
+
+- `ui/src/components/ChatPanel.tsx` — the old 380px slide-in chat panel
+ - **HAZARD:** `ChatMessageList` is hard-bound to `ChatPanelContext` (Phase 9 note). Before deleting `ChatPanelContext.tsx`, migrate `ChatMessageList` off it. See "ChatMessageList migration" below.
+- `ui/src/context/ChatPanelContext.tsx` — only after `ChatMessageList` migration
+- `ui/src/components/PropertiesPanel.tsx` — unless a page-level usage still exists (grep first)
+- `ui/src/components/Sidebar.tsx` — the 280px left sidebar
+- `ui/src/components/InstanceSidebar.tsx` — already slated by Phase 13; confirm deleted
+- `ui/src/components/BreadcrumbBar.tsx` — replaced by ModeBreadcrumb
+- `ui/src/components/MobileBottomNav.tsx` — replaced by MobileTabBar (Phase 15)
+
+For each deletion, also delete the corresponding `.test.tsx` / `.stories.tsx` file and search the codebase for any remaining imports. If any import survives, fix it in the same commit.
+
+### 2. ChatMessageList migration
+
+Phase 9 flagged: `ChatMessageList.tsx` reads `activeConversationId` / `scrollToMessageId` from `ChatPanelContext`. Phase 16 must migrate these reads OFF the legacy context before deleting it.
+
+**Migration strategy:** introduce `AssistantChatContext` in `ui/src/context/AssistantChatContext.tsx` that owns `activeConversationId` and `scrollToMessageId`. Update `ChatMessageList` to consume it. Update `PersonalAssistant` and any other consumers to provide the new context. Delete `ChatPanelContext` only AFTER all consumers moved.
+
+Alternative: if the state is better owned by URL params (`/:conversationId` + a query param for scroll target), refactor that way. Cleaner but more invasive.
+
+### 3. Vocabulary sweep — "company" → "workspace"
+
+Per spec §2: "Any text containing 'company', 'companies', 'workspace member', 'tenant' → vocabulary cleanup."
+
+Scope: **display-only strings** in `ui/src/`. Do NOT rename code symbols. The UI says "workspace" while the backend keeps `company` / `companyId` identifiers forever (upstream sync constraint from PROJECT.md).
+
+- Find all UI strings with "company" / "Company" / "companies" / "Companies" / "Tenant" / "tenant"
+- Replace with "workspace" / "Workspace" / "workspaces" / "Workspaces"
+- Exception: if `@paperclipai/branding` already has a `VOCAB.company` token that resolves to "Workspace", just use that — don't hand-type the replacement
+- Exception: error messages from the backend that include "company" — leave alone, they come from the server
+
+### 4. Minor issues accumulated across Phase 8–15 reviews
+
+These were flagged during reviews but deferred to Phase 16. Fix each in a clearly-labelled commit:
+
+1. **`useKeyboardShortcuts.ts:12-17` destructure bug** — the type declares `onSearch?: () => void` but the destructure omits it, so the call at line 25 always resolves to undefined. If Phase 14 hasn't already fixed this, fix it here.
+2. **`GlobalMicButton.test.tsx` double-render warning** — the test's `render(state)` helper is called 3 times in one test without unmounting between. Cosmetic but noisy. Refactor to unmount between renders or split into 3 separate tests.
+3. **`IconRail.tsx` Projects umbrella regex repetition** — 10 regexes with `||`. Extract to a constant list + `.some()` helper. Coordinate with Phase 11's note about duplicated-umbrella-list-drift.
+4. **`PersonalAssistant.tsx` message-thread fixed height** `calc(100vh - 320px)` — brittle. Investigate replacing with a flex layout that derives height from flex parent, now that the surrounding chrome is stable.
+5. **`Projects.tsx` phase / costBurnedCents / activeAgentsCount gaps** — still rendering as null. Phase 16 doesn't fix the data gaps (that's Phase 11.5), but it removes the `// TODO` comments that were added by Wave 2.5 IF Phase 11.5 has already landed.
+6. **Spec drift from Phase 10 commit 5 missing body** — optional: amend the commit or just leave it. Low priority.
+7. **Phase 10 `PresentationPanel` fold-in** — Wave 2.5 already handled this. If Phase 16 finds `PresentationPanel` is dead code for any reason (e.g. spec changed), delete it.
+
+### 5. Top-level route deletion decisions
+
+Phase 11 demoted several routes to per-project tabs but kept them at the top level for backwards compat:
+
+- `/issues` → now a per-project tab. Delete?
+- `/agents` → now a per-project tab. Delete?
+- `/approvals` → now a per-project `/gates` tab. Delete?
+- `/costs` → now a per-project tab. Delete?
+- `/activity` → now a per-project tab. Delete?
+- `/inbox` → replaced by Assistant dot + ⌘K. Delete?
+- `/goals` → folded into Overview milestone checklist. Delete?
+- `/routines` → moved to Settings in Phase 13. Delete?
+- `/org` → now a per-project tab. Delete?
+- `/dashboard` → replaced by Assistant home greeting. Delete?
+- `/convert` → folded into Studio. Delete?
+
+**Decision rule:** delete IF the corresponding Phase 11 / 13 / 15 replacement fully covers the use case AND there are no external bookmarks / docs pointing at the route. If in doubt, keep and flag for user decision.
+
+**Safer default:** keep all of them for now as backwards-compat redirects. If Phase 16 finds clear dead code (component exists but its route is already a `` to a new home), delete. If the page still renders real UI, keep.
+
+### 6. Visual QA pass
+
+Walk every top-level route in the dev server, compare against DESIGN.md principles and the layout spec. Capture issues as a short list and either fix inline or file follow-up tickets. No code changes unless issues found.
+
+Routes to walk:
+- `/assistant` (with and without active conversation)
+- `/content-studio` + each workshop
+- `/projects` (populated and empty states)
+- `/projects//overview` + each tab
+- `/instance/settings/general`
+- `/convert` (legacy)
+- `/approvals` (legacy)
+
+---
+
+## Sequential commits (suggested)
+
+This phase is naturally sequential. Each commit is reviewable independently.
+
+1. `refactor(nexus): migrate ChatMessageList off ChatPanelContext (phase 16)`
+2. `refactor(nexus): delete legacy chrome files (phase 16)` — Sidebar, InstanceSidebar, ChatPanel, ChatPanelContext, PropertiesPanel, BreadcrumbBar, MobileBottomNav
+3. `refactor(nexus): vocabulary sweep company -> workspace (phase 16)`
+4. `fix(nexus): useKeyboardShortcuts destructure bug (phase 16)`
+5. `refactor(nexus): fix GlobalMicButton test double-render warning (phase 16)`
+6. `refactor(nexus): extract shared PROJECTS_UMBRELLA constant (phase 16)`
+7. `refactor(nexus): PersonalAssistant message-thread flex layout (phase 16)`
+8. `refactor(nexus): delete dead top-level routes (phase 16)` — only routes whose components are pure redirects
+9. `docs(nexus): visual QA findings (phase 16)` — any issues found during QA, either fixed inline or filed
+
+Group commits together only when they touch the same file for the same reason.
+
+---
+
+## Acceptance criteria
+
+1. All files listed in §1 are deleted or clearly documented as intentionally kept
+2. `ChatMessageList` no longer consumes `ChatPanelContext`
+3. Zero UI strings say "company" or "Company" (except via `VOCAB.company` token)
+4. Known minor issues from §4 are fixed or tickets filed
+5. Full test suite passes: `npx vitest run src/components/frame/ src/components/assistant/ src/components/studio/ src/components/projects/ src/components/settings/ src/hooks/`
+6. Typecheck clean on all Wave 1–3 files
+7. Visual QA pass findings logged (either fixed inline or filed)
+8. The commit chain on `nexus/design-system-migration` is complete and ready for merge / PR review
+
+---
+
+## Non-scope
+
+- Backend vocabulary changes (`company` identifiers stay — upstream sync)
+- Phase 11.5 per-project-scoping work (separate plan)
+- Phase 4–7 visual migration polish (can run before or after Phase 16, independent)
+- New features
+- Performance optimization
+- Test coverage expansion beyond what's needed to protect deletions
+
+---
+
+## Report format
+
+- Status (DONE / DONE_WITH_CONCERNS / BLOCKED)
+- Commit SHAs with one-line summaries
+- Files deleted (with confirmation of zero consumers)
+- Files moved / refactored (e.g. ChatMessageList migration)
+- Vocabulary sweep count (how many strings changed)
+- Minor issues fixed vs deferred
+- Visual QA findings
+- Final typecheck + test suite result
+- Concerns