@parhelia/core 0.1.12241 → 0.1.12259

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.
Files changed (137) hide show
  1. package/dist/agents-view/AgentsWorkspaceView.js +9 -1
  2. package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
  3. package/dist/components/MarkdownDisplay.d.ts +10 -0
  4. package/dist/components/MarkdownDisplay.js +197 -0
  5. package/dist/components/MarkdownDisplay.js.map +1 -0
  6. package/dist/config/config.js +4 -1
  7. package/dist/config/config.js.map +1 -1
  8. package/dist/config/notificationRoutes.d.ts +2 -0
  9. package/dist/config/notificationRoutes.js +195 -0
  10. package/dist/config/notificationRoutes.js.map +1 -0
  11. package/dist/config/types.d.ts +7 -0
  12. package/dist/config/types.js.map +1 -1
  13. package/dist/editor/ContentTree.d.ts +2 -1
  14. package/dist/editor/ContentTree.js +11 -3
  15. package/dist/editor/ContentTree.js.map +1 -1
  16. package/dist/editor/Editor.js +12 -3
  17. package/dist/editor/Editor.js.map +1 -1
  18. package/dist/editor/GlobalMenuBar.js +29 -1
  19. package/dist/editor/GlobalMenuBar.js.map +1 -1
  20. package/dist/editor/ai/AgentTerminal.d.ts +9 -1
  21. package/dist/editor/ai/AgentTerminal.js +233 -53
  22. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  23. package/dist/editor/ai/dialogs/AgentDialogHandler.js +5 -0
  24. package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
  25. package/dist/editor/ai/dialogs/QuestionnaireInline.d.ts +3 -1
  26. package/dist/editor/ai/dialogs/QuestionnaireInline.js +5 -5
  27. package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
  28. package/dist/editor/client/EditorShell.js +5 -1
  29. package/dist/editor/client/EditorShell.js.map +1 -1
  30. package/dist/editor/client/editContext.d.ts +4 -1
  31. package/dist/editor/client/editContext.js.map +1 -1
  32. package/dist/editor/client/hooks/useEditorWebSocket.js +36 -4
  33. package/dist/editor/client/hooks/useEditorWebSocket.js.map +1 -1
  34. package/dist/editor/client/hooks/useSocketMessageHandler.js +2 -0
  35. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  36. package/dist/editor/client/operations.d.ts +2 -1
  37. package/dist/editor/client/operations.js +8 -0
  38. package/dist/editor/client/operations.js.map +1 -1
  39. package/dist/editor/commands/itemCommands.d.ts +1 -0
  40. package/dist/editor/commands/itemCommands.js +23 -1
  41. package/dist/editor/commands/itemCommands.js.map +1 -1
  42. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +2 -134
  43. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
  44. package/dist/editor/notifications/NotificationCenter.d.ts +1 -0
  45. package/dist/editor/notifications/NotificationCenter.js +69 -0
  46. package/dist/editor/notifications/NotificationCenter.js.map +1 -0
  47. package/dist/editor/notifications/NotificationItem.d.ts +14 -0
  48. package/dist/editor/notifications/NotificationItem.js +40 -0
  49. package/dist/editor/notifications/NotificationItem.js.map +1 -0
  50. package/dist/editor/notifications/WatchButton.d.ts +7 -0
  51. package/dist/editor/notifications/WatchButton.js +191 -0
  52. package/dist/editor/notifications/WatchButton.js.map +1 -0
  53. package/dist/editor/notifications/notificationListState.d.ts +25 -0
  54. package/dist/editor/notifications/notificationListState.js +38 -0
  55. package/dist/editor/notifications/notificationListState.js.map +1 -0
  56. package/dist/editor/notifications/notificationRoutes.d.ts +15 -0
  57. package/dist/editor/notifications/notificationRoutes.js +58 -0
  58. package/dist/editor/notifications/notificationRoutes.js.map +1 -0
  59. package/dist/editor/notifications/useNotificationSubscriptions.d.ts +14 -0
  60. package/dist/editor/notifications/useNotificationSubscriptions.js +88 -0
  61. package/dist/editor/notifications/useNotificationSubscriptions.js.map +1 -0
  62. package/dist/editor/notifications/useNotifications.d.ts +28 -0
  63. package/dist/editor/notifications/useNotifications.js +166 -0
  64. package/dist/editor/notifications/useNotifications.js.map +1 -0
  65. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  66. package/dist/editor/reviews/CreateReviewConfirmStep.d.ts +2 -1
  67. package/dist/editor/reviews/CreateReviewConfirmStep.js +3 -3
  68. package/dist/editor/reviews/CreateReviewConfirmStep.js.map +1 -1
  69. package/dist/editor/reviews/CreateReviewDialog.js +24 -9
  70. package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
  71. package/dist/editor/reviews/ReviewDetail.js +2 -1
  72. package/dist/editor/reviews/ReviewDetail.js.map +1 -1
  73. package/dist/editor/services/agentService.d.ts +6 -0
  74. package/dist/editor/services/agentService.js +32 -10
  75. package/dist/editor/services/agentService.js.map +1 -1
  76. package/dist/editor/services/contentService.d.ts +1 -0
  77. package/dist/editor/services/contentService.js +3 -0
  78. package/dist/editor/services/contentService.js.map +1 -1
  79. package/dist/editor/services/notificationService.d.ts +73 -0
  80. package/dist/editor/services/notificationService.js +72 -0
  81. package/dist/editor/services/notificationService.js.map +1 -0
  82. package/dist/editor/ui/PublishItemDialog.d.ts +8 -0
  83. package/dist/editor/ui/PublishItemDialog.js +193 -0
  84. package/dist/editor/ui/PublishItemDialog.js.map +1 -0
  85. package/dist/editor/ui/PublishRestrictionsDialog.js +165 -75
  86. package/dist/editor/ui/PublishRestrictionsDialog.js.map +1 -1
  87. package/dist/editor/ui/TreeListSelector.d.ts +2 -1
  88. package/dist/editor/ui/TreeListSelector.js +2 -2
  89. package/dist/editor/ui/TreeListSelector.js.map +1 -1
  90. package/dist/revision.d.ts +2 -2
  91. package/dist/revision.js +2 -2
  92. package/dist/splash-screen/ParheliaAssistantChat.js +9 -9
  93. package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
  94. package/dist/task-board/TaskBoardWorkspace.js +716 -402
  95. package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
  96. package/dist/task-board/components/AssignAgentDialog.js +2 -3
  97. package/dist/task-board/components/AssignAgentDialog.js.map +1 -1
  98. package/dist/task-board/components/CreateProjectDialog.d.ts +4 -1
  99. package/dist/task-board/components/CreateProjectDialog.js +23 -8
  100. package/dist/task-board/components/CreateProjectDialog.js.map +1 -1
  101. package/dist/task-board/components/TaskAgentPanel.js +10 -6
  102. package/dist/task-board/components/TaskAgentPanel.js.map +1 -1
  103. package/dist/task-board/components/TaskBoardMyTasksSidebar.js +48 -2
  104. package/dist/task-board/components/TaskBoardMyTasksSidebar.js.map +1 -1
  105. package/dist/task-board/components/TaskBoardTitlebar.js +4 -4
  106. package/dist/task-board/components/TaskBoardTitlebar.js.map +1 -1
  107. package/dist/task-board/components/TaskDetailPanel.js +2 -1
  108. package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
  109. package/dist/task-board/components/TaskReviewActions.d.ts +8 -0
  110. package/dist/task-board/components/TaskReviewActions.js +110 -0
  111. package/dist/task-board/components/TaskReviewActions.js.map +1 -0
  112. package/dist/task-board/components/TaskRow.js +1 -1
  113. package/dist/task-board/components/TaskRow.js.map +1 -1
  114. package/dist/task-board/components/WizardCommunicationCenter.d.ts +30 -0
  115. package/dist/task-board/components/WizardCommunicationCenter.js +185 -0
  116. package/dist/task-board/components/WizardCommunicationCenter.js.map +1 -0
  117. package/dist/task-board/taskAgentConfig.d.ts +1 -3
  118. package/dist/task-board/taskAgentConfig.js +5 -15
  119. package/dist/task-board/taskAgentConfig.js.map +1 -1
  120. package/dist/task-board/taskAgentLink.js +4 -2
  121. package/dist/task-board/taskAgentLink.js.map +1 -1
  122. package/dist/task-board/taskBoardNavStore.d.ts +0 -1
  123. package/dist/task-board/taskBoardNavStore.js +0 -1
  124. package/dist/task-board/taskBoardNavStore.js.map +1 -1
  125. package/dist/task-board/taskExecutionStatus.js +13 -3
  126. package/dist/task-board/taskExecutionStatus.js.map +1 -1
  127. package/dist/task-board/types.d.ts +2 -0
  128. package/dist/task-board/views/KanbanView.js +3 -0
  129. package/dist/task-board/views/KanbanView.js.map +1 -1
  130. package/dist/task-board/views/ListView.js +3 -0
  131. package/dist/task-board/views/ListView.js.map +1 -1
  132. package/dist/task-board/views/WizardView.d.ts +9 -7
  133. package/dist/task-board/views/WizardView.js +164 -39
  134. package/dist/task-board/views/WizardView.js.map +1 -1
  135. package/dist/types.d.ts +19 -1
  136. package/package.json +1 -2
  137. package/styles.css +6 -10
@@ -2,8 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { toast } from "sonner";
4
4
  import { Splitter } from "../editor/ui/Splitter";
5
- import { deleteProject, getProjects, getTasks, getDependencies, getWizardGuidance, runOrchestrator, triggerPlanning, updateProject, updateTask, } from "./services/taskService";
6
- import { getActiveAgents, getAgent, startAgent, } from "../editor/services/agentService";
5
+ import { deleteProject, getProjects, getTasks, getDependencies, runOrchestrator, triggerPlanning, updateProject, updateTask, } from "./services/taskService";
6
+ import { getActiveAgents, getAgent, } from "../editor/services/agentService";
7
7
  import { loadAiProfiles } from "../editor/services/aiService";
8
8
  import { useEditContext } from "../editor/client/editContext";
9
9
  import { KanbanView } from "./views/KanbanView";
@@ -20,11 +20,13 @@ import { ProjectSettingsDialog } from "./components/ProjectSettingsDialog";
20
20
  import { EditorSlotContextProvider } from "../editor/views/editorSlotContext";
21
21
  import { SingleEditView } from "../editor/views/SingleEditView";
22
22
  import { Select } from "../components/ui/select";
23
- import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "../components/ui/dialog";
24
23
  import { getLinkedAgentId } from "./taskAgentLink";
25
24
  import { setTaskBoardNavState, resetTaskBoardNavState, } from "./taskBoardNavStore";
26
25
  import { flattenProjectsHierarchy } from "./utils/projectHierarchy";
27
26
  import { normalizeTaskStatus } from "./taskStatus";
27
+ import { getTaskExecutionDisplay, } from "./taskExecutionStatus";
28
+ import { WizardCommunicationCenter } from "./components/WizardCommunicationCenter";
29
+ import { Loader2 } from "lucide-react";
28
30
  const TASKBOARD_PROJECT_QUERY_KEY = "tbProjectId";
29
31
  const TASKBOARD_TASK_QUERY_KEY = "tbTaskId";
30
32
  const TASKBOARD_VIEW_QUERY_KEY = "tbView";
@@ -43,13 +45,25 @@ function getInitialWizardMode() {
43
45
  const value = getInitialQueryValue(TASKBOARD_WIZARD_QUERY_KEY)?.toLowerCase();
44
46
  return value === "true" || value === "1";
45
47
  }
48
+ function getTaskBoardStateFromUrl() {
49
+ const projectId = getInitialQueryValue(TASKBOARD_PROJECT_QUERY_KEY);
50
+ return {
51
+ projectId,
52
+ taskId: projectId ? getInitialQueryValue(TASKBOARD_TASK_QUERY_KEY) : null,
53
+ activeTab: getInitialViewTabIndex(),
54
+ isWizardMode: getInitialWizardMode(),
55
+ };
56
+ }
46
57
  function normalizeAgentStatus(status) {
47
58
  if (status === undefined || status === null)
48
59
  return undefined;
49
60
  if (typeof status === "number") {
50
61
  return status;
51
62
  }
52
- const normalized = String(status).trim().replace(/[^a-z0-9]/gi, "").toLowerCase();
63
+ const normalized = String(status)
64
+ .trim()
65
+ .replace(/[^a-z0-9]/gi, "")
66
+ .toLowerCase();
53
67
  switch (normalized) {
54
68
  case "running":
55
69
  return "running";
@@ -97,12 +111,71 @@ function normalizeAgentStatus(status) {
97
111
  return status;
98
112
  }
99
113
  }
114
+ function isClosedAgentStatus(status) {
115
+ if (status === undefined || status === null)
116
+ return false;
117
+ if (typeof status === "number")
118
+ return status === 5;
119
+ return (String(status)
120
+ .replace(/[^a-z0-9]/gi, "")
121
+ .toLowerCase() === "closed");
122
+ }
123
+ function getAgentStatusById(agentStatusesById, agentId) {
124
+ if (!agentId)
125
+ return undefined;
126
+ return agentStatusesById[agentId] ?? agentStatusesById[agentId.toLowerCase()];
127
+ }
128
+ function getTaskAgentStatus(task, agentStatusesById) {
129
+ const linkedAgentId = getLinkedAgentId(task ?? null);
130
+ const liveStatus = getAgentStatusById(agentStatusesById, linkedAgentId);
131
+ if (liveStatus !== undefined) {
132
+ return liveStatus;
133
+ }
134
+ return normalizeAgentStatus(task?.agentStatus);
135
+ }
136
+ function addAgentDisplayNames(taskList, agentProfileTitlesById) {
137
+ return taskList.map((task) => {
138
+ if (task.assigneeType !== "Agent" ||
139
+ !task.assigneeId ||
140
+ task.assigneeDisplayName) {
141
+ return task;
142
+ }
143
+ const profileTitle = agentProfileTitlesById[task.assigneeId.toLowerCase()];
144
+ if (!profileTitle)
145
+ return task;
146
+ return {
147
+ ...task,
148
+ assigneeDisplayName: profileTitle,
149
+ };
150
+ });
151
+ }
152
+ function buildTaskCounts(taskList) {
153
+ const counts = {
154
+ total: taskList.length,
155
+ todo: 0,
156
+ inProgress: 0,
157
+ review: 0,
158
+ done: 0,
159
+ };
160
+ for (const task of taskList) {
161
+ const normalizedStatus = normalizeTaskStatus(task.status, task.executionStatus);
162
+ if (normalizedStatus === "Todo")
163
+ counts.todo += 1;
164
+ else if (normalizedStatus === "InProgress")
165
+ counts.inProgress += 1;
166
+ else if (normalizedStatus === "Review")
167
+ counts.review += 1;
168
+ else if (normalizedStatus === "Done")
169
+ counts.done += 1;
170
+ }
171
+ return counts;
172
+ }
100
173
  // ── component ────────────────────────────────────────────────────────
101
174
  export function TaskBoardWorkspace() {
102
175
  const editContext = useEditContext();
103
176
  const isAdministrator = editContext?.user?.isAdministrator === true;
104
177
  const isMobile = editContext?.isMobile ?? false;
105
- const showEditorPanel = editContext?.showAgentsWorkspaceEditor ?? true;
178
+ const showEditorPanel = editContext?.showAgentsWorkspaceEditor ?? false;
106
179
  useEffect(() => {
107
180
  if (editContext?.isMobile && !showEditorPanel) {
108
181
  editContext?.setShowAgentsWorkspaceEditor(true);
@@ -113,6 +186,7 @@ export function TaskBoardWorkspace() {
113
186
  const [mobileActiveTab, setMobileActiveTab] = useState(0);
114
187
  const [selectedProjectId, setSelectedProjectId] = useState(() => getInitialQueryValue(TASKBOARD_PROJECT_QUERY_KEY));
115
188
  const [tasks, setTasks] = useState([]);
189
+ const [isTasksLoading, setIsTasksLoading] = useState(false);
116
190
  const [dependencies, setDependencies] = useState([]);
117
191
  const [selectedTaskId, setSelectedTaskId] = useState(() => getInitialQueryValue(TASKBOARD_TASK_QUERY_KEY));
118
192
  const [activeTab, setActiveTab] = useState(() => getInitialViewTabIndex());
@@ -125,25 +199,28 @@ export function TaskBoardWorkspace() {
125
199
  const [runningOrchestrator, setRunningOrchestrator] = useState(false);
126
200
  const [showAllProjects, setShowAllProjects] = useState(false);
127
201
  const [isProjectsLoading, setIsProjectsLoading] = useState(false);
202
+ const [projectViewLoadingId, setProjectViewLoadingId] = useState(null);
128
203
  const [agentStatusesById, setAgentStatusesById] = useState({});
129
204
  const [agentProfileTitlesById, setAgentProfileTitlesById] = useState({});
130
205
  const [subprojectTaskCounts, setSubprojectTaskCounts] = useState({});
206
+ const [subprojectTasksByProjectId, setSubprojectTasksByProjectId] = useState({});
131
207
  const [subprojectCountsLoading, setSubprojectCountsLoading] = useState(false);
132
208
  const [previewItemName, setPreviewItemName] = useState("");
133
209
  const [previewItemPath, setPreviewItemPath] = useState("");
134
210
  const [agentContextItems, setAgentContextItems] = useState([]);
135
211
  const [contextItemNamesByKey, setContextItemNamesByKey] = useState({});
136
212
  const [contextItemPathsByKey, setContextItemPathsByKey] = useState({});
137
- const [wizardGuidance, setWizardGuidance] = useState(null);
138
- const [wizardGuidanceLoading, setWizardGuidanceLoading] = useState(false);
139
- const [wizardActionInFlightId, setWizardActionInFlightId] = useState(null);
140
- const [wizardAgentDialogOpen, setWizardAgentDialogOpen] = useState(false);
213
+ const [wizardPinnedTaskId, setWizardPinnedTaskId] = useState(null);
214
+ const [wizardForceOverview, setWizardForceOverview] = useState(false);
141
215
  const wsRefreshTimeoutRef = useRef(null);
142
216
  const wsSubprojectRefreshTimeoutRef = useRef(null);
143
217
  const wsProjectRefreshTimeoutRef = useRef(null);
144
- const wizardGuidanceTimeoutRef = useRef(null);
145
- const wizardGuidanceRequestSeqRef = useRef(0);
218
+ const wizardCloseTransitionTimeoutRef = useRef(null);
146
219
  const projectsRequestCountRef = useRef(0);
220
+ const tasksRequestCountRef = useRef(0);
221
+ const latestTasksProjectIdRef = useRef(null);
222
+ const latestDependenciesProjectIdRef = useRef(null);
223
+ const latestSubprojectCountsProjectIdRef = useRef(null);
147
224
  const refreshProjectsRef = useRef(null);
148
225
  const refreshTasksRef = useRef(null);
149
226
  const refreshDependenciesRef = useRef(null);
@@ -155,6 +232,14 @@ export function TaskBoardWorkspace() {
155
232
  const autoSelectedProjectIdsRef = useRef(new Set());
156
233
  const taskBoardSubscribedAgentIdsRef = useRef(new Set());
157
234
  const taskBoardSocketVersionRef = useRef(null);
235
+ const syncTaskBoardStateFromUrl = useCallback(() => {
236
+ const nextState = getTaskBoardStateFromUrl();
237
+ setSelectedProjectId(nextState.projectId);
238
+ setSelectedTaskId(nextState.taskId);
239
+ setActiveTab(nextState.activeTab);
240
+ setIsWizardMode(nextState.isWizardMode);
241
+ setWizardForceOverview(false);
242
+ }, []);
158
243
  useEffect(() => {
159
244
  let cancelled = false;
160
245
  loadAiProfiles()
@@ -177,50 +262,35 @@ export function TaskBoardWorkspace() {
177
262
  cancelled = true;
178
263
  };
179
264
  }, []);
180
- const tasksWithDisplayAssignees = useMemo(() => {
181
- return tasks.map((task) => {
182
- if (task.assigneeType !== "Agent" ||
183
- !task.assigneeId ||
184
- task.assigneeDisplayName) {
185
- return task;
186
- }
187
- const profileTitle = agentProfileTitlesById[task.assigneeId.toLowerCase()];
188
- if (!profileTitle)
189
- return task;
190
- return {
191
- ...task,
192
- assigneeDisplayName: profileTitle,
193
- };
194
- });
195
- }, [tasks, agentProfileTitlesById]);
265
+ const tasksWithDisplayAssignees = useMemo(() => addAgentDisplayNames(tasks, agentProfileTitlesById), [tasks, agentProfileTitlesById]);
196
266
  const selectedProject = useMemo(() => projects.find((p) => p.project.projectId === selectedProjectId) ?? null, [projects, selectedProjectId]);
197
267
  const settingsProject = useMemo(() => projects.find((p) => p.project.projectId === settingsProjectId) ?? null, [projects, settingsProjectId]);
198
- const selectedProjectTaskCounts = useMemo(() => {
199
- const counts = {
200
- total: tasks.length,
201
- todo: 0,
202
- inProgress: 0,
203
- review: 0,
204
- done: 0,
205
- };
206
- for (const task of tasks) {
207
- const normalizedStatus = normalizeTaskStatus(task.status, task.executionStatus);
208
- if (normalizedStatus === "Todo")
209
- counts.todo += 1;
210
- else if (normalizedStatus === "InProgress")
211
- counts.inProgress += 1;
212
- else if (normalizedStatus === "Review")
213
- counts.review += 1;
214
- else if (normalizedStatus === "Done")
215
- counts.done += 1;
216
- }
217
- return counts;
218
- }, [tasks]);
268
+ const selectedProjectTaskCounts = useMemo(() => buildTaskCounts(tasks), [tasks]);
219
269
  const directSubprojects = useMemo(() => selectedProjectId
220
270
  ? projects
221
271
  .filter((project) => project.project.parentProjectId === selectedProjectId)
222
272
  .sort((a, b) => a.project.title.localeCompare(b.project.title))
223
273
  : [], [projects, selectedProjectId]);
274
+ const subprojectTasksWithDisplayAssignees = useMemo(() => {
275
+ const nextTasks = {};
276
+ for (const [projectId, taskList] of Object.entries(subprojectTasksByProjectId)) {
277
+ nextTasks[projectId] = addAgentDisplayNames(taskList, agentProfileTitlesById);
278
+ }
279
+ return nextTasks;
280
+ }, [subprojectTasksByProjectId, agentProfileTitlesById]);
281
+ const wizardSubprojectTaskLists = useMemo(() => directSubprojects.map((subproject) => ({
282
+ projectId: subproject.project.projectId,
283
+ title: subproject.project.title,
284
+ tasks: subprojectTasksWithDisplayAssignees[subproject.project.projectId] ||
285
+ [],
286
+ })), [directSubprojects, subprojectTasksWithDisplayAssignees]);
287
+ const wizardTasksWithDisplayAssignees = useMemo(() => [
288
+ ...tasksWithDisplayAssignees,
289
+ ...wizardSubprojectTaskLists.flatMap((subproject) => subproject.tasks),
290
+ ], [tasksWithDisplayAssignees, wizardSubprojectTaskLists]);
291
+ const taskSelectionUniverse = useMemo(() => isWizardMode
292
+ ? wizardTasksWithDisplayAssignees
293
+ : tasksWithDisplayAssignees, [isWizardMode, wizardTasksWithDisplayAssignees, tasksWithDisplayAssignees]);
224
294
  const selectedProjectCumulativeCostUsed = useMemo(() => {
225
295
  if (!selectedProjectId)
226
296
  return 0;
@@ -308,14 +378,28 @@ export function TaskBoardWorkspace() {
308
378
  setSelectedProjectId((previousProjectId) => {
309
379
  if (previousProjectId !== projectId) {
310
380
  setSelectedTaskId(null);
381
+ setWizardForceOverview(false);
311
382
  }
312
383
  return projectId;
313
384
  });
314
385
  }, []);
386
+ const clearSelectedProjectData = useCallback(() => {
387
+ setTasks([]);
388
+ setDependencies([]);
389
+ setSubprojectTaskCounts({});
390
+ setSubprojectTasksByProjectId({});
391
+ setAgentStatusesById({});
392
+ }, []);
315
393
  const isPlanning = selectedProject?.project.status === "Planning";
316
- const selectedProjectIsActive = selectedProject?.project.status === "Active";
317
- const selectedTask = useMemo(() => tasksWithDisplayAssignees.find((t) => t.taskId === selectedTaskId) ??
318
- null, [tasksWithDisplayAssignees, selectedTaskId]);
394
+ const selectedTask = useMemo(() => taskSelectionUniverse.find((t) => t.taskId === selectedTaskId) ?? null, [taskSelectionUniverse, selectedTaskId]);
395
+ const wizardPinnedTask = useMemo(() => wizardPinnedTaskId
396
+ ? (taskSelectionUniverse.find((task) => task.taskId === wizardPinnedTaskId) ?? null)
397
+ : null, [taskSelectionUniverse, wizardPinnedTaskId]);
398
+ const selectedTaskProject = useMemo(() => {
399
+ if (!selectedTask)
400
+ return selectedProject;
401
+ return (projects.find((project) => project.project.projectId === selectedTask.projectId) ?? selectedProject);
402
+ }, [projects, selectedProject, selectedTask]);
319
403
  const planningTask = useMemo(() => tasksWithDisplayAssignees.find((t) => String(t.taskType ?? "").toLowerCase() === "plan") ??
320
404
  tasksWithDisplayAssignees.find((t) => t.title === "Project Plan") ??
321
405
  null, [tasksWithDisplayAssignees]);
@@ -326,6 +410,8 @@ export function TaskBoardWorkspace() {
326
410
  const selectedTaskIsBlocked = useMemo(() => {
327
411
  if (!selectedTask)
328
412
  return false;
413
+ if (selectedTask.projectId !== selectedProjectId)
414
+ return false;
329
415
  const blockerIds = dependencies
330
416
  .filter((dependency) => dependency.taskId === selectedTask.taskId &&
331
417
  dependency.dependencyType === "BlockedBy")
@@ -337,38 +423,80 @@ export function TaskBoardWorkspace() {
337
423
  const blockerTask = taskById.get(blockerId);
338
424
  return !blockerTask || blockerTask.status !== "Done";
339
425
  });
340
- }, [selectedTask, dependencies, tasksWithDisplayAssignees]);
426
+ }, [
427
+ selectedTask,
428
+ selectedProjectId,
429
+ dependencies,
430
+ tasksWithDisplayAssignees,
431
+ ]);
341
432
  const selectedTaskHasAssignedAgentProfile = useMemo(() => {
342
433
  if (!selectedTask)
343
434
  return false;
344
435
  return selectedTask.assigneeType === "Agent" && !!selectedTask.assigneeId;
345
436
  }, [selectedTask]);
346
437
  const canRunAssignedAgent = useMemo(() => {
347
- return !selectedTaskIsBlocked && selectedProjectIsActive;
348
- }, [selectedTaskIsBlocked, selectedProjectIsActive]);
438
+ return !selectedTaskIsBlocked;
439
+ }, [selectedTaskIsBlocked]);
349
440
  const runAssignedAgentDisabledReason = useMemo(() => {
350
441
  if (selectedTaskIsBlocked) {
351
442
  return "This task is blocked by unfinished dependencies.";
352
443
  }
353
- if (!selectedProjectIsActive) {
354
- return "Project is not active. Set the project status to Active to run agents.";
355
- }
356
444
  return undefined;
357
- }, [selectedTaskIsBlocked, selectedProjectIsActive]);
445
+ }, [selectedTaskIsBlocked]);
358
446
  // ── current agent ID and panel mode for the right panel ──
359
447
  const currentAgentId = useMemo(() => {
360
448
  return getLinkedAgentId(selectedTask);
361
449
  }, [selectedTask]);
450
+ const selectedTaskStatus = useMemo(() => {
451
+ if (!selectedTask)
452
+ return null;
453
+ return normalizeTaskStatus(selectedTask.status, selectedTask.executionStatus);
454
+ }, [selectedTask]);
455
+ const selectedTaskHasResultData = useMemo(() => {
456
+ return !!selectedTask?.resultData?.trim();
457
+ }, [selectedTask?.resultData]);
458
+ const shouldHideAgentPanelForSelectedTask = useMemo(() => {
459
+ return (!!selectedTask &&
460
+ selectedTaskIsPlan &&
461
+ selectedTaskStatus === "Done" &&
462
+ selectedTaskHasResultData);
463
+ }, [
464
+ selectedTask,
465
+ selectedTaskIsPlan,
466
+ selectedTaskStatus,
467
+ selectedTaskHasResultData,
468
+ ]);
469
+ const currentAgentStatus = useMemo(() => {
470
+ return getTaskAgentStatus(selectedTask, agentStatusesById);
471
+ }, [selectedTask, agentStatusesById]);
472
+ const displayAgentId = useMemo(() => {
473
+ if (!currentAgentId)
474
+ return null;
475
+ if (isClosedAgentStatus(currentAgentStatus) &&
476
+ selectedTaskStatus !== "Done") {
477
+ return null;
478
+ }
479
+ return currentAgentId;
480
+ }, [currentAgentId, currentAgentStatus, selectedTaskStatus]);
362
481
  useEffect(() => {
363
482
  if (isMobile && selectedTaskId) {
364
483
  setMobileActiveTab(1); // Task tab
365
484
  }
366
485
  }, [selectedTaskId, isMobile]);
367
486
  useEffect(() => {
368
- if (isMobile && currentAgentId) {
487
+ if (isMobile && displayAgentId && !shouldHideAgentPanelForSelectedTask) {
369
488
  setMobileActiveTab(2); // Agent tab
370
489
  }
371
- }, [currentAgentId, isMobile]);
490
+ }, [displayAgentId, isMobile, shouldHideAgentPanelForSelectedTask]);
491
+ useEffect(() => {
492
+ if (!isMobile)
493
+ return;
494
+ if (!shouldHideAgentPanelForSelectedTask)
495
+ return;
496
+ if (mobileActiveTab !== 2)
497
+ return;
498
+ setMobileActiveTab(1);
499
+ }, [isMobile, mobileActiveTab, shouldHideAgentPanelForSelectedTask]);
372
500
  useEffect(() => {
373
501
  if (!showEditorPanel && mobileActiveTab === 3) {
374
502
  setMobileActiveTab(0);
@@ -382,10 +510,165 @@ export function TaskBoardWorkspace() {
382
510
  const agentPanelMode = useMemo(() => {
383
511
  if (!selectedTask)
384
512
  return "no-task-selected";
385
- if (currentAgentId)
513
+ if (displayAgentId)
386
514
  return "agent";
387
515
  return "no-agent";
388
- }, [selectedTask, currentAgentId]);
516
+ }, [selectedTask, displayAgentId]);
517
+ const wizardAttentionState = useMemo(() => {
518
+ const sortedTasks = [...wizardTasksWithDisplayAssignees].sort((a, b) => {
519
+ if (a.sortOrder !== b.sortOrder)
520
+ return a.sortOrder - b.sortOrder;
521
+ return a.createdAt.localeCompare(b.createdAt);
522
+ });
523
+ const taskById = new Map(sortedTasks.map((task) => [task.taskId, task]));
524
+ const blockersByTaskId = new Map();
525
+ for (const dependency of dependencies) {
526
+ if (dependency.dependencyType !== "BlockedBy")
527
+ continue;
528
+ const blockerIds = blockersByTaskId.get(dependency.taskId) || [];
529
+ blockerIds.push(dependency.dependsOnTaskId);
530
+ blockersByTaskId.set(dependency.taskId, blockerIds);
531
+ }
532
+ const openTasks = sortedTasks.filter((task) => normalizeTaskStatus(task.status, task.executionStatus) !== "Done");
533
+ const blockedTaskIds = new Set();
534
+ for (const task of openTasks) {
535
+ const blockerIds = blockersByTaskId.get(task.taskId) || [];
536
+ const isBlocked = blockerIds.some((blockerId) => {
537
+ const blockerTask = taskById.get(blockerId);
538
+ if (!blockerTask)
539
+ return true;
540
+ return (normalizeTaskStatus(blockerTask.status, blockerTask.executionStatus) !== "Done");
541
+ });
542
+ if (isBlocked)
543
+ blockedTaskIds.add(task.taskId);
544
+ }
545
+ let runningCount = 0;
546
+ let approvalCount = 0;
547
+ let attentionTask = null;
548
+ let attentionAgentId = null;
549
+ let attentionAgentStatus;
550
+ let attentionExecutionDisplay = null;
551
+ for (const task of openTasks) {
552
+ const normalizedTaskStatus = normalizeTaskStatus(task.status, task.executionStatus);
553
+ const agentId = getLinkedAgentId(task);
554
+ const agentStatus = getTaskAgentStatus(task, agentStatusesById);
555
+ const executionDisplay = getTaskExecutionDisplay(task.executionStatus, agentStatus, task.status);
556
+ const isBlocked = blockedTaskIds.has(task.taskId);
557
+ const normalizedAgentStatus = normalizeAgentStatus(agentStatus);
558
+ const isRunning = !isBlocked &&
559
+ (task.executionStatus === "Running" ||
560
+ task.executionStatus === "Queued" ||
561
+ normalizedAgentStatus === "running");
562
+ if (isRunning) {
563
+ runningCount += 1;
564
+ }
565
+ if (normalizedTaskStatus === "Review") {
566
+ approvalCount += 1;
567
+ }
568
+ if (!attentionTask && !isBlocked && executionDisplay?.needsAttention) {
569
+ attentionTask = task;
570
+ attentionAgentId = agentId;
571
+ attentionAgentStatus = agentStatus;
572
+ attentionExecutionDisplay = executionDisplay;
573
+ }
574
+ }
575
+ let summaryState = "idle";
576
+ if (openTasks.length === 0) {
577
+ summaryState = "complete";
578
+ }
579
+ else if (runningCount > 0) {
580
+ summaryState = "working";
581
+ }
582
+ else if (blockedTaskIds.size === openTasks.length) {
583
+ summaryState = "blocked";
584
+ }
585
+ return {
586
+ task: attentionTask,
587
+ agentId: attentionAgentId,
588
+ agentStatus: attentionAgentStatus,
589
+ executionDisplay: attentionExecutionDisplay,
590
+ summaryState,
591
+ stats: {
592
+ totalCount: sortedTasks.length,
593
+ completedCount: sortedTasks.length - openTasks.length,
594
+ approvalCount,
595
+ blockedCount: blockedTaskIds.size,
596
+ runningCount,
597
+ },
598
+ };
599
+ }, [wizardTasksWithDisplayAssignees, dependencies, agentStatusesById]);
600
+ const wizardDisplayedTask = wizardForceOverview
601
+ ? null
602
+ : selectedTask ?? wizardPinnedTask ?? wizardAttentionState.task;
603
+ const wizardDisplayedAgentId = wizardDisplayedTask
604
+ ? getLinkedAgentId(wizardDisplayedTask)
605
+ : null;
606
+ const wizardDisplayedAgentStatus = getAgentStatusById(agentStatusesById, wizardDisplayedAgentId);
607
+ const wizardDisplayedExecutionDisplay = wizardDisplayedTask
608
+ ? getTaskExecutionDisplay(wizardDisplayedTask.executionStatus, wizardDisplayedAgentStatus, wizardDisplayedTask.status)
609
+ : null;
610
+ const clearWizardCloseTransitionTimeout = useCallback(() => {
611
+ if (wizardCloseTransitionTimeoutRef.current !== null) {
612
+ window.clearTimeout(wizardCloseTransitionTimeoutRef.current);
613
+ wizardCloseTransitionTimeoutRef.current = null;
614
+ }
615
+ }, []);
616
+ useEffect(() => {
617
+ if (!isWizardMode) {
618
+ clearWizardCloseTransitionTimeout();
619
+ }
620
+ }, [isWizardMode, clearWizardCloseTransitionTimeout]);
621
+ useEffect(() => {
622
+ clearWizardCloseTransitionTimeout();
623
+ }, [selectedProjectId, clearWizardCloseTransitionTimeout]);
624
+ const findNextAttentionTaskId = useCallback((excludedTaskId) => {
625
+ const sortedTasks = [...wizardTasksWithDisplayAssignees].sort((a, b) => {
626
+ if (a.sortOrder !== b.sortOrder)
627
+ return a.sortOrder - b.sortOrder;
628
+ return a.createdAt.localeCompare(b.createdAt);
629
+ });
630
+ const taskById = new Map(sortedTasks.map((task) => [task.taskId, task]));
631
+ const blockersByTaskId = new Map();
632
+ for (const dependency of dependencies) {
633
+ if (dependency.dependencyType !== "BlockedBy")
634
+ continue;
635
+ const blockerIds = blockersByTaskId.get(dependency.taskId) || [];
636
+ blockerIds.push(dependency.dependsOnTaskId);
637
+ blockersByTaskId.set(dependency.taskId, blockerIds);
638
+ }
639
+ const openTasks = sortedTasks.filter((task) => {
640
+ if (excludedTaskId && task.taskId === excludedTaskId)
641
+ return false;
642
+ return (normalizeTaskStatus(task.status, task.executionStatus) !== "Done");
643
+ });
644
+ const blockedTaskIds = new Set();
645
+ for (const task of openTasks) {
646
+ const blockerIds = blockersByTaskId.get(task.taskId) || [];
647
+ const isBlocked = blockerIds.some((blockerId) => {
648
+ const blockerTask = taskById.get(blockerId);
649
+ if (!blockerTask)
650
+ return true;
651
+ return (normalizeTaskStatus(blockerTask.status, blockerTask.executionStatus) !== "Done");
652
+ });
653
+ if (isBlocked)
654
+ blockedTaskIds.add(task.taskId);
655
+ }
656
+ for (const task of openTasks) {
657
+ const isBlocked = blockedTaskIds.has(task.taskId);
658
+ const agentId = getLinkedAgentId(task);
659
+ const agentStatus = getTaskAgentStatus(task, agentStatusesById);
660
+ const executionDisplay = getTaskExecutionDisplay(task.executionStatus, agentStatus, task.status);
661
+ const normalizedAgentStatus = normalizeAgentStatus(agentStatus);
662
+ const isRunning = !isBlocked &&
663
+ (task.executionStatus === "Running" ||
664
+ task.executionStatus === "Queued" ||
665
+ normalizedAgentStatus === "running");
666
+ if (!isBlocked && executionDisplay?.needsAttention) {
667
+ return task.taskId;
668
+ }
669
+ }
670
+ return null;
671
+ }, [wizardTasksWithDisplayAssignees, dependencies, agentStatusesById]);
389
672
  // ── data fetching ──
390
673
  const refreshProjects = useCallback(async (options) => {
391
674
  projectsRequestCountRef.current += 1;
@@ -454,17 +737,44 @@ export function TaskBoardWorkspace() {
454
737
  window.history.replaceState(null, "", newUrl);
455
738
  }
456
739
  }, [selectedProjectId, selectedTaskId, activeTab, isWizardMode]);
740
+ useEffect(() => {
741
+ const handlePopState = () => {
742
+ syncTaskBoardStateFromUrl();
743
+ };
744
+ window.addEventListener("popstate", handlePopState);
745
+ return () => window.removeEventListener("popstate", handlePopState);
746
+ }, [syncTaskBoardStateFromUrl]);
457
747
  const refreshTasks = useCallback(async (projectId) => {
458
- const result = await getTasks(projectId);
459
- if (result.type !== "success") {
460
- toast.error(result.summary || "Failed to load tasks");
461
- return;
748
+ tasksRequestCountRef.current += 1;
749
+ latestTasksProjectIdRef.current = projectId;
750
+ const requestProjectId = projectId;
751
+ setIsTasksLoading(true);
752
+ try {
753
+ const result = await getTasks(projectId);
754
+ if (latestTasksProjectIdRef.current !== requestProjectId) {
755
+ return;
756
+ }
757
+ if (result.type !== "success") {
758
+ toast.error(result.summary || "Failed to load tasks");
759
+ return;
760
+ }
761
+ setTasks(result.data || []);
762
+ }
763
+ finally {
764
+ tasksRequestCountRef.current = Math.max(0, tasksRequestCountRef.current - 1);
765
+ if (tasksRequestCountRef.current === 0) {
766
+ setIsTasksLoading(false);
767
+ }
462
768
  }
463
- setTasks(result.data || []);
464
769
  }, []);
465
770
  const refreshSubprojectTaskCounts = useCallback(async () => {
771
+ const requestProjectId = selectedProjectId;
772
+ latestSubprojectCountsProjectIdRef.current = requestProjectId;
466
773
  if (directSubprojects.length === 0) {
467
- setSubprojectTaskCounts({});
774
+ if (latestSubprojectCountsProjectIdRef.current === requestProjectId) {
775
+ setSubprojectTaskCounts({});
776
+ setSubprojectTasksByProjectId({});
777
+ }
468
778
  return;
469
779
  }
470
780
  setSubprojectCountsLoading(true);
@@ -473,40 +783,33 @@ export function TaskBoardWorkspace() {
473
783
  const projectId = subproject.project.projectId;
474
784
  const response = await getTasks(projectId);
475
785
  if (response.type !== "success") {
476
- return [projectId, null];
786
+ return [projectId, null, null];
477
787
  }
478
788
  const tasksForProject = response.data || [];
479
- const counts = {
480
- total: tasksForProject.length,
481
- todo: 0,
482
- inProgress: 0,
483
- review: 0,
484
- done: 0,
485
- };
486
- for (const task of tasksForProject) {
487
- const normalizedStatus = normalizeTaskStatus(task.status, task.executionStatus);
488
- if (normalizedStatus === "Todo")
489
- counts.todo += 1;
490
- else if (normalizedStatus === "InProgress")
491
- counts.inProgress += 1;
492
- else if (normalizedStatus === "Review")
493
- counts.review += 1;
494
- else if (normalizedStatus === "Done")
495
- counts.done += 1;
496
- }
497
- return [projectId, counts];
789
+ return [
790
+ projectId,
791
+ buildTaskCounts(tasksForProject),
792
+ tasksForProject,
793
+ ];
498
794
  }));
795
+ if (latestSubprojectCountsProjectIdRef.current !== requestProjectId) {
796
+ return;
797
+ }
499
798
  const nextCounts = {};
500
- for (const [projectId, counts] of results) {
799
+ const nextTasksByProjectId = {};
800
+ for (const [projectId, counts, taskList] of results) {
501
801
  if (counts)
502
802
  nextCounts[projectId] = counts;
803
+ if (taskList)
804
+ nextTasksByProjectId[projectId] = taskList;
503
805
  }
504
806
  setSubprojectTaskCounts(nextCounts);
807
+ setSubprojectTasksByProjectId(isWizardMode ? nextTasksByProjectId : {});
505
808
  }
506
809
  finally {
507
810
  setSubprojectCountsLoading(false);
508
811
  }
509
- }, [directSubprojects]);
812
+ }, [directSubprojects, isWizardMode, selectedProjectId]);
510
813
  // If a project only has a single task, auto-select it once when loading.
511
814
  // Do not auto-reselect after the user explicitly closes task details.
512
815
  useEffect(() => {
@@ -524,47 +827,89 @@ export function TaskBoardWorkspace() {
524
827
  autoSelectedProjectIdsRef.current.add(selectedProjectId);
525
828
  setSelectedTaskId(firstTask.taskId);
526
829
  }, [selectedProjectId, selectedTaskId, tasks]);
527
- const refreshDependencies = useCallback(async (projectId) => {
528
- const result = await getDependencies(projectId);
529
- if (result.type !== "success") {
530
- toast.error(result.summary || "Failed to load dependencies");
830
+ useEffect(() => {
831
+ if (!wizardPinnedTaskId)
832
+ return;
833
+ if (taskSelectionUniverse.some((task) => task.taskId === wizardPinnedTaskId)) {
531
834
  return;
532
835
  }
533
- setDependencies(result.data || []);
534
- }, []);
535
- const refreshWizardGuidance = useCallback(async (projectId, taskId) => {
536
- const requestSeq = ++wizardGuidanceRequestSeqRef.current;
537
- setWizardGuidanceLoading(true);
538
- const result = await getWizardGuidance(projectId, taskId || undefined);
539
- if (requestSeq !== wizardGuidanceRequestSeqRef.current) {
836
+ setWizardPinnedTaskId(null);
837
+ }, [taskSelectionUniverse, wizardPinnedTaskId]);
838
+ useEffect(() => {
839
+ if (!isWizardMode)
840
+ return;
841
+ if (wizardForceOverview)
842
+ return;
843
+ if (selectedTaskId || wizardPinnedTaskId)
844
+ return;
845
+ const attentionTaskId = wizardAttentionState.task?.taskId;
846
+ if (!attentionTaskId)
847
+ return;
848
+ setWizardPinnedTaskId(attentionTaskId);
849
+ }, [
850
+ isWizardMode,
851
+ selectedTaskId,
852
+ wizardPinnedTaskId,
853
+ wizardForceOverview,
854
+ wizardAttentionState.task?.taskId,
855
+ ]);
856
+ useEffect(() => {
857
+ if (isWizardMode)
858
+ return;
859
+ if (!wizardPinnedTaskId)
540
860
  return;
861
+ setWizardPinnedTaskId(null);
862
+ }, [isWizardMode, wizardPinnedTaskId]);
863
+ useEffect(() => {
864
+ if (!isWizardMode && wizardForceOverview) {
865
+ setWizardForceOverview(false);
541
866
  }
542
- if (result.type !== "success") {
543
- setWizardGuidance({
544
- status: "unavailable",
545
- generatedAt: new Date().toISOString(),
546
- error: result.summary || "Failed to load wizard guidance",
547
- guidance: null,
548
- });
549
- setWizardGuidanceLoading(false);
867
+ }, [isWizardMode, wizardForceOverview]);
868
+ useEffect(() => {
869
+ setWizardPinnedTaskId(null);
870
+ setWizardForceOverview(false);
871
+ }, [selectedProjectId]);
872
+ const refreshDependencies = useCallback(async (projectId) => {
873
+ latestDependenciesProjectIdRef.current = projectId;
874
+ const requestProjectId = projectId;
875
+ const visibleProjectIds = Array.from(new Set([
876
+ projectId,
877
+ ...directSubprojects.map((subproject) => subproject.project.projectId),
878
+ ]));
879
+ const results = await Promise.all(visibleProjectIds.map(async (visibleProjectId) => {
880
+ const result = await getDependencies(visibleProjectId);
881
+ return [visibleProjectId, result];
882
+ }));
883
+ const primaryResult = results.find(([visibleProjectId]) => visibleProjectId === projectId)?.[1] ?? null;
884
+ if (latestDependenciesProjectIdRef.current !== requestProjectId) {
550
885
  return;
551
886
  }
552
- setWizardGuidance(result.data || {
553
- status: "unavailable",
554
- generatedAt: new Date().toISOString(),
555
- error: "Wizard guidance is unavailable",
556
- guidance: null,
557
- });
558
- setWizardGuidanceLoading(false);
559
- }, []);
560
- const scheduleWizardGuidanceRefresh = useCallback((projectId, taskId) => {
561
- if (wizardGuidanceTimeoutRef.current !== null) {
562
- window.clearTimeout(wizardGuidanceTimeoutRef.current);
563
- }
564
- wizardGuidanceTimeoutRef.current = window.setTimeout(() => {
565
- void refreshWizardGuidance(projectId, taskId);
566
- }, 450);
567
- }, [refreshWizardGuidance]);
887
+ if (!primaryResult || primaryResult.type !== "success") {
888
+ toast.error(primaryResult?.summary || "Failed to load dependencies");
889
+ return;
890
+ }
891
+ const mergedDependencies = new Map();
892
+ for (const [, result] of results) {
893
+ if (result.type !== "success")
894
+ continue;
895
+ for (const dependency of result.data || []) {
896
+ mergedDependencies.set(dependency.dependencyId, dependency);
897
+ }
898
+ }
899
+ setDependencies(Array.from(mergedDependencies.values()));
900
+ }, [directSubprojects]);
901
+ const refreshVisibleTaskData = useCallback(async () => {
902
+ if (!selectedProjectId)
903
+ return;
904
+ await refreshTasks(selectedProjectId);
905
+ await refreshDependencies(selectedProjectId);
906
+ await refreshSubprojectTaskCounts();
907
+ }, [
908
+ selectedProjectId,
909
+ refreshTasks,
910
+ refreshDependencies,
911
+ refreshSubprojectTaskCounts,
912
+ ]);
568
913
  const refreshAgentStatuses = useCallback(async (taskList) => {
569
914
  const agentIds = Array.from(new Set(taskList
570
915
  .map((task) => getLinkedAgentId(task))
@@ -585,10 +930,33 @@ export function TaskBoardWorkspace() {
585
930
  for (const agent of response.agents || []) {
586
931
  const normalizedAgentId = agent.id.toLowerCase();
587
932
  if (wanted.has(normalizedAgentId)) {
588
- apiStatuses[normalizedAgentId] = normalizeAgentStatus(agent.status) ?? agent.status;
933
+ apiStatuses[normalizedAgentId] =
934
+ normalizeAgentStatus(agent.status) ?? agent.status;
589
935
  }
590
936
  }
591
- setAgentStatusesById((prev) => ({ ...prev, ...apiStatuses }));
937
+ setAgentStatusesById((prev) => {
938
+ const merged = { ...prev };
939
+ for (const [id, apiStatus] of Object.entries(apiStatuses)) {
940
+ const existing = merged[id];
941
+ const existingNormalized = existing !== undefined ? normalizeAgentStatus(existing) : undefined;
942
+ if (existingNormalized === "waitingForInput" ||
943
+ existingNormalized === "waitingForApproval" ||
944
+ existingNormalized === "error" ||
945
+ existingNormalized === "costLimitReached") {
946
+ const apiNormalized = normalizeAgentStatus(apiStatus);
947
+ if (apiNormalized !== "waitingForInput" &&
948
+ apiNormalized !== "waitingForApproval" &&
949
+ apiNormalized !== "error" &&
950
+ apiNormalized !== "costLimitReached" &&
951
+ apiNormalized !== "completed" &&
952
+ apiNormalized !== "closed") {
953
+ continue;
954
+ }
955
+ }
956
+ merged[id] = apiStatus;
957
+ }
958
+ return merged;
959
+ });
592
960
  }
593
961
  catch {
594
962
  // best-effort only
@@ -602,7 +970,7 @@ export function TaskBoardWorkspace() {
602
970
  refreshSubprojectTaskCountsRef.current = refreshSubprojectTaskCounts;
603
971
  selectedProjectIdRef.current = selectedProjectId;
604
972
  directSubprojectIdsRef.current = new Set(directSubprojects.map((subproject) => subproject.project.projectId));
605
- tasksRef.current = tasks;
973
+ tasksRef.current = taskSelectionUniverse;
606
974
  }, [
607
975
  refreshProjects,
608
976
  refreshTasks,
@@ -610,7 +978,7 @@ export function TaskBoardWorkspace() {
610
978
  refreshSubprojectTaskCounts,
611
979
  selectedProjectId,
612
980
  directSubprojects,
613
- tasks,
981
+ taskSelectionUniverse,
614
982
  ]);
615
983
  // Ensure task-board receives live agent status updates (including WaitingForInput)
616
984
  // without requiring the terminal panel to be opened. Re-run on socket reconnect
@@ -623,7 +991,7 @@ export function TaskBoardWorkspace() {
623
991
  }
624
992
  if (!selectedProjectId)
625
993
  return;
626
- const desiredAgentIds = new Set(tasks
994
+ const desiredAgentIds = new Set(taskSelectionUniverse
627
995
  .map((task) => getLinkedAgentId(task))
628
996
  .filter((id) => typeof id === "string" && id.length > 0)
629
997
  .map((id) => id.toLowerCase()));
@@ -646,27 +1014,10 @@ export function TaskBoardWorkspace() {
646
1014
  return () => {
647
1015
  window.clearTimeout(subscribeTimeout);
648
1016
  };
649
- }, [selectedProjectId, tasks, editContext?.socketConnectionVersion]);
650
- useEffect(() => {
651
- if (!selectedProjectId) {
652
- setWizardGuidance(null);
653
- setWizardGuidanceLoading(false);
654
- return;
655
- }
656
- if (!isWizardMode) {
657
- setWizardGuidanceLoading(false);
658
- return;
659
- }
660
- // Recompute guidance when project/task state changes, but not on task selection alone.
661
- // Selecting a recommended task should not immediately trigger another guidance run.
662
- scheduleWizardGuidanceRefresh(selectedProjectId, selectedTaskId);
663
1017
  }, [
664
1018
  selectedProjectId,
665
- isWizardMode,
666
- tasks,
667
- dependencies,
668
- agentStatusesById,
669
- scheduleWizardGuidanceRefresh,
1019
+ taskSelectionUniverse,
1020
+ editContext?.socketConnectionVersion,
670
1021
  ]);
671
1022
  const handleRunOrchestrator = useCallback(async (options) => {
672
1023
  if (!selectedProjectId)
@@ -702,28 +1053,27 @@ export function TaskBoardWorkspace() {
702
1053
  }
703
1054
  }
704
1055
  }
705
- // Refresh tasks after orchestrator runs
706
- await refreshTasks(selectedProjectId);
707
- await refreshDependencies(selectedProjectId);
1056
+ await refreshVisibleTaskData();
708
1057
  }
709
1058
  finally {
710
1059
  setRunningOrchestrator(false);
711
1060
  }
712
- }, [selectedProjectId, refreshTasks, refreshDependencies]);
1061
+ }, [selectedProjectId, refreshVisibleTaskData]);
713
1062
  const handleRunSelectedTaskAgent = useCallback(async () => {
714
- if (!selectedProjectId)
715
- return;
716
1063
  const task = tasksRef.current.find((t) => t.taskId === selectedTaskId &&
717
1064
  t.assigneeType === "Agent" &&
718
1065
  !!t.assigneeId);
719
1066
  if (!task)
720
1067
  return;
1068
+ const taskProjectId = task.projectId || selectedProjectId;
1069
+ if (!taskProjectId)
1070
+ return;
721
1071
  setRunningOrchestrator(true);
722
1072
  try {
723
1073
  if (task.executionStatus === "Idle") {
724
1074
  await updateTask({ taskId: task.taskId, executionStatus: "Queued" });
725
1075
  }
726
- const result = await runOrchestrator(selectedProjectId);
1076
+ const result = await runOrchestrator(taskProjectId);
727
1077
  if (result.type !== "success") {
728
1078
  toast.error(result.summary || "Failed to launch agent");
729
1079
  return;
@@ -741,13 +1091,80 @@ export function TaskBoardWorkspace() {
741
1091
  toast.info("Could not launch agent. The task may be blocked or already running.");
742
1092
  }
743
1093
  }
744
- await refreshTasks(selectedProjectId);
745
- await refreshDependencies(selectedProjectId);
1094
+ await refreshVisibleTaskData();
1095
+ }
1096
+ finally {
1097
+ setRunningOrchestrator(false);
1098
+ }
1099
+ }, [selectedProjectId, selectedTaskId, refreshVisibleTaskData]);
1100
+ const handleReopenSelectedTask = useCallback(async () => {
1101
+ if (!selectedTask)
1102
+ return;
1103
+ const taskProjectId = selectedTask.projectId || selectedProjectId;
1104
+ const reopenResult = await updateTask({
1105
+ taskId: selectedTask.taskId,
1106
+ status: "Todo",
1107
+ executionStatus: "Idle",
1108
+ });
1109
+ if (reopenResult.type !== "success") {
1110
+ toast.error(reopenResult.summary || "Failed to reopen task");
1111
+ return;
1112
+ }
1113
+ if (selectedTaskIsPlan) {
1114
+ toast.success("Task reopened");
1115
+ await refreshVisibleTaskData();
1116
+ return;
1117
+ }
1118
+ if (!selectedTaskHasAssignedAgentProfile ||
1119
+ !canRunAssignedAgent ||
1120
+ !taskProjectId) {
1121
+ toast.success("Task reopened");
1122
+ await refreshVisibleTaskData();
1123
+ return;
1124
+ }
1125
+ setRunningOrchestrator(true);
1126
+ try {
1127
+ const queueResult = await updateTask({
1128
+ taskId: selectedTask.taskId,
1129
+ executionStatus: "Queued",
1130
+ });
1131
+ if (queueResult.type !== "success") {
1132
+ toast.error(queueResult.summary || "Failed to queue agent");
1133
+ await refreshVisibleTaskData();
1134
+ return;
1135
+ }
1136
+ const result = await runOrchestrator(taskProjectId);
1137
+ if (result.type !== "success") {
1138
+ toast.error(result.summary || "Failed to launch agent");
1139
+ await refreshVisibleTaskData();
1140
+ return;
1141
+ }
1142
+ const data = result.data;
1143
+ if (data?.errors?.length) {
1144
+ toast.error(data.errors.join(", "));
1145
+ }
1146
+ else {
1147
+ const launched = data?.tasksLaunched?.length || 0;
1148
+ if (launched > 0) {
1149
+ toast.success("Task reopened and agent launched.");
1150
+ }
1151
+ else {
1152
+ toast.info("Task reopened, but the agent could not be launched. The task may be blocked or already running.");
1153
+ }
1154
+ }
1155
+ await refreshVisibleTaskData();
746
1156
  }
747
1157
  finally {
748
1158
  setRunningOrchestrator(false);
749
1159
  }
750
- }, [selectedProjectId, selectedTaskId, refreshTasks, refreshDependencies]);
1160
+ }, [
1161
+ selectedTask,
1162
+ selectedProjectId,
1163
+ selectedTaskIsPlan,
1164
+ selectedTaskHasAssignedAgentProfile,
1165
+ canRunAssignedAgent,
1166
+ refreshVisibleTaskData,
1167
+ ]);
751
1168
  const handleStartPlanning = useCallback(async () => {
752
1169
  if (!selectedProjectId)
753
1170
  return;
@@ -770,130 +1187,6 @@ export function TaskBoardWorkspace() {
770
1187
  toast.error("Failed to start planning");
771
1188
  }
772
1189
  }, [selectedProjectId, refreshTasks, refreshDependencies, tasks]);
773
- const handleWizardAction = useCallback(async (action) => {
774
- if (!action?.type)
775
- return;
776
- const actionType = String(action.type).trim().toLowerCase();
777
- const resolvedTaskId = action.taskId || wizardGuidance?.guidance?.targetTaskId || null;
778
- const resolvedTask = resolvedTaskId
779
- ? tasks.find((task) => task.taskId === resolvedTaskId) || null
780
- : null;
781
- const resolvedTaskIsPlan = !!resolvedTask &&
782
- String(resolvedTask.taskType || "").toLowerCase() === "plan";
783
- const resolvedAgentId = action.agentId ||
784
- wizardGuidance?.guidance?.targetAgentId ||
785
- getLinkedAgentId(resolvedTask) ||
786
- null;
787
- const actionId = action.actionId ||
788
- `${action.type}-${action.taskId || "none"}-${action.agentId || "none"}`;
789
- if (resolvedTaskId) {
790
- setSelectedTaskId(resolvedTaskId);
791
- }
792
- if (actionType === "open_task" ||
793
- actionType === "open-task" ||
794
- actionType === "open") {
795
- setWizardAgentDialogOpen(true);
796
- return;
797
- }
798
- if (actionType === "focus_agent" || actionType === "focus-agent") {
799
- setWizardAgentDialogOpen(true);
800
- return;
801
- }
802
- if (actionType === "copy_reply" || actionType === "copy-reply") {
803
- const text = action.promptText || wizardGuidance?.guidance?.suggestedUserReply;
804
- if (!text) {
805
- toast.error("No suggested reply available to copy");
806
- return;
807
- }
808
- try {
809
- await navigator.clipboard.writeText(text);
810
- toast.success("Suggested reply copied");
811
- }
812
- catch {
813
- toast.error("Failed to copy suggested reply");
814
- }
815
- return;
816
- }
817
- if (actionType !== "send_prompt_to_agent" &&
818
- actionType !== "send-prompt-to-agent") {
819
- return;
820
- }
821
- if (!editContext?.sessionId) {
822
- toast.error("Cannot send prompt: missing editor session");
823
- return;
824
- }
825
- if (!resolvedAgentId && resolvedTaskIsPlan) {
826
- await handleStartPlanning();
827
- setWizardAgentDialogOpen(true);
828
- return;
829
- }
830
- if (!resolvedAgentId) {
831
- toast.error("Cannot send prompt: missing target agent");
832
- return;
833
- }
834
- const promptText = (action.promptText || "").trim();
835
- if (!promptText) {
836
- toast.error("Cannot send prompt: prompt text is empty");
837
- return;
838
- }
839
- setWizardActionInFlightId(actionId);
840
- try {
841
- let agent = null;
842
- try {
843
- agent = await getAgent(resolvedAgentId);
844
- }
845
- catch {
846
- if (resolvedTaskIsPlan) {
847
- await handleStartPlanning();
848
- setWizardAgentDialogOpen(true);
849
- setWizardActionInFlightId(null);
850
- return;
851
- }
852
- throw new Error("Target agent was not found");
853
- }
854
- const profileId = agent?.profileId ||
855
- (resolvedTaskId && selectedTask?.taskId === resolvedTaskId
856
- ? selectedTask?.assigneeId
857
- : undefined) ||
858
- "";
859
- if (!profileId) {
860
- toast.error("Cannot send prompt: target agent profile is missing");
861
- return;
862
- }
863
- await startAgent({
864
- agentId: resolvedAgentId,
865
- message: promptText,
866
- sessionId: editContext.sessionId,
867
- profileId,
868
- mode: "autonomous",
869
- });
870
- toast.success("Prompt sent to task agent");
871
- setWizardAgentDialogOpen(true);
872
- if (selectedProjectId) {
873
- await refreshTasks(selectedProjectId);
874
- await refreshDependencies(selectedProjectId);
875
- await refreshWizardGuidance(selectedProjectId, selectedTaskId);
876
- }
877
- }
878
- catch (error) {
879
- const message = error instanceof Error ? error.message : "Failed to send prompt";
880
- toast.error(message);
881
- }
882
- finally {
883
- setWizardActionInFlightId(null);
884
- }
885
- }, [
886
- editContext?.sessionId,
887
- selectedTask,
888
- tasks,
889
- wizardGuidance,
890
- handleStartPlanning,
891
- selectedProjectId,
892
- selectedTaskId,
893
- refreshTasks,
894
- refreshDependencies,
895
- refreshWizardGuidance,
896
- ]);
897
1190
  const handleProjectStatusChange = useCallback(async (status) => {
898
1191
  if (!selectedProject)
899
1192
  return;
@@ -993,12 +1286,6 @@ export function TaskBoardWorkspace() {
993
1286
  onToggleWizardMode: (enabled) => setIsWizardMode(enabled),
994
1287
  onCreateTask: () => setCreateDialogOpen(true),
995
1288
  onRunOrchestrator: handleRunOrchestrator,
996
- onRefresh: () => {
997
- if (selectedProjectId) {
998
- void refreshTasks(selectedProjectId);
999
- void refreshDependencies(selectedProjectId);
1000
- }
1001
- },
1002
1289
  });
1003
1290
  }, [
1004
1291
  selectedProject?.project.title,
@@ -1020,65 +1307,43 @@ export function TaskBoardWorkspace() {
1020
1307
  handleSelectMyTask,
1021
1308
  handleSelectProject,
1022
1309
  handleRunOrchestrator,
1023
- refreshTasks,
1024
- refreshDependencies,
1025
1310
  ]);
1026
1311
  useEffect(() => {
1027
- const currentUserName = editContext?.user?.name?.trim().toLowerCase();
1028
- if (!currentUserName || projects.length === 0) {
1029
- setTaskBoardNavState({ myTasks: [], myTasksLoading: false });
1312
+ return () => {
1313
+ resetTaskBoardNavState();
1314
+ };
1315
+ }, []);
1316
+ useEffect(() => {
1317
+ void refreshProjects();
1318
+ }, [refreshProjects]);
1319
+ useEffect(() => {
1320
+ if (!selectedProjectId) {
1321
+ setProjectViewLoadingId(null);
1322
+ clearSelectedProjectData();
1030
1323
  return;
1031
1324
  }
1325
+ clearSelectedProjectData();
1326
+ setProjectViewLoadingId(selectedProjectId);
1032
1327
  let cancelled = false;
1033
- setTaskBoardNavState({ myTasksLoading: true });
1034
- (async () => {
1035
- const taskRequests = projects.map(async (project) => {
1036
- const result = await getTasks(project.project.projectId);
1037
- if (result.type !== "success")
1038
- return [];
1039
- return (result.data ?? [])
1040
- .filter((task) => task.assigneeType === "Human" &&
1041
- (task.assigneeId ?? "").trim().toLowerCase() === currentUserName)
1042
- .map((task) => ({
1043
- taskId: task.taskId,
1044
- projectId: project.project.projectId,
1045
- title: task.title,
1046
- projectTitle: project.project.title,
1047
- status: normalizeTaskStatus(task.status, task.executionStatus),
1048
- updatedAt: task.updatedAt,
1049
- }));
1050
- });
1051
- const list = (await Promise.all(taskRequests)).flat().sort((a, b) => {
1052
- const aTime = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
1053
- const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
1054
- return bTime - aTime;
1055
- });
1328
+ const refreshTasksFn = refreshTasksRef.current;
1329
+ const refreshDependenciesFn = refreshDependenciesRef.current;
1330
+ void Promise.all([
1331
+ refreshTasksFn?.(selectedProjectId),
1332
+ refreshDependenciesFn?.(selectedProjectId),
1333
+ ]).finally(() => {
1056
1334
  if (cancelled)
1057
1335
  return;
1058
- setTaskBoardNavState({ myTasks: list, myTasksLoading: false });
1059
- })().catch(() => {
1060
- if (cancelled)
1336
+ if (selectedProjectIdRef.current !== selectedProjectId)
1061
1337
  return;
1062
- setTaskBoardNavState({ myTasks: [], myTasksLoading: false });
1338
+ setProjectViewLoadingId(null);
1063
1339
  });
1064
1340
  return () => {
1065
1341
  cancelled = true;
1066
1342
  };
1067
- }, [editContext?.user?.name, projects]);
1068
- useEffect(() => {
1069
- return () => {
1070
- resetTaskBoardNavState();
1071
- };
1072
- }, []);
1073
- useEffect(() => {
1074
- void refreshProjects();
1075
- }, [refreshProjects]);
1076
- useEffect(() => {
1077
- if (!selectedProjectId)
1078
- return;
1079
- void refreshTasks(selectedProjectId);
1080
- void refreshDependencies(selectedProjectId);
1081
- }, [refreshTasks, refreshDependencies, selectedProjectId]);
1343
+ }, [
1344
+ clearSelectedProjectData,
1345
+ selectedProjectId,
1346
+ ]);
1082
1347
  useEffect(() => {
1083
1348
  void refreshSubprojectTaskCounts();
1084
1349
  }, [refreshSubprojectTaskCounts]);
@@ -1087,8 +1352,8 @@ export function TaskBoardWorkspace() {
1087
1352
  setAgentStatusesById({});
1088
1353
  return;
1089
1354
  }
1090
- void refreshAgentStatuses(tasks);
1091
- }, [selectedProjectId, tasks, refreshAgentStatuses]);
1355
+ void refreshAgentStatuses(taskSelectionUniverse);
1356
+ }, [selectedProjectId, taskSelectionUniverse, refreshAgentStatuses]);
1092
1357
  useEffect(() => {
1093
1358
  previousTaskStatusesRef.current = {};
1094
1359
  }, [selectedProjectId]);
@@ -1109,18 +1374,13 @@ export function TaskBoardWorkspace() {
1109
1374
  }
1110
1375
  }
1111
1376
  previousTaskStatusesRef.current = currentStatuses;
1112
- if (movedToDone &&
1113
- canCreate &&
1114
- selectedProjectIsActive &&
1115
- !isPlanning &&
1116
- !runningOrchestrator) {
1377
+ if (movedToDone && canCreate && !isPlanning && !runningOrchestrator) {
1117
1378
  void handleRunOrchestrator({ silentWhenNothingToLaunch: true });
1118
1379
  }
1119
1380
  }, [
1120
1381
  selectedProjectId,
1121
1382
  tasks,
1122
1383
  canCreate,
1123
- selectedProjectIsActive,
1124
1384
  isPlanning,
1125
1385
  runningOrchestrator,
1126
1386
  handleRunOrchestrator,
@@ -1306,9 +1566,9 @@ export function TaskBoardWorkspace() {
1306
1566
  window.clearTimeout(wsProjectRefreshTimeoutRef.current);
1307
1567
  wsProjectRefreshTimeoutRef.current = null;
1308
1568
  }
1309
- if (wizardGuidanceTimeoutRef.current !== null) {
1310
- window.clearTimeout(wizardGuidanceTimeoutRef.current);
1311
- wizardGuidanceTimeoutRef.current = null;
1569
+ if (wizardCloseTransitionTimeoutRef.current !== null) {
1570
+ window.clearTimeout(wizardCloseTransitionTimeoutRef.current);
1571
+ wizardCloseTransitionTimeoutRef.current = null;
1312
1572
  }
1313
1573
  };
1314
1574
  }, []);
@@ -1316,50 +1576,113 @@ export function TaskBoardWorkspace() {
1316
1576
  useEffect(() => {
1317
1577
  if (!selectedTaskId)
1318
1578
  return;
1319
- if (!tasks.some((t) => t.taskId === selectedTaskId))
1579
+ if (!taskSelectionUniverse.some((t) => t.taskId === selectedTaskId)) {
1320
1580
  setSelectedTaskId(null);
1321
- }, [selectedTaskId, tasks]);
1322
- useEffect(() => {
1323
- if (!isWizardMode || !selectedProjectId) {
1324
- setWizardAgentDialogOpen(false);
1325
1581
  }
1326
- }, [isWizardMode, selectedProjectId]);
1582
+ }, [selectedTaskId, taskSelectionUniverse]);
1327
1583
  // No special auto-selection by project status — the agent panel is driven by
1328
1584
  // whichever task the user selects.
1329
1585
  // The backend now creates the Plan task and triggers the planner agent
1330
1586
  // during project creation (TaskService.CreateProjectAsync), so we no longer
1331
1587
  // need to auto-trigger planning from the frontend.
1588
+ const handleCloseWizardTask = useCallback(() => {
1589
+ const closingProjectId = selectedProjectId;
1590
+ const closedTaskId = wizardDisplayedTask?.taskId ?? selectedTaskId ?? wizardPinnedTaskId;
1591
+ clearWizardCloseTransitionTimeout();
1592
+ setSelectedTaskId(null);
1593
+ setWizardPinnedTaskId(null);
1594
+ setWizardForceOverview(true);
1595
+ wizardCloseTransitionTimeoutRef.current = window.setTimeout(() => {
1596
+ wizardCloseTransitionTimeoutRef.current = null;
1597
+ if (!closingProjectId)
1598
+ return;
1599
+ if (selectedProjectIdRef.current !== closingProjectId)
1600
+ return;
1601
+ const nextAttentionTaskId = findNextAttentionTaskId(closedTaskId);
1602
+ setWizardForceOverview(false);
1603
+ if (nextAttentionTaskId) {
1604
+ setWizardPinnedTaskId(nextAttentionTaskId);
1605
+ setSelectedTaskId(nextAttentionTaskId);
1606
+ }
1607
+ }, 1000);
1608
+ }, [
1609
+ selectedProjectId,
1610
+ wizardDisplayedTask?.taskId,
1611
+ selectedTaskId,
1612
+ wizardPinnedTaskId,
1613
+ clearWizardCloseTransitionTimeout,
1614
+ findNextAttentionTaskId,
1615
+ ]);
1616
+ const wizardCommunicationPanel = useMemo(() => (_jsx(WizardCommunicationCenter, { task: wizardDisplayedTask, agentId: wizardDisplayedAgentId, agentStatus: wizardDisplayedAgentStatus, executionDisplay: wizardDisplayedExecutionDisplay, summaryState: wizardAttentionState.summaryState, stats: wizardAttentionState.stats, projectTitle: selectedProject?.project.title ?? "Current project", projectDescription: selectedProject?.project.description, taskProjectTitle: wizardDisplayedTask &&
1617
+ wizardDisplayedTask.projectId !== selectedProjectId
1618
+ ? directSubprojects.find((subproject) => subproject.project.projectId ===
1619
+ wizardDisplayedTask.projectId)?.project.title
1620
+ : undefined, allTasks: wizardTasksWithDisplayAssignees, dependencies: dependencies, canEdit: canEditTasks, onTaskChanged: () => {
1621
+ handleCloseWizardTask();
1622
+ void refreshVisibleTaskData();
1623
+ }, onInteractionSubmitted: () => {
1624
+ if (wizardDisplayedAgentId) {
1625
+ const normalizedId = wizardDisplayedAgentId.toLowerCase();
1626
+ setAgentStatusesById((prev) => ({
1627
+ ...prev,
1628
+ [normalizedId]: "running",
1629
+ }));
1630
+ }
1631
+ handleCloseWizardTask();
1632
+ }, onCloseTask: handleCloseWizardTask, onSelectTask: (taskId) => {
1633
+ clearWizardCloseTransitionTimeout();
1634
+ setWizardForceOverview(false);
1635
+ setWizardPinnedTaskId(taskId);
1636
+ setSelectedTaskId(taskId);
1637
+ } })), [
1638
+ wizardAttentionState,
1639
+ wizardDisplayedTask,
1640
+ wizardDisplayedAgentId,
1641
+ wizardDisplayedAgentStatus,
1642
+ wizardDisplayedExecutionDisplay,
1643
+ canEditTasks,
1644
+ selectedProjectId,
1645
+ directSubprojects,
1646
+ refreshVisibleTaskData,
1647
+ clearWizardCloseTransitionTimeout,
1648
+ handleCloseWizardTask,
1649
+ selectedProject?.project.title,
1650
+ selectedProject?.project.description,
1651
+ ]);
1652
+ const isProjectViewLoading = !!selectedProjectId && projectViewLoadingId === selectedProjectId;
1332
1653
  const taskBoardContent = useMemo(() => {
1654
+ if (isProjectViewLoading) {
1655
+ return (_jsx("div", { className: "flex h-full min-h-[240px] items-center justify-center rounded-lg border border-dashed border-slate-200 bg-slate-50/70", "data-testid": "taskboard-project-loading-state", children: _jsxs("div", { className: "flex flex-col items-center gap-3 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-6 w-6 animate-spin" }), _jsx("div", { children: "Loading project..." })] }) }));
1656
+ }
1333
1657
  if (!selectedProjectId) {
1334
1658
  return (_jsx("div", { className: "text-muted-foreground p-6 text-sm", children: "Select a project to view tasks." }));
1335
1659
  }
1336
1660
  if (isWizardMode) {
1337
- return (_jsx(WizardView, { projectId: selectedProjectId, tasks: tasksWithDisplayAssignees, dependencies: dependencies, selectedTaskId: selectedTaskId, onSelectTask: (id) => setSelectedTaskId(id), agentStatusesById: agentStatusesById, projectTitle: selectedProject?.project.title ?? "Current project", projectDescription: selectedProject?.project.description, canEditProperties: selectedProject?.permission === "Owner", guidance: wizardGuidance, guidanceLoading: wizardGuidanceLoading, actionInFlightId: wizardActionInFlightId, onRunGuidanceAction: handleWizardAction, onOpenAgentTerminal: () => setWizardAgentDialogOpen(true) }));
1661
+ return (_jsx(WizardView, { projectId: selectedProjectId, tasks: tasksWithDisplayAssignees, projectTasksLoading: isTasksLoading, dependencies: dependencies, subprojectTaskLists: wizardSubprojectTaskLists, subprojectTasksLoading: subprojectCountsLoading, selectedTaskId: wizardDisplayedTask?.taskId ?? null, onSelectTask: (id) => {
1662
+ setWizardForceOverview(false);
1663
+ setWizardPinnedTaskId(id);
1664
+ setSelectedTaskId(id);
1665
+ }, agentStatusesById: agentStatusesById, projectTitle: selectedProject?.project.title ?? "Current project", projectDescription: selectedProject?.project.description, canEditProperties: selectedProject?.permission === "Owner", communicationPanel: wizardCommunicationPanel }));
1338
1666
  }
1339
1667
  if (isListView) {
1340
1668
  return (_jsxs("div", { className: "grid gap-3", children: [_jsx(ProjectDashboard, { projectId: selectedProjectId, selectedProjectTitle: selectedProject?.project.title ?? "Current project", selectedProjectDescription: selectedProject?.project.description, selectedProjectStatus: selectedProject?.project.status, selectedProjectCostUsed: selectedProjectCumulativeCostUsed, selectedProjectCostLimit: selectedProject?.project.costLimit ?? null, canEditProjectStatus: selectedProject?.permission === "Owner", savingProjectStatus: savingProjectStatus, onStatusChange: handleProjectStatusChange, selectedProjectTaskCounts: selectedProjectTaskCounts, subprojects: directSubprojects, taskCountsByProjectId: subprojectTaskCounts, loading: subprojectCountsLoading, parentProjectId: parentProject?.project.projectId ?? null, parentProjectTitle: parentProject?.project.title ?? null, onSelectProject: handleSelectProject, canEditProperties: selectedProject?.permission === "Owner" }), _jsx(ListView, { tasks: tasksWithDisplayAssignees, onSelectTask: (id) => setSelectedTaskId(id), selectedTaskId: selectedTaskId, onTasksChanged: () => {
1341
- if (selectedProjectId) {
1342
- void refreshTasks(selectedProjectId);
1343
- void refreshDependencies(selectedProjectId);
1344
- void refreshSubprojectTaskCounts();
1345
- }
1669
+ void refreshVisibleTaskData();
1346
1670
  }, projectId: selectedProjectId, permission: selectedProject?.permission, agentStatusesById: agentStatusesById, activeTab: activeTab, onSetActiveTab: setActiveTab, canCreateTask: canEditTasks, onCreateTask: () => setCreateDialogOpen(true) })] }));
1347
1671
  }
1348
1672
  return (_jsxs("div", { className: "flex h-full min-h-0 flex-col gap-3", children: [_jsx(ProjectDashboard, { projectId: selectedProjectId, selectedProjectTitle: selectedProject?.project.title ?? "Current project", selectedProjectDescription: selectedProject?.project.description, selectedProjectStatus: selectedProject?.project.status, selectedProjectCostUsed: selectedProjectCumulativeCostUsed, selectedProjectCostLimit: selectedProject?.project.costLimit ?? null, canEditProjectStatus: selectedProject?.permission === "Owner", savingProjectStatus: savingProjectStatus, onStatusChange: handleProjectStatusChange, selectedProjectTaskCounts: selectedProjectTaskCounts, subprojects: directSubprojects, taskCountsByProjectId: subprojectTaskCounts, loading: subprojectCountsLoading, parentProjectId: parentProject?.project.projectId ?? null, parentProjectTitle: parentProject?.project.title ?? null, onSelectProject: handleSelectProject, canEditProperties: selectedProject?.permission === "Owner" }), _jsx(KanbanView, { tasks: tasksWithDisplayAssignees, dependencies: dependencies, onSelectTask: (id) => setSelectedTaskId(id), selectedTaskId: selectedTaskId, onTasksChanged: () => {
1349
- if (selectedProjectId) {
1350
- void refreshTasks(selectedProjectId);
1351
- void refreshDependencies(selectedProjectId);
1352
- void refreshSubprojectTaskCounts();
1353
- }
1673
+ void refreshVisibleTaskData();
1354
1674
  }, projectId: selectedProjectId, permission: selectedProject?.permission, agentStatusesById: agentStatusesById, activeTab: activeTab, onSetActiveTab: setActiveTab, canCreateTask: canEditTasks, onCreateTask: () => setCreateDialogOpen(true) })] }));
1355
1675
  }, [
1676
+ isProjectViewLoading,
1356
1677
  selectedProjectId,
1357
1678
  isWizardMode,
1358
1679
  isListView,
1680
+ isTasksLoading,
1359
1681
  tasksWithDisplayAssignees,
1682
+ wizardSubprojectTaskLists,
1360
1683
  selectedTaskId,
1361
- refreshTasks,
1362
- refreshDependencies,
1684
+ wizardDisplayedTask?.taskId,
1685
+ refreshVisibleTaskData,
1363
1686
  selectedProject?.permission,
1364
1687
  selectedProject?.project.description,
1365
1688
  selectedProject?.project.status,
@@ -1374,7 +1697,7 @@ export function TaskBoardWorkspace() {
1374
1697
  selectedProjectTaskCounts,
1375
1698
  subprojectTaskCounts,
1376
1699
  subprojectCountsLoading,
1377
- refreshSubprojectTaskCounts,
1700
+ wizardCommunicationPanel,
1378
1701
  savingProjectStatus,
1379
1702
  handleProjectStatusChange,
1380
1703
  activeTab,
@@ -1392,27 +1715,18 @@ export function TaskBoardWorkspace() {
1392
1715
  canEditTasks,
1393
1716
  selectedTask?.assigneeDisplayName,
1394
1717
  selectedTask?.assigneeId,
1395
- wizardGuidance,
1396
- wizardGuidanceLoading,
1397
- wizardActionInFlightId,
1398
- handleWizardAction,
1399
1718
  ]);
1400
- const taskDetailPanel = useMemo(() => (_jsx(TaskDetailPanel, { project: selectedProject, task: selectedTask, allTasks: tasksWithDisplayAssignees, dependencies: dependencies, agentStatusesById: agentStatusesById, onTaskChanged: () => {
1401
- if (selectedProjectId) {
1402
- void refreshTasks(selectedProjectId);
1403
- void refreshDependencies(selectedProjectId);
1404
- }
1719
+ const taskDetailPanel = useMemo(() => (_jsx(TaskDetailPanel, { project: selectedTaskProject, task: selectedTask, allTasks: taskSelectionUniverse, dependencies: dependencies, agentStatusesById: agentStatusesById, onTaskChanged: () => {
1720
+ void refreshVisibleTaskData();
1405
1721
  }, onSelectTask: (taskId) => setSelectedTaskId(taskId), onClose: () => setSelectedTaskId(null), variant: "panel" })), [
1406
- selectedProject,
1722
+ selectedTaskProject,
1407
1723
  selectedTask,
1408
- tasksWithDisplayAssignees,
1724
+ taskSelectionUniverse,
1409
1725
  dependencies,
1410
1726
  agentStatusesById,
1411
- selectedProjectId,
1412
- refreshTasks,
1413
- refreshDependencies,
1727
+ refreshVisibleTaskData,
1414
1728
  ]);
1415
- const agentTerminalPanel = useMemo(() => (_jsx(TaskAgentPanel, { agentId: currentAgentId, mode: agentPanelMode, label: selectedTaskIsPlan ? "Planner" : "Task Agent", hasAssignedAgentProfile: !selectedTaskIsPlan && selectedTaskHasAssignedAgentProfile, assignedAgentProfileName: !selectedTaskIsPlan && selectedTaskHasAssignedAgentProfile
1729
+ const agentTerminalPanel = useMemo(() => (_jsx(TaskAgentPanel, { agentId: displayAgentId, mode: agentPanelMode, label: selectedTaskIsPlan ? "Planner" : "Task Agent", hasAssignedAgentProfile: !selectedTaskIsPlan && selectedTaskHasAssignedAgentProfile, assignedAgentProfileName: !selectedTaskIsPlan && selectedTaskHasAssignedAgentProfile
1416
1730
  ? selectedTask?.assigneeDisplayName ||
1417
1731
  selectedTask?.assigneeId ||
1418
1732
  null
@@ -1427,16 +1741,10 @@ export function TaskBoardWorkspace() {
1427
1741
  agentPanelMode === "no-agent" &&
1428
1742
  !selectedTaskHasAssignedAgentProfile
1429
1743
  ? () => setAssignAgentDialogOpen(true)
1430
- : undefined, assignedAgentStatus: currentAgentId ? agentStatusesById[currentAgentId] : undefined, onReopenTask: selectedTask && canEditTasks
1431
- ? async () => {
1432
- await updateTask({
1433
- taskId: selectedTask.taskId,
1434
- status: "InProgress",
1435
- });
1436
- await refreshTasks(selectedTask.projectId);
1437
- }
1744
+ : undefined, assignedAgentStatus: displayAgentId ? currentAgentStatus : undefined, onReopenTask: selectedTask && canEditTasks
1745
+ ? () => void handleReopenSelectedTask()
1438
1746
  : undefined })), [
1439
- currentAgentId,
1747
+ displayAgentId,
1440
1748
  agentPanelMode,
1441
1749
  selectedTaskIsPlan,
1442
1750
  selectedTaskHasAssignedAgentProfile,
@@ -1447,9 +1755,9 @@ export function TaskBoardWorkspace() {
1447
1755
  handleStartPlanning,
1448
1756
  selectedTaskIsBlocked,
1449
1757
  canEditTasks,
1450
- agentStatusesById,
1758
+ currentAgentStatus,
1451
1759
  selectedTask,
1452
- refreshTasks,
1760
+ handleReopenSelectedTask,
1453
1761
  ]);
1454
1762
  const slotContext = editContext?.getActiveSlotContext();
1455
1763
  const previewItemVersion = useMemo(() => {
@@ -1643,14 +1951,14 @@ export function TaskBoardWorkspace() {
1643
1951
  const wizardPanels = [
1644
1952
  {
1645
1953
  name: "wizard-view",
1646
- defaultSize: "auto",
1954
+ defaultSize: 70,
1647
1955
  content: (_jsx("div", { className: "h-full overflow-x-hidden overflow-y-auto bg-slate-50/50", children: taskBoardContent })),
1648
1956
  },
1649
1957
  ];
1650
1958
  if (showEditorPanel) {
1651
1959
  wizardPanels.push({
1652
1960
  name: "editor-preview",
1653
- defaultSize: 500,
1961
+ defaultSize: 30,
1654
1962
  content: (_jsx("div", { className: "h-full overflow-hidden border-l border-gray-200 bg-gray-50", children: itemPreviewPanel })),
1655
1963
  });
1656
1964
  }
@@ -1673,7 +1981,7 @@ export function TaskBoardWorkspace() {
1673
1981
  name: "agent-terminal",
1674
1982
  defaultSize: 560,
1675
1983
  content: agentTerminalPanel,
1676
- hidden: !selectedProjectId,
1984
+ hidden: !selectedProjectId || shouldHideAgentPanelForSelectedTask,
1677
1985
  },
1678
1986
  ];
1679
1987
  if (showEditorPanel) {
@@ -1701,7 +2009,7 @@ export function TaskBoardWorkspace() {
1701
2009
  name: "agent-terminal",
1702
2010
  defaultSize: 560,
1703
2011
  content: agentTerminalPanel,
1704
- hidden: !selectedProjectId,
2012
+ hidden: !selectedProjectId || shouldHideAgentPanelForSelectedTask,
1705
2013
  },
1706
2014
  ];
1707
2015
  if (showEditorPanel) {
@@ -1721,6 +2029,7 @@ export function TaskBoardWorkspace() {
1721
2029
  selectedTask,
1722
2030
  showEditorPanel,
1723
2031
  itemPreviewPanel,
2032
+ shouldHideAgentPanelForSelectedTask,
1724
2033
  ]);
1725
2034
  return (_jsxs("div", { className: "text-foreground flex h-full w-full flex-col bg-white select-text", children: [_jsx("div", { className: "min-h-0 flex-1", children: isMobile ? (_jsxs("div", { className: "flex h-full flex-col overflow-hidden", children: [_jsx("div", { className: "shrink-0 border-b border-gray-100 bg-white px-4 shadow-[0_1px_2px_rgba(0,0,0,0.03)]", children: _jsx(SimpleTabs, { tabs: [
1726
2035
  { id: "project", label: "Project", content: null },
@@ -1734,19 +2043,24 @@ export function TaskBoardWorkspace() {
1734
2043
  id: "agent",
1735
2044
  label: "Agent",
1736
2045
  content: null,
1737
- disabled: !currentAgentId,
2046
+ disabled: !currentAgentId || shouldHideAgentPanelForSelectedTask,
1738
2047
  },
1739
2048
  ...(showEditorPanel
1740
2049
  ? [{ id: "editor", label: "Editor", content: null }]
1741
2050
  : []),
1742
- ], activeTab: mobileActiveTab, setActiveTab: setMobileActiveTab, hideContent: true, variant: "elegant", className: "gap-6" }) }), _jsxs("div", { className: "min-h-0 flex-1 overflow-hidden", children: [mobileActiveTab === 0 && (_jsx("div", { className: "h-full overflow-y-auto bg-slate-50/50 p-2 pt-2 sm:p-4 sm:pt-3", children: taskBoardContent })), mobileActiveTab === 1 && (_jsx("div", { className: "h-full overflow-y-auto bg-white", children: taskDetailPanel })), mobileActiveTab === 2 && (_jsx("div", { className: "h-full overflow-hidden bg-white", children: agentTerminalPanel })), mobileActiveTab === 3 && showEditorPanel && (_jsx("div", { className: "h-full overflow-hidden bg-gray-50", children: itemPreviewPanel }))] })] })) : (_jsx(Splitter, { panels: panels, direction: "horizontal", localStorageKey: "task-board-main-splitter" })) }), _jsx(Dialog, { open: wizardAgentDialogOpen, onOpenChange: setWizardAgentDialogOpen, children: _jsxs(DialogContent, { className: "max-h-[88vh] max-w-6xl overflow-hidden p-0", children: [_jsx(DialogHeader, { className: "border-b border-slate-200 px-5 py-3", children: _jsx(DialogTitle, { children: selectedTask?.title ||
1743
- (selectedTaskIsPlan ? "Planner" : "Task Agent") }) }), _jsx("div", { className: "h-[70vh] overflow-hidden", children: agentTerminalPanel })] }) }), selectedProjectId && (_jsx(CreateTaskDialog, { open: createDialogOpen, onOpenChange: setCreateDialogOpen, projectId: selectedProjectId, onCreated: () => {
2051
+ ], activeTab: mobileActiveTab, setActiveTab: setMobileActiveTab, hideContent: true, variant: "elegant", className: "gap-6" }) }), _jsxs("div", { className: "min-h-0 flex-1 overflow-hidden", children: [mobileActiveTab === 0 && (_jsx("div", { className: "h-full overflow-y-auto bg-slate-50/50 p-2 pt-2 sm:p-4 sm:pt-3", children: taskBoardContent })), mobileActiveTab === 1 && (_jsx("div", { className: "h-full overflow-y-auto bg-white", children: taskDetailPanel })), mobileActiveTab === 2 && (_jsx("div", { className: "h-full overflow-hidden bg-white", children: shouldHideAgentPanelForSelectedTask ? taskDetailPanel : agentTerminalPanel })), mobileActiveTab === 3 && showEditorPanel && (_jsx("div", { className: "h-full overflow-hidden bg-gray-50", children: itemPreviewPanel }))] })] })) : (_jsx(Splitter, { panels: panels, direction: "horizontal", localStorageKey: isWizardMode
2052
+ ? "task-board-wizard-splitter-v2"
2053
+ : "task-board-main-splitter" })) }), selectedProjectId && (_jsx(CreateTaskDialog, { open: createDialogOpen, onOpenChange: setCreateDialogOpen, projectId: selectedProjectId, onCreated: () => {
1744
2054
  void refreshTasks(selectedProjectId);
1745
2055
  void refreshDependencies(selectedProjectId);
1746
- } })), _jsx(CreateProjectDialog, { open: createProjectOpen, onOpenChange: setCreateProjectOpen, projects: projects, onCreated: async (newProjectId) => {
1747
- await refreshProjects();
2056
+ } })), _jsx(CreateProjectDialog, { open: createProjectOpen, onOpenChange: setCreateProjectOpen, projects: projects, onCreated: async ({ projectId: newProjectId, openInWizardMode }) => {
2057
+ if (openInWizardMode) {
2058
+ setIsWizardMode(true);
2059
+ setWizardForceOverview(false);
2060
+ }
1748
2061
  if (newProjectId)
1749
2062
  handleSelectProject(newProjectId);
2063
+ await refreshProjects();
1750
2064
  } }), _jsx(ProjectSettingsDialog, { open: !!settingsProjectId, onOpenChange: (open) => {
1751
2065
  if (!open)
1752
2066
  setSettingsProjectId(null);