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
|
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
|
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
|
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
|
**UI hint**: yes
|
||||||
|
|
||||||
### Phase 33: Persistent Memory + Personal Assistant Mode
|
### 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