diff --git a/src/app/api/tasks/route.ts b/src/app/api/tasks/route.ts index 2a18acb..b540a51 100644 --- a/src/app/api/tasks/route.ts +++ b/src/app/api/tasks/route.ts @@ -184,12 +184,9 @@ export async function POST(request: NextRequest) { metadata = {} } = body; const normalizedStatus = normalizeTaskCreateStatus(status, assigned_to) - - // Check for duplicate title - const existingTask = db.prepare('SELECT id FROM tasks WHERE title = ? AND workspace_id = ?').get(title, workspaceId); - if (existingTask) { - return NextResponse.json({ error: 'Task with this title already exists' }, { status: 409 }); - } + + // Resolve project_id for the task + const resolvedProjectId = resolveProjectId(db, workspaceId, project_id) const now = Math.floor(Date.now() / 1000); const mentionResolution = resolveMentionRecipients(description || '', db, workspaceId); @@ -203,7 +200,6 @@ export async function POST(request: NextRequest) { const resolvedCompletedAt = completed_at ?? (normalizedStatus === 'done' ? now : null) const createTaskTx = db.transaction(() => { - const resolvedProjectId = resolveProjectId(db, workspaceId, project_id) db.prepare(` UPDATE projects SET ticket_counter = ticket_counter + 1, updated_at = unixepoch() diff --git a/src/components/panels/task-board-panel.tsx b/src/components/panels/task-board-panel.tsx index d7a276f..ed601ae 100644 --- a/src/components/panels/task-board-panel.tsx +++ b/src/components/panels/task-board-panel.tsx @@ -794,7 +794,7 @@ export function TaskBoardPanel() { {/* Column Body */} -
+
{tasksByStatus[column.key]?.map(task => (
({ error: 'Failed to delete task' })) + throw new Error(errorData.error || 'Failed to delete task') + } + // Close modal immediately on successful deletion + // SSE will handle the task.deleted event and remove the task from the UI onClose() - } catch { - // task.deleted SSE will sync state if needed + } catch (error) { + // Show error to user + const errorMessage = error instanceof Error ? error.message : 'Failed to delete task' + alert(errorMessage) + // Don't close modal on error } }} > diff --git a/src/lib/recurring-tasks.ts b/src/lib/recurring-tasks.ts index b8d78c7..ca6f141 100644 --- a/src/lib/recurring-tasks.ts +++ b/src/lib/recurring-tasks.ts @@ -71,12 +71,12 @@ export async function spawnRecurringTasks(): Promise<{ ok: boolean; message: str const dateSuffix = formatDateSuffix() const childTitle = `${template.title} - ${dateSuffix}` - // Duplicate prevention: check if a child with this exact title already exists + // Duplicate prevention: check if a child with this exact title already exists in the same project const existing = db.prepare(` SELECT id FROM tasks - WHERE title = ? AND workspace_id = ? + WHERE title = ? AND workspace_id = ? AND project_id = ? LIMIT 1 - `).get(childTitle, template.workspace_id) + `).get(childTitle, template.workspace_id, template.project_id) if (existing) continue // Spawn child task diff --git a/tests/tasks-crud.spec.ts b/tests/tasks-crud.spec.ts index 9b85dd5..0d04e8d 100644 --- a/tests/tasks-crud.spec.ts +++ b/tests/tasks-crud.spec.ts @@ -83,7 +83,7 @@ test.describe('Tasks CRUD', () => { expect(res.status()).toBe(400) }) - test('POST rejects duplicate title', async ({ request }) => { + test('POST allows duplicate title', async ({ request }) => { const { id, body: first } = await createTestTask(request) cleanup.push(id) @@ -91,7 +91,11 @@ test.describe('Tasks CRUD', () => { headers: API_KEY_HEADER, data: { title: first.task.title }, }) - expect(res.status()).toBe(409) + expect(res.status()).toBe(201) + const body = await res.json() + cleanup.push(body.task.id) + expect(body.task.title).toBe(first.task.title) + expect(body.task.id).not.toBe(first.task.id) }) // ── GET /api/tasks ───────────────────────────