diff --git a/web-ui/__mocks__/@hugeicons/react.js b/web-ui/__mocks__/@hugeicons/react.js
index 08de8533..aebb6891 100644
--- a/web-ui/__mocks__/@hugeicons/react.js
+++ b/web-ui/__mocks__/@hugeicons/react.js
@@ -5,11 +5,36 @@
const React = require('react');
+// Helper to create mock icon component
+const createIcon = (name) => (props) => React.createElement('svg', { 'data-testid': `${name}-icon`, ...props });
+
// Mock all icon exports
module.exports = {
- Download01Icon: (props) => React.createElement('svg', { 'data-testid': 'download-icon', ...props }),
- Cancel01Icon: (props) => React.createElement('svg', { 'data-testid': 'cancel-icon', ...props }),
- Tick01Icon: (props) => React.createElement('svg', { 'data-testid': 'tick-icon', ...props }),
- ArrowDown01Icon: (props) => React.createElement('svg', { 'data-testid': 'arrow-down-icon', ...props }),
- ArrowUp01Icon: (props) => React.createElement('svg', { 'data-testid': 'arrow-up-icon', ...props }),
+ // UI component icons
+ Download01Icon: createIcon('download'),
+ Cancel01Icon: createIcon('cancel'),
+ Tick01Icon: createIcon('tick'),
+ ArrowDown01Icon: createIcon('arrow-down'),
+ ArrowUp01Icon: createIcon('arrow-up'),
+
+ // TaskStats icons
+ CheckListIcon: createIcon('check-list'),
+ CheckmarkCircle01Icon: createIcon('checkmark-circle'),
+ Alert02Icon: createIcon('alert'),
+ Loading03Icon: createIcon('loading'),
+
+ // Dashboard icons
+ UserGroupIcon: createIcon('user-group'),
+ WorkHistoryIcon: createIcon('work-history'),
+ TestTube01Icon: createIcon('test-tube'),
+ CheckmarkSquare01Icon: createIcon('checkmark-square'),
+ BotIcon: createIcon('bot'),
+ Logout02Icon: createIcon('logout'),
+ GitCommitIcon: createIcon('git-commit'),
+ AnalyticsUpIcon: createIcon('analytics-up'),
+ ClipboardIcon: createIcon('clipboard'),
+ Search01Icon: createIcon('search'),
+ Target02Icon: createIcon('target'),
+ FloppyDiskIcon: createIcon('floppy-disk'),
+ Add01Icon: createIcon('add'),
};
diff --git a/web-ui/__tests__/components/TaskReview.test.tsx b/web-ui/__tests__/components/TaskReview.test.tsx
index cc30bb0c..7bbd9257 100644
--- a/web-ui/__tests__/components/TaskReview.test.tsx
+++ b/web-ui/__tests__/components/TaskReview.test.tsx
@@ -762,7 +762,7 @@ describe('TaskReview', () => {
render();
await waitFor(() => {
- expect(projectsApi.getIssues).toHaveBeenCalledWith('1');
+ expect(projectsApi.getIssues).toHaveBeenCalledWith('1', { include: 'tasks' });
});
});
@@ -770,7 +770,7 @@ describe('TaskReview', () => {
render();
await waitFor(() => {
- expect(projectsApi.getIssues).toHaveBeenCalledWith(1);
+ expect(projectsApi.getIssues).toHaveBeenCalledWith(1, { include: 'tasks' });
});
});
diff --git a/web-ui/__tests__/components/tasks/TaskStats.test.tsx b/web-ui/__tests__/components/tasks/TaskStats.test.tsx
index d204cef5..64a730c1 100644
--- a/web-ui/__tests__/components/tasks/TaskStats.test.tsx
+++ b/web-ui/__tests__/components/tasks/TaskStats.test.tsx
@@ -709,5 +709,83 @@ describe('TaskStats', () => {
String(consistentIssuesData.total_tasks)
);
});
+
+ it('test_planning_phase_uses_total_tasks_when_tasks_arrays_empty', () => {
+ // CRITICAL: This test replicates the actual production bug.
+ // The API returns total_tasks count but does NOT populate the nested
+ // issues[].tasks arrays. The component must use total_tasks directly
+ // rather than trying to flatten empty task arrays.
+ //
+ // Production scenario: "Review (24)" badge shows correct count from
+ // issuesData.total_tasks, but TaskStats shows 0 because it tries to
+ // flatten empty tasks arrays.
+
+ // ARRANGE: Realistic API response - tasks arrays are empty but total_tasks is populated
+ const productionLikeResponse = {
+ issues: [
+ {
+ id: '1',
+ issue_number: '1',
+ title: 'Authentication Feature',
+ description: 'Implement user authentication',
+ status: 'pending' as const,
+ priority: 1,
+ depends_on: [],
+ proposed_by: 'agent' as const,
+ created_at: '2025-01-01T00:00:00Z',
+ updated_at: '2025-01-01T00:00:00Z',
+ completed_at: null,
+ tasks: [], // Empty - API doesn't populate nested tasks
+ },
+ {
+ id: '2',
+ issue_number: '2',
+ title: 'Dashboard Feature',
+ description: 'Build analytics dashboard',
+ status: 'pending' as const,
+ priority: 2,
+ depends_on: [],
+ proposed_by: 'agent' as const,
+ created_at: '2025-01-01T00:00:00Z',
+ updated_at: '2025-01-01T00:00:00Z',
+ completed_at: null,
+ tasks: [], // Empty - API doesn't populate nested tasks
+ },
+ ],
+ total_issues: 2,
+ total_tasks: 24, // This is the authoritative count from the database
+ };
+
+ mockUseAgentState.mockReturnValue(emptyAgentState);
+
+ // ACT
+ render();
+
+ // ASSERT: Must show 24 (from total_tasks), NOT 0 (from flattening empty arrays)
+ expect(screen.getByTestId('total-tasks')).toHaveTextContent('24');
+
+ // During planning phase, completed/blocked/in-progress are unavailable
+ // since we don't have the individual task status data
+ expect(screen.getByTestId('completed-tasks')).toHaveTextContent('0');
+ expect(screen.getByTestId('blocked-tasks')).toHaveTextContent('0');
+ expect(screen.getByTestId('in-progress-tasks')).toHaveTextContent('0');
+ });
+
+ it('test_planning_phase_handles_undefined_total_tasks', () => {
+ // Edge case: total_tasks field is missing from response
+ const responseWithoutTotalTasks = {
+ issues: [],
+ total_issues: 0,
+ // total_tasks intentionally omitted
+ } as unknown as typeof mockIssuesData;
+
+ mockUseAgentState.mockReturnValue(emptyAgentState);
+
+ // ACT
+ render();
+
+ // ASSERT: Should gracefully default to 0
+ expect(screen.getByTestId('total-tasks')).toHaveTextContent('0');
+ });
});
});
diff --git a/web-ui/src/components/TaskReview.tsx b/web-ui/src/components/TaskReview.tsx
index 5502e3ce..7a3105cd 100644
--- a/web-ui/src/components/TaskReview.tsx
+++ b/web-ui/src/components/TaskReview.tsx
@@ -89,13 +89,14 @@ const TaskReview = memo(function TaskReview({
// Refs for indeterminate checkboxes
const issueCheckboxRefs = useRef