From b4e4472df4530a101b8b62070233f3334eb423a0 Mon Sep 17 00:00:00 2001 From: Brad Groux Date: Mon, 26 Jan 2026 04:13:04 -0600 Subject: [PATCH] fix(US-403/404): review state sync bugs - task-service: add missing review field to parseTaskFile return - TaskDetailPanel: useDebouncedSave now tracks changed fields only (prevents overwriting server state with stale local data) --- server/src/services/task-service.ts | 1 + web/src/components/task/TaskDetailPanel.tsx | 35 ++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/server/src/services/task-service.ts b/server/src/services/task-service.ts index 9fd72171..2d5f72cf 100644 --- a/server/src/services/task-service.ts +++ b/server/src/services/task-service.ts @@ -96,6 +96,7 @@ export class TaskService { attempt: data.attempt, attempts: data.attempts, reviewComments, + review: data.review, }; } diff --git a/web/src/components/task/TaskDetailPanel.tsx b/web/src/components/task/TaskDetailPanel.tsx index f91e0f98..11ac7fd0 100644 --- a/web/src/components/task/TaskDetailPanel.tsx +++ b/web/src/components/task/TaskDetailPanel.tsx @@ -76,43 +76,42 @@ const priorityLabels: Record = { function useDebouncedSave(task: Task | null, updateTask: ReturnType) { const [localTask, setLocalTask] = useState(task); - const [isDirty, setIsDirty] = useState(false); + const [changedFields, setChangedFields] = useState>(new Set()); + // Sync from server when task prop changes (e.g., refetch) useEffect(() => { setLocalTask(task); - setIsDirty(false); + setChangedFields(new Set()); }, [task]); + // Debounced save - only send fields that were actually changed useEffect(() => { - if (!isDirty || !localTask) return; + if (changedFields.size === 0 || !localTask) return; const timeout = setTimeout(() => { + // Build input with only changed fields + const input: Record = {}; + changedFields.forEach(field => { + input[field] = localTask[field]; + }); + updateTask.mutate({ id: localTask.id, - input: { - title: localTask.title, - description: localTask.description, - type: localTask.type, - status: localTask.status, - priority: localTask.priority, - project: localTask.project, - tags: localTask.tags, - git: localTask.git, - reviewComments: localTask.reviewComments, - review: localTask.review, - }, + input, }); - setIsDirty(false); + setChangedFields(new Set()); }, 500); return () => clearTimeout(timeout); - }, [localTask, isDirty, updateTask]); + }, [localTask, changedFields, updateTask]); const updateField = useCallback((field: K, value: Task[K]) => { setLocalTask(prev => prev ? { ...prev, [field]: value } : null); - setIsDirty(true); + setChangedFields(prev => new Set(prev).add(field)); }, []); + const isDirty = changedFields.size > 0; + return { localTask, updateField, isDirty }; }