1→import { useEffect, useState } from 'react'
     2→import { useProjectStore } from '../stores/projectStore'
     3→import { useAgentStore } from '../stores/agentStore'
     4→import { useToastStore } from '../components/Toast'
     5→import {
     6→  FolderOpen,
     7→  CheckCircle,
     8→  Clock,
     9→  AlertTriangle,
    10→  Activity,
    11→  Trash2,
    12→  Pencil,
    13→  Check,
    14→  Palette,
    15→  X,
    16→} from 'lucide-react'
    17→import * as AlertDialog from '@radix-ui/react-alert-dialog'
    18→import * as Dialog from '@radix-ui/react-dialog'
    19→
    20→interface TaskStats {
    21→  completed: number
    22→  inProgress: number
    23→  blocked: number
    24→  backlog: number
    25→  qa: number
    26→}
    27→
    28→export default function Dashboard() {
    29→  const { currentProject, fetchProjects, deleteProject, updateProject } = useProjectStore()
    30→  const { activeSessions, fetchActiveSessions } = useAgentStore()
    31→  const { addToast } = useToastStore()
    32→  const [showDeleteDialog, setShowDeleteDialog] = useState(false)
    33→  const [isDeleting, setIsDeleting] = useState(false)
    34→  const [showEditDialog, setShowEditDialog] = useState(false)
    35→  const [editName, setEditName] = useState('')
    36→  const [editDescription, setEditDescription] = useState('')
    37→  const [editDesignMode, setEditDesignMode] = useState<'mvp' | 'finished'>('mvp')
    38→  const [isSaving, setIsSaving] = useState(false)
    39→  const [saveSuccess, setSaveSuccess] = useState(false)
    40→  const [taskStats, setTaskStats] = useState<TaskStats>({
    41→    completed: 0,
    42→    inProgress: 0,
    43→    blocked: 0,
    44→    backlog: 0,
    45→    qa: 0,
    46→  })
    47→
    48→  useEffect(() => {
    49→    fetchProjects()
    50→    fetchActiveSessions()
    51→  }, [fetchProjects, fetchActiveSessions])
    52→
    53→  // Fetch task statistics when current project changes
    54→  useEffect(() => {
    55→    const fetchTaskStats = async () => {
    56→      if (!currentProject) return
    57→
    58→      try {
    59→        const response = await fetch(`http://localhost:8000/api/projects/${currentProject.id}/tasks`)
    60→        if (!response.ok) return
    61→
    62→        const tasks = await response.json()
    63→        const stats: TaskStats = {
    64→          completed: 0,
    65→          inProgress: 0,
    66→          blocked: 0,
    67→          backlog: 0,
    68→          qa: 0,
    69→        }
    70→
    71→        tasks.forEach((task: { status: string }) => {
    72→          switch (task.status) {
    73→            case 'done':
    74→              stats.completed++
    75→              break
    76→            case 'in_progress':
    77→              stats.inProgress++
    78→              break
    79→            case 'blocked':
    80→              stats.blocked++
    81→              break
    82→            case 'backlog':
    83→              stats.backlog++
    84→              break
    85→            case 'qa':
    86→              stats.qa++
    87→              break
    88→          }
    89→        })
    90→
    91→        setTaskStats(stats)
    92→      } catch (error) {
    93→        console.error('Failed to fetch task stats:', error)
    94→      }
    95→    }
    96→
    97→    fetchTaskStats()
    98→  }, [currentProject])
    99→
   100→  const handleDeleteProject = async () => {
   101→    if (!currentProject) return
   102→    setIsDeleting(true)
   103→    try {
   104→      await deleteProject(currentProject.id)
   105→      setShowDeleteDialog(false)
   106→    } catch (error) {
   107→      console.error('Failed to delete project:', error)
   108→      addToast('error', 'Delete Failed', `Failed to delete project "${currentProject.name}". Please try again.`)
   109→    } finally {
   110→      setIsDeleting(false)
   111→    }
   112→  }
   113→
   114→  const handleOpenEditDialog = () => {
   115→    if (currentProject) {
   116→      setEditName(currentProject.name)
   117→      setEditDescription(currentProject.description || '')
   118→      setEditDesignMode((currentProject.design_mode as 'mvp' | 'finished') || 'mvp')
   119→      setSaveSuccess(false)
   120→      setShowEditDialog(true)
   121→    }
   122→  }
   123→
   124→  const handleSaveProject = async () => {
   125→    if (!currentProject) return
   126→    setIsSaving(true)
   127→    setSaveSuccess(false)
   128→    try {
   129→      const response = await fetch(`http://localhost:8000/api/projects/${currentProject.id}`, {
   130→        method: 'PUT',
   131→        headers: { 'Content-Type': 'application/json' },
   132→        body: JSON.stringify({ name: editName, description: editDescription, design_mode: editDesignMode }),
   133→      })
   134→      if (!response.ok) {
   135→        const errorData = await response.json().catch(() => ({}))
   136→        throw new Error(errorData.detail || 'Failed to update project')
   137→      }
   138→      const updated = await response.json()
   139→      updateProject(currentProject.id, updated)
   140→      setSaveSuccess(true)
   141→      setTimeout(() => {
   142→        setShowEditDialog(false)
   143→        setSaveSuccess(false)
   144→      }, 1000)
   145→    } catch (error) {
   146→      console.error('Failed to save project:', error)
   147→      const message = error instanceof Error ? error.message : 'Failed to save project'
   148→      addToast('error', 'Save Failed', message)
   149→    } finally {
   150→      setIsSaving(false)
   151→    }
   152→  }
   153→
   154→  if (!currentProject) {
   155→    return (
   156→

   157→        <FolderOpen size={64} className="text-muted-foreground mb-4" />
   158→

   159→          No Project Selected
   160→

   161→

   162→          Select a project from the sidebar or create a new one to get started.
   163→

   164→

   165→    )
   166→  }
   167→
   168→  const stats = [
   169→    {
   170→      label: 'Tasks Completed',
   171→      value: taskStats.completed,
   172→      icon: CheckCircle,
   173→      color: 'text-green-400',
   174→    },
   175→    {
   176→      label: 'Tasks In Progress',
   177→      value: taskStats.inProgress,
   178→      icon: Clock,
   179→      color: 'text-accent',
   180→    },
   181→    {
   182→      label: 'Blocked Tasks',
   183→      value: taskStats.blocked,
   184→      icon: AlertTriangle,
   185→      color: 'text-warning',
   186→    },
   187→    {
   188→      label: 'Active Agents',
   189→      value: activeSessions.filter((s) => s.status === 'running').length,
   190→      icon: Activity,
   191→      color: 'text-primary-400',
   192→    },
   193→  ]
   194→
   195→  return (
   196→

   197→      {/* Project Header */}
   198→

   199→

   200→

   201→
{currentProject.name}

   202→            {/* Design Mode Badge */}
   203→
   210→              <Palette size={12} />
   211→              {currentProject.design_mode === 'finished' ? 'Finished Mode' : 'MVP Mode'}
   212→
   213→

   214→          {currentProject.description && (
   215→
{currentProject.description}

   216→          )}
   217→

   218→

   219→          {/* Edit Button and Dialog */}
   220→          <Dialog.Root open={showEditDialog} onOpenChange={setShowEditDialog}>
   221→            <Dialog.Trigger asChild>
   222→              <button
   223→                onClick={handleOpenEditDialog}
   224→                className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-accent hover:bg-accent/10 rounded-lg transition-colors"
   225→                title="Edit Project Settings"
   226→              >
   227→                <Pencil size={16} />
   228→                Edit
   229→              </button>
   230→            </Dialog.Trigger>
   231→            <Dialog.Portal>
   232→              <Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
   233→              <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
   234→

   235→                  <Dialog.Title className="text-xl font-semibold text-foreground">
   236→                    Edit Project Settings
   237→                  </Dialog.Title>
   238→                  <Dialog.Close asChild>
   239→                    <button className="p-1 text-muted-foreground hover:text-foreground hover:bg-secondary rounded transition-colors" aria-label="Close">
   240→                      <X size={20} />
   241→                    </button>
   242→                  </Dialog.Close>
   243→

   244→                <Dialog.Description className="mt-2 text-muted-foreground">
   245→                  Update your project name, description, and design mode.
   246→                </Dialog.Description>
   247→

   248→

   249→                    Project Name
   250→                    <input
   251→                      type="text"
   252→                      value={editName}
   253→                      onChange={(e) => setEditName(e.target.value)}
   254→                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
   255→                      placeholder="Enter project name"
   256→                    />
   257→

   258→

   259→                    Description
   260→                    <textarea
   261→                      value={editDescription}
   262→                      onChange={(e) => setEditDescription(e.target.value)}
   263→                      rows={3}
   264→                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent resize-none"
   265→                      placeholder="Enter project description"
   266→                    />
   267→

   268→

   269→                    Design Mode
   270→

   271→                      <button
   272→                        type="button"
   273→                        onClick={() => setEditDesignMode('mvp')}
   274→                        className={`p-3 rounded-lg border text-left transition-colors ${
   275→                          editDesignMode === 'mvp'
   276→                            ? 'border-amber-500 bg-amber-500/10'
   277→                            : 'border-border hover:border-muted-foreground'
   278→                        }`}
   279→                      >
   280→
MVP

   281→
Functional drafts OK

   282→                      </button>
   283→                      <button
   284→                        type="button"
   285→                        onClick={() => setEditDesignMode('finished')}
   286→                        className={`p-3 rounded-lg border text-left transition-colors ${
   287→                          editDesignMode === 'finished'
   288→                            ? 'border-accent bg-accent/10'
   289→                            : 'border-border hover:border-muted-foreground'
   290→                        }`}
   291→                      >
   292→
Finished

   293→
Polished required

   294→                      </button>
   295→

   296→

   297→

   298→

   299→                  <Dialog.Close asChild>
   300→                    <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
   301→                      Cancel
   302→                    </button>
   303→                  </Dialog.Close>
   304→                  <button
   305→                    onClick={handleSaveProject}
   306→                    disabled={isSaving || !editName.trim()}
   307→                    className="flex items-center gap-2 px-4 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
   308→                  >
   309→                    {saveSuccess ? (
   310→                      <>
   311→                        <Check size={16} />
   312→                        Saved!
   313→                      </>
   314→                    ) : isSaving ? (
   315→                      'Saving...'
   316→                    ) : (
   317→                      'Save Changes'
   318→                    )}
   319→                  </button>
   320→

   321→              </Dialog.Content>
   322→            </Dialog.Portal>
   323→          </Dialog.Root>
   324→
   325→          {/* Delete Button and Dialog */}
   326→          <AlertDialog.Root open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
   327→          <AlertDialog.Trigger asChild>
   328→            <button
   329→              className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
   330→              title="Delete Project"
   331→            >
   332→              <Trash2 size={16} />
   333→              Delete
   334→            </button>
   335→          </AlertDialog.Trigger>
   336→          <AlertDialog.Portal>
   337→            <AlertDialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
   338→            <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
   339→              <AlertDialog.Title className="text-xl font-semibold text-foreground">
   340→                Delete Project
   341→              </AlertDialog.Title>
   342→              <AlertDialog.Description className="mt-3 text-muted-foreground">
   343→                Are you sure you want to delete {currentProject.name}?
   344→                This action cannot be undone. All project data, tasks, and history will be permanently deleted.
   345→              </AlertDialog.Description>
   346→

   347→                <AlertDialog.Cancel asChild>
   348→                  <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
   349→                    Cancel
   350→                  </button>
   351→                </AlertDialog.Cancel>
   352→                <AlertDialog.Action asChild>
   353→                  <button
   354→                    onClick={handleDeleteProject}
   355→                    disabled={isDeleting}
   356→                    className="px-4 py-2 text-sm bg-destructive hover:bg-destructive/80 text-white rounded-lg transition-colors disabled:opacity-50"
   357→                  >
   358→                    {isDeleting ? 'Deleting...' : 'Delete Project'}
   359→                  </button>
   360→                </AlertDialog.Action>
   361→

   362→            </AlertDialog.Content>
   363→          </AlertDialog.Portal>
   364→        </AlertDialog.Root>
   365→

   366→

   367→
   368→      {/* Stats Grid */}
   369→

   370→        {stats.map((stat) => {
   371→          const Icon = stat.icon
   372→          return (
   373→

   377→

   378→                <Icon className={stat.color} size={24} />
   379→

   380→
{stat.label}

   381→
{stat.value}

   382→

   383→

   384→

   385→          )
   386→        })}
   387→

   388→
   389→      {/* Phase Progress */}
   390→

   391→
Phase Progress

   392→

   393→

   394→

   395→              Phase {currentProject.current_phase}
   396→              {currentProject.total_phases} total
   397→

   398→

   399→              <div
   400→                className="h-full bg-accent transition-all duration-500"
   401→                style={{
   402→                  width: `${(currentProject.current_phase / currentProject.total_phases) * 100}%`,
   403→                }}
   404→              />
   405→

   406→

   407→
   408→            {Math.round((currentProject.current_phase / currentProject.total_phases) * 100)}%
   409→
   410→

   411→

   412→
   413→      {/* Recent Activity */}
   414→

   415→
Recent Activity

   416→

   417→          {activeSessions.length === 0 ? (
   418→
No recent activity

   419→          ) : (
   420→            activeSessions.slice(0, 5).map((session) => (
   421→

   425→

   426→                  <div
   427→                    className={`w-2 h-2 rounded-full ${
   428→                      session.status === 'running'
   429→                        ? 'bg-accent animate-pulse'
   430→                        : session.status === 'completed'
   431→                        ? 'bg-green-400'
   432→                        : session.status === 'failed'
   433→                        ? 'bg-error'
   434→                        : 'bg-muted-foreground'
   435→                    }`}
   436→                  />
   437→                  {session.agent_type.replace('_', ' ')} Agent
   438→

   439→                {session.status}
   440→

   441→            ))
   442→          )}
   443→

   444→

   445→

   446→  )
   447→}
   448→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import { useEffect, useState } from 'react'
import { useProjectStore } from '../stores/projectStore'
import { useAgentStore } from '../stores/agentStore'
import { useToastStore } from '../components/Toast'
import {
  FolderOpen,
  CheckCircle,
  Clock,
  AlertTriangle,
  Activity,
  Trash2,
  Pencil,
  Check,
  Palette,
  X,
} from 'lucide-react'
import * as AlertDialog from '@radix-ui/react-alert-dialog'
import * as Dialog from '@radix-ui/react-dialog'

interface TaskStats {
  completed: number
  inProgress: number
  blocked: number
  backlog: number
  qa: number
}

export default function Dashboard() {
  const { currentProject, fetchProjects, deleteProject, updateProject } = useProjectStore()
  const { activeSessions, fetchActiveSessions } = useAgentStore()
  const { addToast } = useToastStore()
  const [showDeleteDialog, setShowDeleteDialog] = useState(false)
  const [isDeleting, setIsDeleting] = useState(false)
  const [showEditDialog, setShowEditDialog] = useState(false)
  const [editName, setEditName] = useState('')
  const [editDescription, setEditDescription] = useState('')
  const [editDesignMode, setEditDesignMode] = useState<'mvp' | 'finished'>('mvp')
  const [isSaving, setIsSaving] = useState(false)
  const [saveSuccess, setSaveSuccess] = useState(false)
  const [taskStats, setTaskStats] = useState<TaskStats>({
    completed: 0,
    inProgress: 0,
    blocked: 0,
    backlog: 0,
    qa: 0,
  })

  useEffect(() => {
    fetchProjects()
    fetchActiveSessions()
  }, [fetchProjects, fetchActiveSessions])

  // Fetch task statistics when current project changes
  useEffect(() => {
    const fetchTaskStats = async () => {
      if (!currentProject) return

      try {
        const response = await fetch(`http://localhost:8000/api/projects/${currentProject.id}/tasks`)
        if (!response.ok) return

        const tasks = await response.json()
        const stats: TaskStats = {
          completed: 0,
          inProgress: 0,
          blocked: 0,
          backlog: 0,
          qa: 0,
        }

        tasks.forEach((task: { status: string }) => {
          switch (task.status) {
            case 'done':
              stats.completed++
              break
            case 'in_progress':
              stats.inProgress++
              break
            case 'blocked':
              stats.blocked++
              break
            case 'backlog':
              stats.backlog++
              break
            case 'qa':
              stats.qa++
              break
          }
        })

        setTaskStats(stats)
      } catch (error) {
        console.error('Failed to fetch task stats:', error)
      }
    }

    fetchTaskStats()
  }, [currentProject])

  const handleDeleteProject = async () => {
    if (!currentProject) return
    setIsDeleting(true)
    try {
      await deleteProject(currentProject.id)
      setShowDeleteDialog(false)
    } catch (error) {
      console.error('Failed to delete project:', error)
      addToast('error', 'Delete Failed', `Failed to delete project "${currentProject.name}". Please try again.`)
    } finally {
      setIsDeleting(false)
    }
  }

  const handleOpenEditDialog = () => {
    if (currentProject) {
      setEditName(currentProject.name)
      setEditDescription(currentProject.description || '')
      setEditDesignMode((currentProject.design_mode as 'mvp' | 'finished') || 'mvp')
      setSaveSuccess(false)
      setShowEditDialog(true)
    }
  }

  const handleSaveProject = async () => {
    if (!currentProject) return
    setIsSaving(true)
    setSaveSuccess(false)
    try {
      const response = await fetch(`http://localhost:8000/api/projects/${currentProject.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: editName, description: editDescription, design_mode: editDesignMode }),
      })
      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}))
        throw new Error(errorData.detail || 'Failed to update project')
      }
      const updated = await response.json()
      updateProject(currentProject.id, updated)
      setSaveSuccess(true)
      setTimeout(() => {
        setShowEditDialog(false)
        setSaveSuccess(false)
      }, 1000)
    } catch (error) {
      console.error('Failed to save project:', error)
      const message = error instanceof Error ? error.message : 'Failed to save project'
      addToast('error', 'Save Failed', message)
    } finally {
      setIsSaving(false)
    }
  }

  if (!currentProject) {
    return (

        <FolderOpen size={64} className="text-muted-foreground mb-4" />

          No Project Selected

          Select a project from the sidebar or create a new one to get started.

    )
  }

  const stats = [
    {
      label: 'Tasks Completed',
      value: taskStats.completed,
      icon: CheckCircle,
      color: 'text-green-400',
    },
    {
      label: 'Tasks In Progress',
      value: taskStats.inProgress,
      icon: Clock,
      color: 'text-accent',
    },
    {
      label: 'Blocked Tasks',
      value: taskStats.blocked,
      icon: AlertTriangle,
      color: 'text-warning',
    },
    {
      label: 'Active Agents',
      value: activeSessions.filter((s) => s.status === 'running').length,
      icon: Activity,
      color: 'text-primary-400',
    },
  ]

  return (

      {/* Project Header */}

{currentProject.name}

            {/* Design Mode Badge */}

              <Palette size={12} />
              {currentProject.design_mode === 'finished' ? 'Finished Mode' : 'MVP Mode'}

          {currentProject.description && (

{currentProject.description}

          )}

          {/* Edit Button and Dialog */}
          <Dialog.Root open={showEditDialog} onOpenChange={setShowEditDialog}>
            <Dialog.Trigger asChild>
              <button
                onClick={handleOpenEditDialog}
                className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-accent hover:bg-accent/10 rounded-lg transition-colors"
                title="Edit Project Settings"
              >
                <Pencil size={16} />
                Edit
              </button>
            </Dialog.Trigger>
            <Dialog.Portal>
              <Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
              <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">

                  <Dialog.Title className="text-xl font-semibold text-foreground">
                    Edit Project Settings
                  </Dialog.Title>
                  <Dialog.Close asChild>
                    <button className="p-1 text-muted-foreground hover:text-foreground hover:bg-secondary rounded transition-colors" aria-label="Close">
                      <X size={20} />
                    </button>
                  </Dialog.Close>

                <Dialog.Description className="mt-2 text-muted-foreground">
                  Update your project name, description, and design mode.
                </Dialog.Description>

                    Project Name
                    <input
                      type="text"
                      value={editName}
                      onChange={(e) => setEditName(e.target.value)}
                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
                      placeholder="Enter project name"
                    />

                    Description
                    <textarea
                      value={editDescription}
                      onChange={(e) => setEditDescription(e.target.value)}
                      rows={3}
                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent resize-none"
                      placeholder="Enter project description"
                    />

                    Design Mode

                      <button
                        type="button"
                        onClick={() => setEditDesignMode('mvp')}
                        className={`p-3 rounded-lg border text-left transition-colors ${
                          editDesignMode === 'mvp'
                            ? 'border-amber-500 bg-amber-500/10'
                            : 'border-border hover:border-muted-foreground'
                        }`}
                      >

MVP

Functional drafts OK

                      </button>
                      <button
                        type="button"
                        onClick={() => setEditDesignMode('finished')}
                        className={`p-3 rounded-lg border text-left transition-colors ${
                          editDesignMode === 'finished'
                            ? 'border-accent bg-accent/10'
                            : 'border-border hover:border-muted-foreground'
                        }`}
                      >

Finished

Polished required

                      </button>

                  <Dialog.Close asChild>
                    <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
                      Cancel
                    </button>
                  </Dialog.Close>
                  <button
                    onClick={handleSaveProject}
                    disabled={isSaving || !editName.trim()}
                    className="flex items-center gap-2 px-4 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
                  >
                    {saveSuccess ? (
                      <>
                        <Check size={16} />
                        Saved!
                      </>
                    ) : isSaving ? (
                      'Saving...'
                    ) : (
                      'Save Changes'
                    )}
                  </button>

              </Dialog.Content>
            </Dialog.Portal>
          </Dialog.Root>

          {/* Delete Button and Dialog */}
          <AlertDialog.Root open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
          <AlertDialog.Trigger asChild>
            <button
              className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
              title="Delete Project"
            >
              <Trash2 size={16} />
              Delete
            </button>
          </AlertDialog.Trigger>
          <AlertDialog.Portal>
            <AlertDialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
            <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
              <AlertDialog.Title className="text-xl font-semibold text-foreground">
                Delete Project
              </AlertDialog.Title>
              <AlertDialog.Description className="mt-3 text-muted-foreground">
                Are you sure you want to delete {currentProject.name}?
                This action cannot be undone. All project data, tasks, and history will be permanently deleted.
              </AlertDialog.Description>

                <AlertDialog.Cancel asChild>
                  <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
                    Cancel
                  </button>
                </AlertDialog.Cancel>
                <AlertDialog.Action asChild>
                  <button
                    onClick={handleDeleteProject}
                    disabled={isDeleting}
                    className="px-4 py-2 text-sm bg-destructive hover:bg-destructive/80 text-white rounded-lg transition-colors disabled:opacity-50"
                  >
                    {isDeleting ? 'Deleting...' : 'Delete Project'}
                  </button>
                </AlertDialog.Action>

            </AlertDialog.Content>
          </AlertDialog.Portal>
        </AlertDialog.Root>

      {/* Stats Grid */}

        {stats.map((stat) => {
          const Icon = stat.icon
          return (

                <Icon className={stat.color} size={24} />

{stat.label}

{stat.value}

          )
        })}

      {/* Phase Progress */}

Phase Progress

              Phase {currentProject.current_phase}
              {currentProject.total_phases} total

              <div
                className="h-full bg-accent transition-all duration-500"
                style={{
                  width: `${(currentProject.current_phase / currentProject.total_phases) * 100}%`,
                }}
              />

            {Math.round((currentProject.current_phase / currentProject.total_phases) * 100)}%

      {/* Recent Activity */}

Recent Activity

          {activeSessions.length === 0 ? (

No recent activity

          ) : (
            activeSessions.slice(0, 5).map((session) => (

                  {session.agent_type.replace('_', ' ')} Agent

                {session.status}

            ))
          )}

  )
}


The Dashboard page doesn't have an Export feature. This is something I need to build. Let me implement the Export Project Data feature:

1. Create a backend API endpoint for exporting project data
2. Add an Export button to the Dashboard page

Let me start by creating the backend API:


     1→"""
     2→Projects API Router
     3→
     4→Handles CRUD operations for projects.
     5→"""
     6→
     7→from typing import List, Optional
     8→from fastapi import APIRouter, Depends, HTTPException, status
     9→from sqlalchemy.ext.asyncio import AsyncSession
    10→from sqlalchemy import delete as sql_delete
    11→from pydantic import BaseModel, field_validator
    12→from datetime import datetime
    13→
    14→from services.database import get_db
    15→from models.project import Project
    16→
    17→router = APIRouter()
    18→
    19→
    20→class ProjectCreate(BaseModel):
    21→    """Schema for creating a new project."""
    22→    name: str
    23→    description: Optional[str] = None
    24→    path: Optional[str] = None
    25→    complexity: str = "medium"
    26→    design_mode: str = "mvp"  # mvp (functional drafts acceptable), finished (polished required)
    27→
    28→    @field_validator('name')
    29→    @classmethod
    30→    def validate_name(cls, v: str) -> str:
    31→        """Validate project name matches client-side rules."""
    32→        stripped = v.strip() if v else ''
    33→        if not stripped:
    34→            raise ValueError('Project name is required')
    35→        if len(stripped) < 2:
    36→            raise ValueError('Project name must be at least 2 characters')
    37→        if len(stripped) > 100:
    38→            raise ValueError('Project name must be less than 100 characters')
    39→        return stripped
    40→
    41→
    42→class ProjectUpdate(BaseModel):
    43→    """Schema for updating a project."""
    44→    name: Optional[str] = None
    45→    description: Optional[str] = None
    46→    path: Optional[str] = None
    47→    status: Optional[str] = None
    48→    complexity: Optional[str] = None
    49→    design_mode: Optional[str] = None
    50→    current_phase: Optional[int] = None
    51→
    52→    @field_validator('name')
    53→    @classmethod
    54→    def validate_name(cls, v: Optional[str]) -> Optional[str]:
    55→        """Validate project name matches client-side rules."""
    56→        if v is None:
    57→            return v
    58→        stripped = v.strip() if v else ''
    59→        if not stripped:
    60→            raise ValueError('Project name is required')
    61→        if len(stripped) < 2:
    62→            raise ValueError('Project name must be at least 2 characters')
    63→        if len(stripped) > 100:
    64→            raise ValueError('Project name must be less than 100 characters')
    65→        return stripped
    66→
    67→
    68→class ProjectResponse(BaseModel):
    69→    """Schema for project response."""
    70→    id: int
    71→    name: str
    72→    description: Optional[str]
    73→    path: Optional[str]
    74→    status: str
    75→    complexity: str
    76→    design_mode: str
    77→    current_phase: int
    78→    total_phases: int
    79→    spec_version: int
    80→    created_at: datetime
    81→    updated_at: datetime
    82→
    83→    class Config:
    84→        from_attributes = True
    85→
    86→
    87→@router.get("", response_model=List[ProjectResponse])
    88→async def list_projects(db: AsyncSession = Depends(get_db)):
    89→    """List all projects."""
    90→    projects = await Project.get_all(db)
    91→    return projects
    92→
    93→
    94→@router.post("", response_model=ProjectResponse, status_code=status.HTTP_201_CREATED)
    95→async def create_project(project: ProjectCreate, db: AsyncSession = Depends(get_db)):
    96→    """Create a new project."""
    97→    # Check for duplicate project name
    98→    existing_project = await Project.get_by_name(db, project.name)
    99→    if existing_project:
   100→        raise HTTPException(
   101→            status_code=status.HTTP_409_CONFLICT,
   102→            detail=f"A project named '{project.name}' already exists"
   103→        )
   104→
   105→    new_project = await Project.create(
   106→        db,
   107→        name=project.name,
   108→        description=project.description,
   109→        path=project.path,
   110→        complexity=project.complexity,
   111→        design_mode=project.design_mode,
   112→    )
   113→    return new_project
   114→
   115→
   116→@router.get("/{project_id}", response_model=ProjectResponse)
   117→async def get_project(project_id: int, db: AsyncSession = Depends(get_db)):
   118→    """Get a specific project by ID."""
   119→    project = await Project.get_by_id(db, project_id)
   120→    if not project:
   121→        raise HTTPException(
   122→            status_code=status.HTTP_404_NOT_FOUND,
   123→            detail=f"Project with ID {project_id} not found"
   124→        )
   125→    return project
   126→
   127→
   128→@router.put("/{project_id}", response_model=ProjectResponse)
   129→async def update_project(
   130→    project_id: int,
   131→    project_update: ProjectUpdate,
   132→    db: AsyncSession = Depends(get_db)
   133→):
   134→    """Update a project."""
   135→    project = await Project.get_by_id(db, project_id)
   136→    if not project:
   137→        raise HTTPException(
   138→            status_code=status.HTTP_404_NOT_FOUND,
   139→            detail=f"Project with ID {project_id} not found"
   140→        )
   141→
   142→    updated_project = await Project.update(
   143→        db,
   144→        project_id,
   145→        **project_update.model_dump(exclude_unset=True)
   146→    )
   147→    return updated_project
   148→
   149→
   150→@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
   151→async def delete_project(project_id: int, db: AsyncSession = Depends(get_db)):
   152→    """Delete a project and all related data (cascade delete)."""
   153→    from models.task import Task
   154→    from models.memory import MemoryProject
   155→    from models.spec import Spec
   156→    from models.phase import Phase
   157→    from models.agent_session import AgentSession
   158→    from models.notification import Notification
   159→
   160→    project = await Project.get_by_id(db, project_id)
   161→    if not project:
   162→        raise HTTPException(
   163→            status_code=status.HTTP_404_NOT_FOUND,
   164→            detail=f"Project with ID {project_id} not found"
   165→        )
   166→
   167→    # Delete all related data manually (for SQLite compatibility)
   168→    await db.execute(sql_delete(Task).where(Task.project_id == project_id))
   169→    await db.execute(sql_delete(MemoryProject).where(MemoryProject.project_id == project_id))
   170→    await db.execute(sql_delete(Spec).where(Spec.project_id == project_id))
   171→    await db.execute(sql_delete(Phase).where(Phase.project_id == project_id))
   172→    await db.execute(sql_delete(AgentSession).where(AgentSession.project_id == project_id))
   173→    await db.execute(sql_delete(Notification).where(Notification.project_id == project_id))
   174→
   175→    # Delete the project itself
   176→    await db.delete(project)
   177→    await db.flush()
   178→    return None
   179→
   180→
   181→@router.post("/{project_id}/adopt", response_model=ProjectResponse)
   182→async def adopt_codebase(
   183→    project_id: int,
   184→    path: str,
   185→    db: AsyncSession = Depends(get_db)
   186→):
   187→    """Adopt an existing codebase into a project."""
   188→    project = await Project.get_by_id(db, project_id)
   189→    if not project:
   190→        raise HTTPException(
   191→            status_code=status.HTTP_404_NOT_FOUND,
   192→            detail=f"Project with ID {project_id} not found"
   193→        )
   194→
   195→    # Update project with codebase path
   196→    updated_project = await Project.update(db, project_id, path=path)
   197→
   198→    # TODO: Trigger codebase analysis and spec interview
   199→
   200→    return updated_project
   201→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

"""
Projects API Router

Handles CRUD operations for projects.
"""

from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import delete as sql_delete
from pydantic import BaseModel, field_validator
from datetime import datetime

from services.database import get_db
from models.project import Project

router = APIRouter()

class ProjectCreate(BaseModel):
    """Schema for creating a new project."""
    name: str
    description: Optional[str] = None
    path: Optional[str] = None
    complexity: str = "medium"
    design_mode: str = "mvp"  # mvp (functional drafts acceptable), finished (polished required)

    @field_validator('name')
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Validate project name matches client-side rules."""
        stripped = v.strip() if v else ''
        if not stripped:
            raise ValueError('Project name is required')
        if len(stripped) < 2:
            raise ValueError('Project name must be at least 2 characters')
        if len(stripped) > 100:
            raise ValueError('Project name must be less than 100 characters')
        return stripped

class ProjectUpdate(BaseModel):
    """Schema for updating a project."""
    name: Optional[str] = None
    description: Optional[str] = None
    path: Optional[str] = None
    status: Optional[str] = None
    complexity: Optional[str] = None
    design_mode: Optional[str] = None
    current_phase: Optional[int] = None

    @field_validator('name')
    @classmethod
    def validate_name(cls, v: Optional[str]) -> Optional[str]:
        """Validate project name matches client-side rules."""
        if v is None:
            return v
        stripped = v.strip() if v else ''
        if not stripped:
            raise ValueError('Project name is required')
        if len(stripped) < 2:
            raise ValueError('Project name must be at least 2 characters')
        if len(stripped) > 100:
            raise ValueError('Project name must be less than 100 characters')
        return stripped

class ProjectResponse(BaseModel):
    """Schema for project response."""
    id: int
    name: str
    description: Optional[str]
    path: Optional[str]
    status: str
    complexity: str
    design_mode: str
    current_phase: int
    total_phases: int
    spec_version: int
    created_at: datetime
    updated_at: datetime

    class Config:
        from_attributes = True

@router.get("", response_model=List[ProjectResponse])
async def list_projects(db: AsyncSession = Depends(get_db)):
    """List all projects."""
    projects = await Project.get_all(db)
    return projects

@router.post("", response_model=ProjectResponse, status_code=status.HTTP_201_CREATED)
async def create_project(project: ProjectCreate, db: AsyncSession = Depends(get_db)):
    """Create a new project."""
    # Check for duplicate project name
    existing_project = await Project.get_by_name(db, project.name)
    if existing_project:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"A project named '{project.name}' already exists"
        )

    new_project = await Project.create(
        db,
        name=project.name,
        description=project.description,
        path=project.path,
        complexity=project.complexity,
        design_mode=project.design_mode,
    )
    return new_project

@router.get("/{project_id}", response_model=ProjectResponse)
async def get_project(project_id: int, db: AsyncSession = Depends(get_db)):
    """Get a specific project by ID."""
    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )
    return project

@router.put("/{project_id}", response_model=ProjectResponse)
async def update_project(
    project_id: int,
    project_update: ProjectUpdate,
    db: AsyncSession = Depends(get_db)
):
    """Update a project."""
    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    updated_project = await Project.update(
        db,
        project_id,
        **project_update.model_dump(exclude_unset=True)
    )
    return updated_project

@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_project(project_id: int, db: AsyncSession = Depends(get_db)):
    """Delete a project and all related data (cascade delete)."""
    from models.task import Task
    from models.memory import MemoryProject
    from models.spec import Spec
    from models.phase import Phase
    from models.agent_session import AgentSession
    from models.notification import Notification

    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    # Delete all related data manually (for SQLite compatibility)
    await db.execute(sql_delete(Task).where(Task.project_id == project_id))
    await db.execute(sql_delete(MemoryProject).where(MemoryProject.project_id == project_id))
    await db.execute(sql_delete(Spec).where(Spec.project_id == project_id))
    await db.execute(sql_delete(Phase).where(Phase.project_id == project_id))
    await db.execute(sql_delete(AgentSession).where(AgentSession.project_id == project_id))
    await db.execute(sql_delete(Notification).where(Notification.project_id == project_id))

    # Delete the project itself
    await db.delete(project)
    await db.flush()
    return None

@router.post("/{project_id}/adopt", response_model=ProjectResponse)
async def adopt_codebase(
    project_id: int,
    path: str,
    db: AsyncSession = Depends(get_db)
):
    """Adopt an existing codebase into a project."""
    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    # Update project with codebase path
    updated_project = await Project.update(db, project_id, path=path)

    # TODO: Trigger codebase analysis and spec interview

    return updated_project


Now let me add an export endpoint to the projects API:


Edit

@router.post("/{project_id}/adopt", response_model=ProjectResponse)
async def adopt_codebase(
    project_id: int,
    path: str,
    db: AsyncSession = Depends(get_db)
):
    """Adopt an existing codebase into a project."""
    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    # Update project with codebase path
    updated_project = await Project.update(db, project_id, path=path)

    # TODO: Trigger codebase analysis and spec interview

    return updated_project

@router.post("/{project_id}/adopt", response_model=ProjectResponse)
async def adopt_codebase(
    project_id: int,
    path: str,
    db: AsyncSession = Depends(get_db)
):
    """Adopt an existing codebase into a project."""
    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    # Update project with codebase path
    updated_project = await Project.update(db, project_id, path=path)

    # TODO: Trigger codebase analysis and spec interview

    return updated_project

@router.get("/{project_id}/export")
async def export_project(project_id: int, db: AsyncSession = Depends(get_db)):
    """Export complete project data including tasks and memory as JSON."""
    from models.task import Task
    from models.memory import MemoryProject
    from models.spec import Spec
    from models.phase import Phase
    import json

    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    # Gather all related data
    tasks = await Task.get_all_by_project(db, project_id)
    memory_items = await MemoryProject.get_all_by_project(db, project_id)
    specs = await Spec.get_all_by_project(db, project_id)
    phases = await Phase.get_all_by_project(db, project_id)

    export_data = {
        "export_version": "1.0",
        "exported_at": datetime.utcnow().isoformat(),
        "project": {
            "id": project.id,
            "name": project.name,
            "description": project.description,
            "path": project.path,
            "status": project.status,
            "complexity": project.complexity,
            "design_mode": project.design_mode,
            "current_phase": project.current_phase,
            "total_phases": project.total_phases,
            "spec_version": project.spec_version,
            "created_at": project.created_at.isoformat() if project.created_at else None,
            "updated_at": project.updated_at.isoformat() if project.updated_at else None,
        },
        "tasks": [
            {
                "id": t.id,
                "name": t.name,
                "description": t.description,
                "status": t.status,
                "priority": t.priority,
                "phase_id": t.phase_id,
                "agent_type": t.agent_type,
                "iteration_count": t.iteration_count,
                "flagged_for_review": t.flagged_for_review,
                "estimated_time": t.estimated_time,
                "actual_time": t.actual_time,
                "created_at": t.created_at.isoformat() if t.created_at else None,
                "updated_at": t.updated_at.isoformat() if t.updated_at else None,
            }
            for t in tasks
        ],
        "memory": [
            {
                "id": m.id,
                "type": m.type,
                "content": m.content,
                "metadata": m.metadata,
                "created_at": m.created_at.isoformat() if m.created_at else None,
            }
            for m in memory_items
        ],
        "specs": [
            {
                "id": s.id,
                "version": s.version,
                "content": s.content,
                "is_active": s.is_active,
                "parent_spec_id": s.parent_spec_id,
                "created_at": s.created_at.isoformat() if s.created_at else None,
            }
            for s in specs
        ],
        "phases": [
            {
                "id": p.id,
                "name": p.name,
                "order": p.order,
                "status": p.status,
                "milestone_type": p.milestone_type,
            }
            for p in phases
        ],
    }

    from fastapi.responses import Response

    return Response(
        content=json.dumps(export_data, indent=2),
        media_type="application/json",
        headers={
            "Content-Disposition": f'attachment; filename="{project.name}_export.json"'
        }
    )


The file C:\Users\MikeSwanson\AutoBuilder\My-Auto-Builder\backend\api\projects.py has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
   177→    await db.flush()
   178→    return None
   179→
   180→
   181→@router.post("/{project_id}/adopt", response_model=ProjectResponse)
   182→async def adopt_codebase(
   183→    project_id: int,
   184→    path: str,
   185→    db: AsyncSession = Depends(get_db)
   186→):
   187→    """Adopt an existing codebase into a project."""
   188→    project = await Project.get_by_id(db, project_id)
   189→    if not project:
   190→        raise HTTPException(
   191→            status_code=status.HTTP_404_NOT_FOUND,
   192→            detail=f"Project with ID {project_id} not found"
   193→        )
   194→
   195→    # Update project with codebase path
   196→    updated_project = await Project.update(db, project_id, path=path)
   197→
   198→    # TODO: Trigger codebase analysis and spec interview
   199→
   200→    return updated_project
   201→
   202→
   203→@router.get("/{project_id}/export")
   204→async def export_project(project_id: int, db: AsyncSession = Depends(get_db)):
   205→    """Export complete project data including tasks and memory as JSON."""
   206→    from models.task import Task
   207→    from models.memory import MemoryProject
   208→    from models.spec import Spec
   209→    from models.phase import Phase
   210→    import json
   211→
   212→    project = await Project.get_by_id(db, project_id)
   213→    if not project:
   214→        raise HTTPException(
   215→            status_code=status.HTTP_404_NOT_FOUND,
   216→            detail=f"Project with ID {project_id} not found"
   217→        )
   218→
   219→    # Gather all related data
   220→    tasks = await Task.get_all_by_project(db, project_id)
   221→    memory_items = await MemoryProject.get_all_by_project(db, project_id)
   222→    specs = await Spec.get_all_by_project(db, project_id)
   223→    phases = await Phase.get_all_by_project(db, project_id)
   224→
   225→    export_data = {
   226→        "export_version": "1.0",
   227→        "exported_at": datetime.utcnow().isoformat(),
   228→        "project": {
   229→            "id": project.id,
   230→            "name": project.name,
   231→            "description": project.description,
   232→            "path": project.path,
   233→            "status": project.status,
   234→            "complexity": project.complexity,
   235→            "design_mode": project.design_mode,
   236→            "current_phase": project.current_phase,
   237→            "total_phases": project.total_phases,
   238→            "spec_version": project.spec_version,
   239→            "created_at": project.created_at.isoformat() if project.created_at else None,
   240→            "updated_at": project.updated_at.isoformat() if project.updated_at else None,
   241→        },
   242→        "tasks": [
   243→            {
   244→                "id": t.id,
   245→                "name": t.name,
   246→                "description": t.description,
   247→                "status": t.status,
   248→                "priority": t.priority,
   249→                "phase_id": t.phase_id,
   250→                "agent_type": t.agent_type,
   251→                "iteration_count": t.iteration_count,
   252→                "flagged_for_review": t.flagged_for_review,
   253→                "estimated_time": t.estimated_time,
   254→                "actual_time": t.actual_time,
   255→                "created_at": t.created_at.isoformat() if t.created_at else None,
   256→                "updated_at": t.updated_at.isoformat() if t.updated_at else None,
   257→            }
   258→            for t in tasks
   259→        ],
   260→        "memory": [
   261→            {
   262→                "id": m.id,
   263→                "type": m.type,
   264→                "content": m.content,
   265→                "metadata": m.metadata,
   266→                "created_at": m.created_at.isoformat() if m.created_at else None,
   267→            }
   268→            for m in memory_items
   269→        ],
   270→        "specs": [
   271→            {
   272→                "id": s.id,
   273→                "version": s.version,
   274→                "content": s.content,
   275→                "is_active": s.is_active,
   276→                "parent_spec_id": s.parent_spec_id,
   277→                "created_at": s.created_at.isoformat() if s.created_at else None,
   278→            }
   279→            for s in specs
   280→        ],
   281→        "phases": [
   282→            {
   283→                "id": p.id,
   284→                "name": p.name,
   285→                "order": p.order,
   286→                "status": p.status,
   287→                "milestone_type": p.milestone_type,
   288→            }
   289→            for p in phases
   290→        ],
   291→    }
   292→
   293→    from fastapi.responses import Response
   294→
   295→    return Response(
   296→        content=json.dumps(export_data, indent=2),
   297→        media_type="application/json",
   298→        headers={
   299→            "Content-Disposition": f'attachment; filename="{project.name}_export.json"'
   300→        }
   301→    )
   302→

@router.post("/{project_id}/adopt", response_model=ProjectResponse)
async def adopt_codebase(
    project_id: int,
    path: str,
    db: AsyncSession = Depends(get_db)
):
    """Adopt an existing codebase into a project."""
    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    # Update project with codebase path
    updated_project = await Project.update(db, project_id, path=path)

    # TODO: Trigger codebase analysis and spec interview

    return updated_project

@router.get("/{project_id}/export")
async def export_project(project_id: int, db: AsyncSession = Depends(get_db)):
    """Export complete project data including tasks and memory as JSON."""
    from models.task import Task
    from models.memory import MemoryProject
    from models.spec import Spec
    from models.phase import Phase
    import json

    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    # Gather all related data
    tasks = await Task.get_all_by_project(db, project_id)
    memory_items = await MemoryProject.get_all_by_project(db, project_id)
    specs = await Spec.get_all_by_project(db, project_id)
    phases = await Phase.get_all_by_project(db, project_id)

    export_data = {
        "export_version": "1.0",
        "exported_at": datetime.utcnow().isoformat(),
        "project": {
            "id": project.id,
            "name": project.name,
            "description": project.description,
            "path": project.path,
            "status": project.status,
            "complexity": project.complexity,
            "design_mode": project.design_mode,
            "current_phase": project.current_phase,
            "total_phases": project.total_phases,
            "spec_version": project.spec_version,
            "created_at": project.created_at.isoformat() if project.created_at else None,
            "updated_at": project.updated_at.isoformat() if project.updated_at else None,
        },
        "tasks": [
            {
                "id": t.id,
                "name": t.name,
                "description": t.description,
                "status": t.status,
                "priority": t.priority,
                "phase_id": t.phase_id,
                "agent_type": t.agent_type,
                "iteration_count": t.iteration_count,
                "flagged_for_review": t.flagged_for_review,
                "estimated_time": t.estimated_time,
                "actual_time": t.actual_time,
                "created_at": t.created_at.isoformat() if t.created_at else None,
                "updated_at": t.updated_at.isoformat() if t.updated_at else None,
            }
            for t in tasks
        ],
        "memory": [
            {
                "id": m.id,
                "type": m.type,
                "content": m.content,
                "metadata": m.metadata,
                "created_at": m.created_at.isoformat() if m.created_at else None,
            }
            for m in memory_items
        ],
        "specs": [
            {
                "id": s.id,
                "version": s.version,
                "content": s.content,
                "is_active": s.is_active,
                "parent_spec_id": s.parent_spec_id,
                "created_at": s.created_at.isoformat() if s.created_at else None,
            }
            for s in specs
        ],
        "phases": [
            {
                "id": p.id,
                "name": p.name,
                "order": p.order,
                "status": p.status,
                "milestone_type": p.milestone_type,
            }
            for p in phases
        ],
    }

    from fastapi.responses import Response

    return Response(
        content=json.dumps(export_data, indent=2),
        media_type="application/json",
        headers={
            "Content-Disposition": f'attachment; filename="{project.name}_export.json"'
        }
    )

"""
Projects API Router

Handles CRUD operations for projects.
"""

from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import delete as sql_delete
from pydantic import BaseModel, field_validator
from datetime import datetime

from services.database import get_db
from models.project import Project

router = APIRouter()

class ProjectCreate(BaseModel):
    """Schema for creating a new project."""
    name: str
    description: Optional[str] = None
    path: Optional[str] = None
    complexity: str = "medium"
    design_mode: str = "mvp"  # mvp (functional drafts acceptable), finished (polished required)

    @field_validator('name')
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Validate project name matches client-side rules."""
        stripped = v.strip() if v else ''
        if not stripped:
            raise ValueError('Project name is required')
        if len(stripped) < 2:
            raise ValueError('Project name must be at least 2 characters')
        if len(stripped) > 100:
            raise ValueError('Project name must be less than 100 characters')
        return stripped

class ProjectUpdate(BaseModel):
    """Schema for updating a project."""
    name: Optional[str] = None
    description: Optional[str] = None
    path: Optional[str] = None
    status: Optional[str] = None
    complexity: Optional[str] = None
    design_mode: Optional[str] = None
    current_phase: Optional[int] = None

    @field_validator('name')
    @classmethod
    def validate_name(cls, v: Optional[str]) -> Optional[str]:
        """Validate project name matches client-side rules."""
        if v is None:
            return v
        stripped = v.strip() if v else ''
        if not stripped:
            raise ValueError('Project name is required')
        if len(stripped) < 2:
            raise ValueError('Project name must be at least 2 characters')
        if len(stripped) > 100:
            raise ValueError('Project name must be less than 100 characters')
        return stripped

class ProjectResponse(BaseModel):
    """Schema for project response."""
    id: int
    name: str
    description: Optional[str]
    path: Optional[str]
    status: str
    complexity: str
    design_mode: str
    current_phase: int
    total_phases: int
    spec_version: int
    created_at: datetime
    updated_at: datetime

    class Config:
        from_attributes = True

@router.get("", response_model=List[ProjectResponse])
async def list_projects(db: AsyncSession = Depends(get_db)):
    """List all projects."""
    projects = await Project.get_all(db)
    return projects

@router.post("", response_model=ProjectResponse, status_code=status.HTTP_201_CREATED)
async def create_project(project: ProjectCreate, db: AsyncSession = Depends(get_db)):
    """Create a new project."""
    # Check for duplicate project name
    existing_project = await Project.get_by_name(db, project.name)
    if existing_project:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"A project named '{project.name}' already exists"
        )

    new_project = await Project.create(
        db,
        name=project.name,
        description=project.description,
        path=project.path,
        complexity=project.complexity,
        design_mode=project.design_mode,
    )
    return new_project

@router.get("/{project_id}", response_model=ProjectResponse)
async def get_project(project_id: int, db: AsyncSession = Depends(get_db)):
    """Get a specific project by ID."""
    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )
    return project

@router.put("/{project_id}", response_model=ProjectResponse)
async def update_project(
    project_id: int,
    project_update: ProjectUpdate,
    db: AsyncSession = Depends(get_db)
):
    """Update a project."""
    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    updated_project = await Project.update(
        db,
        project_id,
        **project_update.model_dump(exclude_unset=True)
    )
    return updated_project

@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_project(project_id: int, db: AsyncSession = Depends(get_db)):
    """Delete a project and all related data (cascade delete)."""
    from models.task import Task
    from models.memory import MemoryProject
    from models.spec import Spec
    from models.phase import Phase
    from models.agent_session import AgentSession
    from models.notification import Notification

    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    # Delete all related data manually (for SQLite compatibility)
    await db.execute(sql_delete(Task).where(Task.project_id == project_id))
    await db.execute(sql_delete(MemoryProject).where(MemoryProject.project_id == project_id))
    await db.execute(sql_delete(Spec).where(Spec.project_id == project_id))
    await db.execute(sql_delete(Phase).where(Phase.project_id == project_id))
    await db.execute(sql_delete(AgentSession).where(AgentSession.project_id == project_id))
    await db.execute(sql_delete(Notification).where(Notification.project_id == project_id))

    # Delete the project itself
    await db.delete(project)
    await db.flush()
    return None

@router.post("/{project_id}/adopt", response_model=ProjectResponse)
async def adopt_codebase(
    project_id: int,
    path: str,
    db: AsyncSession = Depends(get_db)
):
    """Adopt an existing codebase into a project."""
    project = await Project.get_by_id(db, project_id)
    if not project:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Project with ID {project_id} not found"
        )

    # Update project with codebase path
    updated_project = await Project.update(db, project_id, path=path)

    # TODO: Trigger codebase analysis and spec interview

    return updated_project

+async def export_project(project_id: int, db: AsyncSession = Depends(get_db)):

+    """Export complete project data including tasks and memory as JSON."""

+    from models.memory import MemoryProject

+    project = await Project.get_by_id(db, project_id)

+            status_code=status.HTTP_404_NOT_FOUND,

+            detail=f"Project with ID {project_id} not found"

+    tasks = await Task.get_all_by_project(db, project_id)

+    memory_items = await MemoryProject.get_all_by_project(db, project_id)

+    specs = await Spec.get_all_by_project(db, project_id)

+    phases = await Phase.get_all_by_project(db, project_id)

+        "exported_at": datetime.utcnow().isoformat(),

+            "description": project.description,

+            "complexity": project.complexity,

+            "design_mode": project.design_mode,

+            "current_phase": project.current_phase,

+            "total_phases": project.total_phases,

+            "spec_version": project.spec_version,

+            "created_at": project.created_at.isoformat() if project.created_at else None,

+            "updated_at": project.updated_at.isoformat() if project.updated_at else None,

+                "description": t.description,

+                "priority": t.priority,

+                "phase_id": t.phase_id,

+                "agent_type": t.agent_type,

+                "iteration_count": t.iteration_count,

+                "flagged_for_review": t.flagged_for_review,

+                "estimated_time": t.estimated_time,

+                "actual_time": t.actual_time,

+                "created_at": t.created_at.isoformat() if t.created_at else None,

+                "updated_at": t.updated_at.isoformat() if t.updated_at else None,

+                "metadata": m.metadata,

+                "created_at": m.created_at.isoformat() if m.created_at else None,

+                "is_active": s.is_active,

+                "parent_spec_id": s.parent_spec_id,

+                "created_at": s.created_at.isoformat() if s.created_at else None,

+                "milestone_type": p.milestone_type,

+    from fastapi.responses import Response

+        content=json.dumps(export_data, indent=2),

+            "Content-Disposition": f'attachment; filename="{project.name}_export.json"'


Now I need to add the Export button to the Dashboard. Let me update Dashboard.tsx:


import {
  FolderOpen,
  CheckCircle,
  Clock,
  AlertTriangle,
  Activity,
  Trash2,
  Pencil,
  Check,
  Palette,
  X,
} from 'lucide-react'

import {
  FolderOpen,
  CheckCircle,
  Clock,
  AlertTriangle,
  Activity,
  Trash2,
  Pencil,
  Check,
  Palette,
  X,
  Download,
} from 'lucide-react'


The file C:\Users\MikeSwanson\AutoBuilder\My-Auto-Builder\frontend\src\pages\Dashboard.tsx has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
     1→import { useEffect, useState } from 'react'
     2→import { useProjectStore } from '../stores/projectStore'
     3→import { useAgentStore } from '../stores/agentStore'
     4→import { useToastStore } from '../components/Toast'
     5→import {
     6→  FolderOpen,
     7→  CheckCircle,
     8→  Clock,
     9→  AlertTriangle,
    10→  Activity,
    11→  Trash2,
    12→  Pencil,
    13→  Check,
    14→  Palette,
    15→  X,
    16→  Download,
    17→} from 'lucide-react'
    18→import * as AlertDialog from '@radix-ui/react-alert-dialog'
    19→import * as Dialog from '@radix-ui/react-dialog'
    20→
    21→interface TaskStats {

import {
  FolderOpen,
  CheckCircle,
  Clock,
  AlertTriangle,
  Activity,
  Trash2,
  Pencil,
  Check,
  Palette,
  X,
  Download,
} from 'lucide-react'

import { useEffect, useState } from 'react'
import { useProjectStore } from '../stores/projectStore'
import { useAgentStore } from '../stores/agentStore'
import { useToastStore } from '../components/Toast'
import {
  FolderOpen,
  CheckCircle,
  Clock,
  AlertTriangle,
  Activity,
  Trash2,
  Pencil,
  Check,
  Palette,
  X,
} from 'lucide-react'
import * as AlertDialog from '@radix-ui/react-alert-dialog'
import * as Dialog from '@radix-ui/react-dialog'

interface TaskStats {
  completed: number
  inProgress: number
  blocked: number
  backlog: number
  qa: number
}

export default function Dashboard() {
  const { currentProject, fetchProjects, deleteProject, updateProject } = useProjectStore()
  const { activeSessions, fetchActiveSessions } = useAgentStore()
  const { addToast } = useToastStore()
  const [showDeleteDialog, setShowDeleteDialog] = useState(false)
  const [isDeleting, setIsDeleting] = useState(false)
  const [showEditDialog, setShowEditDialog] = useState(false)
  const [editName, setEditName] = useState('')
  const [editDescription, setEditDescription] = useState('')
  const [editDesignMode, setEditDesignMode] = useState<'mvp' | 'finished'>('mvp')
  const [isSaving, setIsSaving] = useState(false)
  const [saveSuccess, setSaveSuccess] = useState(false)
  const [taskStats, setTaskStats] = useState<TaskStats>({
    completed: 0,
    inProgress: 0,
    blocked: 0,
    backlog: 0,
    qa: 0,
  })

  useEffect(() => {
    fetchProjects()
    fetchActiveSessions()
  }, [fetchProjects, fetchActiveSessions])

  // Fetch task statistics when current project changes
  useEffect(() => {
    const fetchTaskStats = async () => {
      if (!currentProject) return

      try {
        const response = await fetch(`http://localhost:8000/api/projects/${currentProject.id}/tasks`)
        if (!response.ok) return

        const tasks = await response.json()
        const stats: TaskStats = {
          completed: 0,
          inProgress: 0,
          blocked: 0,
          backlog: 0,
          qa: 0,
        }

        tasks.forEach((task: { status: string }) => {
          switch (task.status) {
            case 'done':
              stats.completed++
              break
            case 'in_progress':
              stats.inProgress++
              break
            case 'blocked':
              stats.blocked++
              break
            case 'backlog':
              stats.backlog++
              break
            case 'qa':
              stats.qa++
              break
          }
        })

        setTaskStats(stats)
      } catch (error) {
        console.error('Failed to fetch task stats:', error)
      }
    }

    fetchTaskStats()
  }, [currentProject])

  const handleDeleteProject = async () => {
    if (!currentProject) return
    setIsDeleting(true)
    try {
      await deleteProject(currentProject.id)
      setShowDeleteDialog(false)
    } catch (error) {
      console.error('Failed to delete project:', error)
      addToast('error', 'Delete Failed', `Failed to delete project "${currentProject.name}". Please try again.`)
    } finally {
      setIsDeleting(false)
    }
  }

  const handleOpenEditDialog = () => {
    if (currentProject) {
      setEditName(currentProject.name)
      setEditDescription(currentProject.description || '')
      setEditDesignMode((currentProject.design_mode as 'mvp' | 'finished') || 'mvp')
      setSaveSuccess(false)
      setShowEditDialog(true)
    }
  }

  const handleSaveProject = async () => {
    if (!currentProject) return
    setIsSaving(true)
    setSaveSuccess(false)
    try {
      const response = await fetch(`http://localhost:8000/api/projects/${currentProject.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: editName, description: editDescription, design_mode: editDesignMode }),
      })
      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}))
        throw new Error(errorData.detail || 'Failed to update project')
      }
      const updated = await response.json()
      updateProject(currentProject.id, updated)
      setSaveSuccess(true)
      setTimeout(() => {
        setShowEditDialog(false)
        setSaveSuccess(false)
      }, 1000)
    } catch (error) {
      console.error('Failed to save project:', error)
      const message = error instanceof Error ? error.message : 'Failed to save project'
      addToast('error', 'Save Failed', message)
    } finally {
      setIsSaving(false)
    }
  }

  if (!currentProject) {
    return (

        <FolderOpen size={64} className="text-muted-foreground mb-4" />

          No Project Selected

          Select a project from the sidebar or create a new one to get started.

    )
  }

  const stats = [
    {
      label: 'Tasks Completed',
      value: taskStats.completed,
      icon: CheckCircle,
      color: 'text-green-400',
    },
    {
      label: 'Tasks In Progress',
      value: taskStats.inProgress,
      icon: Clock,
      color: 'text-accent',
    },
    {
      label: 'Blocked Tasks',
      value: taskStats.blocked,
      icon: AlertTriangle,
      color: 'text-warning',
    },
    {
      label: 'Active Agents',
      value: activeSessions.filter((s) => s.status === 'running').length,
      icon: Activity,
      color: 'text-primary-400',
    },
  ]

  return (

      {/* Project Header */}

{currentProject.name}

            {/* Design Mode Badge */}

              <Palette size={12} />
              {currentProject.design_mode === 'finished' ? 'Finished Mode' : 'MVP Mode'}

          {currentProject.description && (

{currentProject.description}

          )}

          {/* Edit Button and Dialog */}
          <Dialog.Root open={showEditDialog} onOpenChange={setShowEditDialog}>
            <Dialog.Trigger asChild>
              <button
                onClick={handleOpenEditDialog}
                className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-accent hover:bg-accent/10 rounded-lg transition-colors"
                title="Edit Project Settings"
              >
                <Pencil size={16} />
                Edit
              </button>
            </Dialog.Trigger>
            <Dialog.Portal>
              <Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
              <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">

                  <Dialog.Title className="text-xl font-semibold text-foreground">
                    Edit Project Settings
                  </Dialog.Title>
                  <Dialog.Close asChild>
                    <button className="p-1 text-muted-foreground hover:text-foreground hover:bg-secondary rounded transition-colors" aria-label="Close">
                      <X size={20} />
                    </button>
                  </Dialog.Close>

                <Dialog.Description className="mt-2 text-muted-foreground">
                  Update your project name, description, and design mode.
                </Dialog.Description>

                    Project Name
                    <input
                      type="text"
                      value={editName}
                      onChange={(e) => setEditName(e.target.value)}
                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
                      placeholder="Enter project name"
                    />

                    Description
                    <textarea
                      value={editDescription}
                      onChange={(e) => setEditDescription(e.target.value)}
                      rows={3}
                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent resize-none"
                      placeholder="Enter project description"
                    />

                    Design Mode

                      <button
                        type="button"
                        onClick={() => setEditDesignMode('mvp')}
                        className={`p-3 rounded-lg border text-left transition-colors ${
                          editDesignMode === 'mvp'
                            ? 'border-amber-500 bg-amber-500/10'
                            : 'border-border hover:border-muted-foreground'
                        }`}
                      >

MVP

Functional drafts OK

                      </button>
                      <button
                        type="button"
                        onClick={() => setEditDesignMode('finished')}
                        className={`p-3 rounded-lg border text-left transition-colors ${
                          editDesignMode === 'finished'
                            ? 'border-accent bg-accent/10'
                            : 'border-border hover:border-muted-foreground'
                        }`}
                      >

Finished

Polished required

                      </button>

                  <Dialog.Close asChild>
                    <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
                      Cancel
                    </button>
                  </Dialog.Close>
                  <button
                    onClick={handleSaveProject}
                    disabled={isSaving || !editName.trim()}
                    className="flex items-center gap-2 px-4 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
                  >
                    {saveSuccess ? (
                      <>
                        <Check size={16} />
                        Saved!
                      </>
                    ) : isSaving ? (
                      'Saving...'
                    ) : (
                      'Save Changes'
                    )}
                  </button>

              </Dialog.Content>
            </Dialog.Portal>
          </Dialog.Root>

          {/* Delete Button and Dialog */}
          <AlertDialog.Root open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
          <AlertDialog.Trigger asChild>
            <button
              className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
              title="Delete Project"
            >
              <Trash2 size={16} />
              Delete
            </button>
          </AlertDialog.Trigger>
          <AlertDialog.Portal>
            <AlertDialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
            <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
              <AlertDialog.Title className="text-xl font-semibold text-foreground">
                Delete Project
              </AlertDialog.Title>
              <AlertDialog.Description className="mt-3 text-muted-foreground">
                Are you sure you want to delete {currentProject.name}?
                This action cannot be undone. All project data, tasks, and history will be permanently deleted.
              </AlertDialog.Description>

                <AlertDialog.Cancel asChild>
                  <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
                    Cancel
                  </button>
                </AlertDialog.Cancel>
                <AlertDialog.Action asChild>
                  <button
                    onClick={handleDeleteProject}
                    disabled={isDeleting}
                    className="px-4 py-2 text-sm bg-destructive hover:bg-destructive/80 text-white rounded-lg transition-colors disabled:opacity-50"
                  >
                    {isDeleting ? 'Deleting...' : 'Delete Project'}
                  </button>
                </AlertDialog.Action>

            </AlertDialog.Content>
          </AlertDialog.Portal>
        </AlertDialog.Root>

      {/* Stats Grid */}

        {stats.map((stat) => {
          const Icon = stat.icon
          return (

                <Icon className={stat.color} size={24} />

{stat.label}

{stat.value}

          )
        })}

      {/* Phase Progress */}

Phase Progress

              Phase {currentProject.current_phase}
              {currentProject.total_phases} total

              <div
                className="h-full bg-accent transition-all duration-500"
                style={{
                  width: `${(currentProject.current_phase / currentProject.total_phases) * 100}%`,
                }}
              />

            {Math.round((currentProject.current_phase / currentProject.total_phases) * 100)}%

      {/* Recent Activity */}

Recent Activity

          {activeSessions.length === 0 ? (

No recent activity

          ) : (
            activeSessions.slice(0, 5).map((session) => (

                  {session.agent_type.replace('_', ' ')} Agent

                {session.status}

            ))
          )}

  )
}


Now let me add state for export and the export handler:


const [taskStats, setTaskStats] = useState<TaskStats>({
    completed: 0,
    inProgress: 0,
    blocked: 0,
    backlog: 0,
    qa: 0,
  })

const [taskStats, setTaskStats] = useState<TaskStats>({
    completed: 0,
    inProgress: 0,
    blocked: 0,
    backlog: 0,
    qa: 0,
  })
  const [isExporting, setIsExporting] = useState(false)


The file C:\Users\MikeSwanson\AutoBuilder\My-Auto-Builder\frontend\src\pages\Dashboard.tsx has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
    37→  const [editDescription, setEditDescription] = useState('')
    38→  const [editDesignMode, setEditDesignMode] = useState<'mvp' | 'finished'>('mvp')
    39→  const [isSaving, setIsSaving] = useState(false)
    40→  const [saveSuccess, setSaveSuccess] = useState(false)
    41→  const [taskStats, setTaskStats] = useState<TaskStats>({
    42→    completed: 0,
    43→    inProgress: 0,
    44→    blocked: 0,
    45→    backlog: 0,
    46→    qa: 0,
    47→  })
    48→  const [isExporting, setIsExporting] = useState(false)
    49→
    50→  useEffect(() => {
    51→    fetchProjects()
    52→    fetchActiveSessions()

const [taskStats, setTaskStats] = useState<TaskStats>({
    completed: 0,
    inProgress: 0,
    blocked: 0,
    backlog: 0,
    qa: 0,
  })
  const [isExporting, setIsExporting] = useState(false)

import { useEffect, useState } from 'react'
import { useProjectStore } from '../stores/projectStore'
import { useAgentStore } from '../stores/agentStore'
import { useToastStore } from '../components/Toast'
import {
  FolderOpen,
  CheckCircle,
  Clock,
  AlertTriangle,
  Activity,
  Trash2,
  Pencil,
  Check,
  Palette,
  X,
  Download,
} from 'lucide-react'
import * as AlertDialog from '@radix-ui/react-alert-dialog'
import * as Dialog from '@radix-ui/react-dialog'

interface TaskStats {
  completed: number
  inProgress: number
  blocked: number
  backlog: number
  qa: number
}

export default function Dashboard() {
  const { currentProject, fetchProjects, deleteProject, updateProject } = useProjectStore()
  const { activeSessions, fetchActiveSessions } = useAgentStore()
  const { addToast } = useToastStore()
  const [showDeleteDialog, setShowDeleteDialog] = useState(false)
  const [isDeleting, setIsDeleting] = useState(false)
  const [showEditDialog, setShowEditDialog] = useState(false)
  const [editName, setEditName] = useState('')
  const [editDescription, setEditDescription] = useState('')
  const [editDesignMode, setEditDesignMode] = useState<'mvp' | 'finished'>('mvp')
  const [isSaving, setIsSaving] = useState(false)
  const [saveSuccess, setSaveSuccess] = useState(false)
  const [taskStats, setTaskStats] = useState<TaskStats>({
    completed: 0,
    inProgress: 0,
    blocked: 0,
    backlog: 0,
    qa: 0,
  })

  useEffect(() => {
    fetchProjects()
    fetchActiveSessions()
  }, [fetchProjects, fetchActiveSessions])

  // Fetch task statistics when current project changes
  useEffect(() => {
    const fetchTaskStats = async () => {
      if (!currentProject) return

      try {
        const response = await fetch(`http://localhost:8000/api/projects/${currentProject.id}/tasks`)
        if (!response.ok) return

        const tasks = await response.json()
        const stats: TaskStats = {
          completed: 0,
          inProgress: 0,
          blocked: 0,
          backlog: 0,
          qa: 0,
        }

        tasks.forEach((task: { status: string }) => {
          switch (task.status) {
            case 'done':
              stats.completed++
              break
            case 'in_progress':
              stats.inProgress++
              break
            case 'blocked':
              stats.blocked++
              break
            case 'backlog':
              stats.backlog++
              break
            case 'qa':
              stats.qa++
              break
          }
        })

        setTaskStats(stats)
      } catch (error) {
        console.error('Failed to fetch task stats:', error)
      }
    }

    fetchTaskStats()
  }, [currentProject])

  const handleDeleteProject = async () => {
    if (!currentProject) return
    setIsDeleting(true)
    try {
      await deleteProject(currentProject.id)
      setShowDeleteDialog(false)
    } catch (error) {
      console.error('Failed to delete project:', error)
      addToast('error', 'Delete Failed', `Failed to delete project "${currentProject.name}". Please try again.`)
    } finally {
      setIsDeleting(false)
    }
  }

  const handleOpenEditDialog = () => {
    if (currentProject) {
      setEditName(currentProject.name)
      setEditDescription(currentProject.description || '')
      setEditDesignMode((currentProject.design_mode as 'mvp' | 'finished') || 'mvp')
      setSaveSuccess(false)
      setShowEditDialog(true)
    }
  }

  const handleSaveProject = async () => {
    if (!currentProject) return
    setIsSaving(true)
    setSaveSuccess(false)
    try {
      const response = await fetch(`http://localhost:8000/api/projects/${currentProject.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: editName, description: editDescription, design_mode: editDesignMode }),
      })
      if (!response.ok) {
        const errorData = await response.json().catch(() => ({}))
        throw new Error(errorData.detail || 'Failed to update project')
      }
      const updated = await response.json()
      updateProject(currentProject.id, updated)
      setSaveSuccess(true)
      setTimeout(() => {
        setShowEditDialog(false)
        setSaveSuccess(false)
      }, 1000)
    } catch (error) {
      console.error('Failed to save project:', error)
      const message = error instanceof Error ? error.message : 'Failed to save project'
      addToast('error', 'Save Failed', message)
    } finally {
      setIsSaving(false)
    }
  }

  if (!currentProject) {
    return (

        <FolderOpen size={64} className="text-muted-foreground mb-4" />

          No Project Selected

          Select a project from the sidebar or create a new one to get started.

    )
  }

  const stats = [
    {
      label: 'Tasks Completed',
      value: taskStats.completed,
      icon: CheckCircle,
      color: 'text-green-400',
    },
    {
      label: 'Tasks In Progress',
      value: taskStats.inProgress,
      icon: Clock,
      color: 'text-accent',
    },
    {
      label: 'Blocked Tasks',
      value: taskStats.blocked,
      icon: AlertTriangle,
      color: 'text-warning',
    },
    {
      label: 'Active Agents',
      value: activeSessions.filter((s) => s.status === 'running').length,
      icon: Activity,
      color: 'text-primary-400',
    },
  ]

  return (

      {/* Project Header */}

{currentProject.name}

            {/* Design Mode Badge */}

              <Palette size={12} />
              {currentProject.design_mode === 'finished' ? 'Finished Mode' : 'MVP Mode'}

          {currentProject.description && (

{currentProject.description}

          )}

          {/* Edit Button and Dialog */}
          <Dialog.Root open={showEditDialog} onOpenChange={setShowEditDialog}>
            <Dialog.Trigger asChild>
              <button
                onClick={handleOpenEditDialog}
                className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-accent hover:bg-accent/10 rounded-lg transition-colors"
                title="Edit Project Settings"
              >
                <Pencil size={16} />
                Edit
              </button>
            </Dialog.Trigger>
            <Dialog.Portal>
              <Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
              <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">

                  <Dialog.Title className="text-xl font-semibold text-foreground">
                    Edit Project Settings
                  </Dialog.Title>
                  <Dialog.Close asChild>
                    <button className="p-1 text-muted-foreground hover:text-foreground hover:bg-secondary rounded transition-colors" aria-label="Close">
                      <X size={20} />
                    </button>
                  </Dialog.Close>

                <Dialog.Description className="mt-2 text-muted-foreground">
                  Update your project name, description, and design mode.
                </Dialog.Description>

                    Project Name
                    <input
                      type="text"
                      value={editName}
                      onChange={(e) => setEditName(e.target.value)}
                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
                      placeholder="Enter project name"
                    />

                    Description
                    <textarea
                      value={editDescription}
                      onChange={(e) => setEditDescription(e.target.value)}
                      rows={3}
                      className="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground placeholder-muted-foreground focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent resize-none"
                      placeholder="Enter project description"
                    />

                    Design Mode

                      <button
                        type="button"
                        onClick={() => setEditDesignMode('mvp')}
                        className={`p-3 rounded-lg border text-left transition-colors ${
                          editDesignMode === 'mvp'
                            ? 'border-amber-500 bg-amber-500/10'
                            : 'border-border hover:border-muted-foreground'
                        }`}
                      >

MVP

Functional drafts OK

                      </button>
                      <button
                        type="button"
                        onClick={() => setEditDesignMode('finished')}
                        className={`p-3 rounded-lg border text-left transition-colors ${
                          editDesignMode === 'finished'
                            ? 'border-accent bg-accent/10'
                            : 'border-border hover:border-muted-foreground'
                        }`}
                      >

Finished

Polished required

                      </button>

                  <Dialog.Close asChild>
                    <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
                      Cancel
                    </button>
                  </Dialog.Close>
                  <button
                    onClick={handleSaveProject}
                    disabled={isSaving || !editName.trim()}
                    className="flex items-center gap-2 px-4 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
                  >
                    {saveSuccess ? (
                      <>
                        <Check size={16} />
                        Saved!
                      </>
                    ) : isSaving ? (
                      'Saving...'
                    ) : (
                      'Save Changes'
                    )}
                  </button>

              </Dialog.Content>
            </Dialog.Portal>
          </Dialog.Root>

          {/* Delete Button and Dialog */}
          <AlertDialog.Root open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
          <AlertDialog.Trigger asChild>
            <button
              className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
              title="Delete Project"
            >
              <Trash2 size={16} />
              Delete
            </button>
          </AlertDialog.Trigger>
          <AlertDialog.Portal>
            <AlertDialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
            <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-card border border-border rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
              <AlertDialog.Title className="text-xl font-semibold text-foreground">
                Delete Project
              </AlertDialog.Title>
              <AlertDialog.Description className="mt-3 text-muted-foreground">
                Are you sure you want to delete {currentProject.name}?
                This action cannot be undone. All project data, tasks, and history will be permanently deleted.
              </AlertDialog.Description>

                <AlertDialog.Cancel asChild>
                  <button className="px-4 py-2 text-sm text-secondary-foreground hover:bg-secondary rounded-lg transition-colors">
                    Cancel
                  </button>
                </AlertDialog.Cancel>
                <AlertDialog.Action asChild>
                  <button
                    onClick={handleDeleteProject}
                    disabled={isDeleting}
                    className="px-4 py-2 text-sm bg-destructive hover:bg-destructive/80 text-white rounded-lg transition-colors disabled:opacity-50"
                  >
                    {isDeleting ? 'Deleting...' : 'Delete Project'}
                  </button>
                </AlertDialog.Action>

            </AlertDialog.Content>
          </AlertDialog.Portal>
        </AlertDialog.Root>

      {/* Stats Grid */}

        {stats.map((stat) => {
          const Icon = stat.icon
          return (

                <Icon className={stat.color} size={24} />

{stat.label}

{stat.value}

          )
        })}

      {/* Phase Progress */}

Phase Progress

              Phase {currentProject.current_phase}
              {currentProject.total_phases} total

              <div
                className="h-full bg-accent transition-all duration-500"
                style={{
                  width: `${(currentProject.current_phase / currentProject.total_phases) * 100}%`,
                }}
              />

            {Math.round((currentProject.current_phase / currentProject.total_phases) * 100)}%

      {/* Recent Activity */}

Recent Activity

          {activeSessions.length === 0 ? (

No recent activity

          ) : (
            activeSessions.slice(0, 5).map((session) => (

                  {session.agent_type.replace('_', ' ')} Agent

                {session.status}

            ))
          )}

  )
}

+  const [isExporting, setIsExporting] = useState(false)


Now let me add the export handler function after the handleSaveProject function: