@taskforcehq/taskforce 0.3.300 → 0.3.302

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 (153) hide show
  1. package/README.md +19 -0
  2. package/dist/Taskforce.module.css +26 -2
  3. package/dist/TaskforceCore.filter-persistence.test.js +1 -1
  4. package/dist/TaskforceCore.js +13 -24
  5. package/dist/TaskforceCore.test.js +167 -152
  6. package/dist/components/features/TaskSettings.js +14 -82
  7. package/dist/components/features/TaskSettings.test.js +33 -70
  8. package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
  9. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
  10. package/dist/components/task/TaskActionHeader.js +6 -3
  11. package/dist/components/task/TaskCard.js +6 -3
  12. package/dist/components/task/TaskForm.js +2 -2
  13. package/dist/components/task/TaskForm.test.js +41 -2
  14. package/dist/components/ui/ReferenceBadgeButton.js +1 -1
  15. package/dist/components/views/PlansPage.js +9 -8
  16. package/dist/components/views/StandaloneLayout.js +490 -207
  17. package/dist/components/views/panels/FilterToolbar.d.ts +2 -0
  18. package/dist/components/views/panels/FilterToolbarControls.d.ts +1 -0
  19. package/dist/components/views/panels/FilterToolbarControls.js +1 -1
  20. package/dist/components/views/panels/PlanningDrawer.js +19 -2
  21. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +43 -0
  22. package/dist/components/views/standalone/modals/AccountHubModal.js +22 -0
  23. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
  24. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
  25. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
  26. package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
  27. package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
  28. package/dist/components/views/standalone/modals/HelpModal.js +8 -0
  29. package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
  30. package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
  31. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
  32. package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
  33. package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
  34. package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
  35. package/dist/components/views/standalone/modals/types.d.ts +2 -0
  36. package/dist/components/views/standalone/modals/types.js +1 -0
  37. package/dist/components/views/teamManagementAccess.d.ts +1 -1
  38. package/dist/components/views/teamManagementAccess.js +2 -2
  39. package/dist/components/views/teamManagementAccess.test.js +3 -3
  40. package/dist/config/deployment.js +2 -2
  41. package/dist/config/deployment.test.js +5 -3
  42. package/dist/core/AiProfileService.js +7 -2
  43. package/dist/core/AiProfiles.test.js +19 -0
  44. package/dist/core/SyncReconciliationService.d.ts +2 -0
  45. package/dist/core/SyncReconciliationService.js +3 -0
  46. package/dist/core/TaskAuditTimeline.test.js +40 -0
  47. package/dist/core/Taskforce.d.ts +49 -0
  48. package/dist/core/Taskforce.ids.test.js +133 -0
  49. package/dist/core/Taskforce.js +241 -78
  50. package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
  51. package/dist/core/UserProfileAvatarDrafts.test.js +102 -0
  52. package/dist/core/taskReferencePolicy.d.ts +28 -0
  53. package/dist/core/taskReferencePolicy.js +189 -0
  54. package/dist/core/types.d.ts +2 -0
  55. package/dist/hooks/tasks/useTaskFormActions.js +3 -0
  56. package/dist/hooks/useTaskData.js +16 -11
  57. package/dist/hooks/useTaskData.test.js +127 -0
  58. package/dist/hooks/useTaskforce.d.ts +5 -0
  59. package/dist/hooks/useTaskforce.js +63 -15
  60. package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
  61. package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
  62. package/dist/mcp/aiProfileBootstrap.js +80 -0
  63. package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
  64. package/dist/mcp/aiProfileBootstrap.test.js +68 -0
  65. package/dist/mcp/clientIntegrations.d.ts +2 -8
  66. package/dist/mcp/clientIntegrations.js +21 -40
  67. package/dist/mcp/clientIntegrations.test.js +28 -2
  68. package/dist/mcp/clientProfileDefaults.d.ts +12 -0
  69. package/dist/mcp/clientProfileDefaults.js +37 -0
  70. package/dist/mcp/runtime.d.ts +10 -7
  71. package/dist/mcp/runtime.js +45 -23
  72. package/dist/mcp/runtime.test.js +56 -3
  73. package/dist/migrations/taskSchemaMigrations.js +53 -1
  74. package/dist/qaOpen.test.d.ts +1 -0
  75. package/dist/qaOpen.test.js +56 -0
  76. package/dist/server/index.cookieProxy.test.js +1 -0
  77. package/dist/server/index.d.ts +1 -0
  78. package/dist/server/index.js +15 -1
  79. package/dist/server/index.test.js +14 -1
  80. package/dist/server/routes/admin.js +25 -22
  81. package/dist/server/routes/auth.d.ts +5 -0
  82. package/dist/server/routes/auth.js +350 -16
  83. package/dist/server/routes/billing.js +11 -1
  84. package/dist/server/routes/billing.test.js +32 -2
  85. package/dist/server/routes/documents.js +6 -1
  86. package/dist/server/routes/shared.d.ts +1 -1
  87. package/dist/server/routes/shared.js +11 -3
  88. package/dist/server/routes/sync.integration.test.js +130 -0
  89. package/dist/server/routes/sync.js +226 -1924
  90. package/dist/server/routes/tasks.js +18 -4
  91. package/dist/server/routes.js +26 -19
  92. package/dist/server/routes.test.js +352 -6
  93. package/dist/shared/runtimeContract.d.ts +1 -1
  94. package/dist/sync/collaborationSyncPayload.d.ts +17 -0
  95. package/dist/sync/collaborationSyncPayload.js +131 -0
  96. package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
  97. package/dist/sync/collaborationSyncPayload.test.js +104 -0
  98. package/dist/sync/collaborationSyncState.d.ts +15 -0
  99. package/dist/sync/collaborationSyncState.js +60 -0
  100. package/dist/sync/contentSyncPayload.d.ts +45 -0
  101. package/dist/sync/contentSyncPayload.js +104 -0
  102. package/dist/sync/contentSyncPayload.test.d.ts +1 -0
  103. package/dist/sync/contentSyncPayload.test.js +104 -0
  104. package/dist/sync/contentSyncState.d.ts +88 -0
  105. package/dist/sync/contentSyncState.js +743 -0
  106. package/dist/sync/planningSyncPayload.d.ts +9 -0
  107. package/dist/sync/planningSyncPayload.js +82 -0
  108. package/dist/sync/planningSyncPayload.test.d.ts +1 -0
  109. package/dist/sync/planningSyncPayload.test.js +68 -0
  110. package/dist/sync/syncApplyHandlers.d.ts +83 -0
  111. package/dist/sync/syncApplyHandlers.js +445 -0
  112. package/dist/sync/taskSyncPayload.d.ts +33 -0
  113. package/dist/sync/taskSyncPayload.js +126 -0
  114. package/dist/sync/taskSyncPayload.test.d.ts +1 -0
  115. package/dist/sync/taskSyncPayload.test.js +54 -0
  116. package/dist/sync/workspacePullFeed.d.ts +28 -0
  117. package/dist/sync/workspacePullFeed.js +301 -0
  118. package/dist/sync/workspacePullFeed.test.d.ts +1 -0
  119. package/dist/sync/workspacePullFeed.test.js +166 -0
  120. package/dist/sync/workspaceSyncModel.d.ts +4 -1
  121. package/dist/sync/workspaceSyncModel.js +9 -2
  122. package/dist/sync/workspaceSyncModel.test.js +29 -0
  123. package/dist/test/setup.js +1 -1
  124. package/dist/types.d.ts +1 -0
  125. package/dist/ui/assets/{AgentsModule-TVmCgizS.js → AgentsModule-JG5jc3he.js} +1 -1
  126. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-CAyhnPeR.js → AnnotatedAttachmentWorkspace-Dvi02Y7q.js} +1 -1
  127. package/dist/ui/assets/{DocumentWorkspace-BOCw5e4D.js → DocumentWorkspace-iys3zfca.js} +2 -2
  128. package/dist/ui/assets/{InitiativesModule-CN0r02Oi.js → InitiativesModule-DJvLJI3A.js} +1 -1
  129. package/dist/ui/assets/PlansPage-BMPlpFpw.js +1 -0
  130. package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +8 -0
  131. package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
  132. package/dist/ui/assets/{WorkflowsModule-CY9R2zPB.js → WorkflowsModule-BguHeQaV.js} +1 -1
  133. package/dist/ui/assets/index-MXeWoebC.css +1 -0
  134. package/dist/ui/assets/index-YJ4k5QNa.js +6 -0
  135. package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-C8EED3Oh.js} +1 -1
  136. package/dist/ui/index.html +3 -3
  137. package/dist/utils/billingStatusCache.d.ts +21 -0
  138. package/dist/utils/billingStatusCache.js +70 -0
  139. package/dist/utils/taskActivity.js +17 -4
  140. package/dist/utils/taskActivity.test.js +27 -0
  141. package/dist/utils/taskNormalization.js +10 -2
  142. package/dist/utils/taskReferences.d.ts +11 -1
  143. package/dist/utils/taskReferences.js +33 -1
  144. package/package.json +4 -1
  145. package/scripts/deploy-prod.sh +41 -0
  146. package/scripts/deploy-staging.sh +23 -0
  147. package/scripts/qa-open-lib.mjs +175 -0
  148. package/scripts/qa-open-lib.mjs.d.ts +49 -0
  149. package/scripts/qa-open.mjs +352 -0
  150. package/dist/ui/assets/PlansPage-FcQSNLZd.js +0 -1
  151. package/dist/ui/assets/TaskSettings-Cw5zYD1L.js +0 -8
  152. package/dist/ui/assets/index-Bzqy5QGm.js +0 -6
  153. package/dist/ui/assets/index-DxRiI3tY.css +0 -1
@@ -0,0 +1,189 @@
1
+ import { formatLocalTaskReference, parseLocalTaskReference, parseTaskReference, } from '../utils/taskReferences.js';
2
+ export function getTaskReferenceSequenceValue(deps, workspaceId) {
3
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
4
+ const row = deps.db.prepare(`
5
+ SELECT next_reference_number
6
+ FROM workspace_task_reference_sequences
7
+ WHERE tenant_id = ? AND workspace_id = ?
8
+ LIMIT 1
9
+ `).get(deps.tenantId, normalizedWorkspaceId);
10
+ return deps.sanitizeTaskReferenceNumber(row?.next_reference_number) || 0;
11
+ }
12
+ export function getLocalTaskReferenceSequenceValue(deps, workspaceId) {
13
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
14
+ const row = deps.db.prepare(`
15
+ SELECT next_reference_number
16
+ FROM workspace_local_task_reference_sequences
17
+ WHERE tenant_id = ? AND workspace_id = ?
18
+ LIMIT 1
19
+ `).get(deps.tenantId, normalizedWorkspaceId);
20
+ return deps.sanitizeTaskReferenceNumber(row?.next_reference_number) || 0;
21
+ }
22
+ export function getMaxTaskReferenceNumber(deps, workspaceId, excludeTaskId) {
23
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
24
+ const normalizedExcludeTaskId = String(excludeTaskId || '').trim();
25
+ const row = normalizedExcludeTaskId
26
+ ? deps.db.prepare(`
27
+ SELECT MAX(reference_number) AS max_reference_number
28
+ FROM tasks
29
+ WHERE tenant_id = ? AND workspace_id = ? AND id != ?
30
+ `).get(deps.tenantId, normalizedWorkspaceId, normalizedExcludeTaskId)
31
+ : deps.db.prepare(`
32
+ SELECT MAX(reference_number) AS max_reference_number
33
+ FROM tasks
34
+ WHERE tenant_id = ? AND workspace_id = ?
35
+ `).get(deps.tenantId, normalizedWorkspaceId);
36
+ return deps.sanitizeTaskReferenceNumber(row?.max_reference_number) || 0;
37
+ }
38
+ export function getMaxLocalTaskReferenceNumber(deps, workspaceId, excludeTaskId) {
39
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
40
+ const normalizedExcludeTaskId = String(excludeTaskId || '').trim();
41
+ const row = normalizedExcludeTaskId
42
+ ? deps.db.prepare(`
43
+ SELECT MAX(local_reference_number) AS max_reference_number
44
+ FROM tasks
45
+ WHERE tenant_id = ? AND workspace_id = ? AND id != ?
46
+ `).get(deps.tenantId, normalizedWorkspaceId, normalizedExcludeTaskId)
47
+ : deps.db.prepare(`
48
+ SELECT MAX(local_reference_number) AS max_reference_number
49
+ FROM tasks
50
+ WHERE tenant_id = ? AND workspace_id = ?
51
+ `).get(deps.tenantId, normalizedWorkspaceId);
52
+ return deps.sanitizeTaskReferenceNumber(row?.max_reference_number) || 0;
53
+ }
54
+ export function persistTaskReferenceSequence(deps, workspaceId, referenceNumber) {
55
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
56
+ const normalizedReferenceNumber = deps.sanitizeTaskReferenceNumber(referenceNumber);
57
+ if (!normalizedReferenceNumber)
58
+ return;
59
+ deps.db.prepare(`
60
+ INSERT INTO workspace_task_reference_sequences (tenant_id, workspace_id, next_reference_number)
61
+ VALUES (?, ?, ?)
62
+ ON CONFLICT (tenant_id, workspace_id) DO UPDATE SET
63
+ next_reference_number = CASE
64
+ WHEN workspace_task_reference_sequences.next_reference_number < EXCLUDED.next_reference_number
65
+ THEN EXCLUDED.next_reference_number
66
+ ELSE workspace_task_reference_sequences.next_reference_number
67
+ END
68
+ `).run(deps.tenantId, normalizedWorkspaceId, normalizedReferenceNumber);
69
+ }
70
+ export function persistLocalTaskReferenceSequence(deps, workspaceId, referenceNumber) {
71
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
72
+ const normalizedReferenceNumber = deps.sanitizeTaskReferenceNumber(referenceNumber);
73
+ if (!normalizedReferenceNumber)
74
+ return;
75
+ deps.db.prepare(`
76
+ INSERT INTO workspace_local_task_reference_sequences (tenant_id, workspace_id, next_reference_number)
77
+ VALUES (?, ?, ?)
78
+ ON CONFLICT (tenant_id, workspace_id) DO UPDATE SET
79
+ next_reference_number = CASE
80
+ WHEN workspace_local_task_reference_sequences.next_reference_number < EXCLUDED.next_reference_number
81
+ THEN EXCLUDED.next_reference_number
82
+ ELSE workspace_local_task_reference_sequences.next_reference_number
83
+ END
84
+ `).run(deps.tenantId, normalizedWorkspaceId, normalizedReferenceNumber);
85
+ }
86
+ export function allocateNextTaskReferenceNumber(deps, workspaceId, excludeTaskId) {
87
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
88
+ const nextReferenceNumber = Math.max(getTaskReferenceSequenceValue(deps, normalizedWorkspaceId), getMaxTaskReferenceNumber(deps, normalizedWorkspaceId, excludeTaskId)) + 1;
89
+ persistTaskReferenceSequence(deps, normalizedWorkspaceId, nextReferenceNumber);
90
+ return nextReferenceNumber;
91
+ }
92
+ export function allocateNextLocalTaskReferenceNumber(deps, workspaceId, excludeTaskId) {
93
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
94
+ const nextReferenceNumber = Math.max(getLocalTaskReferenceSequenceValue(deps, normalizedWorkspaceId), getMaxLocalTaskReferenceNumber(deps, normalizedWorkspaceId, excludeTaskId)) + 1;
95
+ persistLocalTaskReferenceSequence(deps, normalizedWorkspaceId, nextReferenceNumber);
96
+ return nextReferenceNumber;
97
+ }
98
+ export function relocateTaskReferenceConflict(deps, workspaceId, referenceNumber, claimantTaskId, updatedAt) {
99
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
100
+ const normalizedReferenceNumber = deps.sanitizeTaskReferenceNumber(referenceNumber);
101
+ if (!normalizedReferenceNumber)
102
+ return null;
103
+ const normalizedClaimantTaskId = String(claimantTaskId || '').trim();
104
+ const incumbent = deps.db.prepare(`
105
+ SELECT id
106
+ FROM tasks
107
+ WHERE tenant_id = ? AND workspace_id = ? AND reference_number = ? AND id != ?
108
+ LIMIT 1
109
+ `).get(deps.tenantId, normalizedWorkspaceId, normalizedReferenceNumber, normalizedClaimantTaskId);
110
+ const incumbentTaskId = String(incumbent?.id || '').trim();
111
+ if (!incumbentTaskId)
112
+ return null;
113
+ const nextReferenceNumber = allocateNextTaskReferenceNumber(deps, normalizedWorkspaceId, incumbentTaskId);
114
+ deps.db.prepare(`
115
+ UPDATE tasks
116
+ SET reference_number = ?, updated_at = ?
117
+ WHERE tenant_id = ? AND workspace_id = ? AND id = ?
118
+ `).run(nextReferenceNumber, String(updatedAt || new Date().toISOString()), deps.tenantId, normalizedWorkspaceId, incumbentTaskId);
119
+ return { taskId: incumbentTaskId, referenceNumber: nextReferenceNumber };
120
+ }
121
+ export function reserveTaskReferenceNumber(deps, workspaceId, preferredReferenceNumber, currentTaskId) {
122
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
123
+ const preferred = deps.sanitizeTaskReferenceNumber(preferredReferenceNumber);
124
+ const normalizedCurrentTaskId = String(currentTaskId || '').trim();
125
+ if (preferred) {
126
+ const existing = deps.db.prepare(`
127
+ SELECT id
128
+ FROM tasks
129
+ WHERE tenant_id = ? AND workspace_id = ? AND reference_number = ?
130
+ LIMIT 1
131
+ `).get(deps.tenantId, normalizedWorkspaceId, preferred);
132
+ if (!existing?.id || existing.id === normalizedCurrentTaskId) {
133
+ persistTaskReferenceSequence(deps, normalizedWorkspaceId, preferred);
134
+ return preferred;
135
+ }
136
+ }
137
+ return allocateNextTaskReferenceNumber(deps, normalizedWorkspaceId, normalizedCurrentTaskId || undefined);
138
+ }
139
+ export function getTaskByReferenceNumber(deps, referenceNumber, workspaceId) {
140
+ const normalizedReferenceNumber = deps.sanitizeTaskReferenceNumber(referenceNumber);
141
+ if (!normalizedReferenceNumber)
142
+ return null;
143
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
144
+ const row = deps.db.prepare(`
145
+ SELECT * FROM tasks
146
+ WHERE tenant_id = ? AND workspace_id = ? AND reference_number = ?
147
+ LIMIT 1
148
+ `).get(deps.tenantId, normalizedWorkspaceId, normalizedReferenceNumber);
149
+ if (!row)
150
+ return null;
151
+ return deps.rowToTask(row);
152
+ }
153
+ export function getTaskByLocalReferenceNumber(deps, referenceNumber, workspaceId) {
154
+ const normalizedReferenceNumber = deps.sanitizeTaskReferenceNumber(referenceNumber);
155
+ if (!normalizedReferenceNumber)
156
+ return null;
157
+ const normalizedWorkspaceId = deps.normalizeWorkspaceId(workspaceId);
158
+ const row = deps.db.prepare(`
159
+ SELECT * FROM tasks
160
+ WHERE tenant_id = ? AND workspace_id = ? AND local_reference_number = ?
161
+ LIMIT 1
162
+ `).get(deps.tenantId, normalizedWorkspaceId, normalizedReferenceNumber);
163
+ if (!row)
164
+ return null;
165
+ return deps.rowToTask(row);
166
+ }
167
+ export function resolveTaskIdentifier(deps, value, workspaceId) {
168
+ const normalizedValue = String(value || '').trim();
169
+ if (!normalizedValue)
170
+ return null;
171
+ const referenceNumber = parseTaskReference(normalizedValue);
172
+ if (referenceNumber) {
173
+ const exactReferenceMatch = getTaskByReferenceNumber(deps, referenceNumber, workspaceId);
174
+ if (exactReferenceMatch)
175
+ return exactReferenceMatch;
176
+ }
177
+ const localReferenceNumber = parseLocalTaskReference(normalizedValue);
178
+ if (localReferenceNumber) {
179
+ const exactLocalReferenceMatch = getTaskByLocalReferenceNumber(deps, localReferenceNumber, workspaceId);
180
+ if (exactLocalReferenceMatch)
181
+ return exactLocalReferenceMatch;
182
+ }
183
+ return deps.getTask(normalizedValue, workspaceId);
184
+ }
185
+ export function deriveTaskReferenceLabel(task) {
186
+ return task.referenceNumber
187
+ ? `T-${task.referenceNumber}`
188
+ : formatLocalTaskReference(task.localReferenceNumber);
189
+ }
@@ -27,6 +27,7 @@ export type TaskAssignee = string;
27
27
  export interface TaskItem {
28
28
  id: string;
29
29
  referenceNumber?: number | null;
30
+ localReferenceNumber?: number | null;
30
31
  referenceLabel?: string;
31
32
  title: string;
32
33
  description: string | null;
@@ -330,6 +331,7 @@ export interface UserIdentityRecord {
330
331
  userId: string;
331
332
  email: string;
332
333
  displayName?: string | null;
334
+ avatarUrl?: string | null;
333
335
  systemRole?: SystemRole;
334
336
  betaAccess?: boolean;
335
337
  }
@@ -130,6 +130,9 @@ export function useTaskFormActions(input) {
130
130
  if (!editingTaskId || attachmentsDirty) {
131
131
  body.attachments = attachments;
132
132
  }
133
+ if (editingTaskId) {
134
+ body.saveSource = source;
135
+ }
133
136
  const res = await fetch(url, {
134
137
  method,
135
138
  headers: { 'Content-Type': 'application/json' },
@@ -45,6 +45,16 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
45
45
  const getTaskRevisionKey = useCallback((task) => {
46
46
  return `${task.updatedAt || task.createdAt || ''}|${task.status}|${task.priority}|${task.title}`;
47
47
  }, []);
48
+ const isExpectedAbortFailure = useCallback((error, signal) => {
49
+ if (!signal.aborted)
50
+ return false;
51
+ if (error === signal.reason)
52
+ return true;
53
+ if (error instanceof DOMException) {
54
+ return error.name === 'AbortError';
55
+ }
56
+ return String(error?.name || '').toLowerCase() === 'aborterror';
57
+ }, []);
48
58
  // === SECTION: Recently Changed ===
49
59
  const [recentlyChangedTaskIds, setRecentlyChangedTaskIds] = useState([]);
50
60
  const highlightTimersRef = useRef(new Map());
@@ -137,18 +147,16 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
137
147
  }
138
148
  }
139
149
  catch (err) {
140
- const isAbortError = err instanceof DOMException
141
- ? err.name === 'AbortError'
142
- : String(err?.name || '').toLowerCase() === 'aborterror';
143
- if (isAbortError)
150
+ if (isExpectedAbortFailure(err, abortController.signal))
144
151
  return;
145
152
  console.error('[Taskforce] Failed to fetch tasks:', err);
146
153
  }
147
154
  finally {
148
- if (tasksAbortRef.current === abortController) {
155
+ const isCurrentRequest = tasksAbortRef.current === abortController;
156
+ if (isCurrentRequest) {
149
157
  tasksAbortRef.current = null;
150
158
  }
151
- if (!isSilent)
159
+ if (!isSilent && isCurrentRequest)
152
160
  setLoadingTasks(false);
153
161
  }
154
162
  }, [
@@ -186,10 +194,7 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
186
194
  }
187
195
  }
188
196
  catch (err) {
189
- const isAbortError = err instanceof DOMException
190
- ? err.name === 'AbortError'
191
- : String(err?.name || '').toLowerCase() === 'aborterror';
192
- if (isAbortError)
197
+ if (isExpectedAbortFailure(err, abortController.signal))
193
198
  return;
194
199
  console.error('[Taskforce] Failed to fetch archive:', err);
195
200
  }
@@ -198,7 +203,7 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
198
203
  archiveAbortRef.current = null;
199
204
  }
200
205
  }
201
- }, [getCurrentWorkspaceId, setArchivedTasks, shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls]);
206
+ }, [getCurrentWorkspaceId, isExpectedAbortFailure, setArchivedTasks, shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls]);
202
207
  const refreshTaskCollections = useCallback(async (options) => {
203
208
  const isSilent = options?.isSilent !== false;
204
209
  const ignoreAuthGuard = options?.ignoreAuthGuard === true;
@@ -23,6 +23,46 @@ function makeAbortableRequest(payload) {
23
23
  }
24
24
  };
25
25
  }
26
+ function makeAbortableRequestRejectingWithReason(payload) {
27
+ let rejectRequest = null;
28
+ let resolveRequest = null;
29
+ const promise = new Promise((resolve, reject) => {
30
+ resolveRequest = resolve;
31
+ rejectRequest = reject;
32
+ });
33
+ return {
34
+ promise,
35
+ attach(signal) {
36
+ signal?.addEventListener('abort', () => {
37
+ rejectRequest?.(signal.reason);
38
+ }, { once: true });
39
+ queueMicrotask(() => {
40
+ if (!signal?.aborted) {
41
+ resolveRequest?.(makeAbortableFetchResponse(payload));
42
+ }
43
+ });
44
+ }
45
+ };
46
+ }
47
+ function makeControlledAbortableRequest(payload) {
48
+ let rejectRequest = null;
49
+ let resolveRequest = null;
50
+ const promise = new Promise((resolve, reject) => {
51
+ rejectRequest = reject;
52
+ resolveRequest = () => resolve(makeAbortableFetchResponse(payload));
53
+ });
54
+ return {
55
+ promise,
56
+ resolve() {
57
+ resolveRequest?.();
58
+ },
59
+ attach(signal) {
60
+ signal?.addEventListener('abort', () => {
61
+ rejectRequest?.(new DOMException('Aborted', 'AbortError'));
62
+ }, { once: true });
63
+ }
64
+ };
65
+ }
26
66
  describe('useTaskData', () => {
27
67
  beforeEach(() => {
28
68
  vi.stubGlobal('fetch', vi.fn());
@@ -116,4 +156,91 @@ describe('useTaskData', () => {
116
156
  expect(requestSignals[0]?.aborted).toBe(true);
117
157
  expect(requestSignals[1]?.aborted).toBe(false);
118
158
  });
159
+ it('does not log an error when an archive request is aborted with a string reason', async () => {
160
+ const firstRequest = makeAbortableRequestRejectingWithReason({ archived: [] });
161
+ const secondRequest = makeAbortableRequestRejectingWithReason({ archived: [] });
162
+ const fetchMock = vi.mocked(fetch);
163
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
164
+ let requestCount = 0;
165
+ fetchMock.mockImplementation((_input, init) => {
166
+ const signal = init?.signal;
167
+ requestCount += 1;
168
+ if (requestCount === 1) {
169
+ firstRequest.attach(signal);
170
+ return firstRequest.promise;
171
+ }
172
+ secondRequest.attach(signal);
173
+ return secondRequest.promise;
174
+ });
175
+ const { result } = renderHook(() => useTaskData({
176
+ tasks: [],
177
+ archivedTasks: [],
178
+ setTasks: vi.fn(),
179
+ setArchivedTasks: vi.fn(),
180
+ setLoadingTasks: vi.fn(),
181
+ getCurrentWorkspaceId: () => 'workspace-1',
182
+ workspaceResetKey: 'workspace-1',
183
+ shouldDeferProtectedApiCalls: false,
184
+ shouldBlockProtectedApiCalls: false,
185
+ handleUnauthorized: vi.fn(),
186
+ authRequiredForApi: false
187
+ }));
188
+ let firstPromise;
189
+ let secondPromise;
190
+ await act(async () => {
191
+ firstPromise = result.current.fetchArchive(true);
192
+ secondPromise = result.current.fetchArchive(true);
193
+ await secondPromise;
194
+ await firstPromise;
195
+ });
196
+ expect(consoleErrorSpy).not.toHaveBeenCalledWith('[Taskforce] Failed to fetch archive:', 'superseded');
197
+ expect(consoleErrorSpy).not.toHaveBeenCalled();
198
+ });
199
+ it('keeps loading active until the replacement tasks request finishes', async () => {
200
+ const firstRequest = makeControlledAbortableRequest({ tasks: [] });
201
+ const secondRequest = makeControlledAbortableRequest({ tasks: [] });
202
+ const fetchMock = vi.mocked(fetch);
203
+ const setLoadingTasks = vi.fn();
204
+ let requestCount = 0;
205
+ fetchMock.mockImplementation((_input, init) => {
206
+ const signal = init?.signal;
207
+ requestCount += 1;
208
+ if (requestCount === 1) {
209
+ firstRequest.attach(signal);
210
+ return firstRequest.promise;
211
+ }
212
+ secondRequest.attach(signal);
213
+ return secondRequest.promise;
214
+ });
215
+ const { result } = renderHook(() => useTaskData({
216
+ tasks: [],
217
+ archivedTasks: [],
218
+ setTasks: vi.fn(),
219
+ setArchivedTasks: vi.fn(),
220
+ setLoadingTasks,
221
+ getCurrentWorkspaceId: () => 'workspace-1',
222
+ workspaceResetKey: 'workspace-1',
223
+ shouldDeferProtectedApiCalls: false,
224
+ shouldBlockProtectedApiCalls: false,
225
+ handleUnauthorized: vi.fn(),
226
+ authRequiredForApi: false
227
+ }));
228
+ let firstPromise;
229
+ let secondPromise;
230
+ await act(async () => {
231
+ firstPromise = result.current.fetchTasks(false);
232
+ secondPromise = result.current.fetchTasks(false);
233
+ await Promise.resolve();
234
+ });
235
+ expect(setLoadingTasks).toHaveBeenNthCalledWith(1, true);
236
+ expect(setLoadingTasks).toHaveBeenNthCalledWith(2, true);
237
+ expect(setLoadingTasks).toHaveBeenCalledTimes(2);
238
+ await act(async () => {
239
+ secondRequest.resolve();
240
+ await secondPromise;
241
+ await firstPromise;
242
+ });
243
+ expect(setLoadingTasks).toHaveBeenLastCalledWith(false);
244
+ expect(setLoadingTasks).toHaveBeenCalledTimes(3);
245
+ });
119
246
  });
@@ -117,8 +117,10 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
117
117
  authRequiredForApi: boolean;
118
118
  authBlocked: boolean;
119
119
  isAuthenticated: boolean;
120
+ authUserId: string;
120
121
  authUserEmail: string;
121
122
  authUserDisplayName: string;
123
+ authUserAvatarUrl: string;
122
124
  authWorkspaceId: string;
123
125
  userGlobalSyncStatus: "error" | "idle" | "syncing" | "disconnected";
124
126
  workspaceLastPullAt: string | null;
@@ -250,6 +252,8 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
250
252
  }>;
251
253
  updateCurrentUserProfile: (input: {
252
254
  displayName: string;
255
+ avatarDraftId?: string | null;
256
+ clearAvatar?: boolean;
253
257
  }) => Promise<{
254
258
  success: boolean;
255
259
  error?: string;
@@ -258,6 +262,7 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
258
262
  userId: string;
259
263
  email: string;
260
264
  displayName: string | null;
265
+ avatarUrl: string | null;
261
266
  };
262
267
  }>;
263
268
  requestEmailVerification: (email: string) => Promise<{
@@ -45,7 +45,7 @@ import { cloneDefaultWorkflowDefinitions } from '../shared/workflowDefaults.js';
45
45
  import { resolveClientDeploymentConfig } from '../config/clientDeployment';
46
46
  import { resolveTaskforceMcpBaseUrlFromBaseUrl } from '../config/cloudEnvironment';
47
47
  import { createDefaultAssigneeOptions, createWorkspaceAssigneeOptions, mergeAssigneeOptions } from '../utils/assignees';
48
- import { persistLocalMutationActorUserId } from '../utils/taskforceApiClient';
48
+ import { persistLocalMutationActorUserId, readLocalMutationActorUserId } from '../utils/taskforceApiClient';
49
49
  import { useSyncOrchestrator } from './useSyncOrchestrator';
50
50
  import { useRealtimeSync } from './useRealtimeSync';
51
51
  import { useUiNotice } from './ui/useUiNotice';
@@ -129,8 +129,6 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
129
129
  const workspaceMode = workspaceModeRaw === 'single-local' || workspaceModeRaw === 'multi-cloud'
130
130
  ? workspaceModeRaw
131
131
  : (runtimeModeHint === 'cloud' ? 'multi-cloud' : 'single-local');
132
- const authSourceRaw = String(cfg.authSource || '').trim().toLowerCase();
133
- const authSource = authSourceRaw === 'local' ? 'local' : 'cloud';
134
132
  setRuntimeConfigOverride({
135
133
  cloudEnvironment: typeof cfg.cloudEnvironment === 'string'
136
134
  ? String(cfg.cloudEnvironment).trim().toLowerCase()
@@ -142,7 +140,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
142
140
  cloudAuthBaseUrl: normalizeBaseUrl(cfg.cloudAuthBaseUrl),
143
141
  wsBaseUrl: normalizeBaseUrl(cfg.wsBaseUrl),
144
142
  cloudAuthViaLocalProxy: Boolean(cfg.cloudAuthViaLocalProxy),
145
- authSource,
143
+ authSource: 'cloud',
146
144
  workspaceMode,
147
145
  workspaceSwitchingEnabled: typeof cfg.workspaceSwitchingEnabled === 'boolean'
148
146
  ? Boolean(cfg.workspaceSwitchingEnabled)
@@ -182,8 +180,10 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
182
180
  || '';
183
181
  const explicitConfiguredCloudAuthBaseUrl = propCloudAuthBaseUrl || envCloudAuthBaseUrl || envBaseUrl;
184
182
  const runtimeResolvedCloudAuthBaseUrl = runtimeConfigOverride.cloudAuthBaseUrl || runtimeConfigOverride.baseUrl;
185
- const normalizedCloudAuthBaseUrl = (explicitConfiguredCloudAuthBaseUrl
186
- || runtimeResolvedCloudAuthBaseUrl
183
+ const preferredCloudAuthBaseUrl = isLoopbackHost
184
+ ? (runtimeResolvedCloudAuthBaseUrl || explicitConfiguredCloudAuthBaseUrl)
185
+ : (explicitConfiguredCloudAuthBaseUrl || runtimeResolvedCloudAuthBaseUrl);
186
+ const normalizedCloudAuthBaseUrl = (preferredCloudAuthBaseUrl
187
187
  || normalizedApiBaseUrl
188
188
  || defaultCloudAuthBaseUrl).trim().replace(/\/+$/, '');
189
189
  const inferredCloudMcpBaseUrl = normalizeBaseUrl(resolveTaskforceMcpBaseUrlFromBaseUrl(runtimeConfigOverride.cloudMcpBaseUrl
@@ -208,6 +208,10 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
208
208
  const shouldGateProtectedApiCalls = shouldProbeCloudAuth && !authOnlyCloudMode;
209
209
  const cloudAuthConfigured = Boolean(normalizedCloudAuthBaseUrl || normalizedApiBaseUrl || isCloudHost);
210
210
  const initialRuntimeMode = isCloudHost ? 'cloud' : 'local';
211
+ const optimisticLocalAuthUserId = initialRuntimeMode === 'local' && isLoopbackHost && cloudAuthConfigured
212
+ ? readLocalMutationActorUserId()
213
+ : '';
214
+ const hasOptimisticLocalAuth = optimisticLocalAuthUserId.length > 0;
211
215
  const resolveApiUrl = useCallback((path) => {
212
216
  if (!path)
213
217
  return path;
@@ -310,11 +314,12 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
310
314
  : runtimeContractFallback.workspaceSwitchingEnabled;
311
315
  const [authRequiredForApi, setAuthRequiredForApi] = useState(false);
312
316
  const [authBlocked, setAuthBlocked] = useState(false);
313
- const [isAuthenticated, setIsAuthenticated] = useState(false);
314
- const [authUserId, setAuthUserId] = useState('anonymous');
317
+ const [isAuthenticated, setIsAuthenticated] = useState(hasOptimisticLocalAuth);
318
+ const [authUserId, setAuthUserId] = useState(hasOptimisticLocalAuth ? optimisticLocalAuthUserId : 'anonymous');
315
319
  const [authWorkspaceId, setAuthWorkspaceId] = useState('');
316
320
  const [authUserEmail, setAuthUserEmail] = useState('');
317
321
  const [authUserDisplayName, setAuthUserDisplayName] = useState('');
322
+ const [authUserAvatarUrl, setAuthUserAvatarUrl] = useState('');
318
323
  const [hasBetaAccess, setHasBetaAccess] = useState(true);
319
324
  const [hydratedWorkspaceRole, setHydratedWorkspaceRole] = useState(null);
320
325
  const [currentWorkspaceId, setCurrentWorkspaceId] = useState(() => {
@@ -333,13 +338,13 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
333
338
  const [availableWorkspaces, setAvailableWorkspaces] = useState([]);
334
339
  const [assigneeOptions, setAssigneeOptions] = useState(() => createDefaultAssigneeOptions());
335
340
  const [workspaceBootstrapPending, setWorkspaceBootstrapPending] = useState(false);
336
- const [authSessionResolved, setAuthSessionResolved] = useState(false);
341
+ const [authSessionResolved, setAuthSessionResolved] = useState(hasOptimisticLocalAuth);
337
342
  const [bootstrapPhase, setBootstrapPhase] = useState('idle');
338
343
  const [bootstrapError, setBootstrapError] = useState(null);
339
344
  const [bootstrapStartedAt, setBootstrapStartedAt] = useState(null);
340
345
  const authSessionRequestRef = useRef(null);
341
346
  const authSessionLastCheckedAtRef = useRef(0);
342
- const authSessionLastResultRef = useRef(false);
347
+ const authSessionLastResultRef = useRef(hasOptimisticLocalAuth);
343
348
  const currentWorkspaceIdRef = useRef(currentWorkspaceId);
344
349
  const explicitWorkspaceSelectionRef = useRef(null);
345
350
  const workspaceTransitionEpochRef = useRef(0);
@@ -622,6 +627,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
622
627
  setIsAuthenticated(false);
623
628
  setAuthUserEmail('');
624
629
  setAuthUserDisplayName('');
630
+ setAuthUserAvatarUrl('');
625
631
  setError(AUTH_REQUIRED_ERROR);
626
632
  }, [AUTH_REQUIRED_ERROR, runtimeMode]);
627
633
  // === SECTION: Task Data ===
@@ -822,6 +828,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
822
828
  persistLocalMutationActorUserId(null);
823
829
  setAuthUserEmail('');
824
830
  setAuthUserDisplayName('');
831
+ setAuthUserAvatarUrl('');
825
832
  setUserGlobalSyncStatus('disconnected');
826
833
  setUserGlobalSyncError(null);
827
834
  setHasBetaAccess(true);
@@ -881,6 +888,9 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
881
888
  const userDisplayName = typeof data.displayName === 'string'
882
889
  ? data.displayName.trim()
883
890
  : '';
891
+ const userAvatarUrl = typeof data.avatarUrl === 'string'
892
+ ? data.avatarUrl.trim()
893
+ : '';
884
894
  const isLocalRuntime = runtimeMode === 'local';
885
895
  const keepLocalWorkspaceContext = runtimeMode === 'local' || authOnlyCloudMode;
886
896
  setAuthRequiredForApi(authOnlyCloudMode ? false : (isLocalRuntime ? false : required));
@@ -892,6 +902,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
892
902
  }
893
903
  setAuthUserEmail(authenticated ? userEmail : '');
894
904
  setAuthUserDisplayName(authenticated ? userDisplayName : '');
905
+ setAuthUserAvatarUrl(authenticated ? userAvatarUrl : '');
895
906
  if (authenticated) {
896
907
  setError((prev) => prev === AUTH_REQUIRED_ERROR ? '' : prev);
897
908
  }
@@ -1769,7 +1780,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1769
1780
  if (currentMemberRole === 'owner' || currentMemberRole === 'admin' || currentMemberRole === 'member' || currentMemberRole === 'read-only') {
1770
1781
  setHydratedWorkspaceRole(currentMemberRole);
1771
1782
  }
1772
- else if (!workspaceSwitchingEnabled) {
1783
+ else if (!workspaceSwitchingEnabled && users.length === 0) {
1773
1784
  setHydratedWorkspaceRole(null);
1774
1785
  }
1775
1786
  const workspaceMemberCandidates = users
@@ -1793,7 +1804,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1793
1804
  candidate
1794
1805
  ])).values()).filter((candidate) => String(candidate.userId || '').trim().length > 0);
1795
1806
  }
1796
- else if (!workspaceSwitchingEnabled) {
1807
+ else if (!workspaceSwitchingEnabled && !fallbackCandidates.length) {
1797
1808
  setHydratedWorkspaceRole(null);
1798
1809
  }
1799
1810
  const mergedOptions = mergeAssigneeOptions([
@@ -2233,6 +2244,8 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
2233
2244
  return { success: false, error: 'Cloud auth endpoint is not configured.' };
2234
2245
  }
2235
2246
  const trimmedDisplayName = String(input.displayName || '').trim();
2247
+ const trimmedAvatarDraftId = typeof input.avatarDraftId === 'string' ? input.avatarDraftId.trim() : '';
2248
+ const shouldClearAvatar = input.clearAvatar === true;
2236
2249
  if (!trimmedDisplayName) {
2237
2250
  return { success: false, error: 'Display name is required.' };
2238
2251
  }
@@ -2241,7 +2254,11 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
2241
2254
  method: 'POST',
2242
2255
  headers: { 'Content-Type': 'application/json' },
2243
2256
  credentials: 'include',
2244
- body: JSON.stringify({ displayName: trimmedDisplayName })
2257
+ body: JSON.stringify({
2258
+ displayName: trimmedDisplayName,
2259
+ ...(trimmedAvatarDraftId ? { avatarDraftId: trimmedAvatarDraftId } : {}),
2260
+ ...(shouldClearAvatar ? { clearAvatar: true } : {})
2261
+ })
2245
2262
  });
2246
2263
  const data = await res.json().catch(() => ({}));
2247
2264
  if (!res.ok || !data?.success) {
@@ -2250,9 +2267,11 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
2250
2267
  const profile = {
2251
2268
  userId: typeof data?.profile?.userId === 'string' ? data.profile.userId : authUserId,
2252
2269
  email: typeof data?.profile?.email === 'string' ? data.profile.email : authUserEmail,
2253
- displayName: typeof data?.profile?.displayName === 'string' ? data.profile.displayName : null
2270
+ displayName: typeof data?.profile?.displayName === 'string' ? data.profile.displayName : null,
2271
+ avatarUrl: typeof data?.profile?.avatarUrl === 'string' ? data.profile.avatarUrl : null
2254
2272
  };
2255
2273
  setAuthUserDisplayName(profile.displayName || '');
2274
+ setAuthUserAvatarUrl(profile.avatarUrl || '');
2256
2275
  return { success: true, profile };
2257
2276
  }
2258
2277
  catch {
@@ -2505,13 +2524,20 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
2505
2524
  // no-op
2506
2525
  }
2507
2526
  finally {
2527
+ authSessionRequestRef.current = null;
2528
+ authSessionLastResultRef.current = false;
2529
+ authSessionLastCheckedAtRef.current = Date.now();
2508
2530
  setIsAuthenticated(false);
2531
+ setAuthSessionResolved(true);
2509
2532
  setAuthUserId('anonymous');
2533
+ setAuthWorkspaceId('');
2510
2534
  persistLocalMutationActorUserId(null);
2511
2535
  setAuthUserEmail('');
2512
2536
  setAuthUserDisplayName('');
2537
+ setAuthUserAvatarUrl('');
2513
2538
  setUserGlobalSyncStatus('disconnected');
2514
2539
  setUserGlobalSyncError(null);
2540
+ setHasBetaAccess(true);
2515
2541
  setAuthBlocked(runtimeMode === 'cloud' && authRequiredForApi);
2516
2542
  setCurrentWorkspaceId((prev) => {
2517
2543
  const existing = String(prev || '').trim();
@@ -2527,11 +2553,31 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
2527
2553
  });
2528
2554
  setAvailableWorkspaces([]);
2529
2555
  workspaceListHydratedRef.current = false;
2556
+ setHydratedWorkspaceRole(null);
2557
+ if (runtimeMode === 'local') {
2558
+ await Promise.allSettled([
2559
+ fetchConfig({ ignoreAuthGuard: true }),
2560
+ fetchTasks(true, { ignoreAuthGuard: true }),
2561
+ fetchPlanningEntities({ ignoreAuthGuard: true }),
2562
+ fetchReferenceData({ ignoreAuthGuard: true, force: true })
2563
+ ]);
2564
+ return;
2565
+ }
2530
2566
  setTasks([]);
2531
2567
  setInitiatives([]);
2532
2568
  setWorkstreams([]);
2533
2569
  }
2534
- }, [authRequiredForApi, resolveCloudAuthUrl, cloudAuthConfigured, workspaceSelectionScope, runtimeMode]);
2570
+ }, [
2571
+ authRequiredForApi,
2572
+ resolveCloudAuthUrl,
2573
+ cloudAuthConfigured,
2574
+ workspaceSelectionScope,
2575
+ runtimeMode,
2576
+ fetchConfig,
2577
+ fetchPlanningEntities,
2578
+ fetchReferenceData,
2579
+ fetchTasks
2580
+ ]);
2535
2581
  const createInitiativeFromTemplate = useCallback(async (payload) => {
2536
2582
  try {
2537
2583
  const res = await fetch('/api/taskforce/initiative-templates/create', {
@@ -3456,8 +3502,10 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
3456
3502
  authRequiredForApi,
3457
3503
  authBlocked,
3458
3504
  isAuthenticated,
3505
+ authUserId,
3459
3506
  authUserEmail,
3460
3507
  authUserDisplayName,
3508
+ authUserAvatarUrl,
3461
3509
  authWorkspaceId,
3462
3510
  userGlobalSyncStatus,
3463
3511
  workspaceLastPullAt,