@allpepper/task-orchestrator-tui 1.2.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
1
  import { useState, useEffect, useCallback, useMemo } from 'react';
2
2
  import { useAdapter } from '../context/adapter-context';
3
- import type { Project, Task, Section, EntityType, TaskStatus, Priority, FeatureStatus } from '@allpepper/task-orchestrator';
3
+ import type { Project, Task, Feature, Section, EntityType, Priority } from '@allpepper/task-orchestrator';
4
4
  import type { FeatureWithTasks, ProjectOverview, SearchResults, DependencyInfo, BoardCard, BoardTask } from '../lib/types';
5
5
  import type { TreeRow } from '../../tui/components/tree-view';
6
+ import { isCompletedStatus } from '../lib/colors';
6
7
 
7
8
  /**
8
9
  * Task counts structure
@@ -18,7 +19,7 @@ export interface TaskCounts {
18
19
  export function calculateTaskCounts(tasks: Task[]): TaskCounts {
19
20
  return {
20
21
  total: tasks.length,
21
- completed: tasks.filter(t => t.status === 'COMPLETED').length,
22
+ completed: tasks.filter(t => isCompletedStatus(t.status)).length,
22
23
  };
23
24
  }
24
25
 
@@ -32,7 +33,7 @@ export function calculateTaskCountsByProject(tasks: Task[]): Map<string, TaskCou
32
33
  if (task.projectId) {
33
34
  const counts = countsByProject.get(task.projectId) || { total: 0, completed: 0 };
34
35
  counts.total++;
35
- if (task.status === 'COMPLETED') {
36
+ if (isCompletedStatus(task.status)) {
36
37
  counts.completed++;
37
38
  }
38
39
  countsByProject.set(task.projectId, counts);
@@ -43,22 +44,43 @@ export function calculateTaskCountsByProject(tasks: Task[]): Map<string, TaskCou
43
44
  }
44
45
 
45
46
  /**
46
- * Project with task count information for dashboard display
47
+ * Feature counts structure
48
+ */
49
+ export interface FeatureCounts {
50
+ total: number;
51
+ completed: number;
52
+ }
53
+
54
+ /**
55
+ * Group features by project ID and calculate counts for each
56
+ */
57
+ export function calculateFeatureCountsByProject(featureList: Feature[]): Map<string, FeatureCounts> {
58
+ const countsByProject = new Map<string, FeatureCounts>();
59
+
60
+ for (const feature of featureList) {
61
+ if (feature.projectId) {
62
+ const counts = countsByProject.get(feature.projectId) || { total: 0, completed: 0 };
63
+ counts.total++;
64
+ if (isCompletedStatus(feature.status)) {
65
+ counts.completed++;
66
+ }
67
+ countsByProject.set(feature.projectId, counts);
68
+ }
69
+ }
70
+
71
+ return countsByProject;
72
+ }
73
+
74
+ /**
75
+ * Project with task and feature count information for dashboard display
47
76
  */
48
77
  export interface ProjectWithCounts extends Project {
49
78
  taskCounts: TaskCounts;
79
+ featureCounts: FeatureCounts;
50
80
  }
51
81
 
52
82
  /**
53
83
  * Hook for fetching and managing the list of projects for the dashboard.
54
- * Includes task counts for each project.
55
- *
56
- * @returns Project list state with loading/error states and refresh function
57
- *
58
- * @example
59
- * ```tsx
60
- * const { projects, loading, error, refresh } = useProjects();
61
- * ```
62
84
  */
63
85
  export function useProjects() {
64
86
  const { adapter } = useAdapter();
@@ -70,10 +92,10 @@ export function useProjects() {
70
92
  setLoading(true);
71
93
  setError(null);
72
94
 
73
- // Fetch projects and all tasks in parallel
74
- const [projectsResult, tasksResult] = await Promise.all([
95
+ const [projectsResult, tasksResult, featuresResult] = await Promise.all([
75
96
  adapter.getProjects(),
76
- adapter.getTasks({ limit: 1000 }), // Get all tasks to count by project
97
+ adapter.getTasks({ limit: 1000 }),
98
+ adapter.getFeatures({ limit: 1000 }),
77
99
  ]);
78
100
 
79
101
  if (!projectsResult.success) {
@@ -82,15 +104,18 @@ export function useProjects() {
82
104
  return;
83
105
  }
84
106
 
85
- // Build task counts by project using shared utility
86
107
  const taskCountsByProject = tasksResult.success
87
108
  ? calculateTaskCountsByProject(tasksResult.data)
88
109
  : new Map<string, TaskCounts>();
89
110
 
90
- // Merge task counts into projects
111
+ const featureCountsByProject = featuresResult.success
112
+ ? calculateFeatureCountsByProject(featuresResult.data)
113
+ : new Map<string, FeatureCounts>();
114
+
91
115
  const projectsWithCounts: ProjectWithCounts[] = projectsResult.data.map(project => ({
92
116
  ...project,
93
117
  taskCounts: taskCountsByProject.get(project.id) || { total: 0, completed: 0 },
118
+ featureCounts: featureCountsByProject.get(project.id) || { total: 0, completed: 0 },
94
119
  }));
95
120
 
96
121
  setProjects(projectsWithCounts);
@@ -111,14 +136,6 @@ export function useProjects() {
111
136
 
112
137
  /**
113
138
  * Hook for fetching a single project with its overview statistics.
114
- *
115
- * @param id - The project ID
116
- * @returns Project and overview state with loading/error states and refresh function
117
- *
118
- * @example
119
- * ```tsx
120
- * const { project, overview, loading, error, refresh } = useProjectOverview('proj-123');
121
- * ```
122
139
  */
123
140
  export function useProjectOverview(id: string) {
124
141
  const { adapter } = useAdapter();
@@ -145,7 +162,6 @@ export function useProjectOverview(id: string) {
145
162
  if (overviewResult.success) {
146
163
  setOverview(overviewResult.data);
147
164
  } else if (!error) {
148
- // Only set error if project fetch didn't already fail
149
165
  setError(overviewResult.error);
150
166
  }
151
167
 
@@ -166,16 +182,16 @@ export function useProjectOverview(id: string) {
166
182
  }
167
183
 
168
184
  /**
169
- * Status order for grouping tasks - active statuses first, then terminal statuses
185
+ * v2 pipeline status order for task columns
186
+ * Tasks: NEW → ACTIVE → TO_BE_TESTED → READY_TO_PROD → CLOSED (+ WILL_NOT_IMPLEMENT)
170
187
  */
171
- const STATUS_ORDER: TaskStatus[] = [
172
- 'PENDING' as TaskStatus,
173
- 'IN_PROGRESS' as TaskStatus,
174
- 'IN_REVIEW' as TaskStatus,
175
- 'BLOCKED' as TaskStatus,
176
- 'ON_HOLD' as TaskStatus,
177
- 'COMPLETED' as TaskStatus,
178
- 'CANCELLED' as TaskStatus,
188
+ const TASK_STATUS_ORDER: string[] = [
189
+ 'NEW',
190
+ 'ACTIVE',
191
+ 'TO_BE_TESTED',
192
+ 'READY_TO_PROD',
193
+ 'CLOSED',
194
+ 'WILL_NOT_IMPLEMENT',
179
195
  ];
180
196
 
181
197
  /**
@@ -187,65 +203,43 @@ const PRIORITY_ORDER: Record<Priority, number> = {
187
203
  LOW: 1,
188
204
  };
189
205
 
190
- const BOARD_STATUS_ORDER: TaskStatus[] = [
191
- 'PENDING' as TaskStatus,
192
- 'IN_PROGRESS' as TaskStatus,
193
- 'IN_REVIEW' as TaskStatus,
194
- 'BLOCKED' as TaskStatus,
195
- 'COMPLETED' as TaskStatus,
206
+ /**
207
+ * Board status order (subset for kanban view)
208
+ */
209
+ const BOARD_STATUS_ORDER: string[] = [
210
+ 'NEW',
211
+ 'ACTIVE',
212
+ 'TO_BE_TESTED',
213
+ 'READY_TO_PROD',
214
+ 'CLOSED',
196
215
  ];
197
216
 
198
217
  /**
199
- * Display names for task statuses
218
+ * Display names for v2 pipeline statuses
200
219
  */
201
220
  const STATUS_DISPLAY_NAMES: Record<string, string> = {
202
- BACKLOG: 'Backlog',
203
- PENDING: 'Pending',
204
- IN_PROGRESS: 'In Progress',
205
- IN_REVIEW: 'In Review',
206
- CHANGES_REQUESTED: 'Changes Requested',
207
- TESTING: 'Testing',
208
- READY_FOR_QA: 'Ready for QA',
209
- INVESTIGATING: 'Investigating',
210
- BLOCKED: 'Blocked',
211
- ON_HOLD: 'On Hold',
212
- DEPLOYED: 'Deployed',
213
- COMPLETED: 'Completed',
214
- CANCELLED: 'Cancelled',
215
- DEFERRED: 'Deferred',
216
- DRAFT: 'Draft',
217
- PLANNING: 'Planning',
218
- IN_DEVELOPMENT: 'In Development',
219
- VALIDATING: 'Validating',
220
- PENDING_REVIEW: 'Pending Review',
221
- ARCHIVED: 'Archived',
221
+ NEW: 'New',
222
+ ACTIVE: 'Active',
223
+ TO_BE_TESTED: 'To Be Tested',
224
+ READY_TO_PROD: 'Ready to Prod',
225
+ CLOSED: 'Closed',
226
+ WILL_NOT_IMPLEMENT: 'Will Not Implement',
222
227
  };
223
228
 
224
229
  /**
225
- * Feature status order for grouping features by their own status
230
+ * Feature status order for grouping features
231
+ * Features: NEW → ACTIVE → READY_TO_PROD → CLOSED (+ WILL_NOT_IMPLEMENT)
226
232
  */
227
- const FEATURE_STATUS_ORDER: FeatureStatus[] = [
228
- 'DRAFT' as FeatureStatus,
229
- 'PLANNING' as FeatureStatus,
230
- 'IN_DEVELOPMENT' as FeatureStatus,
231
- 'TESTING' as FeatureStatus,
232
- 'VALIDATING' as FeatureStatus,
233
- 'PENDING_REVIEW' as FeatureStatus,
234
- 'BLOCKED' as FeatureStatus,
235
- 'ON_HOLD' as FeatureStatus,
236
- 'DEPLOYED' as FeatureStatus,
237
- 'COMPLETED' as FeatureStatus,
238
- 'ARCHIVED' as FeatureStatus,
233
+ const FEATURE_STATUS_ORDER: string[] = [
234
+ 'NEW',
235
+ 'ACTIVE',
236
+ 'READY_TO_PROD',
237
+ 'CLOSED',
238
+ 'WILL_NOT_IMPLEMENT',
239
239
  ];
240
240
 
241
241
  /**
242
242
  * Build status-grouped tree rows for tasks
243
- * Groups tasks by status, then by feature within each status
244
- *
245
- * @param tasks - All tasks to group
246
- * @param features - Features to lookup task feature info
247
- * @param expandedGroups - Set of expanded group IDs (both status groups and composite feature keys)
248
- * @returns TreeRow[] grouped by status → feature → tasks
249
243
  */
250
244
  function buildStatusGroupedRows(
251
245
  tasks: Task[],
@@ -260,39 +254,14 @@ function buildStatusGroupedRows(
260
254
  }
261
255
 
262
256
  // Group tasks by status
263
- const tasksByStatus = new Map<TaskStatus, Task[]>();
257
+ const tasksByStatus = new Map<string, Task[]>();
264
258
  for (const task of tasks) {
265
- const status = task.status as TaskStatus;
266
- const group = tasksByStatus.get(status) || [];
259
+ const group = tasksByStatus.get(task.status) || [];
267
260
  group.push(task);
268
- tasksByStatus.set(status, group);
261
+ tasksByStatus.set(task.status, group);
269
262
  }
270
263
 
271
- const featureStatusToTaskStatus = (status: FeatureStatus): TaskStatus => {
272
- switch (status) {
273
- case 'COMPLETED':
274
- case 'DEPLOYED':
275
- return 'COMPLETED' as TaskStatus;
276
- case 'BLOCKED':
277
- return 'BLOCKED' as TaskStatus;
278
- case 'ON_HOLD':
279
- return 'ON_HOLD' as TaskStatus;
280
- case 'ARCHIVED':
281
- return 'CANCELLED' as TaskStatus;
282
- case 'PENDING_REVIEW':
283
- return 'IN_REVIEW' as TaskStatus;
284
- case 'IN_DEVELOPMENT':
285
- case 'TESTING':
286
- case 'VALIDATING':
287
- return 'IN_PROGRESS' as TaskStatus;
288
- case 'PLANNING':
289
- case 'DRAFT':
290
- default:
291
- return 'PENDING' as TaskStatus;
292
- }
293
- };
294
-
295
- // Only inject empty features into status buckets to avoid duplicate feature rows across statuses.
264
+ // Track which features have tasks
296
265
  const featureHasTasks = new Set<string>();
297
266
  for (const task of tasks) {
298
267
  if (task.featureId) {
@@ -300,20 +269,33 @@ function buildStatusGroupedRows(
300
269
  }
301
270
  }
302
271
 
303
- // Group features by their mapped status bucket
304
- const featuresByStatus = new Map<TaskStatus, FeatureWithTasks[]>();
305
- for (const feature of features) {
306
- if (featureHasTasks.has(feature.id)) {
307
- continue;
272
+ // Map feature status to the closest task status for empty features
273
+ const featureStatusToTaskBucket = (featureStatus: string): string => {
274
+ switch (featureStatus) {
275
+ case 'CLOSED':
276
+ case 'WILL_NOT_IMPLEMENT':
277
+ return featureStatus;
278
+ case 'ACTIVE':
279
+ case 'READY_TO_PROD':
280
+ return featureStatus;
281
+ case 'NEW':
282
+ default:
283
+ return 'NEW';
308
284
  }
309
- const mappedStatus = featureStatusToTaskStatus(feature.status as FeatureStatus);
285
+ };
286
+
287
+ // Group empty features by their mapped status bucket
288
+ const featuresByStatus = new Map<string, FeatureWithTasks[]>();
289
+ for (const feature of features) {
290
+ if (featureHasTasks.has(feature.id)) continue;
291
+ const mappedStatus = featureStatusToTaskBucket(feature.status);
310
292
  const group = featuresByStatus.get(mappedStatus) || [];
311
293
  group.push(feature);
312
294
  featuresByStatus.set(mappedStatus, group);
313
295
  }
314
296
 
315
297
  // Build rows in status order
316
- for (const status of STATUS_ORDER) {
298
+ for (const status of TASK_STATUS_ORDER) {
317
299
  const statusTasks = tasksByStatus.get(status) || [];
318
300
  const statusFeatures = featuresByStatus.get(status) || [];
319
301
  if (statusTasks.length === 0 && statusFeatures.length === 0) continue;
@@ -322,7 +304,6 @@ function buildStatusGroupedRows(
322
304
  const statusExpanded = expandedGroups.has(statusGroupId);
323
305
  const statusExpandable = statusTasks.length > 0 || statusFeatures.length > 0;
324
306
 
325
- // Add status group row (depth 0)
326
307
  rows.push({
327
308
  type: 'group',
328
309
  id: statusGroupId,
@@ -334,9 +315,8 @@ function buildStatusGroupedRows(
334
315
  expandable: statusExpandable,
335
316
  });
336
317
 
337
- // If status group is expanded, group tasks by feature
338
318
  if (statusExpanded) {
339
- // Group tasks by featureId (with null for unassigned)
319
+ // Group tasks by featureId
340
320
  const tasksByFeature = new Map<string | null, Task[]>();
341
321
  for (const task of statusTasks) {
342
322
  const featureId = task.featureId || null;
@@ -345,7 +325,7 @@ function buildStatusGroupedRows(
345
325
  tasksByFeature.set(featureId, group);
346
326
  }
347
327
 
348
- // Sort tasks within each feature by priority (descending) then title
328
+ // Sort tasks within each feature by priority then title
349
329
  for (const [_, featureTasks] of tasksByFeature.entries()) {
350
330
  featureTasks.sort((a, b) => {
351
331
  const priorityDiff = PRIORITY_ORDER[b.priority] - PRIORITY_ORDER[a.priority];
@@ -355,7 +335,6 @@ function buildStatusGroupedRows(
355
335
  }
356
336
 
357
337
  // Build feature sub-groups
358
- // First, collect features that have tasks in this status (sorted by creation date)
359
338
  const tasksByFeatureId = new Map<string, Task[]>();
360
339
  for (const [featureId, featureTasks] of tasksByFeature.entries()) {
361
340
  if (featureId !== null) {
@@ -363,7 +342,7 @@ function buildStatusGroupedRows(
363
342
  }
364
343
  }
365
344
 
366
- // Sort mapped features by creation date (ascending, oldest first), including empty features
345
+ // Sort features by creation date
367
346
  const statusFeatureMap = new Map<string, FeatureWithTasks>();
368
347
  for (const feature of statusFeatures) {
369
348
  statusFeatureMap.set(feature.id, feature);
@@ -379,7 +358,6 @@ function buildStatusGroupedRows(
379
358
  new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
380
359
  );
381
360
 
382
- // Add feature sub-group rows
383
361
  for (const feature of sortedStatusFeatures) {
384
362
  const featureId = feature.id;
385
363
  const featureTasks = tasksByFeatureId.get(featureId) || [];
@@ -387,7 +365,6 @@ function buildStatusGroupedRows(
387
365
  const featureExpandable = featureTasks.length > 0;
388
366
  const featureExpanded = featureExpandable && expandedGroups.has(compositeFeatureId);
389
367
 
390
- // Add feature group row (depth 1)
391
368
  rows.push({
392
369
  type: 'group',
393
370
  id: compositeFeatureId,
@@ -400,7 +377,6 @@ function buildStatusGroupedRows(
400
377
  featureId,
401
378
  });
402
379
 
403
- // Add task rows if feature is expanded (depth 1)
404
380
  if (featureExpanded) {
405
381
  featureTasks.forEach((task, index) => {
406
382
  const isLast = index === featureTasks.length - 1;
@@ -409,37 +385,33 @@ function buildStatusGroupedRows(
409
385
  task,
410
386
  isLast,
411
387
  depth: 2,
412
- // No featureName needed - tasks are nested under their feature
413
388
  });
414
389
  });
415
390
  }
416
391
  }
417
392
 
418
- // Add unassigned tasks (if any)
393
+ // Add unassigned tasks
419
394
  const unassignedTasks = tasksByFeature.get(null);
420
395
  if (unassignedTasks && unassignedTasks.length > 0) {
421
396
  const unassignedId = `${status}:unassigned`;
422
397
  const unassignedExpanded = expandedGroups.has(unassignedId);
423
398
 
424
- // Sort unassigned tasks by priority (descending) then title
425
399
  unassignedTasks.sort((a, b) => {
426
400
  const priorityDiff = PRIORITY_ORDER[b.priority] - PRIORITY_ORDER[a.priority];
427
401
  if (priorityDiff !== 0) return priorityDiff;
428
402
  return a.title.localeCompare(b.title);
429
403
  });
430
404
 
431
- // Add unassigned group row (depth 1)
432
405
  rows.push({
433
406
  type: 'group',
434
407
  id: unassignedId,
435
408
  label: 'Unassigned',
436
- status: status, // Use parent status for consistency
409
+ status: status,
437
410
  taskCount: unassignedTasks.length,
438
411
  expanded: unassignedExpanded,
439
412
  depth: 1,
440
413
  });
441
414
 
442
- // Add task rows if unassigned group is expanded (depth 1)
443
415
  if (unassignedExpanded) {
444
416
  unassignedTasks.forEach((task, index) => {
445
417
  const isLast = index === unassignedTasks.length - 1;
@@ -460,11 +432,6 @@ function buildStatusGroupedRows(
460
432
 
461
433
  /**
462
434
  * Build feature-status-grouped tree rows
463
- * Groups features by their feature status, then nests tasks within each feature
464
- *
465
- * @param features - All features with their tasks
466
- * @param expandedGroups - Set of expanded group IDs
467
- * @returns TreeRow[] grouped by feature status → feature → tasks
468
435
  */
469
436
  function buildFeatureStatusGroupedRows(
470
437
  features: FeatureWithTasks[],
@@ -472,16 +439,13 @@ function buildFeatureStatusGroupedRows(
472
439
  ): TreeRow[] {
473
440
  const rows: TreeRow[] = [];
474
441
 
475
- // Group features by their status
476
442
  const featuresByStatus = new Map<string, FeatureWithTasks[]>();
477
443
  for (const feature of features) {
478
- const status = feature.status as string;
479
- const group = featuresByStatus.get(status) || [];
444
+ const group = featuresByStatus.get(feature.status) || [];
480
445
  group.push(feature);
481
- featuresByStatus.set(status, group);
446
+ featuresByStatus.set(feature.status, group);
482
447
  }
483
448
 
484
- // Build rows in feature status order
485
449
  for (const status of FEATURE_STATUS_ORDER) {
486
450
  const statusFeatures = featuresByStatus.get(status) || [];
487
451
  if (statusFeatures.length === 0) continue;
@@ -489,10 +453,6 @@ function buildFeatureStatusGroupedRows(
489
453
  const statusGroupId = `fs:${status}`;
490
454
  const statusExpanded = expandedGroups.has(statusGroupId);
491
455
 
492
- // Count total tasks across all features in this status
493
- const totalTasks = statusFeatures.reduce((sum, f) => sum + f.tasks.length, 0);
494
-
495
- // Add status group row (depth 0)
496
456
  rows.push({
497
457
  type: 'group',
498
458
  id: statusGroupId,
@@ -505,7 +465,6 @@ function buildFeatureStatusGroupedRows(
505
465
  });
506
466
 
507
467
  if (statusExpanded) {
508
- // Sort features by creation date (oldest first)
509
468
  const sortedFeatures = [...statusFeatures].sort(
510
469
  (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
511
470
  );
@@ -515,7 +474,6 @@ function buildFeatureStatusGroupedRows(
515
474
  const featureExpandable = feature.tasks.length > 0;
516
475
  const featureExpanded = featureExpandable && expandedGroups.has(compositeFeatureId);
517
476
 
518
- // Add feature group row (depth 1)
519
477
  rows.push({
520
478
  type: 'group',
521
479
  id: compositeFeatureId,
@@ -528,9 +486,7 @@ function buildFeatureStatusGroupedRows(
528
486
  featureId: feature.id,
529
487
  });
530
488
 
531
- // Add task rows if feature is expanded (depth 2)
532
489
  if (featureExpanded) {
533
- // Sort tasks by priority (descending) then title
534
490
  const sortedTasks = [...feature.tasks].sort((a, b) => {
535
491
  const priorityDiff = PRIORITY_ORDER[b.priority] - PRIORITY_ORDER[a.priority];
536
492
  if (priorityDiff !== 0) return priorityDiff;
@@ -555,15 +511,6 @@ function buildFeatureStatusGroupedRows(
555
511
 
556
512
  /**
557
513
  * Hook for fetching a project tree with features and their tasks.
558
- * Also includes unassigned tasks (tasks without a feature).
559
- *
560
- * @param projectId - The project ID
561
- * @returns Project, features with nested tasks, unassigned tasks, task counts, status-grouped rows, loading/error states, and refresh function
562
- *
563
- * @example
564
- * ```tsx
565
- * const { project, features, unassignedTasks, taskCounts, statusGroupedRows, loading, error, refresh } = useProjectTree('proj-123');
566
- * ```
567
514
  */
568
515
  export function useProjectTree(projectId: string, expandedGroups: Set<string> = new Set()) {
569
516
  const { adapter } = useAdapter();
@@ -607,18 +554,15 @@ export function useProjectTree(projectId: string, expandedGroups: Set<string> =
607
554
  const allFeatures = featuresResult.data;
608
555
  const tasks = tasksResult.data;
609
556
 
610
- // Sort features by creation date ascending (oldest first)
611
557
  const sortedFeatures = [...allFeatures].sort(
612
558
  (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
613
559
  );
614
560
 
615
- // Build feature tree with nested tasks
616
561
  const featuresWithTasks: FeatureWithTasks[] = sortedFeatures.map((feature) => ({
617
562
  ...feature,
618
563
  tasks: tasks.filter((task) => task.featureId === feature.id),
619
564
  }));
620
565
 
621
- // Find unassigned tasks (no featureId)
622
566
  const unassigned = tasks.filter((task) => !task.featureId);
623
567
 
624
568
  setProject(projectData);
@@ -633,12 +577,10 @@ export function useProjectTree(projectId: string, expandedGroups: Set<string> =
633
577
  loadProjectTree();
634
578
  }, [loadProjectTree]);
635
579
 
636
- // Build status-grouped rows using useMemo to recalculate when data changes
637
580
  const statusGroupedRows = useMemo(() => {
638
581
  return buildStatusGroupedRows(allTasks, features, expandedGroups);
639
582
  }, [allTasks, features, expandedGroups]);
640
583
 
641
- // Build feature-status-grouped rows
642
584
  const featureStatusGroupedRows = useMemo(() => {
643
585
  return buildFeatureStatusGroupedRows(features, expandedGroups);
644
586
  }, [features, expandedGroups]);
@@ -658,7 +600,6 @@ export function useProjectTree(projectId: string, expandedGroups: Set<string> =
658
600
 
659
601
  /**
660
602
  * Hook for fetching board data (kanban columns) for a project.
661
- * Returns tasks grouped by status with feature labels for each card.
662
603
  */
663
604
  export function useBoardData(projectId: string) {
664
605
  const { adapter } = useAdapter();
@@ -737,14 +678,6 @@ export function useBoardData(projectId: string) {
737
678
 
738
679
  /**
739
680
  * Hook for fetching a single task with its sections and dependencies.
740
- *
741
- * @param id - The task ID
742
- * @returns Task, sections, dependencies, loading/error states, and refresh function
743
- *
744
- * @example
745
- * ```tsx
746
- * const { task, sections, dependencies, loading, error, refresh } = useTask('task-123');
747
- * ```
748
681
  */
749
682
  export function useTask(id: string) {
750
683
  const { adapter } = useAdapter();
@@ -801,14 +734,6 @@ export function useTask(id: string) {
801
734
 
802
735
  /**
803
736
  * Hook for fetching a single feature with its tasks and sections.
804
- *
805
- * @param id - The feature ID
806
- * @returns Feature, tasks, sections, loading/error states, and refresh function
807
- *
808
- * @example
809
- * ```tsx
810
- * const { feature, tasks, sections, loading, error, refresh } = useFeature('feat-123');
811
- * ```
812
737
  */
813
738
  export function useFeature(id: string) {
814
739
  const { adapter } = useAdapter();
@@ -865,17 +790,6 @@ export function useFeature(id: string) {
865
790
 
866
791
  /**
867
792
  * Hook for performing full-text search across all entities.
868
- * Use with useDebounce to avoid excessive API calls.
869
- *
870
- * @param query - The search query string
871
- * @returns Search results with loading/error states
872
- *
873
- * @example
874
- * ```tsx
875
- * const [inputValue, setInputValue] = useState('');
876
- * const debouncedQuery = useDebounce(inputValue, 300);
877
- * const { results, loading, error } = useSearch(debouncedQuery);
878
- * ```
879
793
  */
880
794
  export function useSearch(query: string) {
881
795
  const { adapter } = useAdapter();
@@ -884,7 +798,6 @@ export function useSearch(query: string) {
884
798
  const [error, setError] = useState<string | null>(null);
885
799
 
886
800
  useEffect(() => {
887
- // Don't search for empty queries
888
801
  if (!query.trim()) {
889
802
  setResults(null);
890
803
  setLoading(false);