diff --git a/docs/plans/2026-02-24-ai-builder-ux-improvements-plan.md b/docs/plans/2026-02-24-ai-builder-ux-improvements-plan.md new file mode 100644 index 00000000..80e0c9d9 --- /dev/null +++ b/docs/plans/2026-02-24-ai-builder-ux-improvements-plan.md @@ -0,0 +1,354 @@ +# AI Builder UX Improvements Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Three frontend-only UX improvements: always-available Publish button, "Generate All" branches in the AI wizard, and rotating activity messages during generation. + +**Architecture:** All changes are frontend-only. Feature 1 is a one-line fix in `TreeEditorPage.tsx`. Features 2 and 3 involve adding state to `aiFlowBuilderStore.ts` and updating `BranchDetailView.tsx` and `GeneratingAnimation.tsx`. No backend changes. + +**Tech Stack:** React 19, TypeScript, Zustand, Tailwind CSS v3, Lucide React icons. + +**Design doc:** `docs/plans/2026-02-24-ai-builder-ux-improvements.md` + +--- + +## Task 1: Fix Publish button — remove `!isDirty` gate + +**Files:** +- Modify: `frontend/src/pages/TreeEditorPage.tsx` (line ~654) + +**Step 1: Make the change** + +Find this line in `TreeEditorPage.tsx`: +```tsx +disabled={isSaving || !isDirty || hasBlockingErrors} +``` +Change to: +```tsx +disabled={isSaving || hasBlockingErrors} +``` +That's the only change needed. The button's `title` text references "Ctrl+S when no errors" which is still accurate — leave it. + +**Step 2: Verify build passes** + +```bash +cd frontend && npm run build 2>&1 | tail -20 +``` +Expected: no errors. + +**Step 3: Commit** + +```bash +git add frontend/src/pages/TreeEditorPage.tsx +git commit -m "fix: allow publishing clean drafts without requiring local edits" +``` + +--- + +## Task 2: Add rotating activity messages to `GeneratingAnimation` + +**Files:** +- Modify: `frontend/src/components/ai-builder/GeneratingAnimation.tsx` + +**Step 1: Read the current file** + +Read `frontend/src/components/ai-builder/GeneratingAnimation.tsx` to understand current structure before changing it. + +**Step 2: Rewrite with rotating messages** + +Replace the entire file content with: + +```tsx +import { useEffect, useState } from 'react' +import { cn } from '@/lib/utils' + +const MESSAGES = [ + 'Setting up your flow...', + 'Building diagnostic paths...', + 'Putting the pieces in place...', + 'Almost there...', +] as const + +const MESSAGE_DURATIONS = [4000, 8000, 8000, Infinity] // ms each message shows + +interface GeneratingAnimationProps { + branchContext?: { current: number; total: number } +} + +export function GeneratingAnimation({ branchContext }: GeneratingAnimationProps) { + const [messageIndex, setMessageIndex] = useState(0) + + // Reset and advance message on mount/remount + useEffect(() => { + setMessageIndex(0) + let current = 0 + + const advance = () => { + current += 1 + if (current < MESSAGES.length - 1) { + setMessageIndex(current) + timer = setTimeout(advance, MESSAGE_DURATIONS[current]) + } else { + setMessageIndex(MESSAGES.length - 1) + } + } + + let timer = setTimeout(advance, MESSAGE_DURATIONS[0]) + return () => clearTimeout(timer) + }, []) + + return ( +
+ {/* Spinner */} +
+ + {/* Branch context (Generate All mode) */} + {branchContext && ( +

+ Branch {branchContext.current} of {branchContext.total} +

+ )} + + {/* Rotating message */} +

+ {MESSAGES[messageIndex]} +

+
+ ) +} +``` + +**Step 3: Verify build passes** + +```bash +cd frontend && npm run build 2>&1 | tail -20 +``` +Expected: no errors. + +**Step 4: Commit** + +```bash +git add frontend/src/components/ai-builder/GeneratingAnimation.tsx +git commit -m "feat: add rotating activity messages to generation loading state" +``` + +--- + +## Task 3: Add `generateAllBranchDetails` and cancel to the store + +**Files:** +- Modify: `frontend/src/store/aiFlowBuilderStore.ts` + +**Step 1: Read the current store** + +Read `frontend/src/store/aiFlowBuilderStore.ts` fully to understand current state shape before modifying. + +**Step 2: Add new state fields and actions** + +Add to the `AIFlowBuilderState` interface (after `isLoading: boolean`): +```tsx +isGeneratingAll: boolean +stopGeneratingAll: boolean +generateAllBranchDetails: () => Promise +cancelGenerateAll: () => void +``` + +Add to the initial state in `create()(...)` (after `isLoading: false`): +```tsx +isGeneratingAll: false, +stopGeneratingAll: false, +``` + +Add the two new actions after `assemble`: + +```tsx +generateAllBranchDetails: async () => { + const { selectedBranches, generateBranchDetail } = get() + const undetailed = selectedBranches.filter((b) => !b.steps) + if (undetailed.length === 0) return + + set({ isGeneratingAll: true, stopGeneratingAll: false, error: null }) + + for (const branch of undetailed) { + if (get().stopGeneratingAll) break + // Set currentBranchIndex so tabs show the active branch + const idx = get().selectedBranches.findIndex((b) => b.name === branch.name) + if (idx !== -1) set({ currentBranchIndex: idx }) + await generateBranchDetail(branch.name) + // If generateBranchDetail set phase to 'error', stop + if (get().phase === 'error') break + } + + set({ isGeneratingAll: false }) +}, + +cancelGenerateAll: () => { + set({ stopGeneratingAll: true }) +}, +``` + +Also add `isGeneratingAll: false, stopGeneratingAll: false` to the `reset()` action's `set({...})` call. + +**Step 3: Verify TypeScript compiles** + +```bash +cd frontend && npm run build 2>&1 | tail -20 +``` +Expected: no errors. + +**Step 4: Commit** + +```bash +git add frontend/src/store/aiFlowBuilderStore.ts +git commit -m "feat: add generateAllBranchDetails and cancelGenerateAll to AI builder store" +``` + +--- + +## Task 4: Update `BranchDetailView` with Generate All UI + +**Files:** +- Modify: `frontend/src/components/ai-builder/BranchDetailView.tsx` + +**Step 1: Read the current file** + +Read `frontend/src/components/ai-builder/BranchDetailView.tsx` fully. + +**Step 2: Add imports and pull new store state** + +Add `Zap, Square` to the lucide-react import (Zap = lightning bolt for "Generate All", Square = stop). + +Pull new state from store in the component: +```tsx +const { + // existing... + isGeneratingAll, + generateAllBranchDetails, + cancelGenerateAll, +} = useAIFlowBuilderStore() +``` + +**Step 3: Add Generate All / Stop button above branch tabs** + +After the opening `
` and before the branch tabs div, add: + +```tsx +{/* Generate All / Stop control */} +{(() => { + const undetailedCount = selectedBranches.filter((b) => !b.steps).length + if (undetailedCount === 0) return null + return ( +
+ + {undetailedCount} branch{undetailedCount !== 1 ? 'es' : ''} need detail + + {isGeneratingAll ? ( + + ) : ( + + )} +
+ ) +})()} +``` + +**Step 4: Disable individual controls during `isGeneratingAll`** + +On the "Generate Detail" button (the primary one in the empty-state section): +```tsx +disabled={isLoading || isGeneratingAll} +``` + +On the "Skip" button: +```tsx +disabled={isGeneratingAll} +// also add: className includes opacity-50 when disabled +``` + +On the "Regenerate" button: +```tsx +disabled={isLoading || isGeneratingAll} +``` + +**Step 5: Pass `branchContext` to `GeneratingAnimation`** + +The `GeneratingAnimation` is rendered when `phase === 'generating' && isLoading`. Update that render: + +```tsx +if (phase === 'generating' && isLoading) { + return ( + b.steps).length + 1, + total: selectedBranches.length, + } + : undefined + } + /> + ) +} +``` + +**Step 6: Verify build passes** + +```bash +cd frontend && npm run build 2>&1 | tail -20 +``` +Expected: no errors. + +**Step 7: Commit** + +```bash +git add frontend/src/components/ai-builder/BranchDetailView.tsx +git commit -m "feat: add Generate All button and per-branch progress to AI builder detail stage" +``` + +--- + +## Task 5: Push and verify + +**Step 1: Push branch** + +```bash +git push +``` + +**Step 2: Verify CI passes** + +```bash +gh pr checks 88 2>&1 | head -20 +``` + +Expected: all checks passing (or wait for them to run). + +**Step 3: Manual smoke test checklist** + +- [ ] Open a fresh AI-generated draft in the tree editor → Publish button is enabled +- [ ] Open AI Flow Builder, complete foundation → scaffold → select branches +- [ ] On detail stage: "Generate All" button is visible +- [ ] Click "Generate All" → branches generate one at a time, tabs show progress, "Branch X of Y" appears in animation +- [ ] "Stop" button appears during run, clicking it halts after current branch +- [ ] Activity messages cycle: "Setting up your flow..." → "Building diagnostic paths..." → "Putting the pieces in place..." → "Almost there..." +- [ ] Single-branch generate still works as before