@taskforcehq/taskforce 0.3.306 → 0.3.307

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 (215) hide show
  1. package/README.md +0 -2
  2. package/dist/Taskforce.module.css +317 -27
  3. package/dist/TaskforceCore.js +1 -0
  4. package/dist/TaskforceCore.test.js +178 -0
  5. package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
  6. package/dist/components/activity/EntityActivityTimeline.js +81 -0
  7. package/dist/components/context/ContextAttachmentManager.js +15 -3
  8. package/dist/components/features/AgentsModule.d.ts +5 -1
  9. package/dist/components/features/AgentsModule.js +142 -39
  10. package/dist/components/features/AgentsModule.test.js +260 -11
  11. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
  12. package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
  13. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
  14. package/dist/components/features/DocumentIndex.d.ts +2 -2
  15. package/dist/components/features/DocumentIndex.js +3 -4
  16. package/dist/components/features/DocumentViewer.d.ts +3 -1
  17. package/dist/components/features/DocumentViewer.js +6 -5
  18. package/dist/components/features/DocumentWorkspace.d.ts +10 -2
  19. package/dist/components/features/DocumentWorkspace.js +15 -4
  20. package/dist/components/features/DocumentWorkspace.test.js +20 -3
  21. package/dist/components/features/TaskSettings.d.ts +2 -0
  22. package/dist/components/features/TaskSettings.js +83 -78
  23. package/dist/components/features/TaskSettings.test.js +171 -82
  24. package/dist/components/task/TaskActionHeader.js +11 -5
  25. package/dist/components/task/TaskActionHeader.test.js +10 -0
  26. package/dist/components/task/TaskCard.js +6 -2
  27. package/dist/components/task/TaskContextUpload.test.js +3 -3
  28. package/dist/components/task/TaskForm.js +14 -64
  29. package/dist/components/task/TaskKanban.js +0 -8
  30. package/dist/components/task/TaskList.js +5 -1
  31. package/dist/components/task/TaskList.test.js +4 -0
  32. package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
  33. package/dist/components/ui/HierarchyActionBadge.js +5 -0
  34. package/dist/components/views/AppShellHeader.d.ts +3 -1
  35. package/dist/components/views/AppShellHeader.js +27 -11
  36. package/dist/components/views/AppShellHeader.test.js +18 -1
  37. package/dist/components/views/PlanComparisonPage.js +9 -1
  38. package/dist/components/views/PlanComparisonPage.test.js +19 -0
  39. package/dist/components/views/PlansPage.js +1 -1
  40. package/dist/components/views/StandaloneLayout.js +256 -69
  41. package/dist/components/views/WidgetView.js +10 -8
  42. package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
  43. package/dist/components/views/panels/PlanningDrawer.js +259 -51
  44. package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
  45. package/dist/config/envSchema.js +1 -0
  46. package/dist/core/AiProfileService.d.ts +7 -0
  47. package/dist/core/AiProfileService.js +460 -35
  48. package/dist/core/AiProfiles.test.js +648 -2
  49. package/dist/core/AttachmentLinkService.d.ts +3 -0
  50. package/dist/core/AttachmentLinkService.js +58 -13
  51. package/dist/core/GlobalSettingsService.js +12 -0
  52. package/dist/core/McpTokenService.d.ts +12 -0
  53. package/dist/core/McpTokenService.js +44 -1
  54. package/dist/core/PlanningEntities.test.js +366 -4
  55. package/dist/core/TaskActivityService.d.ts +28 -1
  56. package/dist/core/TaskActivityService.js +227 -46
  57. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  58. package/dist/core/TaskActivitySync.test.js +90 -0
  59. package/dist/core/TaskAuditTimeline.test.js +96 -0
  60. package/dist/core/Taskforce.d.ts +190 -26
  61. package/dist/core/Taskforce.ids.test.js +58 -0
  62. package/dist/core/Taskforce.js +775 -71
  63. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  64. package/dist/core/types.d.ts +7 -0
  65. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
  66. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  67. package/dist/hooks/bootstrapTimeouts.js +8 -0
  68. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  69. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  70. package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
  71. package/dist/hooks/sync/orchestratorShared.js +61 -1
  72. package/dist/hooks/sync/recovery.js +8 -0
  73. package/dist/hooks/sync/recovery.test.js +5 -5
  74. package/dist/hooks/sync/transfers.d.ts +2 -1
  75. package/dist/hooks/sync/transfers.js +36 -9
  76. package/dist/hooks/sync/transfers.test.js +534 -2
  77. package/dist/hooks/useFilterSort.js +13 -13
  78. package/dist/hooks/useFilterSort.test.js +87 -1
  79. package/dist/hooks/useSyncOrchestrator.js +50 -5
  80. package/dist/hooks/useTaskforce.d.ts +85 -50
  81. package/dist/hooks/useTaskforce.js +44 -14
  82. package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
  83. package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
  84. package/dist/hooks/useWorkspaceSyncController.js +7 -1
  85. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  86. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
  87. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  88. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  89. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  90. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  91. package/dist/mcp/clientIntegrations.d.ts +1 -0
  92. package/dist/mcp/clientIntegrations.js +25 -0
  93. package/dist/mcp/clientIntegrations.test.js +14 -3
  94. package/dist/mcp/clientProfileDefaults.js +3 -3
  95. package/dist/mcp/collaborationRegistrar.js +7 -7
  96. package/dist/mcp/documentAssetRegistrar.js +113 -7
  97. package/dist/mcp/runtime.d.ts +1 -0
  98. package/dist/mcp/runtime.js +969 -122
  99. package/dist/mcp/runtime.test.js +1083 -11
  100. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  101. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  102. package/dist/mcp/toolRegistry.d.ts +56 -0
  103. package/dist/mcp/toolRegistry.js +8 -0
  104. package/dist/mcp/toolRegistry.test.js +4 -0
  105. package/dist/migrations/taskSchemaMigrations.js +21 -0
  106. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  107. package/dist/server/aiProfileAvatarAssets.js +319 -0
  108. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  109. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  110. package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
  111. package/dist/server/index.js +4 -0
  112. package/dist/server/index.test.js +6 -1
  113. package/dist/server/routes/admin.js +187 -9
  114. package/dist/server/routes/annotatedAttachments.js +51 -0
  115. package/dist/server/routes/auth.d.ts +11 -0
  116. package/dist/server/routes/auth.js +189 -27
  117. package/dist/server/routes/authSupport.d.ts +4 -0
  118. package/dist/server/routes/authSupport.js +12 -1
  119. package/dist/server/routes/billing.js +11 -0
  120. package/dist/server/routes/billing.test.js +3 -0
  121. package/dist/server/routes/documents.js +10 -0
  122. package/dist/server/routes/shared.js +0 -10
  123. package/dist/server/routes/sync.integration.test.js +221 -0
  124. package/dist/server/routes/sync.js +13 -3
  125. package/dist/server/routes/syncSnapshotRoutes.js +2 -0
  126. package/dist/server/routes/tasks.js +12 -9
  127. package/dist/server/routes.js +87 -8
  128. package/dist/server/routes.test.js +890 -22
  129. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  130. package/dist/shared/aiProfileSurfaceType.js +10 -8
  131. package/dist/storage/objectStorage.d.ts +1 -0
  132. package/dist/storage/objectStorage.js +2 -1
  133. package/dist/storage/objectStorageClient.d.ts +1 -0
  134. package/dist/storage/objectStorageClient.js +41 -8
  135. package/dist/storage/objectStorageClient.test.js +26 -1
  136. package/dist/storage/objectStorageConfig.js +3 -1
  137. package/dist/storage/objectStorageConfig.test.js +13 -1
  138. package/dist/sync/collaborationSyncPayload.js +2 -0
  139. package/dist/sync/collaborationSyncPayload.test.js +1 -0
  140. package/dist/sync/collaborationSyncState.d.ts +3 -3
  141. package/dist/sync/collaborationSyncState.js +19 -12
  142. package/dist/sync/contentSyncPayload.d.ts +5 -1
  143. package/dist/sync/contentSyncPayload.js +9 -0
  144. package/dist/sync/contentSyncPayload.test.js +39 -0
  145. package/dist/sync/contentSyncState.d.ts +5 -1
  146. package/dist/sync/contentSyncState.js +231 -37
  147. package/dist/sync/planningSyncPayload.js +5 -0
  148. package/dist/sync/planningSyncPayload.test.js +24 -0
  149. package/dist/sync/syncApplyHandlers.d.ts +4 -4
  150. package/dist/sync/syncApplyHandlers.js +89 -31
  151. package/dist/sync/syncApplyHandlers.test.js +184 -4
  152. package/dist/sync/syncResourceAdapters.d.ts +2 -0
  153. package/dist/sync/syncResourceAdapters.js +2 -0
  154. package/dist/sync/syncService.d.ts +1 -0
  155. package/dist/sync/syncService.js +13 -1
  156. package/dist/sync/syncService.test.js +39 -0
  157. package/dist/sync/workspacePullFeed.d.ts +4 -0
  158. package/dist/sync/workspacePullFeed.js +129 -70
  159. package/dist/sync/workspacePullFeed.test.js +49 -0
  160. package/dist/sync/workspaceSyncModel.d.ts +28 -6
  161. package/dist/sync/workspaceSyncModel.js +159 -45
  162. package/dist/sync/workspaceSyncModel.test.js +269 -3
  163. package/dist/types.d.ts +33 -2
  164. package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
  165. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
  166. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  167. package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
  168. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  169. package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
  170. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  171. package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
  172. package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-D4XseTwW.js} +1 -1
  173. package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
  174. package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
  175. package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
  176. package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
  177. package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-oOA9bcdK.js} +4 -4
  178. package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
  179. package/dist/ui/assets/index-607WPo_X.js +4 -0
  180. package/dist/ui/assets/index-CYyodXsg.css +1 -0
  181. package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
  182. package/dist/ui/index.html +3 -3
  183. package/dist/ui/product/agent manager.png +0 -0
  184. package/dist/ui/product/agent_manager_02.png +0 -0
  185. package/dist/utils/aiProfileEvents.d.ts +7 -0
  186. package/dist/utils/aiProfileEvents.js +6 -0
  187. package/dist/utils/assignees.d.ts +6 -0
  188. package/dist/utils/assignees.js +8 -1
  189. package/dist/utils/assignees.test.d.ts +1 -0
  190. package/dist/utils/assignees.test.js +25 -0
  191. package/dist/utils/avatarDisplay.d.ts +1 -0
  192. package/dist/utils/avatarDisplay.js +15 -0
  193. package/dist/utils/contextFiles.d.ts +3 -1
  194. package/dist/utils/contextFiles.js +2 -1
  195. package/dist/utils/taskActivity.d.ts +7 -1
  196. package/dist/utils/taskActivity.js +63 -1
  197. package/dist/utils/taskActivity.test.js +23 -1
  198. package/dist/utils/taskSearch.d.ts +22 -0
  199. package/dist/utils/taskSearch.js +87 -0
  200. package/dist/utils/taskSearch.test.d.ts +1 -0
  201. package/dist/utils/taskSearch.test.js +69 -0
  202. package/dist/utils/taskUtils.js +10 -3
  203. package/dist/utils/taskUtils.test.d.ts +1 -0
  204. package/dist/utils/taskUtils.test.js +19 -0
  205. package/package.json +1 -1
  206. package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
  207. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  208. package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
  209. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
  210. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
  211. package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
  212. package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
  213. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
  214. package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
  215. package/dist/ui/assets/index-C6k75jr8.js +0 -6
@@ -26,7 +26,8 @@ function makeTask(overrides) {
26
26
  comments: overrides.comments || [],
27
27
  attachments: overrides.attachments || [],
28
28
  activity: overrides.activity || [],
29
- taskEvents: overrides.taskEvents || []
29
+ taskEvents: overrides.taskEvents || [],
30
+ referenceLabel: overrides.referenceLabel
30
31
  };
31
32
  }
32
33
  describe('useFilterSort', () => {
@@ -107,6 +108,91 @@ describe('useFilterSort', () => {
107
108
  expect(result.current.searchAgnosticTasks.map((task) => task.id)).toEqual(['known-task', 'unknown-task']);
108
109
  expect(result.current.filteredArchive.map((task) => task.id)).toEqual(['unknown-archived-task']);
109
110
  });
111
+ it('matches task reference labels when the search query has surrounding spaces or pasted formatting', async () => {
112
+ const matchingTask = makeTask({
113
+ id: 'matching-task',
114
+ title: 'Reference match',
115
+ referenceLabel: 'T-123'
116
+ });
117
+ const otherTask = makeTask({
118
+ id: 'other-task',
119
+ title: 'Other task',
120
+ referenceLabel: 'T-456'
121
+ });
122
+ const archivedMatch = makeTask({
123
+ id: 'archived-match',
124
+ title: 'Archived reference match',
125
+ referenceLabel: 'T-123',
126
+ isArchived: true
127
+ });
128
+ const { result } = renderHook(() => useFilterSort({
129
+ tasks: [matchingTask, otherTask],
130
+ archivedTasks: [archivedMatch],
131
+ activeCategories,
132
+ activeTypes,
133
+ priorities,
134
+ taxonomies: [],
135
+ configLoaded: true,
136
+ referenceDataLoaded: true,
137
+ assigneeOptionsLoaded: true,
138
+ assigneeOptions
139
+ }));
140
+ await waitFor(() => {
141
+ expect(result.current.hasInitedFilters).toBe(true);
142
+ });
143
+ act(() => {
144
+ result.current.setSearchQuery(' T - 123 ');
145
+ });
146
+ expect(result.current.filteredTasks.map((task) => task.id)).toEqual(['matching-task']);
147
+ expect(result.current.filteredArchive.map((task) => task.id)).toEqual(['archived-match']);
148
+ act(() => {
149
+ result.current.setSearchQuery('t–123');
150
+ });
151
+ expect(result.current.filteredTasks.map((task) => task.id)).toEqual(['matching-task']);
152
+ act(() => {
153
+ result.current.setSearchQuery(' ');
154
+ });
155
+ expect(result.current.filteredTasks.map((task) => task.id)).toEqual(['matching-task', 'other-task']);
156
+ });
157
+ it('matches tokenized search text and task metadata labels', async () => {
158
+ const matchingTask = makeTask({
159
+ id: 'matching-task',
160
+ title: 'Billing API cleanup',
161
+ category: 'ops',
162
+ priority: 3,
163
+ assignee: 'member-1'
164
+ });
165
+ const otherTask = makeTask({
166
+ id: 'other-task',
167
+ title: 'Marketing copy cleanup',
168
+ category: 'default',
169
+ priority: 1,
170
+ assignee: 'unassigned'
171
+ });
172
+ const { result } = renderHook(() => useFilterSort({
173
+ tasks: [matchingTask, otherTask],
174
+ archivedTasks: [],
175
+ activeCategories,
176
+ activeTypes,
177
+ priorities,
178
+ taxonomies: [],
179
+ configLoaded: true,
180
+ referenceDataLoaded: true,
181
+ assigneeOptionsLoaded: true,
182
+ assigneeOptions
183
+ }));
184
+ await waitFor(() => {
185
+ expect(result.current.hasInitedFilters).toBe(true);
186
+ });
187
+ act(() => {
188
+ result.current.setSearchQuery('api billing');
189
+ });
190
+ expect(result.current.filteredTasks.map((task) => task.id)).toEqual(['matching-task']);
191
+ act(() => {
192
+ result.current.setSearchQuery('Member One');
193
+ });
194
+ expect(result.current.filteredTasks.map((task) => task.id)).toEqual(['matching-task']);
195
+ });
110
196
  it('still excludes unknown values when the user selects a subset filter', async () => {
111
197
  const knownTask = makeTask({
112
198
  id: 'known-task',
@@ -26,6 +26,7 @@ import { useWorkspaceSyncController } from './useWorkspaceSyncController';
26
26
  import { normalizeTaskforceTheme } from '../utils/theme';
27
27
  import { useRealtimeSync } from './useRealtimeSync';
28
28
  import { TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT } from '../utils/contextAssetEvents';
29
+ import { TASKFORCE_AI_PROFILES_MUTATED_EVENT } from '../utils/aiProfileEvents';
29
30
  import { setLocale as setAppLocale } from '../localization';
30
31
  import { normalizeTaskFromApi } from '../utils/taskNormalization.js';
31
32
  import { normalizeSyncEventDetails } from '../utils/syncEventDetails';
@@ -642,7 +643,7 @@ export function useSyncOrchestrator(input) {
642
643
  return buildWorkspaceSyncPayloadModel({
643
644
  tasks: workspaceTasksRef.current,
644
645
  archivedTasks: workspaceArchivedTasksRef.current,
645
- taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current),
646
+ taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current, workspaceInitiativesRef.current, workspaceWorkstreamsRef.current),
646
647
  lastPushedTaskIds: workspaceLastPushedTaskIdsRef.current,
647
648
  pendingDeletedTaskIds: workspaceDeletedTaskIdsRef.current,
648
649
  pendingDeletedTaskWatermarks: workspaceDeletedTaskWatermarksRef.current,
@@ -720,7 +721,7 @@ export function useSyncOrchestrator(input) {
720
721
  workspaceId: currentWorkspaceId,
721
722
  tasks: workspaceTasksRef.current,
722
723
  archivedTasks: workspaceArchivedTasksRef.current,
723
- taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current),
724
+ taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current, workspaceInitiativesRef.current, workspaceWorkstreamsRef.current),
724
725
  pendingDeletedTaskIds: workspaceDeletedTaskIdsRef.current,
725
726
  initiatives: workspaceInitiativesRef.current,
726
727
  workstreams: workspaceWorkstreamsRef.current,
@@ -767,10 +768,11 @@ export function useSyncOrchestrator(input) {
767
768
  isArchived: Boolean(workstream.isArchived)
768
769
  }))
769
770
  .sort((left, right) => left.id.localeCompare(right.id)),
770
- taskEvents: flattenTaskEvents(tasks || [], archivedTasks || [])
771
+ taskEvents: flattenTaskEvents(tasks || [], archivedTasks || [], initiatives || [], workstreams || [])
771
772
  .map((event) => ({
772
773
  id: String(event.id || '').trim(),
773
- taskId: String(event.taskId || '').trim(),
774
+ entityType: String(event.entityType || 'task').trim(),
775
+ entityId: String(event.entityId || event.taskId || '').trim(),
774
776
  createdAt: String(event.createdAt || '').trim(),
775
777
  action: String(event.action || '').trim(),
776
778
  }))
@@ -1005,6 +1007,37 @@ export function useSyncOrchestrator(input) {
1005
1007
  markWorkspaceSyncReferenceSnapshotReady('assets');
1006
1008
  }
1007
1009
  }, [runtimeMode, currentWorkspaceId, cloudAuthConfigured, authSessionResolved, isAuthenticated, markWorkspaceSyncReferenceSnapshotReady]);
1010
+ const refreshWorkspaceSyncPlanningEntitiesSnapshot = useCallback(async () => {
1011
+ if (runtimeMode !== 'local')
1012
+ return;
1013
+ if (!isValidWorkspaceKey(currentWorkspaceId))
1014
+ return;
1015
+ if (cloudAuthConfigured && (!authSessionResolved || !isAuthenticated))
1016
+ return;
1017
+ try {
1018
+ const res = await fetch(`/api/taskforce/planning/bootstrap?workspaceId=${encodeURIComponent(currentWorkspaceId)}`, {
1019
+ method: 'GET',
1020
+ credentials: 'include'
1021
+ });
1022
+ if (!res.ok)
1023
+ return;
1024
+ const data = await res.json().catch(() => ({}));
1025
+ const nextInitiatives = Array.isArray(data?.initiatives)
1026
+ ? data.initiatives.filter((entry) => (Boolean(entry && typeof entry === 'object' && typeof entry.id === 'string')))
1027
+ : [];
1028
+ const nextWorkstreams = Array.isArray(data?.workstreams)
1029
+ ? data.workstreams.filter((entry) => (Boolean(entry && typeof entry === 'object' && typeof entry.id === 'string')))
1030
+ : [];
1031
+ workspaceInitiativesRef.current = nextInitiatives;
1032
+ workspaceWorkstreamsRef.current = nextWorkstreams;
1033
+ }
1034
+ catch (error) {
1035
+ logSyncDebug('planning_snapshot_refresh_failed', {
1036
+ workspaceId: currentWorkspaceId,
1037
+ error: String(error?.message || error || '')
1038
+ });
1039
+ }
1040
+ }, [runtimeMode, currentWorkspaceId, cloudAuthConfigured, authSessionResolved, isAuthenticated]);
1008
1041
  const refreshWorkspaceSyncDocumentReviewSessionsSnapshot = useCallback(async () => {
1009
1042
  if (runtimeMode !== 'local')
1010
1043
  return;
@@ -1196,7 +1229,7 @@ export function useSyncOrchestrator(input) {
1196
1229
  workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map(workspaceSyncAnnotatedAttachmentSessionsRef.current
1197
1230
  .map((entry) => [String(entry?.id || '').trim(), String(entry?.updatedAt || entry?.createdAt || '').trim()])
1198
1231
  .filter(([id]) => id.length > 0));
1199
- workspaceLastPushedTaskEventWatermarksRef.current = new Map(flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current)
1232
+ workspaceLastPushedTaskEventWatermarksRef.current = new Map(flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current, workspaceInitiativesRef.current, workspaceWorkstreamsRef.current)
1200
1233
  .map((entry) => [String(entry?.id || '').trim(), String(entry?.createdAt || '').trim()])
1201
1234
  .filter(([id]) => id.length > 0));
1202
1235
  const persisted = persistWorkspaceSyncWatermarksSnapshotBestEffort();
@@ -1292,6 +1325,7 @@ export function useSyncOrchestrator(input) {
1292
1325
  refreshWorkspaceSyncMarkdownDocumentsSnapshot,
1293
1326
  refreshWorkspaceSyncAiProfilesSnapshot,
1294
1327
  refreshWorkspaceSyncAssetsSnapshot,
1328
+ refreshWorkspaceSyncPlanningEntitiesSnapshot,
1295
1329
  refreshWorkspaceSyncDocumentReviewSessionsSnapshot,
1296
1330
  refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot,
1297
1331
  refreshLocalWorkspaceTaskCollections,
@@ -1373,6 +1407,7 @@ export function useSyncOrchestrator(input) {
1373
1407
  refreshWorkspaceSyncMarkdownDocumentsSnapshot,
1374
1408
  refreshWorkspaceSyncAiProfilesSnapshot,
1375
1409
  refreshWorkspaceSyncAssetsSnapshot,
1410
+ refreshWorkspaceSyncPlanningEntitiesSnapshot,
1376
1411
  refreshWorkspaceSyncDocumentReviewSessionsSnapshot,
1377
1412
  refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot,
1378
1413
  refreshLocalWorkspaceTaskCollections,
@@ -1656,7 +1691,16 @@ export function useSyncOrchestrator(input) {
1656
1691
  return;
1657
1692
  refreshWorkspaceSyncContextAssetSnapshots();
1658
1693
  };
1694
+ const handleAiProfilesMutated = (event) => {
1695
+ const detail = event.detail;
1696
+ const eventWorkspaceId = String(detail?.workspaceId || '').trim() || 'default';
1697
+ if (eventWorkspaceId !== currentWorkspaceId)
1698
+ return;
1699
+ void refreshWorkspaceSyncAiProfilesSnapshot();
1700
+ void refreshWorkspaceSyncAssetsSnapshot();
1701
+ };
1659
1702
  window.addEventListener(TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT, handleContextAssetsMutated);
1703
+ window.addEventListener(TASKFORCE_AI_PROFILES_MUTATED_EVENT, handleAiProfilesMutated);
1660
1704
  const docsIntervalId = window.setInterval(() => {
1661
1705
  void refreshWorkspaceSyncMarkdownDocumentsSnapshot();
1662
1706
  }, workspaceDocumentsRefreshIntervalMs);
@@ -1674,6 +1718,7 @@ export function useSyncOrchestrator(input) {
1674
1718
  }, workspaceDocumentsRefreshIntervalMs);
1675
1719
  return () => {
1676
1720
  window.removeEventListener(TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT, handleContextAssetsMutated);
1721
+ window.removeEventListener(TASKFORCE_AI_PROFILES_MUTATED_EVENT, handleAiProfilesMutated);
1677
1722
  window.clearInterval(docsIntervalId);
1678
1723
  window.clearInterval(aiProfilesIntervalId);
1679
1724
  window.clearInterval(assetsIntervalId);
@@ -1,3 +1,38 @@
1
+ /**
2
+ * useTaskforce
3
+ *
4
+ * Central React hook for the Taskforce task-management system.
5
+ * Orchestrates all application state: runtime config, auth, workspace,
6
+ * bootstrap, task data, filtering/sorting, form editing, settings,
7
+ * sync, and UI notices.
8
+ *
9
+ * SECTION MAP (use Ctrl+F on the === markers to jump):
10
+ * === SECTION: Runtime Config === Line ~189 — URL resolution, cloud/local mode detection
11
+ * === SECTION: Settings State === Line ~388 — Theme, shortcut, locale, feature flags
12
+ * === SECTION: Workspace Storage === Line ~415 — workspaceStorageScope, project root/name
13
+ * === SECTION: Auth State === Line ~426 — isAuthenticated, authBlocked, userId, beta access
14
+ * === SECTION: Bootstrap State === Line ~442 — bootstrapPhase, setupState, runtimeCapabilities
15
+ * === SECTION: Fetch Intercept === Line ~490 — window.fetch patch for cloud API routing
16
+ * === SECTION: Workflow Templates === Line ~577 — fetchWorkflows, fetchInitiativeTemplates
17
+ * === SECTION: Export State === Line ~678 — exportEnvironment, exportWorkflowsPath
18
+ * === SECTION: Task State === Line ~1065 — tasks, archivedTasks, editingTaskId
19
+ * === SECTION: Filter & Sort State === Line ~1394 — searchQuery, filterCategories, sortBy, sortOrder
20
+ * === SECTION: UI State === Line ~1469 — uiStateReady, persisted UI preferences
21
+ * === SECTION: Config Fetch === Line ~1578 — fetchConfig (the main bootstrap loader)
22
+ * === SECTION: Workspace Actions === Line ~1855 — fetchWorkspaces, switchWorkspace, createWorkspace
23
+ * === SECTION: Auth Actions === Line ~2210 — loginWithCredentials, logout, invite flows
24
+ * === SECTION: Task Fetch === Line ~2158 — fetchTasks, fetchArchive, refreshTaskCollections
25
+ * === SECTION: Bootstrap Effects === Line ~2641 — initialization useEffects
26
+ * === SECTION: Filtering Logic === Line ~3068 — filteredTasks, searchAgnosticTasks, filteredArchive
27
+ * === SECTION: Navigation === Line ~3410 — setActiveTab, handleNavigation
28
+ * === SECTION: Form === Line ~3489 — resetForm, handleEdit, handleSubmit, saveTask
29
+ * === SECTION: Task CRUD === Line ~3691 — handleDelete, handleUpdateTask, handleToggle*
30
+ * === SECTION: Planning Links === Line ~4129 — workstream and initiative link handlers
31
+ * === SECTION: Category/Type Mgmt === Line ~4462 — handleSaveCategory, handleRemoveCategory
32
+ * === SECTION: Settings Model === Line ~4625 — settingsModel object
33
+ * === SECTION: Return Value === Line ~4817 — final return object (public API shape)
34
+ */
35
+ import { type Dispatch, type SetStateAction } from 'react';
1
36
  import type { TaskforceConfig, TaskforceTheme, Comment, AttachmentItem, CategoryConfig, TaskItem, TaxonomyDefinition, Environment, TaskAssignee, TaskStatus, DeletedTaskRecord, InitiativeItem, WorkstreamItem, ChecklistItem, PlanningBootstrapWorkstreamTaskSummary } from '../types';
2
37
  import { type FirstClassTaxonomyDisplayLabels } from '../shared/taxonomyState.js';
3
38
  import type { UiNotice } from '../utils/uiNotice';
@@ -106,31 +141,31 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
106
141
  showTaskCardStatusLabel: boolean;
107
142
  };
108
143
  isOpen: boolean;
109
- setIsOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
144
+ setIsOpen: Dispatch<SetStateAction<boolean>>;
110
145
  activeTab: "tasks" | "settings" | "add";
111
146
  setActiveTab: (tab: "add" | "tasks" | "settings") => void;
112
147
  currentTheme: "light" | "dawn" | "dark" | "midnight";
113
- setCurrentTheme: import("react").Dispatch<import("react").SetStateAction<"light" | "dawn" | "dark" | "midnight">>;
148
+ setCurrentTheme: Dispatch<SetStateAction<"light" | "dawn" | "dark" | "midnight">>;
114
149
  configLoaded: boolean;
115
150
  storagePath: string;
116
151
  saveSettings: () => Promise<boolean>;
117
152
  pathSaved: boolean;
118
153
  keyShortcut: string;
119
- setKeyShortcut: import("react").Dispatch<import("react").SetStateAction<string>>;
154
+ setKeyShortcut: Dispatch<SetStateAction<string>>;
120
155
  globalWeekStartsOn: WeekStartsOn;
121
156
  locale: "en-US" | "es-419" | "pt-BR";
122
157
  supportedLocales: ("en-US" | "es-419" | "pt-BR")[];
123
158
  saveLocale: (nextLocale: string) => Promise<boolean>;
124
159
  jsonBackupEnabled: boolean;
125
- setJsonBackupEnabled: import("react").Dispatch<import("react").SetStateAction<boolean>>;
160
+ setJsonBackupEnabled: Dispatch<SetStateAction<boolean>>;
126
161
  manualComplexityEnabled: boolean;
127
162
  checklistDropdownEnabled: boolean;
128
163
  showTaskCardStatusLabel: boolean;
129
- setManualComplexityEnabled: import("react").Dispatch<import("react").SetStateAction<boolean>>;
164
+ setManualComplexityEnabled: Dispatch<SetStateAction<boolean>>;
130
165
  mcpHostRoot: string;
131
- setMcpHostRoot: import("react").Dispatch<import("react").SetStateAction<string>>;
166
+ setMcpHostRoot: Dispatch<SetStateAction<string>>;
132
167
  settingsSection: "mcp" | "categories" | "types" | "general" | "agents" | "taxonomy" | "documents" | "appearance-application" | "appearance-task-cards" | "appearance-task-forms" | "commands" | "workflow-editor" | "integrations" | "resources" | null;
133
- setSettingsSection: import("react").Dispatch<import("react").SetStateAction<"mcp" | "categories" | "types" | "general" | "agents" | "taxonomy" | "documents" | "appearance-application" | "appearance-task-cards" | "appearance-task-forms" | "commands" | "workflow-editor" | "integrations" | "resources" | null>>;
168
+ setSettingsSection: Dispatch<SetStateAction<"mcp" | "categories" | "types" | "general" | "agents" | "taxonomy" | "documents" | "appearance-application" | "appearance-task-cards" | "appearance-task-forms" | "commands" | "workflow-editor" | "integrations" | "resources" | null>>;
134
169
  runtimeMode: "local" | "cloud";
135
170
  workspaceMode: "single-local" | "multi-cloud";
136
171
  workspaceSwitchingEnabled: boolean;
@@ -383,7 +418,7 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
383
418
  }>;
384
419
  logout: () => Promise<void>;
385
420
  showFolderBrowser: boolean;
386
- setShowFolderBrowser: import("react").Dispatch<import("react").SetStateAction<boolean>>;
421
+ setShowFolderBrowser: Dispatch<SetStateAction<boolean>>;
387
422
  folders: string[];
388
423
  files: string[];
389
424
  currentBrowsePath: string;
@@ -392,16 +427,16 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
392
427
  type: "category";
393
428
  value: string;
394
429
  } | null;
395
- setBrowserTarget: import("react").Dispatch<import("react").SetStateAction<"storage" | {
430
+ setBrowserTarget: Dispatch<SetStateAction<"storage" | {
396
431
  type: "category";
397
432
  value: string;
398
433
  } | null>>;
399
434
  groupBy: "category" | "type" | "priority" | "status" | "complexity" | "approach" | "assignee" | "schedule";
400
- setGroupBy: import("react").Dispatch<import("react").SetStateAction<"category" | "type" | "priority" | "status" | "complexity" | "approach" | "assignee" | "schedule">>;
435
+ setGroupBy: Dispatch<SetStateAction<"category" | "type" | "priority" | "status" | "complexity" | "approach" | "assignee" | "schedule">>;
401
436
  activeWorkspaceModule: WorkspaceModuleId;
402
- setActiveWorkspaceModule: import("react").Dispatch<import("react").SetStateAction<WorkspaceModuleId>>;
437
+ setActiveWorkspaceModule: Dispatch<SetStateAction<WorkspaceModuleId>>;
403
438
  emptyColumnMode: "show" | "collapse" | "hide";
404
- setEmptyColumnMode: import("react").Dispatch<import("react").SetStateAction<"show" | "collapse" | "hide">>;
439
+ setEmptyColumnMode: Dispatch<SetStateAction<"show" | "collapse" | "hide">>;
405
440
  zenMode: boolean;
406
441
  setZenMode: () => Promise<void>;
407
442
  handleSelectPath: (path: string) => void;
@@ -438,36 +473,36 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
438
473
  }[];
439
474
  taxonomies: TaxonomyDefinition[];
440
475
  searchQuery: string;
441
- setSearchQuery: import("react").Dispatch<import("react").SetStateAction<string>>;
476
+ setSearchQuery: Dispatch<SetStateAction<string>>;
442
477
  filterCategories: string[];
443
- setFilterCategories: import("react").Dispatch<import("react").SetStateAction<string[]>>;
478
+ setFilterCategories: Dispatch<SetStateAction<string[]>>;
444
479
  filterTypes: string[];
445
- setFilterTypes: import("react").Dispatch<import("react").SetStateAction<string[]>>;
480
+ setFilterTypes: Dispatch<SetStateAction<string[]>>;
446
481
  filterPriorities: (string | number)[];
447
- setFilterPriorities: import("react").Dispatch<import("react").SetStateAction<(string | number)[]>>;
482
+ setFilterPriorities: Dispatch<SetStateAction<(string | number)[]>>;
448
483
  filterStatus: (string | number)[];
449
- setFilterStatus: import("react").Dispatch<import("react").SetStateAction<(string | number)[]>>;
484
+ setFilterStatus: Dispatch<SetStateAction<(string | number)[]>>;
450
485
  filterAssignees: string[];
451
486
  setFilterAssignees: (nextValues: TaskAssignee[] | ((prev: TaskAssignee[]) => TaskAssignee[])) => void;
452
487
  filterTaxonomies: Record<string, (string | number)[]>;
453
- setFilterTaxonomies: import("react").Dispatch<import("react").SetStateAction<Record<string, (string | number)[]>>>;
488
+ setFilterTaxonomies: Dispatch<SetStateAction<Record<string, (string | number)[]>>>;
454
489
  hasInitedFilters: boolean;
455
490
  sortBy: import("../utils/taxonomySorting").TaskSortKey;
456
- setSortBy: import("react").Dispatch<import("react").SetStateAction<import("../utils/taxonomySorting").TaskSortKey>>;
491
+ setSortBy: Dispatch<SetStateAction<import("../utils/taxonomySorting").TaskSortKey>>;
457
492
  sortOrder: "asc" | "desc";
458
- setSortOrder: import("react").Dispatch<import("react").SetStateAction<"asc" | "desc">>;
493
+ setSortOrder: Dispatch<SetStateAction<"asc" | "desc">>;
459
494
  toggleSortOrder: () => void;
460
495
  showArchive: boolean;
461
- setShowArchive: import("react").Dispatch<import("react").SetStateAction<boolean>>;
496
+ setShowArchive: Dispatch<SetStateAction<boolean>>;
462
497
  taskScope: TaskScope;
463
- setTaskScope: import("react").Dispatch<import("react").SetStateAction<TaskScope>>;
498
+ setTaskScope: Dispatch<SetStateAction<TaskScope>>;
464
499
  clearFilters: () => void;
465
500
  filteredTasks: TaskItem[];
466
501
  searchAgnosticTasks: TaskItem[];
467
502
  filteredArchive: TaskItem[];
468
503
  groupedTasks: Record<string, TaskItem[]>;
469
504
  collapsedCategories: Record<string, boolean>;
470
- setCollapsedCategories: import("react").Dispatch<import("react").SetStateAction<Record<string, boolean>>>;
505
+ setCollapsedCategories: Dispatch<SetStateAction<Record<string, boolean>>>;
471
506
  fetchTasks: (isSilent?: boolean, options?: {
472
507
  ignoreAuthGuard?: boolean;
473
508
  }) => Promise<void>;
@@ -514,50 +549,50 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
514
549
  loading: boolean;
515
550
  error: string;
516
551
  title: string;
517
- setTitle: import("react").Dispatch<import("react").SetStateAction<string>>;
552
+ setTitle: Dispatch<SetStateAction<string>>;
518
553
  description: string;
519
- setDescription: import("react").Dispatch<import("react").SetStateAction<string>>;
554
+ setDescription: Dispatch<SetStateAction<string>>;
520
555
  checklistItems: ChecklistItem[];
521
- setChecklistItems: import("react").Dispatch<import("react").SetStateAction<ChecklistItem[]>>;
556
+ setChecklistItems: Dispatch<SetStateAction<ChecklistItem[]>>;
522
557
  category: string;
523
- setCategory: import("react").Dispatch<import("react").SetStateAction<string>>;
558
+ setCategory: Dispatch<SetStateAction<string>>;
524
559
  type: string;
525
- setType: import("react").Dispatch<import("react").SetStateAction<string>>;
560
+ setType: Dispatch<SetStateAction<string>>;
526
561
  priority: string | number;
527
- setPriority: import("react").Dispatch<import("react").SetStateAction<string | number>>;
562
+ setPriority: Dispatch<SetStateAction<string | number>>;
528
563
  complexity: string | number;
529
- setComplexity: import("react").Dispatch<import("react").SetStateAction<string | number>>;
564
+ setComplexity: Dispatch<SetStateAction<string | number>>;
530
565
  status: TaskStatus;
531
- setStatus: import("react").Dispatch<import("react").SetStateAction<TaskStatus>>;
566
+ setStatus: Dispatch<SetStateAction<TaskStatus>>;
532
567
  approach: string;
533
- setApproach: import("react").Dispatch<import("react").SetStateAction<string>>;
568
+ setApproach: Dispatch<SetStateAction<string>>;
534
569
  assignee: string;
535
- setAssignee: import("react").Dispatch<import("react").SetStateAction<string>>;
570
+ setAssignee: Dispatch<SetStateAction<string>>;
536
571
  scheduledDate: string;
537
- setScheduledDate: import("react").Dispatch<import("react").SetStateAction<string>>;
572
+ setScheduledDate: Dispatch<SetStateAction<string>>;
538
573
  dueDate: string;
539
- setDueDate: import("react").Dispatch<import("react").SetStateAction<string>>;
574
+ setDueDate: Dispatch<SetStateAction<string>>;
540
575
  workstreamInput: string;
541
- setWorkstreamInput: import("react").Dispatch<import("react").SetStateAction<string>>;
576
+ setWorkstreamInput: Dispatch<SetStateAction<string>>;
542
577
  formTaxonomies: Record<string, string | number | string[] | number[]>;
543
- setFormTaxonomies: import("react").Dispatch<import("react").SetStateAction<Record<string, string | number | string[] | number[]>>>;
578
+ setFormTaxonomies: Dispatch<SetStateAction<Record<string, string | number | string[] | number[]>>>;
544
579
  comments: Comment[];
545
580
  newCommentText: string;
546
- setNewCommentText: import("react").Dispatch<import("react").SetStateAction<string>>;
581
+ setNewCommentText: Dispatch<SetStateAction<string>>;
547
582
  attachments: (string | AttachmentItem)[];
548
- setAttachments: import("react").Dispatch<import("react").SetStateAction<(string | AttachmentItem)[]>>;
583
+ setAttachments: Dispatch<SetStateAction<(string | AttachmentItem)[]>>;
549
584
  attachmentsDirty: boolean;
550
- setAttachmentsDirty: import("react").Dispatch<import("react").SetStateAction<boolean>>;
585
+ setAttachmentsDirty: Dispatch<SetStateAction<boolean>>;
551
586
  descriptionFocused: boolean;
552
- setDescriptionFocused: import("react").Dispatch<import("react").SetStateAction<boolean>>;
587
+ setDescriptionFocused: Dispatch<SetStateAction<boolean>>;
553
588
  showMarkdownHelp: boolean;
554
- setShowMarkdownHelp: import("react").Dispatch<import("react").SetStateAction<boolean>>;
589
+ setShowMarkdownHelp: Dispatch<SetStateAction<boolean>>;
555
590
  showChecklist: boolean;
556
- setShowChecklist: import("react").Dispatch<import("react").SetStateAction<boolean>>;
591
+ setShowChecklist: Dispatch<SetStateAction<boolean>>;
557
592
  showComments: boolean;
558
- setShowComments: import("react").Dispatch<import("react").SetStateAction<boolean>>;
593
+ setShowComments: Dispatch<SetStateAction<boolean>>;
559
594
  isCapturingScreenshot: boolean;
560
- setIsCapturingScreenshot: import("react").Dispatch<import("react").SetStateAction<boolean>>;
595
+ setIsCapturingScreenshot: Dispatch<SetStateAction<boolean>>;
561
596
  handleSubmit: (event: React.FormEvent) => Promise<void>;
562
597
  resetForm: () => void;
563
598
  handleAddComment: (text: string) => Promise<void>;
@@ -566,7 +601,7 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
566
601
  returnToPreviousTask: () => boolean;
567
602
  autoSaveState: import("./tasks/useTaskFormActions").TaskAutoSaveState;
568
603
  unsavedModalOpen: boolean;
569
- setUnsavedModalOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
604
+ setUnsavedModalOpen: Dispatch<SetStateAction<boolean>>;
570
605
  pendingNavigation: (() => void) | null;
571
606
  handleNavigation: (action: () => void) => void;
572
607
  handleClose: () => void;
@@ -588,11 +623,11 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
588
623
  confirmScheduleWarning: () => Promise<void>;
589
624
  cancelScheduleWarning: () => void;
590
625
  tasksScrollRef: import("react").RefObject<HTMLDivElement | null>;
591
- setTasksScrollPos: import("react").Dispatch<import("react").SetStateAction<number>>;
626
+ setTasksScrollPos: Dispatch<SetStateAction<number>>;
592
627
  exportEnvironment: string;
593
- setExportEnvironment: import("react").Dispatch<import("react").SetStateAction<string>>;
628
+ setExportEnvironment: Dispatch<SetStateAction<string>>;
594
629
  exportWorkflowsPath: string;
595
- setExportWorkflowsPath: import("react").Dispatch<import("react").SetStateAction<string>>;
630
+ setExportWorkflowsPath: Dispatch<SetStateAction<string>>;
596
631
  exportResult: {
597
632
  type: "success" | "error";
598
633
  message: string;
@@ -627,7 +662,7 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
627
662
  validatePaths: (paths: string[]) => Promise<void>;
628
663
  getCategoryPaths: (cat: CategoryConfig) => string[];
629
664
  customCategories: CategoryConfig[];
630
- setCustomCategories: import("react").Dispatch<import("react").SetStateAction<CategoryConfig[]>>;
665
+ setCustomCategories: Dispatch<SetStateAction<CategoryConfig[]>>;
631
666
  projectRoot: string;
632
667
  projectName: string;
633
668
  mcpScriptPath: string;
@@ -68,6 +68,7 @@ import { useTaskforceAuthBootstrap } from './auth/useTaskforceAuthBootstrap';
68
68
  import { deriveAuthGuardPolicy } from './auth/useAuthGuardPolicy';
69
69
  import { useTaskforceWorkspaceBootstrap } from './workspace/useTaskforceWorkspaceBootstrap';
70
70
  import { useDataVersionRefresh } from './sync/useDataVersionRefresh';
71
+ import { BOOTSTRAP_REQUEST_TIMEOUT_MS } from './bootstrapTimeouts';
71
72
  function upsertEntityById(items, nextItem) {
72
73
  const existingIndex = items.findIndex((item) => item.id === nextItem.id);
73
74
  if (existingIndex === -1) {
@@ -89,7 +90,6 @@ const DEFAULT_CLOUD_APP_ORIGIN = (() => {
89
90
  // Safety default for local runtime cloud auth/sync fallback.
90
91
  return 'https://app.taskforcehq.ai';
91
92
  })();
92
- const BOOTSTRAP_REQUEST_TIMEOUT_MS = 12_000;
93
93
  const PLANNING_BOOTSTRAP_REQUEST_TIMEOUT_MS = 20_000;
94
94
  const EXPECTED_WORKSPACE_ABORT_REASONS = new Set([
95
95
  'external-abort',
@@ -944,7 +944,10 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
944
944
  setPlanningBootstrapTaskSummaries([]);
945
945
  return;
946
946
  }
947
- if (bootstrapRes?.ok) {
947
+ if (bootstrapRes === null) {
948
+ throw createBootstrapTimeoutError(PLANNING_BOOTSTRAP_REQUEST_TIMEOUT_MS);
949
+ }
950
+ if (bootstrapRes.ok) {
948
951
  bootstrapPayload = sanitizePlanningBootstrapPayload(await bootstrapRes.json().catch(() => ({})));
949
952
  }
950
953
  else {
@@ -982,7 +985,18 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
982
985
  catch (err) {
983
986
  if (isExpectedWorkspaceAbortFailure(err, abortController.signal) || isAbortError(err))
984
987
  return;
985
- console.error('[Taskforce] Failed to fetch planning entities:', err);
988
+ const startupTimeout = isBootstrapTimeoutError(err);
989
+ if (startupTimeout) {
990
+ logBootstrapDebug('planning_bootstrap_timeout', {
991
+ workspaceId: requestState.workspaceId,
992
+ epoch: requestState.epoch,
993
+ timeoutMs: PLANNING_BOOTSTRAP_REQUEST_TIMEOUT_MS,
994
+ });
995
+ console.warn('[Taskforce] Planning bootstrap timed out during startup; retrying in background.');
996
+ }
997
+ else {
998
+ console.error('[Taskforce] Failed to fetch planning entities:', err);
999
+ }
986
1000
  if (typeof window !== 'undefined'
987
1001
  && !isWorkspaceRequestStale(requestState)
988
1002
  && initiatives.length === 0
@@ -1160,7 +1174,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1160
1174
  }
1161
1175
  });
1162
1176
  checkAuthSessionImplRef.current = checkAuthSessionImpl;
1163
- const [activeWorkspaceModule, setActiveWorkspaceModule] = useState('tasks');
1177
+ const [activeWorkspaceModule, setActiveWorkspaceModuleState] = useState('tasks');
1164
1178
  const [showComments, setShowComments] = useState(false);
1165
1179
  const [descriptionFocused, setDescriptionFocused] = useState(false);
1166
1180
  const [isCapturingScreenshot, setIsCapturingScreenshot] = useState(false);
@@ -1181,10 +1195,17 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1181
1195
  const lastTaskSurfaceLoadKeyRef = useRef('');
1182
1196
  const lastSettingsSurfaceLoadKeyRef = useRef('');
1183
1197
  const skipNextUiStatePersistRef = useRef(false);
1198
+ const activeWorkspaceModuleOverridePendingRef = useRef(false);
1184
1199
  const referenceDataRequestRef = useRef(null);
1185
1200
  const workspaceListHydratedRef = useRef(false);
1186
1201
  const hasBootstrappedDataRef = useRef(false);
1187
1202
  const [uiStateReady, setUiStateReady] = useState(false);
1203
+ const setActiveWorkspaceModule = useCallback((nextModule) => {
1204
+ if (!uiStateReady) {
1205
+ activeWorkspaceModuleOverridePendingRef.current = true;
1206
+ }
1207
+ setActiveWorkspaceModuleState(nextModule);
1208
+ }, [uiStateReady]);
1188
1209
  const { handleSaveSettings, handleSaveTheme, handleSaveGlobalTheme, handleJsonBackupEnabledChange, handleSaveGlobalJsonBackupEnabled, handleSaveGlobalWeekStartsOn, handleSaveLocale, handleManualComplexityEnabledChange, handleChecklistDropdownEnabledChange, handleShowTaskCardStatusLabelChange, handleResetProjectToGlobal } = useSettingsPersistence({
1189
1210
  keyShortcut,
1190
1211
  themeUseGlobalDefault,
@@ -1217,7 +1238,7 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1217
1238
  authRequiredForApiRef.current = authRequiredForApi;
1218
1239
  isAuthenticatedRef.current = isAuthenticated;
1219
1240
  }, [authSessionResolved, authRequiredForApi, isAuthenticated]);
1220
- const applyPersistedUiState = useCallback((state) => {
1241
+ const applyPersistedUiState = useCallback((state, options) => {
1221
1242
  if (!state)
1222
1243
  return;
1223
1244
  const hasPersistedFilterState = Array.isArray(state.filterCategories)
@@ -1227,11 +1248,13 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1227
1248
  || Array.isArray(state.filterAssignees)
1228
1249
  || typeof state.filterAssigneesAllSelected === 'boolean'
1229
1250
  || (state.filterTaxonomies && typeof state.filterTaxonomies === 'object');
1230
- if (typeof state.activeWorkspaceModule === 'string') {
1231
- setActiveWorkspaceModule(state.activeWorkspaceModule);
1232
- }
1233
- else if (state.groupBy === 'docs') {
1234
- setActiveWorkspaceModule('docs');
1251
+ if (!options?.skipActiveWorkspaceModule) {
1252
+ if (typeof state.activeWorkspaceModule === 'string') {
1253
+ setActiveWorkspaceModuleState(state.activeWorkspaceModule);
1254
+ }
1255
+ else if (state.groupBy === 'docs') {
1256
+ setActiveWorkspaceModuleState('docs');
1257
+ }
1235
1258
  }
1236
1259
  if (state.groupBy && state.groupBy !== 'docs')
1237
1260
  setGroupBy(state.groupBy);
@@ -1295,19 +1318,24 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1295
1318
  if (currentWorkspaceIdRef.current !== targetWorkspaceId) {
1296
1319
  return;
1297
1320
  }
1298
- applyPersistedUiState(state);
1321
+ applyPersistedUiState(state, {
1322
+ skipActiveWorkspaceModule: activeWorkspaceModuleOverridePendingRef.current
1323
+ });
1299
1324
  lastLoadedUiStateKeyRef.current = uiStateLoadKey;
1300
1325
  }
1301
1326
  catch {
1302
1327
  const localState = readPersistedAppUiState(workspaceUiStateScope);
1303
1328
  if (currentWorkspaceIdRef.current === targetWorkspaceId) {
1304
- applyPersistedUiState(localState);
1329
+ applyPersistedUiState(localState, {
1330
+ skipActiveWorkspaceModule: activeWorkspaceModuleOverridePendingRef.current
1331
+ });
1305
1332
  lastLoadedUiStateKeyRef.current = uiStateLoadKey;
1306
1333
  }
1307
1334
  }
1308
1335
  finally {
1309
1336
  if (currentWorkspaceIdRef.current !== targetWorkspaceId)
1310
1337
  return;
1338
+ activeWorkspaceModuleOverridePendingRef.current = false;
1311
1339
  skipNextUiStatePersistRef.current = true;
1312
1340
  setUiStateReady(true);
1313
1341
  }
@@ -1533,10 +1561,12 @@ export function useTaskforce({ config = {}, initialTaskId, onTaskCountChange, on
1533
1561
  }
1534
1562
  catch (versionError) {
1535
1563
  if (isBootstrapTimeoutError(versionError)) {
1536
- markBootstrapStalled('Startup checks timed out.');
1564
+ logBootstrapDebug('build_info_timeout', {
1565
+ durationMs: Math.round((typeof performance !== 'undefined' ? performance.now() : Date.now()) - startedAt)
1566
+ });
1537
1567
  }
1538
1568
  }
1539
- }, [fetchWithTimeout, isBootstrapTimeoutError, markBootstrapStalled]);
1569
+ }, [fetchWithTimeout, isBootstrapTimeoutError]);
1540
1570
  const { fetchReferenceData, fetchBootstrapConfig, fetchConfig, fetchWorkspaces, fetchAssigneeOptions, switchWorkspace, resolveWorkspaceAfterAuth, retryBootstrapChecks, runCloudBootstrap } = useTaskforceWorkspaceBootstrap({
1541
1571
  shouldDeferProtectedApiCalls,
1542
1572
  shouldBlockProtectedApiCalls,