This repository was archived by the owner on May 15, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Expand file tree
/
Copy pathTaskDashboard.tsx
More file actions
213 lines (188 loc) · 6.43 KB
/
Copy pathTaskDashboard.tsx
File metadata and controls
213 lines (188 loc) · 6.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
import { memo, useState, useCallback, useMemo } from "react"
import { ChevronDown, ChevronRight, GitBranch } from "lucide-react"
import type { ModeConfig } from "@roo-code/types"
import { getAllModes } from "@roo/modes"
import { cn } from "@src/lib/utils"
import { vscode } from "@src/utils/vscode"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import type { TaskTreeNode } from "./useTaskTree"
import { useTaskTree } from "./useTaskTree"
/**
* Status badge colors for task states.
*/
const statusConfig: Record<string, { label: string; className: string }> = {
active: { label: "Active", className: "bg-vscode-charts-green text-white" },
delegated: { label: "Delegated", className: "bg-vscode-charts-blue text-white" },
completed: {
label: "Completed",
className: "bg-vscode-descriptionForeground/30 text-vscode-descriptionForeground",
},
}
interface TaskNodeRowProps {
node: TaskTreeNode
depth: number
currentTaskId?: string
modeMap: Map<string, ModeConfig>
}
/**
* A single row in the task tree, showing mode name, status badge,
* and active indicator. Supports click-to-navigate.
*/
const TaskNodeRow = memo(({ node, depth, currentTaskId, modeMap }: TaskNodeRowProps) => {
const { item, children } = node
const hasChildren = children.length > 0
const [isNodeExpanded, setIsNodeExpanded] = useState(true)
const isCurrentTask = item.id === currentTaskId
const modeConfig = item.mode ? modeMap.get(item.mode) : undefined
const modeName = modeConfig?.name ?? item.mode ?? "Unknown"
const status = item.status ?? "active"
const statusInfo = statusConfig[status] ?? statusConfig.active
const handleClick = useCallback(() => {
vscode.postMessage({ type: "showTaskWithId", text: item.id })
}, [item.id])
const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
handleClick()
}
},
[handleClick],
)
const toggleNodeExpanded = useCallback((e: React.MouseEvent) => {
e.stopPropagation()
setIsNodeExpanded((prev) => !prev)
}, [])
// Truncate task description for display
const taskSummary = item.task.length > 60 ? item.task.slice(0, 57) + "..." : item.task
return (
<div data-testid={`task-node-${item.id}`}>
<div
className={cn(
"group flex items-center gap-1 py-1.5 px-2 cursor-pointer rounded-sm transition-colors",
"hover:bg-vscode-list-hoverBackground",
isCurrentTask &&
"bg-vscode-list-activeSelectionBackground/20 border-l-2 border-vscode-charts-green",
!isCurrentTask && "border-l-2 border-transparent",
)}
style={{ paddingLeft: `${depth * 16 + 8}px` }}
onClick={handleClick}
role="button"
tabIndex={0}
onKeyDown={handleKeyDown}>
{/* Expand/collapse toggle for nodes with children */}
{hasChildren ? (
<button
className="shrink-0 p-0 bg-transparent border-none cursor-pointer text-vscode-descriptionForeground hover:text-vscode-foreground flex items-center"
onClick={toggleNodeExpanded}
data-testid={`task-node-toggle-${item.id}`}
aria-label={isNodeExpanded ? "Collapse" : "Expand"}>
{isNodeExpanded ? <ChevronDown className="size-3" /> : <ChevronRight className="size-3" />}
</button>
) : (
<span className="shrink-0 size-3" />
)}
{/* Mode icon/indicator */}
<span
className={cn(
"shrink-0 size-2 rounded-full",
status === "active" && "bg-vscode-charts-green",
status === "delegated" && "bg-vscode-charts-blue",
status === "completed" && "bg-vscode-descriptionForeground/50",
)}
/>
{/* Mode name */}
<span
className={cn(
"text-xs font-medium shrink-0",
isCurrentTask ? "text-vscode-foreground" : "text-vscode-descriptionForeground",
)}>
{modeName}
</span>
{/* Status badge */}
<span
className={cn(
"text-[10px] px-1.5 py-0.5 rounded-full leading-none shrink-0",
statusInfo.className,
)}>
{statusInfo.label}
</span>
{/* Task summary (truncated) */}
<span className="text-xs text-vscode-descriptionForeground truncate min-w-0" title={item.task}>
{taskSummary}
</span>
</div>
{/* Render children (collapsible) */}
{hasChildren && isNodeExpanded && (
<div data-testid={`task-node-children-${item.id}`}>
{children.map((child) => (
<TaskNodeRow
key={child.item.id}
node={child}
depth={depth + 1}
currentTaskId={currentTaskId}
modeMap={modeMap}
/>
))}
</div>
)}
</div>
)
})
TaskNodeRow.displayName = "TaskNodeRow"
/**
* The Task Coordination Dashboard component.
*
* Displays a collapsible tree view of the current delegation session,
* showing each task's mode, status, and delegation relationships.
* Only visible when the current task is part of a multi-task delegation hierarchy.
*/
const TaskDashboard = () => {
const { taskHistory, currentTaskItem, currentTaskId, customModes } = useExtensionState()
const { rootNode, hasDelegationHierarchy, taskCount } = useTaskTree(taskHistory, currentTaskItem)
const [isExpanded, setIsExpanded] = useState(true)
// Build a mode lookup map
const modeMap = useMemo(() => {
const allModes = getAllModes(customModes)
const map = new Map<string, ModeConfig>()
for (const mode of allModes) {
map.set(mode.slug, mode)
}
return map
}, [customModes])
const toggleExpanded = useCallback(() => {
setIsExpanded((prev) => !prev)
}, [])
// Don't render if there's no delegation hierarchy
if (!hasDelegationHierarchy || !rootNode) {
return null
}
return (
<div
data-testid="task-dashboard"
className="border-b border-vscode-panel-border bg-vscode-sideBar-background/50">
{/* Header */}
<button
className={cn(
"w-full flex items-center gap-2 px-3 py-2 text-xs font-medium",
"text-vscode-descriptionForeground hover:text-vscode-foreground",
"transition-colors cursor-pointer select-none",
)}
onClick={toggleExpanded}
data-testid="task-dashboard-toggle">
{isExpanded ? <ChevronDown className="size-3.5" /> : <ChevronRight className="size-3.5" />}
<GitBranch className="size-3.5" />
<span>
Task Delegation ({taskCount} {taskCount === 1 ? "task" : "tasks"})
</span>
</button>
{/* Tree content */}
{isExpanded && (
<div className="pb-2" data-testid="task-dashboard-content">
<TaskNodeRow node={rootNode} depth={0} currentTaskId={currentTaskId} modeMap={modeMap} />
</div>
)}
</div>
)
}
export default memo(TaskDashboard)