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