- AI flow builder: scaffold → branch detail → assemble → review flow - Generate All one-click branch generation with stop/cancel - Regenerate scaffold suggestions button - 3-action review screen: Start Flow, Open in Editor, Build Another - Fix Publish button gated behind !isDirty - Fix visibility column enforcement in tree access filter - Add ?visibility filter and author_name to GET /trees - Dashboard tabbed flows: My Flows / My Team / Public / All - Create button in My Flows tab, window focus reload (stale data fix) - Fork UI with optional reason modal - Fix account_id nullability in User type and schema - Keep is_public and visibility in sync on updates Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.6 KiB
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:
disabled={isSaving || !isDirty || hasBlockingErrors}
Change to:
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
cd frontend && npm run build 2>&1 | tail -20
Expected: no errors.
Step 3: Commit
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:
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
cd frontend && npm run build 2>&1 | tail -20
Expected: no errors.
Step 4: Commit
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):
isGeneratingAll: boolean
stopGeneratingAll: boolean
generateAllBranchDetails: () => Promise<void>
cancelGenerateAll: () => void
Add to the initial state in create()(...) (after isLoading: false):
isGeneratingAll: false,
stopGeneratingAll: false,
Add the two new actions after assemble:
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
cd frontend && npm run build 2>&1 | tail -20
Expected: no errors.
Step 4: Commit
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:
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:
{/* 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):
disabled={isLoading || isGeneratingAll}
On the "Skip" button:
disabled={isGeneratingAll}
// also add: className includes opacity-50 when disabled
On the "Regenerate" button:
disabled={isLoading || isGeneratingAll}
Step 5: Pass branchContext to GeneratingAnimation
The GeneratingAnimation is rendered when phase === 'generating' && isLoading. Update that render:
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
cd frontend && npm run build 2>&1 | tail -20
Expected: no errors.
Step 7: Commit
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
git push
Step 2: Verify CI passes
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