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>
This commit is contained in:
parent
2dbf281117
commit
3f0d3377e7
3 changed files with 1027 additions and 0 deletions
357
docs/plans/2026-04-11-nexus-phase-10-studio.md
Normal file
357
docs/plans/2026-04-11-nexus-phase-10-studio.md
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
# Nexus Phase 10 — Studio Mode Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:test-driven-development`. Commit atomically per logical unit.
|
||||
|
||||
**Goal:** Refactor ContentStudio from a 7-tab page into an **8-card workshop grid** that folds in the existing ConvertPage as the 8th workshop. Add a freeform prompt input at the bottom of the Studio home that routes to a workshop based on intent. Build a workshop detail layout (two columns: params left, preview right) for when a user selects a workshop.
|
||||
|
||||
**Source of truth:** `docs/specs/2026-04-11-nexus-layout-overhaul.md` **§6** (Mode 2 — Studio). Read §6.1–§6.5 before starting. DESIGN.md governs visuals.
|
||||
|
||||
**Branch:** `nexus/design-system-migration`. Commit directly.
|
||||
|
||||
---
|
||||
|
||||
## Ownership boundaries
|
||||
|
||||
**You may create or modify ONLY:**
|
||||
|
||||
| Path | Action |
|
||||
|---|---|
|
||||
| `ui/src/pages/ContentStudio.tsx` | Modify (major rewrite: from tabs to workshop grid) |
|
||||
| `ui/src/pages/StudioWorkshopDetail.tsx` | Create (new routed view for a single workshop) |
|
||||
| `ui/src/components/studio/**` | Create (new subdir for Phase 10 components) |
|
||||
| `ui/src/pages/ConvertPage.tsx` | MAY read and reference, but DO NOT DELETE. Phase 16 handles dead-file deletion. Leave the file in place; it just stops being linked from the rail. |
|
||||
|
||||
**You MUST NOT touch:**
|
||||
|
||||
- `ui/src/App.tsx` — routing is controller-owned. If you need new sub-routes for workshops (e.g. `/content-studio/:workshopSlug`), report them in your final report; the controller wires them up after Wave 2.
|
||||
- Existing content generator backends under `server/` or `packages/`. Phase 10 is purely a frontend IA reshape.
|
||||
- Any file outside `ui/src/` except as noted.
|
||||
- `ui/src/components/Layout.tsx`, `ui/src/components/frame/*` — Phase 8 territory, do not touch.
|
||||
- Other phases' owned paths (`ui/src/pages/PersonalAssistant.tsx`, `ui/src/pages/Projects.tsx`, `ui/src/pages/ProjectDetail.tsx`, `ui/src/components/assistant/**`, `ui/src/components/projects/**`).
|
||||
|
||||
**Existing code you may reuse (read-only):**
|
||||
|
||||
- The 7 existing content-generator subcomponents that the current `ContentStudio.tsx` references for its tabs. Read `ContentStudio.tsx` first to find them. You'll reuse them as the body content of each workshop's detail view, unchanged.
|
||||
- `ui/src/pages/ConvertPage.tsx` — you'll embed its main content (not the whole page shell) as the body of the Convert workshop detail view. Do not delete the file; the route path `/convert` still works in Phase 10 for backwards compatibility.
|
||||
- `ui/src/components/frame/*` — Phase 8 patterns (test shape, semantic tokens, focus-visible styles, cn helper, slide-overs if you need any).
|
||||
|
||||
---
|
||||
|
||||
## Scope (strictly)
|
||||
|
||||
**In Phase 10:**
|
||||
|
||||
1. **Studio home rewrite** — replace the 7-tab layout in `ContentStudio.tsx` with a grid of **8 workshop cards**:
|
||||
- `diagrams`, `icons`, `themes`, `wallpapers`, `documents`, `brand-kits`, `social`, `convert`
|
||||
- 3-column grid on `>= 1024px`, 2-column on `>= 640px`, 1-column below
|
||||
- Card: 1px charcoal border, 8px radius, transparent fill on idle, `bg-card` near-black on hover, 24px padding
|
||||
- Title in Inter 700 uppercase 24px, subtitle in silver 14px, Lucide icon in volt top-right
|
||||
2. **Convert workshop fold-in** — the 8th card routes to the existing ConvertPage body rendered inside a Studio workshop detail shell. The `/convert` top-level route must continue to work (because users might have it bookmarked) but the IconRail's Studio destination is the canonical path forward.
|
||||
3. **Workshop detail view** at `StudioWorkshopDetail.tsx` — a new routed view that renders one workshop at a time:
|
||||
- Two-column layout on `>= 1024px`: left = params/prompt input (40% width), right = preview (60% width)
|
||||
- Mobile stacks to a single column
|
||||
- Top of page shows `STUDIO / WORKSHOP-NAME` as the page title (the actual mode-breadcrumb is in TopStrip from Phase 8; this is an in-page heading)
|
||||
- Action bar at the bottom of the right column: `Save`, `Export`, `Send to Assistant`
|
||||
4. **StudioPromptBar** — freeform text input at the bottom of the Studio home. Placeholder: `Or just describe it: "I need a 1920×1080 wallpaper of …"`. Submits to a classifier (§6.5 of the spec) that maps intent to a workshop slug. If routing succeeds, navigate to `/content-studio/{workshopSlug}` with the prompt pre-filled. If routing fails, the prompt falls through to the Assistant as a new conversation message (see §Implementation notes).
|
||||
5. **Workshop definitions** — a single source-of-truth data structure that both the grid and the detail view consume.
|
||||
|
||||
**NOT in Phase 10:**
|
||||
|
||||
- Any backend changes to content generators.
|
||||
- The freeform prompt classifier itself — use a simple regex/keyword matching stub for Phase 10 (see §Implementation notes). A real LLM classifier is out of scope.
|
||||
- Recipe integration (v1.8 planned; not part of Wave 2).
|
||||
- Deleting `ConvertPage.tsx`. Phase 16 cleanup handles dead files.
|
||||
- Editing `App.tsx` routes — report needed routes to the controller.
|
||||
- Mobile polish beyond "grid collapses to 1 column" — Phase 15 handles mobile.
|
||||
|
||||
---
|
||||
|
||||
## File plan
|
||||
|
||||
### Create
|
||||
|
||||
| File | Responsibility | Est. lines |
|
||||
|---|---|---|
|
||||
| `ui/src/components/studio/workshops.ts` | Single source-of-truth data: array of 8 workshops with `slug`, `title`, `subtitle`, `icon`, `componentKey`. Exported for consumption by both the grid and the detail view. | ~80 |
|
||||
| `ui/src/components/studio/workshops.test.ts` | Tests: all 8 workshops present, slugs unique, icons are valid Lucide imports, each `componentKey` maps to a real component | ~60 |
|
||||
| `ui/src/components/studio/WorkshopCard.tsx` | Single card for the grid. Props: `workshop`, `onClick`. Renders title, subtitle, icon, hover state. | ~70 |
|
||||
| `ui/src/components/studio/WorkshopCard.test.tsx` | Tests: renders workshop data, click fires callback, hover state applies correctly | ~90 |
|
||||
| `ui/src/components/studio/WorkshopGrid.tsx` | Responsive grid of WorkshopCards. Takes `workshops` array and `onSelect` callback. | ~60 |
|
||||
| `ui/src/components/studio/WorkshopGrid.test.tsx` | Tests: renders N cards, onSelect fires with correct workshop slug, responsive class application | ~80 |
|
||||
| `ui/src/components/studio/StudioPromptBar.tsx` | Freeform text input + submit button. Calls `classifyIntent` helper and either navigates or calls `onFallbackToAssistant`. | ~80 |
|
||||
| `ui/src/components/studio/StudioPromptBar.test.tsx` | Tests: renders input, classifier routing, fallback path | ~110 |
|
||||
| `ui/src/components/studio/classifyIntent.ts` | Pure function. Given a prompt string, returns `{ slug: string; prefilledPrompt: string } \| null`. Keyword-matching only in Phase 10. | ~70 |
|
||||
| `ui/src/components/studio/classifyIntent.test.ts` | Parameterized tests: "diagram of X" → diagrams, "wallpaper of Y" → wallpapers, "convert pdf to docx" → convert, "make me a logo" → brand-kits, "random chat" → null | ~80 |
|
||||
| `ui/src/pages/StudioWorkshopDetail.tsx` | Routed view. Reads `:workshopSlug` from params, looks up in workshops data, renders two-column layout with the workshop's existing generator component on the right and a params/prompt input on the left. Action bar at the bottom: Save / Export / Send to Assistant. | ~180 |
|
||||
| `ui/src/pages/StudioWorkshopDetail.test.tsx` | Tests: renders workshop by slug, 404 fallback for unknown slug, action bar renders | ~120 |
|
||||
|
||||
### Modify
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `ui/src/pages/ContentStudio.tsx` | Replace the 7-tab layout with the new WorkshopGrid + StudioPromptBar. Remove tab state and tab container. Preserve any cross-cutting concerns (e.g. error boundaries, query client usage) that the existing page has. |
|
||||
|
||||
**Do not create or modify any other files.**
|
||||
|
||||
---
|
||||
|
||||
## Implementation notes
|
||||
|
||||
### Workshop data structure
|
||||
|
||||
```ts
|
||||
// ui/src/components/studio/workshops.ts
|
||||
import { Sparkles, Layers, Palette, Image, FileText, Award, Share2, Repeat } from "lucide-react";
|
||||
import type { ComponentType } from "react";
|
||||
|
||||
export type WorkshopSlug =
|
||||
| "diagrams" | "icons" | "themes" | "wallpapers"
|
||||
| "documents" | "brand-kits" | "social" | "convert";
|
||||
|
||||
export interface WorkshopDefinition {
|
||||
slug: WorkshopSlug;
|
||||
title: string; // Inter 700 uppercase, e.g. "DIAGRAMS"
|
||||
subtitle: string; // Inter 400 silver, single line
|
||||
icon: ComponentType<{ className?: string }>; // Lucide icon
|
||||
/**
|
||||
* Key for looking up the generator component. The detail view maps
|
||||
* this to an existing component from ContentStudio's current tab
|
||||
* implementations. Phase 10 does NOT rewrite any generator internals.
|
||||
*/
|
||||
componentKey: string;
|
||||
}
|
||||
|
||||
export const WORKSHOPS: WorkshopDefinition[] = [
|
||||
{ slug: "diagrams", title: "DIAGRAMS", subtitle: "Mermaid → rendered SVG", icon: Sparkles, componentKey: "diagram-renderer" },
|
||||
{ slug: "icons", title: "ICONS", subtitle: "SVG sets from description", icon: Layers, componentKey: "icon-renderer" },
|
||||
{ slug: "themes", title: "THEMES", subtitle: "Color → full palette", icon: Palette, componentKey: "theme-renderer" },
|
||||
{ slug: "wallpapers", title: "WALLPAPERS", subtitle: "Desktop, mobile, banners", icon: Image, componentKey: "wallpaper-renderer" },
|
||||
{ slug: "documents", title: "DOCUMENTS", subtitle: "PDF reports, invoices", icon: FileText, componentKey: "document-renderer" },
|
||||
{ slug: "brand-kits", title: "BRAND KITS", subtitle: "Full brand identity", icon: Award, componentKey: "brand-renderer" },
|
||||
{ slug: "social", title: "SOCIAL", subtitle: "Platform-ready posts", icon: Share2, componentKey: "social-renderer" },
|
||||
{ slug: "convert", title: "CONVERT", subtitle: "File format conversion", icon: Repeat, componentKey: "convert" },
|
||||
];
|
||||
```
|
||||
|
||||
Pick Lucide icons that fit. If a more appropriate icon exists for a slug, use it. The above is a sensible default.
|
||||
|
||||
### The `componentKey` → real component mapping
|
||||
|
||||
Phase 10 does NOT rewrite any generator. For each workshop, the detail view renders the existing generator component as-is. You must read the current `ContentStudio.tsx` to find those components. They'll be something like `<DiagramGeneratorTab />`, `<IconGeneratorTab />`, etc. Build a map:
|
||||
|
||||
```ts
|
||||
// In StudioWorkshopDetail.tsx (or a small helper module within studio/)
|
||||
const WORKSHOP_COMPONENTS: Record<string, ComponentType> = {
|
||||
"diagram-renderer": DiagramGeneratorTab,
|
||||
"icon-renderer": IconGeneratorTab,
|
||||
// ... etc.
|
||||
"convert": ConvertPageBody, // see Convert fold-in notes below
|
||||
};
|
||||
```
|
||||
|
||||
If a generator doesn't currently exist in `ContentStudio.tsx`'s tabs (e.g. if the current ContentStudio has only 6 tabs and we're adding Social as a future extension), render a `<WorkshopPlaceholder />` inline-component that shows `"Coming soon"` for that slug. Do NOT stub out a fake generator.
|
||||
|
||||
### Convert fold-in
|
||||
|
||||
`ui/src/pages/ConvertPage.tsx` currently exists and is routed via `/convert`. Phase 10 needs the Convert workshop body rendered inside the Studio workshop detail shell, but the existing `/convert` route MUST continue to work (backwards compat).
|
||||
|
||||
Strategy:
|
||||
1. Extract the inner content of `ConvertPage.tsx` into a reusable component `ConvertPageBody`. Do this by **reading** `ConvertPage.tsx` and identifying the JSX that's the actual convert UI (the file uploader, format selector, conversion runner) vs. the page-level shell.
|
||||
2. If `ConvertPage.tsx` is already structured so the inner content is a single child component, just import and reuse that component — no refactor needed.
|
||||
3. If `ConvertPage.tsx` is a monolithic component and extracting `ConvertPageBody` requires a real refactor, take the smallest possible subset: wrap the whole `<ConvertPage />` in the workshop detail and accept that there's a small amount of redundant chrome. Flag as a concern.
|
||||
4. Leave `ConvertPage.tsx` in place; the `/convert` route keeps rendering it directly.
|
||||
|
||||
### StudioPromptBar fallback to Assistant
|
||||
|
||||
When the classifier returns `null`, the prompt should fall through to the Assistant as a new conversation. The cleanest implementation is:
|
||||
|
||||
1. Navigate to `/<companyPrefix>/assistant?prompt=<urlencoded prompt>`.
|
||||
2. The Assistant page (Phase 9's work) reads the `prompt` query param on mount and pre-fills the input.
|
||||
|
||||
**BUT:** Phase 9's `PersonalAssistant.tsx` rewrite may not include query-param pre-fill logic. Coordinate: add a note to your final report saying "StudioPromptBar fallback assumes Phase 9 will honor `?prompt=` query param on the Assistant route". Phase 9's plan should also have this noted; if it doesn't, the controller will coordinate.
|
||||
|
||||
For Phase 10's implementation, go ahead and navigate with the query param. If Phase 9 doesn't honor it yet, the worst case is the prompt is dropped and the user lands on an empty assistant — acceptable degradation.
|
||||
|
||||
### classifyIntent — Phase 10 rules
|
||||
|
||||
Simple keyword-based classifier. Pure function, no state, no async.
|
||||
|
||||
```ts
|
||||
export function classifyIntent(prompt: string): { slug: WorkshopSlug; prefilledPrompt: string } | null {
|
||||
const lower = prompt.toLowerCase().trim();
|
||||
if (!lower) return null;
|
||||
|
||||
// Convert intent — "convert X to Y", "from X to Y", explicit formats
|
||||
if (/\bconvert\b|from\s+\w+\s+to\s+\w+|\bpdf\b|\bdocx?\b|\bmd\b/i.test(lower)) {
|
||||
return { slug: "convert", prefilledPrompt: prompt };
|
||||
}
|
||||
// Wallpaper intent
|
||||
if (/wallpaper|background\s+image|desktop\s+bg/i.test(lower)) {
|
||||
return { slug: "wallpapers", prefilledPrompt: prompt };
|
||||
}
|
||||
// Diagram intent
|
||||
if (/diagram|flowchart|sequence|mermaid|architecture/i.test(lower)) {
|
||||
return { slug: "diagrams", prefilledPrompt: prompt };
|
||||
}
|
||||
// Icon intent
|
||||
if (/\bicon\b|icon\s+set|svg\s+icon/i.test(lower)) {
|
||||
return { slug: "icons", prefilledPrompt: prompt };
|
||||
}
|
||||
// Theme intent
|
||||
if (/\btheme\b|color\s+palette|palette/i.test(lower)) {
|
||||
return { slug: "themes", prefilledPrompt: prompt };
|
||||
}
|
||||
// Document intent
|
||||
if (/pdf|document|invoice|report|one-?pager/i.test(lower)) {
|
||||
return { slug: "documents", prefilledPrompt: prompt };
|
||||
}
|
||||
// Brand intent
|
||||
if (/brand|logo|identity|style\s+guide/i.test(lower)) {
|
||||
return { slug: "brand-kits", prefilledPrompt: prompt };
|
||||
}
|
||||
// Social intent
|
||||
if (/social|tweet|post|instagram|linkedin|twitter|x\.com/i.test(lower)) {
|
||||
return { slug: "social", prefilledPrompt: prompt };
|
||||
}
|
||||
|
||||
return null; // unclassified → fall through to Assistant
|
||||
}
|
||||
```
|
||||
|
||||
Test exhaustively with the parameterized test pattern from Phase 8's `ModeBreadcrumb.test.tsx`.
|
||||
|
||||
### ContentStudio.tsx target structure
|
||||
|
||||
```tsx
|
||||
export function ContentStudio() {
|
||||
const navigate = useNavigate();
|
||||
const { selectedCompanyId, selectedCompany } = useCompany();
|
||||
const prefix = selectedCompany?.issuePrefix ?? "";
|
||||
|
||||
const handleSelectWorkshop = (slug: WorkshopSlug) => {
|
||||
navigate(`/${prefix}/content-studio/${slug}`);
|
||||
};
|
||||
|
||||
const handleFallbackToAssistant = (prompt: string) => {
|
||||
navigate(`/${prefix}/assistant?prompt=${encodeURIComponent(prompt)}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-[1200px] px-6 py-8">
|
||||
<header className="mb-8">
|
||||
<h1 className="text-[14px] font-semibold uppercase tracking-[0.1em] text-primary">STUDIO</h1>
|
||||
<p className="mt-2 text-[14px] text-muted-foreground">
|
||||
Eight workshops. Pick one or describe what you need.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<WorkshopGrid workshops={WORKSHOPS} onSelect={handleSelectWorkshop} />
|
||||
|
||||
<div className="mt-12">
|
||||
<StudioPromptBar
|
||||
onClassified={(slug, prefilled) => navigate(`/${prefix}/content-studio/${slug}?prompt=${encodeURIComponent(prefilled)}`)}
|
||||
onFallbackToAssistant={handleFallbackToAssistant}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Preserve any existing cross-cutting concerns (error boundaries, query client, auth checks) from the current ContentStudio.
|
||||
|
||||
### StudioWorkshopDetail routing
|
||||
|
||||
**You are NOT allowed to edit App.tsx.** You'll need sub-routes for Studio workshops, e.g. `/<prefix>/content-studio/:workshopSlug`. The existing App.tsx currently only has `/<prefix>/content-studio` without a sub-route param.
|
||||
|
||||
**Workaround for Phase 10:** render StudioWorkshopDetail as a child of ContentStudio, not as a separate route. ContentStudio checks for a `:workshopSlug` path segment via `useLocation()` (not `useParams()`, because the App.tsx routes don't define the param). If there's a slug, render StudioWorkshopDetail; otherwise render the grid.
|
||||
|
||||
```tsx
|
||||
// ContentStudio.tsx pseudocode
|
||||
export function ContentStudio() {
|
||||
const location = useLocation();
|
||||
const match = location.pathname.match(/\/content-studio\/([^/]+)/);
|
||||
const workshopSlug = match?.[1];
|
||||
|
||||
if (workshopSlug) {
|
||||
return <StudioWorkshopDetail slug={workshopSlug} />;
|
||||
}
|
||||
return <StudioHomeGrid ... />;
|
||||
}
|
||||
```
|
||||
|
||||
This is hacky but correct for Phase 10. Report to the controller: "New App.tsx routes needed: `content-studio/:workshopSlug`". The controller will replace the pathname-matching with a proper route param after Wave 2.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
Phase 10 is complete when:
|
||||
|
||||
1. Loading `/NEX/content-studio` renders an 8-card grid with the new workshop definitions — no tab strip visible anywhere on the page.
|
||||
2. Each card navigates to `/NEX/content-studio/{slug}` when clicked.
|
||||
3. Loading `/NEX/content-studio/{slug}` renders the workshop detail view with the existing generator component on the right and a params panel on the left.
|
||||
4. Loading `/NEX/content-studio/convert` renders the ConvertPage body inside the workshop detail shell.
|
||||
5. Loading `/NEX/convert` (the legacy route) still works — backwards compat.
|
||||
6. The StudioPromptBar at the bottom of the grid view accepts text input and routes correctly:
|
||||
- `"diagram of the auth flow"` → navigates to `/NEX/content-studio/diagrams?prompt=...`
|
||||
- `"convert this pdf to markdown"` → navigates to `/NEX/content-studio/convert?prompt=...`
|
||||
- `"random musing"` → navigates to `/NEX/assistant?prompt=...` (fallback)
|
||||
7. All new components have tests using the Phase 8 manual createRoot + act pattern.
|
||||
8. `npx vitest run src/components/studio/ src/pages/StudioWorkshopDetail.test.tsx` passes.
|
||||
9. `npx tsc --noEmit 2>&1 | grep -E "studio/|ContentStudio\.tsx|StudioWorkshopDetail\.tsx"` returns no errors.
|
||||
10. The `/convert` top-level route still renders the ConvertPage.
|
||||
11. No file outside the declared ownership is modified.
|
||||
|
||||
---
|
||||
|
||||
## Commit scheme
|
||||
|
||||
Suggested atomic commits (one per logical unit):
|
||||
|
||||
1. `feat(nexus): add WORKSHOPS data + classifyIntent helper (phase 10)` — workshops.ts, classifyIntent.ts, their tests
|
||||
2. `feat(nexus): add WorkshopCard and WorkshopGrid components (phase 10)` — cards + grid + tests
|
||||
3. `feat(nexus): add StudioPromptBar with intent routing (phase 10)` — prompt bar + tests
|
||||
4. `feat(nexus): add StudioWorkshopDetail page (phase 10)` — detail view + tests
|
||||
5. `refactor(nexus): rewire ContentStudio to workshop grid (phase 10)` — ContentStudio.tsx rewrite
|
||||
|
||||
Each commit must build and pass tests. Include `Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>` on every commit.
|
||||
|
||||
---
|
||||
|
||||
## Before you begin
|
||||
|
||||
Ask the controller if any of these are unclear:
|
||||
|
||||
- The component names and import paths of the current ContentStudio's 7 tabs (read the file first and report what's there)
|
||||
- Whether `ConvertPage.tsx` is extractable or if you should wrap the whole thing
|
||||
- Whether the new sub-routes the controller needs to add warrant a pause and App.tsx edit, or if the hacky path-matching workaround is acceptable
|
||||
- Anything in §6 of the spec that reads as ambiguous
|
||||
|
||||
If you find that the current ContentStudio has fewer than 7 tabs (e.g. only 5 generators are actually built), report it — the spec assumes 7 existing + 1 new (convert). The workshop grid can still show 8 cards with placeholder content for missing generators.
|
||||
|
||||
---
|
||||
|
||||
## When you're in over your head
|
||||
|
||||
Escalate early if:
|
||||
|
||||
- ConvertPage body extraction breaks tests in the legacy `/convert` route
|
||||
- The current ContentStudio has a global state or context that doesn't compose cleanly with the new grid → detail flow
|
||||
- Any generator component has hardcoded dependencies on the tab layout you're removing
|
||||
- classifyIntent is pulling in too many edge cases (stop at ~10 keyword patterns; more is Phase 14's classifier)
|
||||
|
||||
---
|
||||
|
||||
## Report format (final)
|
||||
|
||||
- **Status**
|
||||
- **Commits produced**
|
||||
- **Files created / modified**
|
||||
- **Tests added and passing count**
|
||||
- **Typecheck result** for Phase 10 files
|
||||
- **Routing needs** for the controller to add to App.tsx
|
||||
- **ConvertPage fold-in notes** — did you extract a body component or wrap the whole page?
|
||||
- **Open concerns**
|
||||
- **Deviations from the plan**
|
||||
- **Self-review findings**
|
||||
336
docs/plans/2026-04-11-nexus-phase-11-projects-builder.md
Normal file
336
docs/plans/2026-04-11-nexus-phase-11-projects-builder.md
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
# 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 0–100)
|
||||
- `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:
|
||||
|
||||
```tsx
|
||||
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:
|
||||
|
||||
```tsx
|
||||
<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
|
||||
|
||||
```tsx
|
||||
// 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.tsx` — **read 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 `approvals` → `gates` 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**
|
||||
334
docs/plans/2026-04-11-nexus-phase-9-assistant.md
Normal file
334
docs/plans/2026-04-11-nexus-phase-9-assistant.md
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
# Nexus Phase 9 — Assistant Mode Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:test-driven-development` for each unit. Commit atomically per logical unit (see §Commit scheme).
|
||||
|
||||
**Goal:** Rebuild the Assistant experience at `/assistant` as the canonical home screen of Nexus — full-bleed chat with no inner conversation-list column, History + Memory slide-overs, a conversational "home greeting" state when no conversation is active, and an action strip that includes Promote-to-Project (the transition is Phase 12's job, not Phase 9's — Phase 9 just wires the button).
|
||||
|
||||
**Source of truth:** `docs/specs/2026-04-11-nexus-layout-overhaul.md` **§5** (Mode 1 — Assistant). Read §5.1–§5.7 end-to-end before starting. DESIGN.md governs all visuals. Phase 8 components at `ui/src/components/frame/*` are the reference pattern for new UI pieces.
|
||||
|
||||
**Branch:** `nexus/design-system-migration`. Commit directly; do not create a worktree.
|
||||
|
||||
---
|
||||
|
||||
## Ownership boundaries
|
||||
|
||||
**You may create or modify ONLY the following paths:**
|
||||
|
||||
| Path | Action |
|
||||
|---|---|
|
||||
| `ui/src/pages/PersonalAssistant.tsx` | Modify (major rewrite) |
|
||||
| `ui/src/components/assistant/**` | Create (new subdir for Phase 9 components) |
|
||||
| `ui/src/hooks/useAssistantHomeStatus.ts` | Create (new hook for home-state greeting data) |
|
||||
|
||||
**You MUST NOT touch:**
|
||||
|
||||
- `ui/src/App.tsx` — routing is controller-owned. If your rewrite needs a new route, STOP and report it in your final report; the controller will wire it up after Wave 2 completes.
|
||||
- `ui/src/components/ChatPanel.tsx` — the old global slide-in chat panel. Phase 8 killed it from the Layout chrome; Phase 16 deletes the file. Do not edit it in Phase 9.
|
||||
- `ui/src/components/MobileChatView.tsx` — Phase 9 is desktop only. Mobile parity is Phase 15.
|
||||
- `ui/src/context/ChatPanelContext.tsx` — legacy. Phase 9's Assistant route does not consume this context.
|
||||
- `ui/src/components/Layout.tsx` — already rewritten in Phase 8, do not touch.
|
||||
- Any file under `server/`, `packages/`, `cli/`. Phase 9 is UI-only; the backend chat streaming, voice pipeline, and memory APIs are already built (v1.3–v1.6).
|
||||
- Any file outside `ui/src/` except as noted in this plan.
|
||||
|
||||
**Existing code you may reuse (read-only dependencies):**
|
||||
|
||||
- `ui/src/components/ChatMessageList.tsx` — existing message thread renderer. Consume it as-is.
|
||||
- `ui/src/components/ChatInput.tsx` — existing text input with Enter/Shift-Enter handling. Consume it as-is inside the new AssistantInputBar wrapper.
|
||||
- `ui/src/components/VoiceWaveform.tsx` — existing visualizer. Lift it into the new layout.
|
||||
- `ui/src/components/ChatAgentSelector.tsx` — agent selector popover. Use it as-is in the conversation header.
|
||||
- `ui/src/hooks/useStreamingChat.ts` — SSE streaming hook. Reuse as-is.
|
||||
- `ui/src/api/chat.ts` (or whatever the chat API client is named) — do not modify.
|
||||
- `ui/src/components/frame/*` — Phase 8 patterns (createRoot test pattern, semantic tokens, focus-visible styles, cn helper).
|
||||
|
||||
If you need a component that already exists in `ui/src/components/` but isn't listed above, read it first to understand its contract, then reuse it. Do not duplicate functionality.
|
||||
|
||||
---
|
||||
|
||||
## Scope (strictly)
|
||||
|
||||
**In Phase 9:**
|
||||
|
||||
1. **Full-bleed chat surface** at `/assistant` — no inner conversation-list column, max-width 760px centered in canvas, messages alternate left/right.
|
||||
2. **AssistantInputBar** — focal element at the bottom. Voice waveform visible above the text input, hairline divider between, generous 24px vertical padding, near-black `#141414` fill, charcoal border, sharp 8px radius.
|
||||
3. **ActionStrip** — 4 actions below the input bar: `⊕ Promote to project` (volt outline, eligibility TBD but render button always for Phase 9 — Phase 12 wires the transition), `📎 Attach` (reuses existing upload flow), `🧠 Memory` (opens MemorySheet), `📁 History` (opens HistorySheet).
|
||||
4. **HistorySheet** — left slide-over, 320px wide, butts against the icon rail. Lists conversations grouped by today/yesterday/this-week/older. Uses existing `ChatConversationList.tsx` as the inner content; wraps it in a slide-over container with close-on-outside-click and ESC-key-to-close.
|
||||
5. **MemorySheet** — right slide-over, 340px wide. Displays what the assistant remembers about the user, with simple edit controls (reuses existing memory API if one exists; if not, render a read-only view with a "coming soon" placeholder for edit). Close-on-outside-click and ESC.
|
||||
6. **AssistantHomeGreeting** — when there is no active conversation, render a synthesized greeting message at the top of the empty thread. Lists: active agents (count), pending gates (count), recent completions (list of up to 3), stale projects (list of up to 3). Data comes from a new `useAssistantHomeStatus` hook that reads existing endpoints (projects list, agents list, approvals/gates list, activity feed). Rendered as an assistant-turn bubble, NOT as a grid of widgets.
|
||||
7. **PersonalAssistant.tsx rewrite** — rewire to compose the new pieces. Kill the 160px inner conversation-list column. Kill any hardcoded right-panel logic. Preserve existing streaming, voice I/O, agent switching, handoff-to-PM.
|
||||
|
||||
**NOT in Phase 9 (explicitly deferred):**
|
||||
|
||||
- The promote-to-project animated transition (Phase 12). Phase 9 renders the button; Phase 12 wires the 700ms compress-and-rise animation.
|
||||
- Voice routing from non-Assistant modes to the Assistant inbox (Phase 14).
|
||||
- The globalized ⌘K palette searching across conversations (Phase 14).
|
||||
- Mobile full-screen chat (Phase 15 — keep existing MobileChatView untouched for now; Phase 9 is desktop-only).
|
||||
- Any visual edge-polish beyond what the semantic tokens already produce.
|
||||
- Memory API changes or new persistence. If no memory API exists, render MemorySheet as read-only "coming soon" stub.
|
||||
- Editing the home-greeting data source or the underlying endpoints. Compose from what exists.
|
||||
- Deletion of `ChatPanel.tsx` / `ChatPanelContext.tsx` — Phase 16 cleanup.
|
||||
|
||||
---
|
||||
|
||||
## File plan
|
||||
|
||||
### Create
|
||||
|
||||
| File | Responsibility | Est. lines |
|
||||
|---|---|---|
|
||||
| `ui/src/components/assistant/AssistantInputBar.tsx` | Voice waveform + text input composite with hairline divider. Wraps existing `ChatInput` and `VoiceWaveform`. | ~80 |
|
||||
| `ui/src/components/assistant/AssistantInputBar.test.tsx` | Tests: renders waveform + input, forwards onSend, shows correct state when streaming | ~100 |
|
||||
| `ui/src/components/assistant/ActionStrip.tsx` | 4-action row: Promote, Attach, Memory, History. Icon + label buttons, volt outline on Promote when eligible. | ~90 |
|
||||
| `ui/src/components/assistant/ActionStrip.test.tsx` | Tests: all 4 buttons render, onClick callbacks fire, Promote disabled when `canPromote={false}` | ~120 |
|
||||
| `ui/src/components/assistant/HistorySheet.tsx` | Left slide-over (320px), close-on-outside-click, ESC-to-close. Wraps existing `ChatConversationList`. | ~90 |
|
||||
| `ui/src/components/assistant/HistorySheet.test.tsx` | Tests: opens when `open`, closes on ESC, closes on backdrop click, renders conversation list inside | ~100 |
|
||||
| `ui/src/components/assistant/MemorySheet.tsx` | Right slide-over (340px). Read-only memory view with placeholder edit hook. | ~90 |
|
||||
| `ui/src/components/assistant/MemorySheet.test.tsx` | Tests: open/close, renders memory content, read-only mode disables edit | ~80 |
|
||||
| `ui/src/components/assistant/AssistantHomeGreeting.tsx` | Synthesized home-state message rendered as an assistant-turn bubble when no active conversation. | ~100 |
|
||||
| `ui/src/components/assistant/AssistantHomeGreeting.test.tsx` | Tests: renders greeting when no conversation, lists active agents/pending gates/etc., hidden when conversation active | ~110 |
|
||||
| `ui/src/hooks/useAssistantHomeStatus.ts` | Data hook: aggregates projects, agents, gates, activity from existing APIs. Returns `{ activeAgents, pendingGates, recentCompletions, staleProjects, loading }`. | ~80 |
|
||||
| `ui/src/hooks/useAssistantHomeStatus.test.ts` | Tests: aggregates correctly, handles loading state, handles errors | ~100 |
|
||||
|
||||
### Modify
|
||||
|
||||
| File | Change |
|
||||
|---|---|
|
||||
| `ui/src/pages/PersonalAssistant.tsx` | Major rewrite: compose new pieces, remove inner conversation list column, remove any right-panel logic, preserve streaming + voice + handoff |
|
||||
|
||||
**Do not create or modify any other files.**
|
||||
|
||||
---
|
||||
|
||||
## Implementation notes
|
||||
|
||||
### Layout skeleton (the target JSX)
|
||||
|
||||
```tsx
|
||||
// ui/src/pages/PersonalAssistant.tsx (simplified target structure)
|
||||
export function PersonalAssistant() {
|
||||
const { conversationId } = useParams();
|
||||
const [historyOpen, setHistoryOpen] = useState(false);
|
||||
const [memoryOpen, setMemoryOpen] = useState(false);
|
||||
// ... existing chat state, streaming hook, voice state ...
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full flex-col">
|
||||
{/* conversation thread: max-w-[760px] centered */}
|
||||
<div className="flex-1 overflow-auto">
|
||||
<div className="mx-auto w-full max-w-[760px] px-6 py-8">
|
||||
{!activeConversation && <AssistantHomeGreeting />}
|
||||
{activeConversation && <ChatMessageList ... />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* input bar + action strip anchored at bottom */}
|
||||
<div className="border-t border-border bg-background">
|
||||
<div className="mx-auto w-full max-w-[760px] px-6 py-4">
|
||||
<AssistantInputBar ... />
|
||||
<ActionStrip
|
||||
canPromote={canPromoteCurrentConversation}
|
||||
onPromote={() => {/* Phase 12 wires the transition */}}
|
||||
onAttach={...}
|
||||
onOpenMemory={() => setMemoryOpen(true)}
|
||||
onOpenHistory={() => setHistoryOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* slide-overs */}
|
||||
<HistorySheet open={historyOpen} onClose={() => setHistoryOpen(false)} />
|
||||
<MemorySheet open={memoryOpen} onClose={() => setMemoryOpen(false)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Design tokens to use (from Phase 1–3 migration and DESIGN.md)
|
||||
|
||||
- `bg-background` for canvas (pure black)
|
||||
- `bg-card` for input-bar fill (`#141414` near-black)
|
||||
- `border-border` for charcoal borders
|
||||
- `text-primary` / `bg-primary` for volt accents
|
||||
- `text-muted-foreground` for silver
|
||||
- `rounded-[8px]` for input bar
|
||||
- `rounded-[4px]` for small buttons/badges
|
||||
- Inter weights via Tailwind defaults; no custom font classes needed
|
||||
- Uppercase labels with `tracking-[0.1em]` for small section titles (matches ModeBreadcrumb)
|
||||
- Focus-visible: `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background` on all interactive elements
|
||||
|
||||
### Slide-over pattern (reusable mental model for HistorySheet and MemorySheet)
|
||||
|
||||
Use a simple portal-free pattern:
|
||||
|
||||
```tsx
|
||||
export function Sheet({
|
||||
open,
|
||||
onClose,
|
||||
side, // "left" | "right"
|
||||
width, // e.g. 320 or 340
|
||||
children,
|
||||
"aria-label": ariaLabel,
|
||||
}: SheetProps) {
|
||||
// Close on ESC
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onKey = (e: KeyboardEvent) => e.key === "Escape" && onClose();
|
||||
document.addEventListener("keydown", onKey);
|
||||
return () => document.removeEventListener("keydown", onKey);
|
||||
}, [open, onClose]);
|
||||
|
||||
if (!open) return null;
|
||||
return (
|
||||
<>
|
||||
{/* backdrop */}
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="Close"
|
||||
className="fixed inset-0 z-40 bg-black/40"
|
||||
/>
|
||||
{/* panel */}
|
||||
<aside
|
||||
aria-label={ariaLabel}
|
||||
className={cn(
|
||||
"fixed top-12 bottom-0 z-50 bg-background border-border flex flex-col",
|
||||
side === "left" ? "left-[56px] border-r" : "right-0 border-l",
|
||||
)}
|
||||
style={{ width }}
|
||||
>
|
||||
{children}
|
||||
</aside>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
(`top-12` = 48px — below the TopStrip. `left-[56px]` on the left side = butts against the IconRail.)
|
||||
|
||||
You don't need to extract this into a shared `Sheet` primitive — inlining the same pattern twice in HistorySheet and MemorySheet is fine. But if you choose to extract, put it at `ui/src/components/assistant/Sheet.tsx` (still within your ownership).
|
||||
|
||||
### ActionStrip eligibility for Promote
|
||||
|
||||
For Phase 9, `canPromote` should be `true` when:
|
||||
- There is an active conversation
|
||||
- AND the conversation has at least one user message and one assistant message
|
||||
|
||||
Do not implement the actual brainstormer-eligibility check — that's Phase 12 territory. Phase 9 just needs a boolean that correctly enables/disables the button.
|
||||
|
||||
### AssistantHomeGreeting data shape
|
||||
|
||||
```ts
|
||||
interface HomeStatus {
|
||||
activeAgents: number; // count of agents currently working
|
||||
pendingGates: Array<{ projectName: string; gateName: string; href: string }>;
|
||||
recentCompletions: Array<{ projectName: string; summary: string; when: string }>;
|
||||
staleProjects: Array<{ name: string; lastActivity: string; href: string }>;
|
||||
loading: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
The `useAssistantHomeStatus` hook reads:
|
||||
- Active agents: from existing `agentsApi` or similar — filter by `status === "working"`
|
||||
- Pending gates: from existing `approvalsApi` (still named "approvals" in the backend; the UI label is "Gates" but the API endpoint stays as `/api/approvals`) — filter by `status === "pending"`
|
||||
- Recent completions: from existing `activityApi` — last 24h, filter by `type === "completion"` or similar
|
||||
- Stale projects: from existing `projectsApi` — filter by `lastActivity > 3 days ago`
|
||||
|
||||
Do not create new API endpoints. Compose from what exists. If any of these endpoints are missing, gracefully degrade — return empty arrays and log a console.warn once in development.
|
||||
|
||||
### Rendering the home greeting
|
||||
|
||||
Not as a grid of widgets. Render as an assistant-turn chat bubble with formatted markdown content:
|
||||
|
||||
```
|
||||
Good morning, Mikkel.
|
||||
|
||||
Since we last talked:
|
||||
• nexus-design-migration: 4 commits, Phase 4 ready
|
||||
• personal-finance-dashboard: idle 3 days
|
||||
• 1 gate awaiting approval (nexus, Phase 4 audit)
|
||||
|
||||
What do you want to do?
|
||||
```
|
||||
|
||||
Reuse existing markdown rendering components (`ChatMarkdownMessage.tsx` or similar) to match the rest of the chat visual language. The greeting should look indistinguishable from a real assistant turn.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
Phase 9 is complete when:
|
||||
|
||||
1. Loading `/NEX/assistant` with no active conversation renders the AssistantHomeGreeting inside a max-w-760px centered column, with no inner left conversation list.
|
||||
2. Loading `/NEX/assistant` with an active conversation renders the existing message thread using `ChatMessageList`, with no inner left conversation list.
|
||||
3. The AssistantInputBar shows the voice waveform above the text input with a hairline divider between them, anchored at the bottom of the canvas.
|
||||
4. The ActionStrip renders 4 buttons below the input bar: Promote (volt outline, disabled when no conversation), Attach, Memory, History.
|
||||
5. Clicking History opens a left slide-over at 320px wide, butted against the 56px icon rail, showing the existing ChatConversationList inside. ESC closes. Backdrop click closes.
|
||||
6. Clicking Memory opens a right slide-over at 340px wide. ESC closes. Backdrop click closes.
|
||||
7. Streaming, voice I/O, agent switching, and handoff-to-PM from the existing PersonalAssistant still work — Phase 9 rewires composition but preserves functionality.
|
||||
8. All new components have tests using the Phase 8 pattern (manual createRoot + act, `// @vitest-environment jsdom`, `vi.mock("@/context/CompanyContext", ...)` where `Link` is used).
|
||||
9. `npx vitest run src/components/assistant/ src/hooks/useAssistantHomeStatus.test.ts` passes.
|
||||
10. `npx tsc --noEmit 2>&1 | grep -E "assistant/|PersonalAssistant\.tsx|useAssistantHomeStatus"` returns no errors in Phase 9 files.
|
||||
11. Typecheck on the full UI may still show pre-existing errors in other files — do not try to fix them.
|
||||
12. No file outside the declared ownership is modified.
|
||||
|
||||
---
|
||||
|
||||
## Commit scheme
|
||||
|
||||
One atomic commit per logical unit. Each commit is independently reviewable. Prefix with `feat(nexus):` or `refactor(nexus):` per existing repo convention. Include `Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>` on every commit.
|
||||
|
||||
Suggested commits (adjust as needed):
|
||||
|
||||
1. `feat(nexus): add useAssistantHomeStatus hook (phase 9)` — the hook + its tests
|
||||
2. `feat(nexus): add AssistantHomeGreeting component (phase 9)` — greeting + tests
|
||||
3. `feat(nexus): add AssistantInputBar composite (phase 9)` — input bar + tests
|
||||
4. `feat(nexus): add ActionStrip for assistant actions (phase 9)` — action strip + tests
|
||||
5. `feat(nexus): add HistorySheet slide-over (phase 9)` — history sheet + tests
|
||||
6. `feat(nexus): add MemorySheet slide-over (phase 9)` — memory sheet + tests
|
||||
7. `refactor(nexus): rewire PersonalAssistant to use new frame pieces (phase 9)` — the main page rewrite
|
||||
|
||||
If you find a cleaner grouping, use it. Each commit must build and pass its own tests.
|
||||
|
||||
---
|
||||
|
||||
## Before you begin
|
||||
|
||||
Ask the controller (your dispatching session) questions if any of the following are unclear:
|
||||
|
||||
- The existing memory API — does it exist, and what's its shape?
|
||||
- The existing `activityApi` / `approvalsApi` / `projectsApi` — what endpoints are available for the home greeting?
|
||||
- Whether `ChatMessageList` / `ChatInput` / `VoiceWaveform` / `ChatConversationList` / `ChatMarkdownMessage` exist and what their props are (read their files first; if any are missing or named differently, report it)
|
||||
- Anything in §5 of the spec that reads as ambiguous after a careful re-read
|
||||
|
||||
If you find a genuine architectural obstacle (e.g., `useStreamingChat` is tangled with the old ChatPanel context in a way that makes full-bleed rendering hard), STOP and report BLOCKED with the specifics. Do not silently invent workarounds.
|
||||
|
||||
---
|
||||
|
||||
## When you're in over your head
|
||||
|
||||
It is always OK to stop and say "this is too hard for me." Bad work is worse than no work. Escalate early if:
|
||||
|
||||
- You can't find a reference API for the home greeting data
|
||||
- The existing chat state machine doesn't compose cleanly with the new layout
|
||||
- Any of the reused components (ChatInput, ChatMessageList, etc.) turn out to need changes you're not allowed to make
|
||||
- You hit a TypeScript error in a Phase 9 file you can't resolve
|
||||
- Phase 8's Layout.tsx behavior conflicts with how PersonalAssistant wants to render
|
||||
|
||||
Report BLOCKED with specific details about what's stuck, what you've tried, and what kind of help you need.
|
||||
|
||||
---
|
||||
|
||||
## Report format (final)
|
||||
|
||||
When Phase 9 is complete or blocked, report:
|
||||
|
||||
- **Status:** DONE | DONE_WITH_CONCERNS | BLOCKED | NEEDS_CONTEXT
|
||||
- **Commits produced:** list of SHAs with one-line summaries
|
||||
- **Files created:** list
|
||||
- **Files modified:** list (with line counts)
|
||||
- **Tests added:** total count + per-file breakdown
|
||||
- **Tests passing:** `npx vitest run src/components/assistant/ src/hooks/useAssistantHomeStatus.test.ts` output summary
|
||||
- **Typecheck result:** grep output for Phase 9 files
|
||||
- **Routing needs:** any new routes the controller needs to add to `App.tsx` after Wave 2 (describe the route pattern and the page component it maps to)
|
||||
- **Open concerns:** anything the reviewer should look at specially
|
||||
- **Deviations from the plan:** any places where you had to adapt because of codebase reality, with justification
|
||||
- **Self-review findings:** what you found when re-reading your own code
|
||||
Loading…
Add table
Reference in a new issue