From 030c1966c321c1fbb300c871fe029d370618fc92 Mon Sep 17 00:00:00 2001 From: Louis Knight-Webb Date: Sat, 21 Jun 2025 23:14:00 +0100 Subject: [PATCH] Fix bug in create and start --- backend/src/bin/generate_types.rs | 1 + backend/src/models/task.rs | 9 ++++ backend/src/routes/tasks.rs | 18 ++++++-- frontend/src/components/layout/navbar.tsx | 3 +- .../src/components/tasks/TaskFormDialog.tsx | 8 ++-- frontend/src/pages/project-tasks.tsx | 45 +++++++++++++------ shared/types.ts | 2 + 7 files changed, 64 insertions(+), 22 deletions(-) diff --git a/backend/src/bin/generate_types.rs b/backend/src/bin/generate_types.rs index 014eb19d..8b39625b 100644 --- a/backend/src/bin/generate_types.rs +++ b/backend/src/bin/generate_types.rs @@ -25,6 +25,7 @@ fn main() { vibe_kanban::models::project::SearchResult::decl(), vibe_kanban::models::project::SearchMatchType::decl(), vibe_kanban::models::task::CreateTask::decl(), + vibe_kanban::models::task::CreateTaskAndStart::decl(), vibe_kanban::models::task::TaskStatus::decl(), vibe_kanban::models::task::Task::decl(), vibe_kanban::models::task::TaskWithAttemptStatus::decl(), diff --git a/backend/src/models/task.rs b/backend/src/models/task.rs index f325b53f..89753451 100644 --- a/backend/src/models/task.rs +++ b/backend/src/models/task.rs @@ -50,6 +50,15 @@ pub struct CreateTask { pub description: Option, } +#[derive(Debug, Deserialize, TS)] +#[ts(export)] +pub struct CreateTaskAndStart { + pub project_id: Uuid, + pub title: String, + pub description: Option, + pub executor: Option, +} + #[derive(Debug, Deserialize, TS)] #[ts(export)] pub struct UpdateTask { diff --git a/backend/src/routes/tasks.rs b/backend/src/routes/tasks.rs index 893fc59d..e79354ad 100644 --- a/backend/src/routes/tasks.rs +++ b/backend/src/routes/tasks.rs @@ -10,7 +10,7 @@ use uuid::Uuid; use crate::models::{ project::Project, - task::{CreateTask, Task, TaskWithAttemptStatus, UpdateTask}, + task::{CreateTask, CreateTaskAndStart, Task, TaskWithAttemptStatus, UpdateTask}, task_attempt::{CreateTaskAttempt, TaskAttempt}, ApiResponse, }; @@ -98,7 +98,7 @@ pub async fn create_task_and_start( Path(project_id): Path, Extension(pool): Extension, Extension(app_state): Extension, - Json(mut payload): Json, + Json(mut payload): Json, ) -> Result>, StatusCode> { let task_id = Uuid::new_v4(); @@ -122,7 +122,12 @@ pub async fn create_task_and_start( ); // Create the task first - let task = match Task::create(&pool, &payload, task_id).await { + let create_task_payload = CreateTask { + project_id: payload.project_id, + title: payload.title.clone(), + description: payload.description.clone(), + }; + let task = match Task::create(&pool, &create_task_payload, task_id).await { Ok(task) => task, Err(e) => { tracing::error!("Failed to create task: {}", e); @@ -132,8 +137,13 @@ pub async fn create_task_and_start( // Create task attempt let attempt_id = Uuid::new_v4(); + let executor_string = payload.executor.as_ref().map(|exec| match exec { + crate::executor::ExecutorConfig::Echo => "echo".to_string(), + crate::executor::ExecutorConfig::Claude => "claude".to_string(), + crate::executor::ExecutorConfig::Amp => "amp".to_string(), + }); let attempt_payload = CreateTaskAttempt { - executor: Some("claude".to_string()), // Default executor + executor: executor_string, }; match TaskAttempt::create(&pool, &attempt_payload, attempt_id, task_id).await { diff --git a/frontend/src/components/layout/navbar.tsx b/frontend/src/components/layout/navbar.tsx index 2a543380..d2435cc2 100644 --- a/frontend/src/components/layout/navbar.tsx +++ b/frontend/src/components/layout/navbar.tsx @@ -1,11 +1,10 @@ import { Link, useLocation } from "react-router-dom"; import { Button } from "@/components/ui/button"; -import { ArrowLeft, FolderOpen, Settings } from "lucide-react"; +import { FolderOpen, Settings } from "lucide-react"; import { Logo } from "@/components/logo"; export function Navbar() { const location = useLocation(); - const isHome = location.pathname === "/"; return (
diff --git a/frontend/src/components/tasks/TaskFormDialog.tsx b/frontend/src/components/tasks/TaskFormDialog.tsx index fc0c14c0..1d9e264b 100644 --- a/frontend/src/components/tasks/TaskFormDialog.tsx +++ b/frontend/src/components/tasks/TaskFormDialog.tsx @@ -16,7 +16,8 @@ import { SelectTrigger, SelectValue } from '@/components/ui/select' -import type { TaskStatus } from 'shared/types' +import { useConfig } from '@/components/config-provider' +import type { TaskStatus, ExecutorConfig } from 'shared/types' interface Task { id: string @@ -34,7 +35,7 @@ interface TaskFormDialogProps { task?: Task | null // Optional for create mode projectId?: string // For file search functionality onCreateTask?: (title: string, description: string) => Promise - onCreateAndStartTask?: (title: string, description: string) => Promise + onCreateAndStartTask?: (title: string, description: string, executor?: ExecutorConfig) => Promise onUpdateTask?: (title: string, description: string, status: TaskStatus) => Promise } @@ -53,6 +54,7 @@ export function TaskFormDialog({ const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmittingAndStart, setIsSubmittingAndStart] = useState(false) + const { config } = useConfig() const isEditMode = Boolean(task) useEffect(() => { @@ -99,7 +101,7 @@ export function TaskFormDialog({ setIsSubmittingAndStart(true) try { if (!isEditMode && onCreateAndStartTask) { - await onCreateAndStartTask(title, description) + await onCreateAndStartTask(title, description, config?.executor) } // Reset form on successful creation diff --git a/frontend/src/pages/project-tasks.tsx b/frontend/src/pages/project-tasks.tsx index 76936d48..3255cbd4 100644 --- a/frontend/src/pages/project-tasks.tsx +++ b/frontend/src/pages/project-tasks.tsx @@ -14,7 +14,13 @@ import { import { TaskKanbanBoard } from "@/components/tasks/TaskKanbanBoard"; import { TaskDetailsPanel } from "@/components/tasks/TaskDetailsPanel"; -import type { TaskStatus, TaskWithAttemptStatus, Project } from "shared/types"; +import type { + TaskStatus, + TaskWithAttemptStatus, + Project, + ExecutorConfig, + CreateTaskAndStart, +} from "shared/types"; import type { DragEndEvent } from "@/components/ui/shadcn-io/kanban"; type Task = TaskWithAttemptStatus; @@ -26,7 +32,10 @@ interface ApiResponse { } export function ProjectTasks() { - const { projectId, taskId } = useParams<{ projectId: string; taskId?: string }>(); + const { projectId, taskId } = useParams<{ + projectId: string; + taskId?: string; + }>(); const navigate = useNavigate(); const [tasks, setTasks] = useState([]); const [project, setProject] = useState(null); @@ -73,7 +82,7 @@ export function ProjectTasks() { // Handle direct navigation to task URLs useEffect(() => { if (taskId && tasks.length > 0) { - const task = tasks.find(t => t.id === taskId); + const task = tasks.find((t) => t.id === taskId); if (task) { setSelectedTask(task); setIsPanelOpen(true); @@ -115,15 +124,21 @@ export function ProjectTasks() { if (JSON.stringify(prevTasks) === JSON.stringify(newTasks)) { return prevTasks; // Return same reference to prevent re-render } - + // Update selectedTask if it exists and has been modified if (selectedTask) { - const updatedSelectedTask = newTasks.find(task => task.id === selectedTask.id); - if (updatedSelectedTask && JSON.stringify(selectedTask) !== JSON.stringify(updatedSelectedTask)) { + const updatedSelectedTask = newTasks.find( + (task) => task.id === selectedTask.id + ); + if ( + updatedSelectedTask && + JSON.stringify(selectedTask) !== + JSON.stringify(updatedSelectedTask) + ) { setSelectedTask(updatedSelectedTask); } } - + return newTasks; }); } @@ -162,18 +177,22 @@ export function ProjectTasks() { const handleCreateAndStartTask = async ( title: string, - description: string + description: string, + executor?: ExecutorConfig ) => { try { + const payload: CreateTaskAndStart = { + project_id: projectId!, + title, + description: description || null, + executor: executor || null, + }; + const response = await makeRequest( `/api/projects/${projectId}/tasks/create-and-start`, { method: "POST", - body: JSON.stringify({ - project_id: projectId, - title, - description: description || null, - }), + body: JSON.stringify(payload), } ); diff --git a/shared/types.ts b/shared/types.ts index 9efcf54a..d8bf11ca 100644 --- a/shared/types.ts +++ b/shared/types.ts @@ -26,6 +26,8 @@ export type SearchMatchType = "FileName" | "DirectoryName" | "FullPath"; export type CreateTask = { project_id: string, title: string, description: string | null, }; +export type CreateTaskAndStart = { project_id: string, title: string, description: string | null, executor: ExecutorConfig | null, }; + export type TaskStatus = "todo" | "inprogress" | "inreview" | "done" | "cancelled"; export type Task = { id: string, project_id: string, title: string, description: string | null, status: TaskStatus, created_at: string, updated_at: string, };