# 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 (
    <div className="flex flex-col items-center justify-center gap-4 py-10">
      {/* Spinner */}
      <div className="h-8 w-8 animate-spin rounded-full border-2 border-border border-t-primary" />

      {/* Branch context (Generate All mode) */}
      {branchContext && (
        <p className="text-xs font-label uppercase tracking-wide text-muted-foreground">
          Branch {branchContext.current} of {branchContext.total}
        </p>
      )}

      {/* Rotating message */}
      <p
        key={messageIndex}
        className={cn(
          'text-sm text-muted-foreground transition-opacity duration-500',
        )}
      >
        {MESSAGES[messageIndex]}
      </p>
    </div>
  )
}
```

**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<void>
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 `<div className="flex flex-col gap-4">` 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 (
    <div className="flex items-center justify-between">
      <span className="text-xs text-muted-foreground">
        {undetailedCount} branch{undetailedCount !== 1 ? 'es' : ''} need detail
      </span>
      {isGeneratingAll ? (
        <button
          type="button"
          onClick={cancelGenerateAll}
          className="flex items-center gap-1.5 rounded-lg border border-red-400/30 bg-red-400/10 px-3 py-1.5 text-xs font-medium text-red-400 hover:bg-red-400/20"
        >
          <Square className="h-3 w-3" />
          Stop
        </button>
      ) : (
        <button
          type="button"
          onClick={generateAllBranchDetails}
          disabled={isLoading}
          className="flex items-center gap-1.5 rounded-lg bg-gradient-brand px-3 py-1.5 text-xs font-medium text-white shadow-lg shadow-primary/20 hover:opacity-90 disabled:opacity-50"
        >
          <Zap className="h-3 w-3" />
          Generate All
        </button>
      )}
    </div>
  )
})()}
```

**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 (
    <GeneratingAnimation
      branchContext={
        isGeneratingAll
          ? {
              current: selectedBranches.filter((b) => 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
