fix: resolve subtasks tab not updating on Linux (#794)

* auto-claude: subtask-2-1 - Fix the selectedTask update logic in App.tsx

* auto-claude: subtask-2-2 - Add console logging for debugging state updates

* refactor: gate debug logs behind DEBUG flag and optimize deep comparison

- Import debugLog from shared utils to gate console.log statements
- Debug logs only emit when DEBUG=true (via npm run dev:debug)
- Replace full task object comparison with specific field checks
- Only compare subtasks array and status field for better performance
- Add clear reason logging for what changed

Addresses code review feedback about verbose production logs
and expensive JSON.stringify comparisons.

* refactor: optimize debug logging and expand task field comparison

- Export isDebugEnabled from debug-logger for performance gating
- Guard expensive debugLog computations (Date, map, stringify) with isDebugEnabled()
- Add title, description, metadata to field comparisons
- TaskDetailModal now refreshes when these fields are edited in TaskEditDialog

This prevents performance overhead from debug log argument construction
when debug mode is disabled and ensures modal updates for all task edits.

* fix: complete task field comparisons and simplify debug logging

Add missing comparisons for executionProgress, qaReport, reviewReason, and
logs to prevent stale UI. Remove redundant isDebugEnabled() checks since
debugLog() guards internally. Consolidate debug logging with early-return
pattern for better readability.

* refactor: remove unused isDebugEnabled import

---------

Co-authored-by: StillKnotKnown <stillknotknown@users.noreply.github.com>
This commit is contained in:
StillKnotKnown
2026-01-07 22:44:41 +02:00
committed by GitHub
parent a47354b470
commit 29ef46d733
2 changed files with 80 additions and 8 deletions
+79 -7
View File
@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Download, RefreshCw, AlertCircle } from 'lucide-react'; import { Download, RefreshCw, AlertCircle } from 'lucide-react';
import { debugLog } from '../shared/utils/debug-logger';
import { import {
DndContext, DndContext,
DragOverlay, DragOverlay,
@@ -439,13 +440,84 @@ export function App() {
// Update selected task when tasks change (for real-time updates) // Update selected task when tasks change (for real-time updates)
useEffect(() => { useEffect(() => {
if (selectedTask) { if (!selectedTask) {
const updatedTask = tasks.find( debugLog('[App] No selected task to update');
(t) => t.id === selectedTask.id || t.specId === selectedTask.specId return;
); }
if (updatedTask && updatedTask !== selectedTask) {
setSelectedTask(updatedTask); const updatedTask = tasks.find(
} (t) => t.id === selectedTask.id || t.specId === selectedTask.specId
);
debugLog('[App] Task lookup result', {
found: !!updatedTask,
updatedTaskId: updatedTask?.id,
selectedTaskId: selectedTask.id,
});
if (!updatedTask) {
debugLog('[App] Updated task not found in tasks array');
return;
}
// Compare all mutable fields that affect UI state
const subtasksChanged =
JSON.stringify(selectedTask.subtasks || []) !==
JSON.stringify(updatedTask.subtasks || []);
const statusChanged = selectedTask.status !== updatedTask.status;
const titleChanged = selectedTask.title !== updatedTask.title;
const descriptionChanged = selectedTask.description !== updatedTask.description;
const metadataChanged =
JSON.stringify(selectedTask.metadata || {}) !==
JSON.stringify(updatedTask.metadata || {});
const executionProgressChanged =
JSON.stringify(selectedTask.executionProgress || {}) !==
JSON.stringify(updatedTask.executionProgress || {});
const qaReportChanged =
JSON.stringify(selectedTask.qaReport || {}) !==
JSON.stringify(updatedTask.qaReport || {});
const reviewReasonChanged = selectedTask.reviewReason !== updatedTask.reviewReason;
const logsChanged =
JSON.stringify(selectedTask.logs || []) !==
JSON.stringify(updatedTask.logs || []);
const hasChanged =
subtasksChanged || statusChanged || titleChanged || descriptionChanged ||
metadataChanged || executionProgressChanged || qaReportChanged ||
reviewReasonChanged || logsChanged;
debugLog('[App] Task comparison', {
hasChanged,
changes: {
subtasks: subtasksChanged,
status: statusChanged,
title: titleChanged,
description: descriptionChanged,
metadata: metadataChanged,
executionProgress: executionProgressChanged,
qaReport: qaReportChanged,
reviewReason: reviewReasonChanged,
logs: logsChanged,
},
});
if (hasChanged) {
const reasons = [];
if (subtasksChanged) reasons.push('Subtasks');
if (statusChanged) reasons.push('Status');
if (titleChanged) reasons.push('Title');
if (descriptionChanged) reasons.push('Description');
if (metadataChanged) reasons.push('Metadata');
if (executionProgressChanged) reasons.push('ExecutionProgress');
if (qaReportChanged) reasons.push('QAReport');
if (reviewReasonChanged) reasons.push('ReviewReason');
if (logsChanged) reasons.push('Logs');
debugLog('[App] Updating selectedTask', {
taskId: updatedTask.id,
reason: reasons.join(', '),
});
setSelectedTask(updatedTask);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps -- Intentionally omit selectedTask object to prevent infinite re-render loop // eslint-disable-next-line react-hooks/exhaustive-deps -- Intentionally omit selectedTask object to prevent infinite re-render loop
}, [tasks, selectedTask?.id, selectedTask?.specId]); }, [tasks, selectedTask?.id, selectedTask?.specId]);
@@ -3,7 +3,7 @@
* Only logs when DEBUG=true in environment * Only logs when DEBUG=true in environment
*/ */
const isDebugEnabled = (): boolean => { export const isDebugEnabled = (): boolean => {
if (typeof process !== 'undefined' && process.env) { if (typeof process !== 'undefined' && process.env) {
return process.env.DEBUG === 'true'; return process.env.DEBUG === 'true';
} }