From 705f72ab633708bde3fd170e3ef0ea1b909398e1 Mon Sep 17 00:00:00 2001 From: MikeeBuilds Date: Mon, 22 Dec 2025 16:03:25 -0500 Subject: [PATCH 1/3] chore: remove legacy dashboard directories --- vibes-dashboard-v2 | 1 - 1 file changed, 1 deletion(-) delete mode 160000 vibes-dashboard-v2 diff --git a/vibes-dashboard-v2 b/vibes-dashboard-v2 deleted file mode 160000 index 384522e..0000000 --- a/vibes-dashboard-v2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 384522e54037e419fe59aa243651c0d2056d5129 From f2aea16eac941d6578336e8cd36f2688d6f68bb3 Mon Sep 17 00:00:00 2001 From: MikeeBuilds Date: Mon, 22 Dec 2025 16:12:08 -0500 Subject: [PATCH 2/3] feat: keep tasks in queue and sync status names with Kanban --- desktop/package-lock.json | 61 +++++++- desktop/package.json | 3 + desktop/src/App.tsx | 89 ++++-------- desktop/src/components/KanbanBoard.tsx | 153 ++++++++++++++++++++ desktop/src/components/KanbanColumn.tsx | 65 +++++++++ desktop/src/components/TaskCard.tsx | 79 +++++++++++ desktop/src/components/TaskWizard.tsx | 179 ++++++++++++++++++++++++ desktop/src/lib/api.ts | 37 +++++ squadron/server.py | 37 ++++- squadron/swarm/overseer.py | 35 ++++- 10 files changed, 667 insertions(+), 71 deletions(-) create mode 100644 desktop/src/components/KanbanBoard.tsx create mode 100644 desktop/src/components/KanbanColumn.tsx create mode 100644 desktop/src/components/TaskCard.tsx create mode 100644 desktop/src/components/TaskWizard.tsx diff --git a/desktop/package-lock.json b/desktop/package-lock.json index 94adb3d..c135ebb 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -8,6 +8,9 @@ "name": "desktop", "version": "0.0.0", "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@tanstack/react-query": "^5.90.12", "axios": "^1.13.2", "class-variance-authority": "^0.7.1", @@ -358,6 +361,60 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@electron/asar": { "version": "3.2.18", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.18.tgz", @@ -2669,7 +2726,6 @@ "integrity": "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -4314,6 +4370,7 @@ "integrity": "sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "26.0.12", "builder-util": "26.0.11", @@ -7351,6 +7408,7 @@ "node_modules/react-dom": { "version": "19.2.3", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -8183,7 +8241,6 @@ }, "node_modules/tslib": { "version": "2.8.1", - "dev": true, "license": "0BSD" }, "node_modules/type-check": { diff --git a/desktop/package.json b/desktop/package.json index aa54a1a..b465113 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -16,6 +16,9 @@ "build:main": "tsc -p electron/tsconfig.json" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@tanstack/react-query": "^5.90.12", "axios": "^1.13.2", "class-variance-authority": "^0.7.1", diff --git a/desktop/src/App.tsx b/desktop/src/App.tsx index 77b0db4..064075e 100644 --- a/desktop/src/App.tsx +++ b/desktop/src/App.tsx @@ -2,10 +2,14 @@ import { useState, useEffect } from 'react' import { LayoutDashboard, Terminal, Activity, Map, Lightbulb, FileClock, Settings, Plus, Github, GitBranch } from 'lucide-react' import { cn } from '@/lib/utils' import { getSystemStatus, type SystemStatus } from '@/lib/api' +import { KanbanBoard } from '@/components/KanbanBoard' +import { TaskWizard } from '@/components/TaskWizard' export default function App() { const [activeTab, setActiveTab] = useState('kanban') const [systemStatus, setSystemStatus] = useState(null) + const [isWizardOpen, setIsWizardOpen] = useState(false) + const [kanbanKey, setKanbanKey] = useState(0) // Used to force refresh Kanban after creation useEffect(() => { const fetchStatus = async () => { @@ -18,6 +22,10 @@ export default function App() { return () => clearInterval(interval) }, []) + const handleTaskCreated = () => { + setKanbanKey(prev => prev + 1) + } + return (
{/* Sidebar */} @@ -57,17 +65,20 @@ export default function App() { {/* Bottom Actions */}
} label="Settings" isActive={activeTab === 'settings'} onClick={() => setActiveTab('settings')} /> -
{/* Main Content */} -
+
-

{activeTab.charAt(0).toUpperCase() + activeTab.slice(1).replace('-', ' ')}

+

{activeTab.charAt(0).toUpperCase() + activeTab.slice(1).replace('-', ' ')}

@@ -75,20 +86,24 @@ export default function App() { {systemStatus && ( <> - - {systemStatus.agents_online} Agents Online - - {systemStatus.missions_active} Active Missions + | + {systemStatus.agents_online} Agents Online + | + {systemStatus.missions_active} Active Missions )}

- {activeTab === 'kanban' && } - {activeTab === 'terminals' &&
Agent Terminals Placeholder
} - + {activeTab === 'kanban' && } + {activeTab === 'terminals' &&
Agent Terminals placeholder - Coming soon in Issue 7
} + setIsWizardOpen(false)} + onTaskCreated={handleTaskCreated} + />
) @@ -111,57 +126,3 @@ function SidebarItem({ icon, label, isActive, onClick }: { icon: any, label: str ) } -function KanbanView() { - return ( -
- - - - - - - - -
- ) -} - -function KanbanColumn({ title, count, status, children }: { title: string, count: number, status: 'neutral' | 'blue' | 'yellow' | 'green', children?: React.ReactNode }) { - const statusColor = { - neutral: 'border-zinc-700', - blue: 'border-blue-500/50', - yellow: 'border-yellow-500/50', - green: 'border-green-500/50' - }[status] - - return ( -
-
-

{title} {count}

- {title === 'Planning' && } -
-
- {children ? children : ( -
- Empty -
- )} -
-
- ) -} - -function KanbanCard({ title, time, tag }: { title: string, time: string, tag: string }) { - return ( -
-
- {title} - {tag} -
-
- - {time} -
-
- ) -} diff --git a/desktop/src/components/KanbanBoard.tsx b/desktop/src/components/KanbanBoard.tsx new file mode 100644 index 0000000..e6153e7 --- /dev/null +++ b/desktop/src/components/KanbanBoard.tsx @@ -0,0 +1,153 @@ +import { useState, useEffect } from 'react' +import { + DndContext, + DragOverlay, + closestCorners, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, + type DragStartEvent, + type DragOverEvent, + type DragEndEvent, +} from '@dnd-kit/core' +import { + arrayMove, + sortableKeyboardCoordinates, +} from '@dnd-kit/sortable' +import { KanbanColumn } from './KanbanColumn' +import { TaskCard } from './TaskCard' +import { type Task, getTasks, updateTaskStatus } from '@/lib/api' + + +export function KanbanBoard() { + const [tasks, setTasks] = useState([]) + const [activeTask, setActiveTask] = useState(null) + + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + distance: 5, + }, + }), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ) + + const fetchTasks = async () => { + try { + const data = await getTasks() + setTasks(data) + } catch (err) { + console.error('Failed to fetch tasks:', err) + } + } + + useEffect(() => { + fetchTasks() + const interval = setInterval(fetchTasks, 5000) + return () => clearInterval(interval) + }, []) + + const columns = [ + { id: 'backlog', title: 'Backlog', status: 'neutral' }, + { id: 'planning', title: 'Planning', status: 'blue' }, + { id: 'in_progress', title: 'In Progress', status: 'yellow' }, + { id: 'review', title: 'Review', status: 'red' }, + { id: 'done', title: 'Done', status: 'green' }, + ] as const + + const handleDragStart = (event: DragStartEvent) => { + const { active } = event + const task = tasks.find(t => t.id === active.id) + if (task) setActiveTask(task) + } + + const handleDragOver = (event: DragOverEvent) => { + const { active, over } = event + if (!over) return + + const activeId = active.id as string + const overId = over.id as string + + // Find the task and the column it's over + const activeTask = tasks.find(t => t.id === activeId) + if (!activeTask) return + + // If over a column + const overColumn = columns.find(c => c.id === overId) + if (overColumn && activeTask.status !== overId) { + setTasks(prev => prev.map(t => + t.id === activeId ? { ...t, status: overId as Task['status'] } : t + )) + return + } + + // If over another task + const overTask = tasks.find(t => t.id === overId) + if (overTask && activeTask.status !== overTask.status) { + setTasks(prev => prev.map(t => + t.id === activeId ? { ...t, status: overTask.status } : t + )) + } + } + + const handleDragEnd = async (event: DragEndEvent) => { + const { active, over } = event + setActiveTask(null) + if (!over) return + + const activeId = active.id as string + const overId = over.id as string + + const task = tasks.find(t => t.id === activeId) + if (!task) return + + // Finalize status update on backend + try { + await updateTaskStatus(activeId, task.status) + } catch (err) { + console.error('Failed to update task status:', err) + fetchTasks() // Rollback on error + } + + if (activeId !== overId) { + setTasks((items) => { + const oldIndex = items.findIndex((t) => t.id === activeId); + const newIndex = items.findIndex((t) => t.id === overId); + return arrayMove(items, oldIndex, newIndex); + }); + } + } + + return ( +
+ + {columns.map(col => ( + t.status === col.id)} + /> + ))} + + + {activeTask ? ( +
+ +
+ ) : null} +
+
+
+ ) +} diff --git a/desktop/src/components/KanbanColumn.tsx b/desktop/src/components/KanbanColumn.tsx new file mode 100644 index 0000000..a57a455 --- /dev/null +++ b/desktop/src/components/KanbanColumn.tsx @@ -0,0 +1,65 @@ +import { useDroppable } from '@dnd-kit/core' +import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable' +import { Plus } from 'lucide-react' +import { cn } from '@/lib/utils' +import { TaskCard } from './TaskCard' +import type { Task } from '@/lib/api' + + +interface KanbanColumnProps { + id: string + title: string + tasks: Task[] + status: 'neutral' | 'blue' | 'yellow' | 'green' | 'red' + onAddTask?: () => void +} + +export function KanbanColumn({ id, title, tasks, status, onAddTask }: KanbanColumnProps) { + const { setNodeRef } = useDroppable({ id }) + + const statusColor = { + neutral: 'border-zinc-800/50', + blue: 'border-blue-500/20', + yellow: 'border-yellow-500/20', + green: 'border-green-500/20', + red: 'border-red-500/20' + }[status] + + return ( +
+
+

+ {title} + + {tasks.length} + +

+ {onAddTask && ( + + )} +
+ +
+ t.id)} strategy={verticalListSortingStrategy}> + {tasks.length > 0 ? ( + tasks.map(task => ( + + )) + ) : ( +
+ No Tasks +
+ )} +
+
+
+ ) +} diff --git a/desktop/src/components/TaskCard.tsx b/desktop/src/components/TaskCard.tsx new file mode 100644 index 0000000..4f4df53 --- /dev/null +++ b/desktop/src/components/TaskCard.tsx @@ -0,0 +1,79 @@ +import { useSortable } from '@dnd-kit/sortable' +import { CSS } from '@dnd-kit/utilities' +import { FileClock, User } from 'lucide-react' +import { cn } from '@/lib/utils' +import type { Task } from '@/lib/api' + + +interface TaskCardProps { + task: Task +} + +export function TaskCard({ task }: TaskCardProps) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging + } = useSortable({ id: task.id }) + + const style = { + transform: CSS.Transform.toString(transform), + transition, + zIndex: isDragging ? 100 : 1, + opacity: isDragging ? 0.5 : 1, + } + + const priorityColor = { + 1: 'text-zinc-500', + 2: 'text-blue-400', + 3: 'text-yellow-400', + 4: 'text-orange-400', + 5: 'text-red-400', + }[task.priority as 1 | 2 | 3 | 4 | 5] || 'text-zinc-400' + + return ( +
+
+ + {task.task} + + + P{task.priority} + +
+ +
+
+ + {new Date(task.created).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} +
+ + {task.assigned_to && ( +
+ + {task.assigned_to} +
+ )} +
+ + {task.status === 'in_progress' && ( +
+
+
+ )} + +
+ ) +} diff --git a/desktop/src/components/TaskWizard.tsx b/desktop/src/components/TaskWizard.tsx new file mode 100644 index 0000000..7e1a51d --- /dev/null +++ b/desktop/src/components/TaskWizard.tsx @@ -0,0 +1,179 @@ +import { useState, useEffect } from 'react' +import { X, Zap } from 'lucide-react' +import { cn } from '@/lib/utils' +import { type Agent, getAgents, createTask } from '@/lib/api' + + +interface TaskWizardProps { + isOpen: boolean + onClose: () => void + onTaskCreated: () => void +} + +export function TaskWizard({ isOpen, onClose, onTaskCreated }: TaskWizardProps) { + const [step, setStep] = useState(1) + const [taskName, setTaskName] = useState('') + const [priority, setPriority] = useState(3) + const [assignedTo, setAssignedTo] = useState() + const [agents, setAgents] = useState([]) + const [isLoading, setIsLoading] = useState(false) + + useEffect(() => { + if (isOpen) { + getAgents().then(setAgents).catch(console.error) + setStep(1) + setTaskName('') + setPriority(3) + setAssignedTo(undefined) + } + }, [isOpen]) + + if (!isOpen) return null + + const handleCreate = async () => { + setIsLoading(true) + try { + await createTask(taskName, priority, assignedTo) + onTaskCreated() + onClose() + } catch (err) { + console.error('Failed to create task:', err) + } finally { + setIsLoading(false) + } + } + + return ( +
+
+
+

+ + New Mission +

+ +
+ +
+ {/* Progress Bar */} +
+ {[1, 2, 3].map(s => ( +
= s && "bg-yellow-400")} /> + ))} +
+ + {step === 1 && ( +
+