docs(32): create phase plan
This commit is contained in:
parent
27a5da3912
commit
bb4554b2e3
2 changed files with 354 additions and 1 deletions
|
|
@ -141,7 +141,10 @@ Plans:
|
|||
1. A user can click "Skip" on every onboarding step (hardware, provider, voice) and reach the summary screen; the resulting workspace has at least one working agent with a valid provider
|
||||
2. The summary screen shows the configured providers and agent-model pairings for the selected mode; no corporate language ("company", "CEO", "mission") appears anywhere in the flow
|
||||
3. From the summary screen, one click navigates directly to the Personal Assistant chat or the project dashboard (depending on chosen mode) with no additional prompts
|
||||
**Plans**: TBD
|
||||
**Plans**: 1 plan
|
||||
|
||||
Plans:
|
||||
- [ ] 32-01-PLAN.md — Summary step, skip buttons, chat handoff
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 33: Persistent Memory + Personal Assistant Mode
|
||||
|
|
|
|||
350
.planning/phases/32-multi-step-onboarding-wizard/32-01-PLAN.md
Normal file
350
.planning/phases/32-multi-step-onboarding-wizard/32-01-PLAN.md
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
---
|
||||
phase: 32-multi-step-onboarding-wizard
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- ui/src/components/onboarding/OnboardingSummaryStep.tsx
|
||||
- ui/src/components/NexusOnboardingWizard.tsx
|
||||
- ui/src/components/onboarding/OnboardingSummaryStep.test.tsx
|
||||
autonomous: true
|
||||
requirements: [ONBD-04, ONBD-05, ONBD-06]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "User can click Skip on step 1 (hardware) and advance to step 2"
|
||||
- "User can click Skip on step 2 (mode) and advance to step 3"
|
||||
- "User can click Skip to summary on step 4 (root dir) and advance to step 5"
|
||||
- "Step 3 (provider) already has skip — no change needed"
|
||||
- "User sees summary screen showing hardware tier, mode, provider, and root directory"
|
||||
- "Summary screen shows 'None selected' when provider was skipped"
|
||||
- "User clicks 'Start chatting' on summary and lands on dashboard with chat panel open"
|
||||
- "Step indicator shows 'Summary' on step 5 instead of 'Step 5 of 5'"
|
||||
artifacts:
|
||||
- path: "ui/src/components/onboarding/OnboardingSummaryStep.tsx"
|
||||
provides: "Read-only summary display with Start chatting CTA"
|
||||
exports: ["OnboardingSummaryStep"]
|
||||
- path: "ui/src/components/NexusOnboardingWizard.tsx"
|
||||
provides: "5-step wizard with skip buttons on steps 1, 2, 4 and summary as step 5"
|
||||
contains: "step === 5"
|
||||
- path: "ui/src/components/onboarding/OnboardingSummaryStep.test.tsx"
|
||||
provides: "Unit tests for summary step rendering and CTA behavior"
|
||||
key_links:
|
||||
- from: "ui/src/components/NexusOnboardingWizard.tsx"
|
||||
to: "ui/src/components/onboarding/OnboardingSummaryStep.tsx"
|
||||
via: "import and render at step === 5"
|
||||
pattern: "OnboardingSummaryStep"
|
||||
- from: "ui/src/components/NexusOnboardingWizard.tsx"
|
||||
to: "useChatPanel"
|
||||
via: "setChatOpen(true) after navigate in handleStartChat"
|
||||
pattern: "setChatOpen\\(true\\)"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Complete the onboarding wizard by adding skip buttons to steps 1, 2, and 4, creating a summary screen (step 5) that displays all configured settings, and wiring a "Start chatting" CTA that creates the workspace and opens the chat panel.
|
||||
|
||||
Purpose: Users can skip through onboarding quickly or review their choices before starting — fulfilling ONBD-04 (skip any step), ONBD-05 (summary screen), and ONBD-06 (one-click to chat).
|
||||
|
||||
Output: OnboardingSummaryStep component, updated NexusOnboardingWizard with 5 steps and skip buttons, unit tests.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/32-multi-step-onboarding-wizard/32-RESEARCH.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Key types and contracts the executor needs -->
|
||||
|
||||
From ui/src/api/hardware.ts:
|
||||
```typescript
|
||||
export type HardwareTier = "gpu" | "apple_silicon" | "cpu_only";
|
||||
|
||||
export interface HardwareInfo {
|
||||
totalGb: number;
|
||||
freeGb: number;
|
||||
usableGb: number;
|
||||
platform: string;
|
||||
gpuName: string | null;
|
||||
gpuVramGb: number | null;
|
||||
unifiedMemory: boolean;
|
||||
hardwareTier: HardwareTier;
|
||||
cpuModel: string | null;
|
||||
}
|
||||
|
||||
export type NexusMode = "personal_ai" | "project_builder" | "both";
|
||||
```
|
||||
|
||||
From ui/src/context/ChatPanelContext.tsx:
|
||||
```typescript
|
||||
export function useChatPanel(): {
|
||||
chatOpen: boolean;
|
||||
setChatOpen: (open: boolean) => void;
|
||||
};
|
||||
```
|
||||
|
||||
From ui/src/components/NexusOnboardingWizard.tsx (existing state available to summary):
|
||||
```typescript
|
||||
// These are the wizard state variables available to pass as props:
|
||||
const [selectedMode, setSelectedMode] = useState<NexusMode>("both");
|
||||
const [puterToken, setPuterToken] = useState<string | null>(null);
|
||||
const [googleOAuthStateId, setGoogleOAuthStateId] = useState<string | null>(null);
|
||||
const [apiKeyData, setApiKeyData] = useState<{ provider: string; apiKey: string } | null>(null);
|
||||
const [rootDir, setRootDir] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { data: hardwareInfo } = useHardwareInfo(effectiveOnboardingOpen);
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Create OnboardingSummaryStep component and tests</name>
|
||||
<files>ui/src/components/onboarding/OnboardingSummaryStep.tsx, ui/src/components/onboarding/OnboardingSummaryStep.test.tsx</files>
|
||||
<read_first>
|
||||
- ui/src/components/onboarding/HardwareSummaryStep.tsx (existing step pattern, styling conventions)
|
||||
- ui/src/components/onboarding/ModeSelector.tsx (NexusMode usage, label patterns)
|
||||
- ui/src/api/hardware.ts (HardwareInfo and NexusMode types)
|
||||
</read_first>
|
||||
<action>
|
||||
Create `ui/src/components/onboarding/OnboardingSummaryStep.tsx`:
|
||||
|
||||
1. Define props interface:
|
||||
```typescript
|
||||
interface OnboardingSummaryStepProps {
|
||||
hardwareInfo: HardwareInfo | undefined;
|
||||
selectedMode: NexusMode;
|
||||
providerLabel: string;
|
||||
rootDir: string;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
onStartChat: () => void;
|
||||
onBack: () => void;
|
||||
}
|
||||
```
|
||||
|
||||
2. Implement a read-only summary with 4 rows in a bordered card (`rounded-lg border border-border p-4`):
|
||||
- **Hardware**: Show `hardwareInfo.hardwareTier` mapped to display labels ("GPU", "Apple Silicon", "CPU Only"), or "Unknown" if undefined
|
||||
- **Mode**: Map `NexusMode` to labels: `personal_ai` -> "Personal AI Assistant", `project_builder` -> "Project Builder", `both` -> "Both (recommended)"
|
||||
- **Provider**: Render the `providerLabel` string directly (caller computes it)
|
||||
- **Root directory**: Only show row if `rootDir` is non-empty, render value in `font-mono text-sm`
|
||||
|
||||
3. Create a helper `SummaryRow` component (internal, not exported) that renders a label-value pair:
|
||||
- Label in `text-muted-foreground` on the left
|
||||
- Value on the right, optionally mono (`font-mono text-sm` when `mono` prop is true)
|
||||
- Use flexbox `justify-between`
|
||||
|
||||
4. Show error message if `error` is non-null (`text-sm text-destructive bg-destructive/10 rounded-md px-3 py-2`)
|
||||
|
||||
5. "Start chatting" primary button — `onClick={onStartChat}`, `disabled={loading}`, shows "Setting up..." when loading (with the same spinner SVG used in the root dir step)
|
||||
|
||||
6. "Back" ghost button — `onClick={onBack}`, `disabled={loading}`
|
||||
|
||||
7. Do NOT include any corporate language ("company", "CEO", "mission") per ROADMAP success criteria.
|
||||
|
||||
Create `ui/src/components/onboarding/OnboardingSummaryStep.test.tsx`:
|
||||
|
||||
Using Vitest + React Testing Library (project standard):
|
||||
|
||||
- Test 1: Renders all summary rows with provided data (hardware tier, mode label, provider label, root dir)
|
||||
- Test 2: Hides root directory row when rootDir is empty string
|
||||
- Test 3: Shows error message when error prop is non-null
|
||||
- Test 4: Calls onStartChat when "Start chatting" button clicked
|
||||
- Test 5: Disables "Start chatting" button when loading is true
|
||||
- Test 6: Shows "Setting up..." text when loading is true
|
||||
|
||||
Mock data: `hardwareInfo` with `hardwareTier: "apple_silicon"`, `selectedMode: "both"`, `providerLabel: "Puter (free, zero-config)"`, `rootDir: "~/projects/test"`.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /opt/nexus && pnpm vitest run ui/src/components/onboarding/OnboardingSummaryStep.test.tsx</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- grep -q "export function OnboardingSummaryStep" ui/src/components/onboarding/OnboardingSummaryStep.tsx
|
||||
- grep -q "Start chatting" ui/src/components/onboarding/OnboardingSummaryStep.tsx
|
||||
- grep -q "onStartChat" ui/src/components/onboarding/OnboardingSummaryStep.tsx
|
||||
- grep -q "SummaryRow" ui/src/components/onboarding/OnboardingSummaryStep.tsx
|
||||
- grep -q "describe" ui/src/components/onboarding/OnboardingSummaryStep.test.tsx
|
||||
</acceptance_criteria>
|
||||
<done>OnboardingSummaryStep renders hardware, mode, provider, root dir in a read-only card with Start chatting CTA. All 6 tests pass.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Wire summary step into wizard, add skip buttons, connect chat handoff</name>
|
||||
<files>ui/src/components/NexusOnboardingWizard.tsx</files>
|
||||
<read_first>
|
||||
- ui/src/components/NexusOnboardingWizard.tsx (full file — the primary modification target)
|
||||
- ui/src/context/ChatPanelContext.tsx (useChatPanel hook export and setChatOpen signature)
|
||||
- ui/src/components/onboarding/OnboardingSummaryStep.tsx (the component just created in Task 1)
|
||||
</read_first>
|
||||
<action>
|
||||
Modify `ui/src/components/NexusOnboardingWizard.tsx` with these changes:
|
||||
|
||||
**1. Import additions (top of file):**
|
||||
- `import { OnboardingSummaryStep } from "./onboarding/OnboardingSummaryStep";`
|
||||
- `import { useChatPanel } from "../context/ChatPanelContext";`
|
||||
|
||||
**2. Add useChatPanel hook** inside the `OnboardingWizard` component body, near the other hooks:
|
||||
```typescript
|
||||
const { setChatOpen } = useChatPanel();
|
||||
```
|
||||
|
||||
**3. Add `deriveProviderLabel` helper** inside or before the component:
|
||||
```typescript
|
||||
function deriveProviderLabel(
|
||||
puterToken: string | null,
|
||||
googleOAuthStateId: string | null,
|
||||
apiKeyData: { provider: string; apiKey: string } | null,
|
||||
): string {
|
||||
if (puterToken) return "Puter (free, zero-config)";
|
||||
if (googleOAuthStateId) return "Google Gemini (free tier)";
|
||||
if (apiKeyData) return `API key — ${apiKeyData.provider}`;
|
||||
return "None selected";
|
||||
}
|
||||
```
|
||||
|
||||
**4. Update step indicator** — change `<p className="text-xs text-muted-foreground text-center">Step {step} of 4</p>` to:
|
||||
```tsx
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
{step === 5 ? "Summary" : `Step ${step} of 4`}
|
||||
</p>
|
||||
```
|
||||
Note: Keep "of 4" — the summary is not a form step, so steps 1-4 are the data entry steps. Step 5 shows "Summary" label instead.
|
||||
|
||||
**5. Add skip button to Step 1** (hardware detection) — after the existing Continue button, add:
|
||||
```tsx
|
||||
<Button type="button" variant="ghost" onClick={() => setStep(2)} className="w-full">
|
||||
Skip
|
||||
</Button>
|
||||
```
|
||||
|
||||
**6. Add skip button to Step 2** (mode selection) — after the Back button in the button group, add:
|
||||
```tsx
|
||||
<Button type="button" variant="ghost" onClick={() => setStep(3)} className="w-full">
|
||||
Skip
|
||||
</Button>
|
||||
```
|
||||
|
||||
**7. Modify Step 4 "Get Started" button** — change from `type="submit"` to `type="button"` and change its onClick to advance to step 5 instead of submitting:
|
||||
```tsx
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => setStep(5)}
|
||||
disabled={loading || probing}
|
||||
className="w-full"
|
||||
>
|
||||
Review & finish
|
||||
</Button>
|
||||
```
|
||||
Also remove the `<form onSubmit={handleSubmit}>` wrapper around step 4 — replace with a plain `<div className="flex flex-col gap-4">`. The submit now happens on step 5.
|
||||
|
||||
Also add skip button to Step 4 after the Back button:
|
||||
```tsx
|
||||
<Button type="button" variant="ghost" onClick={() => setStep(5)} className="w-full" disabled={loading}>
|
||||
Skip to summary
|
||||
</Button>
|
||||
```
|
||||
|
||||
**8. Add Step 5 — Summary** after the step 4 block:
|
||||
```tsx
|
||||
{step === 5 && (
|
||||
<OnboardingSummaryStep
|
||||
hardwareInfo={hardwareInfo}
|
||||
selectedMode={selectedMode}
|
||||
providerLabel={deriveProviderLabel(puterToken, googleOAuthStateId, apiKeyData)}
|
||||
rootDir={rootDir}
|
||||
loading={loading}
|
||||
error={error}
|
||||
onStartChat={handleStartChat}
|
||||
onBack={() => setStep(4)}
|
||||
/>
|
||||
)}
|
||||
```
|
||||
|
||||
**9. Create `handleStartChat` function** — adapts existing `handleSubmit` logic but also opens chat:
|
||||
```typescript
|
||||
async function handleStartChat() {
|
||||
// Guard: claude_local requires rootDir
|
||||
if (defaultAdapter === "claude_local" && !rootDir.trim()) {
|
||||
setError("Root directory is required for Claude Code. Go back to step 4 to set it.");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
// === Same company creation logic as handleSubmit ===
|
||||
const company = await companiesApi.create({ name: VOCAB.appName });
|
||||
setSelectedCompanyId(company.id);
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.companies.all });
|
||||
|
||||
// ... (copy all the agent creation, mode save, and credential storage code
|
||||
// from handleSubmit — the baseAdapterConfig, hermesPromptTemplate,
|
||||
// adapterConfig, runtimeConfig, PM agent create, Engineer agent create,
|
||||
// queryClient invalidation, updateNexusSettings, puterToken/google/apiKey storage)
|
||||
|
||||
// Navigate to dashboard then open chat panel
|
||||
closeOnboarding();
|
||||
navigate(`/${company.issuePrefix}/dashboard`);
|
||||
setChatOpen(true); // <-- ONBD-06: opens chat panel after navigation
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Setup failed. Please try again.");
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
IMPORTANT: The `handleStartChat` function contains the SAME company/agent creation logic as the existing `handleSubmit`. The only differences are: (a) no `e: React.FormEvent` parameter, (b) error message mentions going back to step 4 for rootDir, (c) `setChatOpen(true)` is called after `navigate()`. You can either refactor the shared logic into an internal helper or duplicate it — refactoring into a helper like `createWorkspace()` is cleaner.
|
||||
|
||||
**10. Update the reset effect** — in the `useEffect` that resets form state when wizard closes, change `setStep(1)` to still reset to 1 (no change needed — step 5 naturally resets).
|
||||
|
||||
**11. Update the top comment** — change "4-step flow" to "5-step flow" and update the step list to include "summary".
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /opt/nexus && pnpm vitest run ui/src/components/onboarding/</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- grep -q "OnboardingSummaryStep" ui/src/components/NexusOnboardingWizard.tsx
|
||||
- grep -q "useChatPanel" ui/src/components/NexusOnboardingWizard.tsx
|
||||
- grep -q "setChatOpen(true)" ui/src/components/NexusOnboardingWizard.tsx
|
||||
- grep -q "step === 5" ui/src/components/NexusOnboardingWizard.tsx
|
||||
- grep -q "deriveProviderLabel" ui/src/components/NexusOnboardingWizard.tsx
|
||||
- grep -c "Skip" ui/src/components/NexusOnboardingWizard.tsx shows at least 3 skip buttons
|
||||
- grep -q "Summary" ui/src/components/NexusOnboardingWizard.tsx (step indicator label)
|
||||
- grep -q "handleStartChat" ui/src/components/NexusOnboardingWizard.tsx
|
||||
</acceptance_criteria>
|
||||
<done>Wizard has 5 steps with skip on 1/2/4, summary on step 5, and "Start chatting" creates workspace then opens chat panel. TypeScript compiles and tests pass.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. `pnpm vitest run ui/src/components/onboarding/` — all unit tests pass
|
||||
2. `cd /opt/nexus && pnpm tsc --noEmit` — no TypeScript errors
|
||||
3. Grep checks: `grep -q "setChatOpen(true)" ui/src/components/NexusOnboardingWizard.tsx` confirms ONBD-06 wiring
|
||||
4. Grep checks: `grep -c "Skip" ui/src/components/NexusOnboardingWizard.tsx` confirms at least 3 skip buttons (ONBD-04)
|
||||
5. Grep checks: `grep -q "OnboardingSummaryStep" ui/src/components/NexusOnboardingWizard.tsx` confirms summary wired (ONBD-05)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- OnboardingSummaryStep component exists and renders hardware, mode, provider, root dir
|
||||
- Skip buttons on steps 1, 2, and 4 (step 3 already has one)
|
||||
- Step 5 shows "Summary" in the step indicator
|
||||
- "Start chatting" on summary creates workspace, navigates to dashboard, and opens chat panel
|
||||
- No corporate language ("company", "CEO", "mission") in the summary step
|
||||
- All unit tests pass
|
||||
- TypeScript compiles cleanly
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/32-multi-step-onboarding-wizard/32-01-SUMMARY.md`
|
||||
</output>
|
||||
Loading…
Add table
Reference in a new issue