@allpepper/task-orchestrator-tui 1.3.0 → 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, Feature, 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);
@@ -50,11 +51,6 @@ export interface FeatureCounts {
50
51
  completed: number;
51
52
  }
52
53
 
53
- /**
54
- * Terminal feature statuses considered "completed"
55
- */
56
- const COMPLETED_FEATURE_STATUSES = ['COMPLETED', 'DEPLOYED'];
57
-
58
54
  /**
59
55
  * Group features by project ID and calculate counts for each
60
56
  */
@@ -65,7 +61,7 @@ export function calculateFeatureCountsByProject(featureList: Feature[]): Map<str
65
61
  if (feature.projectId) {
66
62
  const counts = countsByProject.get(feature.projectId) || { total: 0, completed: 0 };
67
63
  counts.total++;
68
- if (COMPLETED_FEATURE_STATUSES.includes(feature.status)) {
64
+ if (isCompletedStatus(feature.status)) {
69
65
  counts.completed++;
70
66
  }
71
67
  countsByProject.set(feature.projectId, counts);
@@ -85,14 +81,6 @@ export interface ProjectWithCounts extends Project {
85
81
 
86
82
  /**
87
83
  * Hook for fetching and managing the list of projects for the dashboard.
88
- * Includes task counts for each project.
89
- *
90
- * @returns Project list state with loading/error states and refresh function
91
- *
92
- * @example
93
- * ```tsx
94
- * const { projects, loading, error, refresh } = useProjects();
95
- * ```
96
84
  */
97
85
  export function useProjects() {
98
86
  const { adapter } = useAdapter();
@@ -104,11 +92,10 @@ export function useProjects() {
104
92
  setLoading(true);
105
93
  setError(null);
106
94
 
107
- // Fetch projects, tasks, and features in parallel
108
95
  const [projectsResult, tasksResult, featuresResult] = await Promise.all([
109
96
  adapter.getProjects(),
110
- adapter.getTasks({ limit: 1000 }), // Get all tasks to count by project
111
- adapter.getFeatures({ limit: 1000 }), // Get all features to count by project
97
+ adapter.getTasks({ limit: 1000 }),
98
+ adapter.getFeatures({ limit: 1000 }),
112
99
  ]);
113
100
 
114
101
  if (!projectsResult.success) {
@@ -117,17 +104,14 @@ export function useProjects() {
117
104
  return;
118
105
  }
119
106
 
120
- // Build task counts by project using shared utility
121
107
  const taskCountsByProject = tasksResult.success
122
108
  ? calculateTaskCountsByProject(tasksResult.data)
123
109
  : new Map<string, TaskCounts>();
124
110
 
125
- // Build feature counts by project using shared utility
126
111
  const featureCountsByProject = featuresResult.success
127
112
  ? calculateFeatureCountsByProject(featuresResult.data)
128
113
  : new Map<string, FeatureCounts>();
129
114
 
130
- // Merge task and feature counts into projects
131
115
  const projectsWithCounts: ProjectWithCounts[] = projectsResult.data.map(project => ({
132
116
  ...project,
133
117
  taskCounts: taskCountsByProject.get(project.id) || { total: 0, completed: 0 },
@@ -152,14 +136,6 @@ export function useProjects() {
152
136
 
153
137
  /**
154
138
  * Hook for fetching a single project with its overview statistics.
155
- *
156
- * @param id - The project ID
157
- * @returns Project and overview state with loading/error states and refresh function
158
- *
159
- * @example
160
- * ```tsx
161
- * const { project, overview, loading, error, refresh } = useProjectOverview('proj-123');
162
- * ```
163
139
  */
164
140
  export function useProjectOverview(id: string) {
165
141
  const { adapter } = useAdapter();
@@ -186,7 +162,6 @@ export function useProjectOverview(id: string) {
186
162
  if (overviewResult.success) {
187
163
  setOverview(overviewResult.data);
188
164
  } else if (!error) {
189
- // Only set error if project fetch didn't already fail
190
165
  setError(overviewResult.error);
191
166
  }
192
167
 
@@ -207,16 +182,16 @@ export function useProjectOverview(id: string) {
207
182
  }
208
183
 
209
184
  /**
210
- * 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)
211
187
  */
212
- const STATUS_ORDER: TaskStatus[] = [
213
- 'PENDING' as TaskStatus,
214
- 'IN_PROGRESS' as TaskStatus,
215
- 'IN_REVIEW' as TaskStatus,
216
- 'BLOCKED' as TaskStatus,
217
- 'ON_HOLD' as TaskStatus,
218
- 'COMPLETED' as TaskStatus,
219
- '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',
220
195
  ];
221
196
 
222
197
  /**
@@ -228,65 +203,43 @@ const PRIORITY_ORDER: Record<Priority, number> = {
228
203
  LOW: 1,
229
204
  };
230
205
 
231
- const BOARD_STATUS_ORDER: TaskStatus[] = [
232
- 'PENDING' as TaskStatus,
233
- 'IN_PROGRESS' as TaskStatus,
234
- 'IN_REVIEW' as TaskStatus,
235
- 'BLOCKED' as TaskStatus,
236
- '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',
237
215
  ];
238
216
 
239
217
  /**
240
- * Display names for task statuses
218
+ * Display names for v2 pipeline statuses
241
219
  */
242
220
  const STATUS_DISPLAY_NAMES: Record<string, string> = {
243
- BACKLOG: 'Backlog',
244
- PENDING: 'Pending',
245
- IN_PROGRESS: 'In Progress',
246
- IN_REVIEW: 'In Review',
247
- CHANGES_REQUESTED: 'Changes Requested',
248
- TESTING: 'Testing',
249
- READY_FOR_QA: 'Ready for QA',
250
- INVESTIGATING: 'Investigating',
251
- BLOCKED: 'Blocked',
252
- ON_HOLD: 'On Hold',
253
- DEPLOYED: 'Deployed',
254
- COMPLETED: 'Completed',
255
- CANCELLED: 'Cancelled',
256
- DEFERRED: 'Deferred',
257
- DRAFT: 'Draft',
258
- PLANNING: 'Planning',
259
- IN_DEVELOPMENT: 'In Development',
260
- VALIDATING: 'Validating',
261
- PENDING_REVIEW: 'Pending Review',
262
- 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',
263
227
  };
264
228
 
265
229
  /**
266
- * 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)
267
232
  */
268
- const FEATURE_STATUS_ORDER: FeatureStatus[] = [
269
- 'DRAFT' as FeatureStatus,
270
- 'PLANNING' as FeatureStatus,
271
- 'IN_DEVELOPMENT' as FeatureStatus,
272
- 'TESTING' as FeatureStatus,
273
- 'VALIDATING' as FeatureStatus,
274
- 'PENDING_REVIEW' as FeatureStatus,
275
- 'BLOCKED' as FeatureStatus,
276
- 'ON_HOLD' as FeatureStatus,
277
- 'DEPLOYED' as FeatureStatus,
278
- 'COMPLETED' as FeatureStatus,
279
- 'ARCHIVED' as FeatureStatus,
233
+ const FEATURE_STATUS_ORDER: string[] = [
234
+ 'NEW',
235
+ 'ACTIVE',
236
+ 'READY_TO_PROD',
237
+ 'CLOSED',
238
+ 'WILL_NOT_IMPLEMENT',
280
239
  ];
281
240
 
282
241
  /**
283
242
  * Build status-grouped tree rows for tasks
284
- * Groups tasks by status, then by feature within each status
285
- *
286
- * @param tasks - All tasks to group
287
- * @param features - Features to lookup task feature info
288
- * @param expandedGroups - Set of expanded group IDs (both status groups and composite feature keys)
289
- * @returns TreeRow[] grouped by status → feature → tasks
290
243
  */
291
244
  function buildStatusGroupedRows(
292
245
  tasks: Task[],
@@ -301,39 +254,14 @@ function buildStatusGroupedRows(
301
254
  }
302
255
 
303
256
  // Group tasks by status
304
- const tasksByStatus = new Map<TaskStatus, Task[]>();
257
+ const tasksByStatus = new Map<string, Task[]>();
305
258
  for (const task of tasks) {
306
- const status = task.status as TaskStatus;
307
- const group = tasksByStatus.get(status) || [];
259
+ const group = tasksByStatus.get(task.status) || [];
308
260
  group.push(task);
309
- tasksByStatus.set(status, group);
261
+ tasksByStatus.set(task.status, group);
310
262
  }
311
263
 
312
- const featureStatusToTaskStatus = (status: FeatureStatus): TaskStatus => {
313
- switch (status) {
314
- case 'COMPLETED':
315
- case 'DEPLOYED':
316
- return 'COMPLETED' as TaskStatus;
317
- case 'BLOCKED':
318
- return 'BLOCKED' as TaskStatus;
319
- case 'ON_HOLD':
320
- return 'ON_HOLD' as TaskStatus;
321
- case 'ARCHIVED':
322
- return 'CANCELLED' as TaskStatus;
323
- case 'PENDING_REVIEW':
324
- return 'IN_REVIEW' as TaskStatus;
325
- case 'IN_DEVELOPMENT':
326
- case 'TESTING':
327
- case 'VALIDATING':
328
- return 'IN_PROGRESS' as TaskStatus;
329
- case 'PLANNING':
330
- case 'DRAFT':
331
- default:
332
- return 'PENDING' as TaskStatus;
333
- }
334
- };
335
-
336
- // Only inject empty features into status buckets to avoid duplicate feature rows across statuses.
264
+ // Track which features have tasks
337
265
  const featureHasTasks = new Set<string>();
338
266
  for (const task of tasks) {
339
267
  if (task.featureId) {
@@ -341,20 +269,33 @@ function buildStatusGroupedRows(
341
269
  }
342
270
  }
343
271
 
344
- // Group features by their mapped status bucket
345
- const featuresByStatus = new Map<TaskStatus, FeatureWithTasks[]>();
346
- for (const feature of features) {
347
- if (featureHasTasks.has(feature.id)) {
348
- 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';
349
284
  }
350
- 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);
351
292
  const group = featuresByStatus.get(mappedStatus) || [];
352
293
  group.push(feature);
353
294
  featuresByStatus.set(mappedStatus, group);
354
295
  }
355
296
 
356
297
  // Build rows in status order
357
- for (const status of STATUS_ORDER) {
298
+ for (const status of TASK_STATUS_ORDER) {
358
299
  const statusTasks = tasksByStatus.get(status) || [];
359
300
  const statusFeatures = featuresByStatus.get(status) || [];
360
301
  if (statusTasks.length === 0 && statusFeatures.length === 0) continue;
@@ -363,7 +304,6 @@ function buildStatusGroupedRows(
363
304
  const statusExpanded = expandedGroups.has(statusGroupId);
364
305
  const statusExpandable = statusTasks.length > 0 || statusFeatures.length > 0;
365
306
 
366
- // Add status group row (depth 0)
367
307
  rows.push({
368
308
  type: 'group',
369
309
  id: statusGroupId,
@@ -375,9 +315,8 @@ function buildStatusGroupedRows(
375
315
  expandable: statusExpandable,
376
316
  });
377
317
 
378
- // If status group is expanded, group tasks by feature
379
318
  if (statusExpanded) {
380
- // Group tasks by featureId (with null for unassigned)
319
+ // Group tasks by featureId
381
320
  const tasksByFeature = new Map<string | null, Task[]>();
382
321
  for (const task of statusTasks) {
383
322
  const featureId = task.featureId || null;
@@ -386,7 +325,7 @@ function buildStatusGroupedRows(
386
325
  tasksByFeature.set(featureId, group);
387
326
  }
388
327
 
389
- // Sort tasks within each feature by priority (descending) then title
328
+ // Sort tasks within each feature by priority then title
390
329
  for (const [_, featureTasks] of tasksByFeature.entries()) {
391
330
  featureTasks.sort((a, b) => {
392
331
  const priorityDiff = PRIORITY_ORDER[b.priority] - PRIORITY_ORDER[a.priority];
@@ -396,7 +335,6 @@ function buildStatusGroupedRows(
396
335
  }
397
336
 
398
337
  // Build feature sub-groups
399
- // First, collect features that have tasks in this status (sorted by creation date)
400
338
  const tasksByFeatureId = new Map<string, Task[]>();
401
339
  for (const [featureId, featureTasks] of tasksByFeature.entries()) {
402
340
  if (featureId !== null) {
@@ -404,7 +342,7 @@ function buildStatusGroupedRows(
404
342
  }
405
343
  }
406
344
 
407
- // Sort mapped features by creation date (ascending, oldest first), including empty features
345
+ // Sort features by creation date
408
346
  const statusFeatureMap = new Map<string, FeatureWithTasks>();
409
347
  for (const feature of statusFeatures) {
410
348
  statusFeatureMap.set(feature.id, feature);
@@ -420,7 +358,6 @@ function buildStatusGroupedRows(
420
358
  new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
421
359
  );
422
360
 
423
- // Add feature sub-group rows
424
361
  for (const feature of sortedStatusFeatures) {
425
362
  const featureId = feature.id;
426
363
  const featureTasks = tasksByFeatureId.get(featureId) || [];
@@ -428,7 +365,6 @@ function buildStatusGroupedRows(
428
365
  const featureExpandable = featureTasks.length > 0;
429
366
  const featureExpanded = featureExpandable && expandedGroups.has(compositeFeatureId);
430
367
 
431
- // Add feature group row (depth 1)
432
368
  rows.push({
433
369
  type: 'group',
434
370
  id: compositeFeatureId,
@@ -441,7 +377,6 @@ function buildStatusGroupedRows(
441
377
  featureId,
442
378
  });
443
379
 
444
- // Add task rows if feature is expanded (depth 1)
445
380
  if (featureExpanded) {
446
381
  featureTasks.forEach((task, index) => {
447
382
  const isLast = index === featureTasks.length - 1;
@@ -450,37 +385,33 @@ function buildStatusGroupedRows(
450
385
  task,
451
386
  isLast,
452
387
  depth: 2,
453
- // No featureName needed - tasks are nested under their feature
454
388
  });
455
389
  });
456
390
  }
457
391
  }
458
392
 
459
- // Add unassigned tasks (if any)
393
+ // Add unassigned tasks
460
394
  const unassignedTasks = tasksByFeature.get(null);
461
395
  if (unassignedTasks && unassignedTasks.length > 0) {
462
396
  const unassignedId = `${status}:unassigned`;
463
397
  const unassignedExpanded = expandedGroups.has(unassignedId);
464
398
 
465
- // Sort unassigned tasks by priority (descending) then title
466
399
  unassignedTasks.sort((a, b) => {
467
400
  const priorityDiff = PRIORITY_ORDER[b.priority] - PRIORITY_ORDER[a.priority];
468
401
  if (priorityDiff !== 0) return priorityDiff;
469
402
  return a.title.localeCompare(b.title);
470
403
  });
471
404
 
472
- // Add unassigned group row (depth 1)
473
405
  rows.push({
474
406
  type: 'group',
475
407
  id: unassignedId,
476
408
  label: 'Unassigned',
477
- status: status, // Use parent status for consistency
409
+ status: status,
478
410
  taskCount: unassignedTasks.length,
479
411
  expanded: unassignedExpanded,
480
412
  depth: 1,
481
413
  });
482
414
 
483
- // Add task rows if unassigned group is expanded (depth 1)
484
415
  if (unassignedExpanded) {
485
416
  unassignedTasks.forEach((task, index) => {
486
417
  const isLast = index === unassignedTasks.length - 1;
@@ -501,11 +432,6 @@ function buildStatusGroupedRows(
501
432
 
502
433
  /**
503
434
  * Build feature-status-grouped tree rows
504
- * Groups features by their feature status, then nests tasks within each feature
505
- *
506
- * @param features - All features with their tasks
507
- * @param expandedGroups - Set of expanded group IDs
508
- * @returns TreeRow[] grouped by feature status → feature → tasks
509
435
  */
510
436
  function buildFeatureStatusGroupedRows(
511
437
  features: FeatureWithTasks[],
@@ -513,16 +439,13 @@ function buildFeatureStatusGroupedRows(
513
439
  ): TreeRow[] {
514
440
  const rows: TreeRow[] = [];
515
441
 
516
- // Group features by their status
517
442
  const featuresByStatus = new Map<string, FeatureWithTasks[]>();
518
443
  for (const feature of features) {
519
- const status = feature.status as string;
520
- const group = featuresByStatus.get(status) || [];
444
+ const group = featuresByStatus.get(feature.status) || [];
521
445
  group.push(feature);
522
- featuresByStatus.set(status, group);
446
+ featuresByStatus.set(feature.status, group);
523
447
  }
524
448
 
525
- // Build rows in feature status order
526
449
  for (const status of FEATURE_STATUS_ORDER) {
527
450
  const statusFeatures = featuresByStatus.get(status) || [];
528
451
  if (statusFeatures.length === 0) continue;
@@ -530,10 +453,6 @@ function buildFeatureStatusGroupedRows(
530
453
  const statusGroupId = `fs:${status}`;
531
454
  const statusExpanded = expandedGroups.has(statusGroupId);
532
455
 
533
- // Count total tasks across all features in this status
534
- const totalTasks = statusFeatures.reduce((sum, f) => sum + f.tasks.length, 0);
535
-
536
- // Add status group row (depth 0)
537
456
  rows.push({
538
457
  type: 'group',
539
458
  id: statusGroupId,
@@ -546,7 +465,6 @@ function buildFeatureStatusGroupedRows(
546
465
  });
547
466
 
548
467
  if (statusExpanded) {
549
- // Sort features by creation date (oldest first)
550
468
  const sortedFeatures = [...statusFeatures].sort(
551
469
  (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
552
470
  );
@@ -556,7 +474,6 @@ function buildFeatureStatusGroupedRows(
556
474
  const featureExpandable = feature.tasks.length > 0;
557
475
  const featureExpanded = featureExpandable && expandedGroups.has(compositeFeatureId);
558
476
 
559
- // Add feature group row (depth 1)
560
477
  rows.push({
561
478
  type: 'group',
562
479
  id: compositeFeatureId,
@@ -569,9 +486,7 @@ function buildFeatureStatusGroupedRows(
569
486
  featureId: feature.id,
570
487
  });
571
488
 
572
- // Add task rows if feature is expanded (depth 2)
573
489
  if (featureExpanded) {
574
- // Sort tasks by priority (descending) then title
575
490
  const sortedTasks = [...feature.tasks].sort((a, b) => {
576
491
  const priorityDiff = PRIORITY_ORDER[b.priority] - PRIORITY_ORDER[a.priority];
577
492
  if (priorityDiff !== 0) return priorityDiff;
@@ -596,15 +511,6 @@ function buildFeatureStatusGroupedRows(
596
511
 
597
512
  /**
598
513
  * Hook for fetching a project tree with features and their tasks.
599
- * Also includes unassigned tasks (tasks without a feature).
600
- *
601
- * @param projectId - The project ID
602
- * @returns Project, features with nested tasks, unassigned tasks, task counts, status-grouped rows, loading/error states, and refresh function
603
- *
604
- * @example
605
- * ```tsx
606
- * const { project, features, unassignedTasks, taskCounts, statusGroupedRows, loading, error, refresh } = useProjectTree('proj-123');
607
- * ```
608
514
  */
609
515
  export function useProjectTree(projectId: string, expandedGroups: Set<string> = new Set()) {
610
516
  const { adapter } = useAdapter();
@@ -648,18 +554,15 @@ export function useProjectTree(projectId: string, expandedGroups: Set<string> =
648
554
  const allFeatures = featuresResult.data;
649
555
  const tasks = tasksResult.data;
650
556
 
651
- // Sort features by creation date ascending (oldest first)
652
557
  const sortedFeatures = [...allFeatures].sort(
653
558
  (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
654
559
  );
655
560
 
656
- // Build feature tree with nested tasks
657
561
  const featuresWithTasks: FeatureWithTasks[] = sortedFeatures.map((feature) => ({
658
562
  ...feature,
659
563
  tasks: tasks.filter((task) => task.featureId === feature.id),
660
564
  }));
661
565
 
662
- // Find unassigned tasks (no featureId)
663
566
  const unassigned = tasks.filter((task) => !task.featureId);
664
567
 
665
568
  setProject(projectData);
@@ -674,12 +577,10 @@ export function useProjectTree(projectId: string, expandedGroups: Set<string> =
674
577
  loadProjectTree();
675
578
  }, [loadProjectTree]);
676
579
 
677
- // Build status-grouped rows using useMemo to recalculate when data changes
678
580
  const statusGroupedRows = useMemo(() => {
679
581
  return buildStatusGroupedRows(allTasks, features, expandedGroups);
680
582
  }, [allTasks, features, expandedGroups]);
681
583
 
682
- // Build feature-status-grouped rows
683
584
  const featureStatusGroupedRows = useMemo(() => {
684
585
  return buildFeatureStatusGroupedRows(features, expandedGroups);
685
586
  }, [features, expandedGroups]);
@@ -699,7 +600,6 @@ export function useProjectTree(projectId: string, expandedGroups: Set<string> =
699
600
 
700
601
  /**
701
602
  * Hook for fetching board data (kanban columns) for a project.
702
- * Returns tasks grouped by status with feature labels for each card.
703
603
  */
704
604
  export function useBoardData(projectId: string) {
705
605
  const { adapter } = useAdapter();
@@ -778,14 +678,6 @@ export function useBoardData(projectId: string) {
778
678
 
779
679
  /**
780
680
  * Hook for fetching a single task with its sections and dependencies.
781
- *
782
- * @param id - The task ID
783
- * @returns Task, sections, dependencies, loading/error states, and refresh function
784
- *
785
- * @example
786
- * ```tsx
787
- * const { task, sections, dependencies, loading, error, refresh } = useTask('task-123');
788
- * ```
789
681
  */
790
682
  export function useTask(id: string) {
791
683
  const { adapter } = useAdapter();
@@ -842,14 +734,6 @@ export function useTask(id: string) {
842
734
 
843
735
  /**
844
736
  * Hook for fetching a single feature with its tasks and sections.
845
- *
846
- * @param id - The feature ID
847
- * @returns Feature, tasks, sections, loading/error states, and refresh function
848
- *
849
- * @example
850
- * ```tsx
851
- * const { feature, tasks, sections, loading, error, refresh } = useFeature('feat-123');
852
- * ```
853
737
  */
854
738
  export function useFeature(id: string) {
855
739
  const { adapter } = useAdapter();
@@ -906,17 +790,6 @@ export function useFeature(id: string) {
906
790
 
907
791
  /**
908
792
  * Hook for performing full-text search across all entities.
909
- * Use with useDebounce to avoid excessive API calls.
910
- *
911
- * @param query - The search query string
912
- * @returns Search results with loading/error states
913
- *
914
- * @example
915
- * ```tsx
916
- * const [inputValue, setInputValue] = useState('');
917
- * const debouncedQuery = useDebounce(inputValue, 300);
918
- * const { results, loading, error } = useSearch(debouncedQuery);
919
- * ```
920
793
  */
921
794
  export function useSearch(query: string) {
922
795
  const { adapter } = useAdapter();
@@ -925,7 +798,6 @@ export function useSearch(query: string) {
925
798
  const [error, setError] = useState<string | null>(null);
926
799
 
927
800
  useEffect(() => {
928
- // Don't search for empty queries
929
801
  if (!query.trim()) {
930
802
  setResults(null);
931
803
  setLoading(false);