fix: align web-client and BFF with TRACKER-PROTOCOL.md
All checks were successful
Deploy Web Client / deploy (push) Successful in 37s

- Add missing API functions: takeTask, rejectTask, assignTask, watchTask, unwatchTask
- Add WebSocket event handlers for task.created, task.updated, task.assigned in KanbanBoard
- Ensure real-time updates for task operations
- All components now fully compatible with TRACKER-PROTOCOL.md specification
This commit is contained in:
Markov 2026-02-23 17:26:36 +01:00
parent f99713195f
commit c37d9aed15
2 changed files with 55 additions and 1 deletions

View File

@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
import { Task, getTasks, updateTask } from "@/lib/api";
import TaskModal from "@/components/TaskModal";
import CreateTaskModal from "@/components/CreateTaskModal";
import { wsClient } from "@/lib/ws";
const COLUMNS = [
{ key: "backlog", label: "Backlog", color: "#737373" },
@ -44,7 +45,34 @@ export default function KanbanBoard({ projectId, projectSlug }: Props) {
}
};
useEffect(() => { loadTasks(); }, [projectId]);
useEffect(() => {
loadTasks();
// Subscribe to WebSocket events for real-time updates
const unsubscribeCreated = wsClient.on("task.created", (data: any) => {
if (data.project_id === projectId) {
setTasks((prev) => [...prev, data as Task]);
}
});
const unsubscribeUpdated = wsClient.on("task.updated", (data: any) => {
if (data.project_id === projectId) {
setTasks((prev) => prev.map((t) => t.id === data.id ? { ...t, ...data } : t));
}
});
const unsubscribeAssigned = wsClient.on("task.assigned", (data: any) => {
if (data.project_id === projectId) {
setTasks((prev) => prev.map((t) => t.id === data.id ? { ...t, assignee_slug: data.assignee_slug, assigned_at: data.assigned_at } : t));
}
});
return () => {
unsubscribeCreated?.();
unsubscribeUpdated?.();
unsubscribeAssigned?.();
};
}, [projectId]);
const handleDrop = async (status: string) => {
if (!draggedTask) return;

View File

@ -173,6 +173,32 @@ export async function deleteTask(taskId: string): Promise<void> {
await request(`/api/v1/tasks/${taskId}`, { method: "DELETE" });
}
export async function takeTask(taskId: string, slug: string): Promise<Task> {
return request(`/api/v1/tasks/${taskId}/take?slug=${slug}`, { method: "POST" });
}
export async function rejectTask(taskId: string, reason: string): Promise<{ok: boolean; reason: string; old_assignee: string}> {
return request(`/api/v1/tasks/${taskId}/reject`, {
method: "POST",
body: JSON.stringify({ reason })
});
}
export async function assignTask(taskId: string, assigneeSlug: string): Promise<Task> {
return request(`/api/v1/tasks/${taskId}/assign`, {
method: "POST",
body: JSON.stringify({ assignee_slug: assigneeSlug })
});
}
export async function watchTask(taskId: string, slug: string): Promise<{ok: boolean; watchers: string[]}> {
return request(`/api/v1/tasks/${taskId}/watch?slug=${slug}`, { method: "POST" });
}
export async function unwatchTask(taskId: string, slug: string): Promise<{ok: boolean; watchers: string[]}> {
return request(`/api/v1/tasks/${taskId}/watch?slug=${slug}`, { method: "DELETE" });
}
// --- Steps ---
export async function getSteps(taskId: string): Promise<Step[]> {