Files
resolutionflow/docs/plans/2026-02-24-ai-builder-ux-improvements-plan.md
chihlasm ed4ab059bf feat: AI flow builder, visibility model, dashboard tabs, fork UI (#88)
- 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>
2026-02-24 07:40:44 -05:00

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