@taskforcehq/taskforce 0.3.328 → 0.3.329

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 (264) hide show
  1. package/dist/Taskforce.module.css +233 -34
  2. package/dist/TaskforceCore.js +237 -230
  3. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +1 -1
  4. package/dist/cli.js +34 -0
  5. package/dist/cliCloudTargetGuard.d.ts +5 -0
  6. package/dist/cliCloudTargetGuard.js +11 -0
  7. package/dist/components/features/AnnotatedAttachmentWorkspace.js +24 -8
  8. package/dist/components/features/TaskSettings.d.ts +12 -1
  9. package/dist/components/features/TaskSettings.js +147 -40
  10. package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +1 -1
  11. package/dist/components/features/planning/PlanningModule.js +2 -5
  12. package/dist/components/features/planning/PlanningTaskOrderList.d.ts +1 -1
  13. package/dist/components/features/planning/PlanningTaskOrderList.js +17 -6
  14. package/dist/components/features/planning/planningTaskArrangement.d.ts +1 -0
  15. package/dist/components/features/planning/planningTaskArrangement.js +5 -5
  16. package/dist/components/features/taskforceAgentEditorModel.js +3 -1
  17. package/dist/components/task/TaskCard.d.ts +5 -0
  18. package/dist/components/task/TaskCard.js +65 -10
  19. package/dist/components/task/TaskImageReviews.js +14 -5
  20. package/dist/components/task/TaskKanban.d.ts +6 -1
  21. package/dist/components/task/TaskKanban.js +152 -6
  22. package/dist/components/task/TaskList.d.ts +5 -0
  23. package/dist/components/task/TaskList.js +3 -3
  24. package/dist/components/ui/PasswordInput.d.ts +6 -0
  25. package/dist/components/ui/PasswordInput.js +11 -0
  26. package/dist/components/ui/TopNoticeLayer.js +1 -1
  27. package/dist/components/views/AppShellHeader.js +32 -11
  28. package/dist/components/views/PlanComparisonPage.js +12 -0
  29. package/dist/components/views/StandaloneLayout.js +7 -31
  30. package/dist/components/views/WidgetView.js +3 -3
  31. package/dist/components/views/panels/PlanningDrawer.js +5 -12
  32. package/dist/components/views/standalone/modals/AccountSettingsModal.js +26 -17
  33. package/dist/config/envSchema.js +12 -0
  34. package/dist/config/localServiceRuntimeContract.d.ts +13 -0
  35. package/dist/config/localServiceRuntimeContract.js +135 -0
  36. package/dist/core/AiProfileService.js +46 -8
  37. package/dist/core/DeletedTaskLifecycleService.d.ts +1 -0
  38. package/dist/core/DeletedTaskLifecycleService.js +37 -6
  39. package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +3 -2
  40. package/dist/core/TaskAttachmentLinksDurableMutationService.js +33 -9
  41. package/dist/core/Taskforce.d.ts +62 -2
  42. package/dist/core/Taskforce.js +635 -32
  43. package/dist/core/types.d.ts +6 -0
  44. package/dist/hooks/sync/useDurableLocalOutboxDrain.d.ts +1 -1
  45. package/dist/hooks/sync/useDurableTaskCreateMutation.js +1 -1
  46. package/dist/hooks/sync/useDurableTaskMetadataMutation.d.ts +1 -0
  47. package/dist/hooks/sync/useDurableTaskMetadataMutation.js +1 -0
  48. package/dist/hooks/sync/useDurableTaskStatusMutation.d.ts +1 -0
  49. package/dist/hooks/tasks/taskHttpMutation.js +1 -0
  50. package/dist/hooks/tasks/useCloudTaskSearch.d.ts +12 -0
  51. package/dist/hooks/tasks/useCloudTaskSearch.js +71 -0
  52. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.d.ts +16 -0
  53. package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.js +197 -0
  54. package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +16 -3
  55. package/dist/hooks/tasks/useTaskEditorNavigation.js +72 -13
  56. package/dist/hooks/tasks/useTaskFormActions.d.ts +6 -0
  57. package/dist/hooks/tasks/useTaskFormActions.js +65 -12
  58. package/dist/hooks/ui/useSettingsModel.d.ts +24 -2
  59. package/dist/hooks/ui/useSettingsModel.js +13 -2
  60. package/dist/hooks/ui/useSettingsPersistence.d.ts +12 -1
  61. package/dist/hooks/ui/useSettingsPersistence.js +74 -47
  62. package/dist/hooks/useFilterSort.d.ts +7 -3
  63. package/dist/hooks/useFilterSort.js +15 -5
  64. package/dist/hooks/useTaskData.d.ts +10 -1
  65. package/dist/hooks/useTaskData.js +216 -7
  66. package/dist/hooks/useTaskMutations.d.ts +10 -2
  67. package/dist/hooks/useTaskMutations.js +223 -43
  68. package/dist/hooks/useTaskforce.d.ts +37 -9
  69. package/dist/hooks/useTaskforce.js +349 -39
  70. package/dist/hooks/useUiNotice.d.ts +4 -0
  71. package/dist/hooks/useUiNotice.js +4 -0
  72. package/dist/mcp/adminWorkspaceRegistrar.js +3 -3
  73. package/dist/mcp/documentAssetRegistrar.js +44 -35
  74. package/dist/mcp/documentHelpers.d.ts +1 -0
  75. package/dist/mcp/documentHelpers.js +7 -3
  76. package/dist/mcp/runtime.js +172 -42
  77. package/dist/mcp/taskPlanningRegistrar.js +41 -38
  78. package/dist/mcp/toolCallCompatibility.js +38 -1
  79. package/dist/mcp/toolCatalog.d.ts +1 -0
  80. package/dist/migrations/taskSchemaMigrations.js +12 -0
  81. package/dist/runtime/appGateDefinitions.d.ts +30 -6
  82. package/dist/runtime/appGateDefinitions.js +6 -6
  83. package/dist/runtime/appGates.d.ts +12 -13
  84. package/dist/runtime/appGates.js +29 -13
  85. package/dist/runtime/nodeAppGates.d.ts +8 -0
  86. package/dist/runtime/nodeAppGates.js +27 -0
  87. package/dist/server/cloudCollectionRead.d.ts +1 -1
  88. package/dist/server/cloudCollectionRead.js +2 -2
  89. package/dist/server/index.d.ts +3 -0
  90. package/dist/server/index.js +15 -3
  91. package/dist/server/localServiceLease.d.ts +3 -0
  92. package/dist/server/localServiceLease.js +9 -1
  93. package/dist/server/localWorkspaceSyncServerRuntime.js +10 -3
  94. package/dist/server/rateLimit.d.ts +1 -0
  95. package/dist/server/rateLimit.js +4 -0
  96. package/dist/server/realtimeSyncWs.js +36 -12
  97. package/dist/server/routes/admin.js +1 -0
  98. package/dist/server/routes/agentRoles.js +3 -2
  99. package/dist/server/routes/agentSkills.js +3 -2
  100. package/dist/server/routes/agents.js +118 -16
  101. package/dist/server/routes/annotatedAttachments.js +3 -2
  102. package/dist/server/routes/auth.js +3 -1
  103. package/dist/server/routes/durableTaskHttpRouters.d.ts +9 -2
  104. package/dist/server/routes/localService.js +2 -0
  105. package/dist/server/routes/primitives.d.ts +2 -0
  106. package/dist/server/routes/syncAuxRoutes.js +5 -2
  107. package/dist/server/routes/syncPullApplyRoutes.js +23 -4
  108. package/dist/server/routes/syncV3FeedRoutes.js +18 -16
  109. package/dist/server/routes/syncV3LocalCaptureRoutes.js +28 -2
  110. package/dist/server/routes/syncV3LocalFeedApplyRoutes.d.ts +1 -0
  111. package/dist/server/routes/syncV3LocalFeedApplyRoutes.js +26 -0
  112. package/dist/server/routes/syncV3OperationRoutes.js +10 -0
  113. package/dist/server/routes/tasks.js +103 -11
  114. package/dist/server/routes/workspaces.js +43 -1
  115. package/dist/server/workspaceDeletionAdmission.d.ts +23 -0
  116. package/dist/server/workspaceDeletionAdmission.js +50 -0
  117. package/dist/service/localServiceCli.js +16 -0
  118. package/dist/service/localServiceClientLifecycle.js +36 -10
  119. package/dist/services/codexAppServerModelGateway.d.ts +112 -0
  120. package/dist/services/codexAppServerModelGateway.js +438 -0
  121. package/dist/services/taskforceAgentMcpBridge.d.ts +7 -1
  122. package/dist/services/taskforceAgentMcpBridge.js +23 -3
  123. package/dist/shared/taskforceAgentDefinition.js +7 -1
  124. package/dist/shared/taskforceAgentModels.d.ts +1 -0
  125. package/dist/shared/taskforceAgentModels.js +30 -1
  126. package/dist/storage/postgresAdapter.d.ts +20 -3
  127. package/dist/storage/postgresAdapter.js +106 -14
  128. package/dist/storage/postgresWorker.js +13 -4
  129. package/dist/storage/postgresWorkerProtocol.d.ts +29 -0
  130. package/dist/storage/postgresWorkerProtocol.js +30 -0
  131. package/dist/sync/cloudSyncApi.d.ts +4 -1
  132. package/dist/sync/cloudSyncApi.js +7 -2
  133. package/dist/sync/contentSyncState.d.ts +1 -1
  134. package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +1 -0
  135. package/dist/sync/coordinator/localSyncExecutionPermitStore.js +7 -1
  136. package/dist/sync/coordinator/localWorkspaceSyncRunner.js +6 -2
  137. package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +20 -2
  138. package/dist/sync/engine/workspaceSyncEngineLocalServices.d.ts +4 -0
  139. package/dist/sync/engine/workspaceSyncEngineLocalServices.js +11 -0
  140. package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +11 -0
  141. package/dist/sync/engine/workspaceSyncEngineRemoteServices.js +2 -1
  142. package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +31 -4
  143. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.d.ts +3 -1
  144. package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +25 -8
  145. package/dist/sync/engine/workspaceSyncEngineTransfers.d.ts +1 -1
  146. package/dist/sync/engine/workspaceSyncEngineTransfers.js +8 -3
  147. package/dist/sync/syncService.d.ts +2 -0
  148. package/dist/sync/syncService.js +4 -1
  149. package/dist/sync/v3/combinedFeedCapability.js +4 -0
  150. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +2 -0
  151. package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +11 -1
  152. package/dist/sync/v3/durableTaskChecklistMutationRouter.d.ts +2 -0
  153. package/dist/sync/v3/durableTaskChecklistMutationRouter.js +4 -0
  154. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.d.ts +1 -0
  155. package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +30 -8
  156. package/dist/sync/v3/durableTaskHttpRootMutationRouter.d.ts +5 -3
  157. package/dist/sync/v3/durableTaskHttpRootMutationRouter.js +27 -2
  158. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +2 -0
  159. package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +47 -7
  160. package/dist/sync/v3/localTaskCreateOutboxService.js +15 -6
  161. package/dist/sync/v3/localTaskMetadataClient.d.ts +1 -0
  162. package/dist/sync/v3/localTaskMetadataClient.js +1 -0
  163. package/dist/sync/v3/localTaskMetadataOutboxHandler.js +5 -1
  164. package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -0
  165. package/dist/sync/v3/localTaskMetadataOutboxService.js +7 -0
  166. package/dist/sync/v3/localTaskRestoreOutboxService.js +14 -6
  167. package/dist/sync/v3/localTaskStatusClient.d.ts +1 -0
  168. package/dist/sync/v3/localTaskStatusClient.js +6 -1
  169. package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -0
  170. package/dist/sync/v3/localTaskStatusOutboxService.js +54 -13
  171. package/dist/sync/v3/syncDurabilityStore.d.ts +7 -0
  172. package/dist/sync/v3/syncDurabilityStore.js +31 -11
  173. package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +6 -4
  174. package/dist/sync/v3/taskAttachmentLinksMutationService.js +19 -10
  175. package/dist/sync/v3/taskChecklistMutationService.d.ts +4 -2
  176. package/dist/sync/v3/taskChecklistMutationService.js +4 -0
  177. package/dist/sync/v3/taskCreateMutationService.d.ts +2 -2
  178. package/dist/sync/v3/taskCreateMutationService.js +7 -4
  179. package/dist/sync/v3/taskMetadataMutationService.d.ts +3 -0
  180. package/dist/sync/v3/taskMetadataMutationService.js +4 -0
  181. package/dist/sync/v3/taskRestoreMutationService.d.ts +2 -2
  182. package/dist/sync/v3/taskRestoreMutationService.js +7 -4
  183. package/dist/sync/v3/taskStatusMutationService.d.ts +2 -0
  184. package/dist/sync/v3/taskStatusMutationService.js +4 -0
  185. package/dist/sync/v3/workflowAssignmentSync.d.ts +1 -0
  186. package/dist/sync/v3/workflowAssignmentSync.js +3 -0
  187. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.d.ts +1 -1
  188. package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +13 -3
  189. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.d.ts +11 -0
  190. package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.js +27 -10
  191. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.d.ts +2 -1
  192. package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.js +52 -10
  193. package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +9 -1
  194. package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +8 -3
  195. package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +51 -10
  196. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +7 -0
  197. package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +30 -18
  198. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +9 -4
  199. package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +98 -47
  200. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.d.ts +10 -0
  201. package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +58 -21
  202. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.d.ts +3 -1
  203. package/dist/sync/v3/workspaceSyncV3CombinedWireContract.js +18 -0
  204. package/dist/sync/v3/workspaceSyncV3CoverageRegistry.js +6 -0
  205. package/dist/sync/v3/workspaceSyncV3FeedProfile.d.ts +1 -1
  206. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.d.ts +1 -0
  207. package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.js +23 -10
  208. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.d.ts +3 -0
  209. package/dist/sync/v3/workspaceSyncV3NestedTaskApply.js +64 -13
  210. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.d.ts +9 -0
  211. package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.js +26 -0
  212. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +15 -5
  213. package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +50 -14
  214. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.d.ts +2 -1
  215. package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.js +11 -3
  216. package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +2 -0
  217. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.d.ts +2 -0
  218. package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.js +19 -7
  219. package/dist/sync/v3/workspaceSyncV3TaskCommentAudit.js +9 -2
  220. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.d.ts +7 -0
  221. package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.js +37 -0
  222. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.d.ts +6 -0
  223. package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.js +9 -0
  224. package/dist/sync/workspacePullFeed.js +2 -1
  225. package/dist/sync/workspaceRepair.d.ts +3 -0
  226. package/dist/sync/workspaceRepair.js +67 -6
  227. package/dist/types.d.ts +47 -1
  228. package/dist/types.js +6 -0
  229. package/dist/ui/assets/{AiIdentityRosterCard-MZs7lVED.js → AiIdentityRosterCard-BZM4aQnM.js} +1 -1
  230. package/dist/ui/assets/AiProfilesModule-BbAEWtHH.js +1 -0
  231. package/dist/ui/assets/AnnotatedAttachmentWorkspace-B68Wg-RV.js +3 -0
  232. package/dist/ui/assets/AnnotatedAttachmentWorkspace-TzYu7pkG.css +1 -0
  233. package/dist/ui/assets/{AssetTraySortControl-Kf1pelSy.js → AssetTraySortControl-C8Rztyal.js} +1 -1
  234. package/dist/ui/assets/{ContextAttachmentManager-B8boOs6Q.js → ContextAttachmentManager-Cj3c7X9y.js} +2 -2
  235. package/dist/ui/assets/{DocumentWorkspace-DXH4XUsB.js → DocumentWorkspace-B5uvzems.js} +1 -1
  236. package/dist/ui/assets/{EntityActivityTimeline-BtSTq0ON.js → EntityActivityTimeline-BVFvCB5R.js} +1 -1
  237. package/dist/ui/assets/PlanningModule-B5DWQOGz.js +1 -0
  238. package/dist/ui/assets/PlansPage-AY4cGWco.js +1 -0
  239. package/dist/ui/assets/TaskContextUpload-DZxTVesW.js +1 -0
  240. package/dist/ui/assets/TaskSettings-BeWAuyZj.js +12 -0
  241. package/dist/ui/assets/{TaskforceAgentsModule-BazdUwxZ.js → TaskforceAgentsModule-BifCYtjR.js} +6 -6
  242. package/dist/ui/assets/{WorkflowManagerModule-DWFspC-o.js → WorkflowManagerModule-DEr5di_r.js} +1 -1
  243. package/dist/ui/assets/index-I5zhdtgt.js +7 -0
  244. package/dist/ui/assets/index-VRFsx3wz.css +1 -0
  245. package/dist/ui/assets/{vendor-icons-BkFLXavV.js → vendor-icons-D9Lpw-j4.js} +1 -1
  246. package/dist/ui/index.html +3 -3
  247. package/dist/utils/taskActivity.d.ts +3 -2
  248. package/dist/utils/taskActivity.js +19 -2
  249. package/dist/utils/taskEditorReconciliation.d.ts +40 -0
  250. package/dist/utils/taskEditorReconciliation.js +217 -0
  251. package/dist/utils/taskNormalization.d.ts +1 -0
  252. package/dist/utils/taskNormalization.js +39 -0
  253. package/dist/utils/taskSearch.js +2 -2
  254. package/dist/utils/uiNotice.d.ts +4 -0
  255. package/package.json +16 -4
  256. package/dist/ui/assets/AiProfilesModule-C3i0LOxe.js +0 -1
  257. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CBRZ8rca.css +0 -1
  258. package/dist/ui/assets/AnnotatedAttachmentWorkspace-DVZk5Tln.js +0 -3
  259. package/dist/ui/assets/PlanningModule-BPVyEa-f.js +0 -1
  260. package/dist/ui/assets/PlansPage-ChqgxALc.js +0 -1
  261. package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +0 -1
  262. package/dist/ui/assets/TaskSettings-g4ECKNOz.js +0 -12
  263. package/dist/ui/assets/index-BUplSsv_.js +0 -7
  264. package/dist/ui/assets/index-gz2EXuoK.css +0 -1
@@ -23,9 +23,9 @@
23
23
  * - getTaskRevisionKey(task) — stable revision string for change detection
24
24
  */
25
25
  import { useState, useEffect, useCallback, useRef } from 'react';
26
- import { getTaskContentRevision, mergeNormalizedTaskIntoCollections, normalizeTaskFromApi, reconcileTaskCollection } from '../utils/taskNormalization.js';
26
+ import { getTaskContentRevision, mergeNormalizedTaskIntoCollections, normalizeTaskFromApi, reconcileTaskCollection, toTaskCollectionSummary } from '../utils/taskNormalization.js';
27
27
  import { resolveTaskReferenceId } from '../utils/taskReferences.js';
28
- export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks, setLoadingTasks, getCurrentWorkspaceId, workspaceResetKey, shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls, handleUnauthorized, authRequiredForApi }) {
28
+ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks, setLoadingTasks, getCurrentWorkspaceId, workspaceResetKey, shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls, handleUnauthorized, authRequiredForApi, setError, setLoading }) {
29
29
  // === SECTION: Revision Tracking ===
30
30
  const taskRevisionMapRef = useRef(new Map());
31
31
  const hasFetchedTasksRef = useRef(false);
@@ -34,6 +34,21 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
34
34
  const archivedTasksRef = useRef(archivedTasks);
35
35
  const tasksAbortRef = useRef(null);
36
36
  const archiveAbortRef = useRef(null);
37
+ const taskDetailAbortRef = useRef(null);
38
+ const taskDetailCacheRef = useRef(new Map());
39
+ const [taskDetails, setTaskDetails] = useState([]);
40
+ const [staleTaskDetailRevisions, setStaleTaskDetailRevisions] = useState({});
41
+ const getTaskSummaryRevision = useCallback((task) => (String(task.detailRevision || JSON.stringify([
42
+ task.updatedAt || task.createdAt || '',
43
+ task.commentCount || 0,
44
+ task.checklistCount || 0,
45
+ task.completedChecklistCount || 0,
46
+ task.attachmentCount || 0,
47
+ task.relationshipCount || 0,
48
+ ]))), []);
49
+ const publishTaskDetailCache = useCallback(() => {
50
+ setTaskDetails(Array.from(taskDetailCacheRef.current.values(), (entry) => entry.detail));
51
+ }, []);
37
52
  useEffect(() => {
38
53
  tasksRef.current = tasks;
39
54
  }, [tasks]);
@@ -87,12 +102,68 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
87
102
  tasksAbortRef.current = null;
88
103
  archiveAbortRef.current?.abort('workspace-reset');
89
104
  archiveAbortRef.current = null;
105
+ const hadPendingTaskDetail = taskDetailAbortRef.current !== null;
106
+ taskDetailAbortRef.current?.abort('workspace-reset');
107
+ taskDetailAbortRef.current = null;
108
+ if (hadPendingTaskDetail)
109
+ setLoading?.(false);
110
+ taskDetailCacheRef.current = new Map();
111
+ setTaskDetails([]);
112
+ setStaleTaskDetailRevisions({});
90
113
  if (typeof window !== 'undefined') {
91
114
  highlightTimersRef.current.forEach((timerId) => window.clearTimeout(timerId));
92
115
  highlightTimersRef.current.clear();
93
116
  }
94
117
  setRecentlyChangedTaskIds([]);
95
- }, [workspaceResetKey]);
118
+ }, [setLoading, workspaceResetKey]);
119
+ useEffect(() => () => {
120
+ taskDetailAbortRef.current?.abort('unmount');
121
+ }, []);
122
+ const reconcileTaskDetailCache = useCallback((summaries, archived) => {
123
+ const summaryById = new Map(summaries.map((task) => [task.id, task]));
124
+ let changed = false;
125
+ const next = new Map(taskDetailCacheRef.current);
126
+ const nextStaleRevisions = {};
127
+ const reconciledTaskIds = new Set();
128
+ for (const [taskId, cached] of next) {
129
+ const summary = summaryById.get(taskId);
130
+ const belongsToCollection = Boolean(cached.detail.isArchived) === archived;
131
+ if (belongsToCollection)
132
+ reconciledTaskIds.add(taskId);
133
+ if (!summary && belongsToCollection) {
134
+ next.delete(taskId);
135
+ changed = true;
136
+ continue;
137
+ }
138
+ if (summary) {
139
+ const summaryRevision = getTaskSummaryRevision(summary);
140
+ if (summaryRevision !== cached.summaryRevision) {
141
+ nextStaleRevisions[taskId] = summaryRevision;
142
+ }
143
+ }
144
+ }
145
+ setStaleTaskDetailRevisions((current) => {
146
+ const retained = Object.fromEntries(Object.entries(current).filter(([taskId]) => (!reconciledTaskIds.has(taskId))));
147
+ const combined = { ...retained, ...nextStaleRevisions };
148
+ const currentEntries = Object.entries(current);
149
+ const combinedEntries = Object.entries(combined);
150
+ return currentEntries.length === combinedEntries.length
151
+ && currentEntries.every(([taskId, revision]) => combined[taskId] === revision)
152
+ ? current
153
+ : combined;
154
+ });
155
+ if (changed) {
156
+ taskDetailCacheRef.current = next;
157
+ publishTaskDetailCache();
158
+ }
159
+ }, [getTaskSummaryRevision, publishTaskDetailCache]);
160
+ const cancelTaskDetailLoad = useCallback(() => {
161
+ const hadPendingTaskDetail = taskDetailAbortRef.current !== null;
162
+ taskDetailAbortRef.current?.abort('navigation');
163
+ taskDetailAbortRef.current = null;
164
+ if (hadPendingTaskDetail)
165
+ setLoading?.(false);
166
+ }, [setLoading]);
96
167
  // === SECTION: Fetch Functions ===
97
168
  const fetchTasks = useCallback(async (isSilent = false, options) => {
98
169
  const ignoreAuthGuard = options?.ignoreAuthGuard === true;
@@ -126,6 +197,7 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
126
197
  if (liveWorkspaceId !== requestWorkspaceId) {
127
198
  return;
128
199
  }
200
+ reconcileTaskDetailCache(sanitized, false);
129
201
  const nextRevisionMap = new Map();
130
202
  const changedTaskIds = [];
131
203
  const highlightChangedTaskIds = options?.highlightChangedTaskIds === undefined
@@ -178,7 +250,8 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
178
250
  setLoadingTasks,
179
251
  authRequiredForApi,
180
252
  shouldDeferProtectedApiCalls,
181
- shouldBlockProtectedApiCalls
253
+ shouldBlockProtectedApiCalls,
254
+ reconcileTaskDetailCache
182
255
  ]);
183
256
  const fetchArchive = useCallback(async (isSilent = false, options) => {
184
257
  const ignoreAuthGuard = options?.ignoreAuthGuard === true;
@@ -202,6 +275,7 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
202
275
  ...normalizeTaskFromApi(t),
203
276
  isArchived: true
204
277
  }));
278
+ reconcileTaskDetailCache(sanitized, true);
205
279
  const previousArchivedTasks = archivedTasksRef.current;
206
280
  const reconciledArchivedTasks = reconcileTaskCollection(previousArchivedTasks, sanitized);
207
281
  archivedTasksRef.current = reconciledArchivedTasks;
@@ -225,7 +299,109 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
225
299
  setLoadingTasks(false);
226
300
  }
227
301
  }
228
- }, [getCurrentWorkspaceId, isExpectedAbortFailure, setArchivedTasks, setLoadingTasks, shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls]);
302
+ }, [getCurrentWorkspaceId, isExpectedAbortFailure, reconcileTaskDetailCache, setArchivedTasks, setLoadingTasks, shouldDeferProtectedApiCalls, shouldBlockProtectedApiCalls]);
303
+ const loadTaskDetail = useCallback(async (task, options) => {
304
+ const force = options?.force === true;
305
+ if (!force && !task.isSummary) {
306
+ const summary = toTaskCollectionSummary(task);
307
+ taskDetailCacheRef.current.set(task.id, { detail: task, summaryRevision: getTaskSummaryRevision(summary) });
308
+ setStaleTaskDetailRevisions((current) => {
309
+ if (!Object.prototype.hasOwnProperty.call(current, task.id))
310
+ return current;
311
+ const next = { ...current };
312
+ delete next[task.id];
313
+ return next;
314
+ });
315
+ publishTaskDetailCache();
316
+ return task;
317
+ }
318
+ const cached = taskDetailCacheRef.current.get(task.id);
319
+ let requestedSummary = task;
320
+ let summaryRevision = getTaskSummaryRevision(requestedSummary);
321
+ if (!force && cached && cached.summaryRevision === summaryRevision)
322
+ return cached.detail;
323
+ const requestWorkspaceId = String(getCurrentWorkspaceId() || '').trim();
324
+ taskDetailAbortRef.current?.abort('superseded');
325
+ const abortController = new AbortController();
326
+ taskDetailAbortRef.current = abortController;
327
+ setLoading?.(true);
328
+ setError?.('');
329
+ try {
330
+ for (let attempt = 0; attempt < 2; attempt += 1) {
331
+ const response = await fetch(`/api/taskforce/task/${encodeURIComponent(requestedSummary.id)}`, {
332
+ signal: abortController.signal,
333
+ });
334
+ if (response.status === 401) {
335
+ handleUnauthorized();
336
+ return null;
337
+ }
338
+ if (response.status === 404) {
339
+ setError?.('Task not found. It may have moved or been deleted.');
340
+ return null;
341
+ }
342
+ if (!response.ok) {
343
+ setError?.('Unable to load task details.');
344
+ return null;
345
+ }
346
+ const normalizedDetail = normalizeTaskFromApi(await response.json());
347
+ const liveWorkspaceId = String(getCurrentWorkspaceId() || '').trim();
348
+ if (abortController.signal.aborted || liveWorkspaceId !== requestWorkspaceId)
349
+ return null;
350
+ const currentSummary = [...tasksRef.current, ...archivedTasksRef.current]
351
+ .find((candidate) => candidate.id === requestedSummary.id);
352
+ if (!currentSummary) {
353
+ setError?.('Task not found. It may have moved or been deleted.');
354
+ return null;
355
+ }
356
+ const currentRevision = getTaskSummaryRevision(currentSummary);
357
+ if (currentRevision !== summaryRevision) {
358
+ if (attempt === 0 && currentSummary) {
359
+ requestedSummary = currentSummary;
360
+ summaryRevision = currentRevision;
361
+ continue;
362
+ }
363
+ setError?.('Task details changed while loading. Please open the task again.');
364
+ return null;
365
+ }
366
+ if (requestedSummary.updatedAt
367
+ && normalizedDetail.updatedAt
368
+ && Date.parse(normalizedDetail.updatedAt) < Date.parse(requestedSummary.updatedAt)) {
369
+ setError?.('Task details changed while loading. Please open the task again.');
370
+ return null;
371
+ }
372
+ const detail = {
373
+ ...normalizedDetail,
374
+ detailRevision: normalizedDetail.detailRevision || summaryRevision,
375
+ };
376
+ if (options?.shouldCommit && !options.shouldCommit())
377
+ return null;
378
+ taskDetailCacheRef.current.set(detail.id, { detail, summaryRevision });
379
+ setStaleTaskDetailRevisions((current) => {
380
+ if (!Object.prototype.hasOwnProperty.call(current, detail.id))
381
+ return current;
382
+ const next = { ...current };
383
+ delete next[detail.id];
384
+ return next;
385
+ });
386
+ publishTaskDetailCache();
387
+ return detail;
388
+ }
389
+ return null;
390
+ }
391
+ catch (error) {
392
+ if (isExpectedAbortFailure(error, abortController.signal))
393
+ return null;
394
+ console.error('[Taskforce] Failed to fetch task detail:', error);
395
+ setError?.('Unable to load task details.');
396
+ return null;
397
+ }
398
+ finally {
399
+ if (taskDetailAbortRef.current === abortController) {
400
+ taskDetailAbortRef.current = null;
401
+ setLoading?.(false);
402
+ }
403
+ }
404
+ }, [getCurrentWorkspaceId, getTaskSummaryRevision, handleUnauthorized, isExpectedAbortFailure, publishTaskDetailCache, setError, setLoading]);
229
405
  const refreshTaskCollections = useCallback(async (options) => {
230
406
  const isSilent = options?.isSilent !== false;
231
407
  const ignoreAuthGuard = options?.ignoreAuthGuard === true;
@@ -253,7 +429,36 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
253
429
  }, [refreshTaskCollections]);
254
430
  // === SECTION: Merge Helper ===
255
431
  const mergeTaskFromServer = useCallback((task) => {
256
- const merged = mergeNormalizedTaskIntoCollections(task, tasksRef.current, archivedTasksRef.current);
432
+ const normalized = normalizeTaskFromApi(task);
433
+ const existingSummary = [...tasksRef.current, ...archivedTasksRef.current]
434
+ .find((candidate) => candidate.id === normalized.id);
435
+ const collectionSummary = normalized.isSummary
436
+ ? normalized
437
+ : toTaskCollectionSummary({
438
+ ...normalized,
439
+ detailRevision: undefined,
440
+ commentCount: normalized.commentCount ?? (normalized.comments ? undefined : existingSummary?.commentCount),
441
+ checklistCount: normalized.checklistCount ?? (normalized.checklistItems ? undefined : existingSummary?.checklistCount),
442
+ completedChecklistCount: normalized.completedChecklistCount
443
+ ?? (normalized.checklistItems ? undefined : existingSummary?.completedChecklistCount),
444
+ attachmentCount: normalized.attachmentCount ?? (normalized.attachments ? undefined : existingSummary?.attachmentCount),
445
+ relationshipCount: normalized.relationshipCount ?? existingSummary?.relationshipCount ?? 0,
446
+ });
447
+ if (!normalized.isSummary) {
448
+ taskDetailCacheRef.current.set(normalized.id, {
449
+ detail: normalized,
450
+ summaryRevision: getTaskSummaryRevision(collectionSummary),
451
+ });
452
+ publishTaskDetailCache();
453
+ setStaleTaskDetailRevisions((current) => {
454
+ if (!Object.prototype.hasOwnProperty.call(current, normalized.id))
455
+ return current;
456
+ const next = { ...current };
457
+ delete next[normalized.id];
458
+ return next;
459
+ });
460
+ }
461
+ const merged = mergeNormalizedTaskIntoCollections(collectionSummary, tasksRef.current, archivedTasksRef.current);
257
462
  const tasksChanged = merged.tasks !== tasksRef.current;
258
463
  const archivedTasksChanged = merged.archivedTasks !== archivedTasksRef.current;
259
464
  tasksRef.current = merged.tasks;
@@ -262,17 +467,21 @@ export function useTaskData({ tasks, archivedTasks, setTasks, setArchivedTasks,
262
467
  setTasks(merged.tasks);
263
468
  if (archivedTasksChanged)
264
469
  setArchivedTasks(merged.archivedTasks);
265
- }, [setTasks, setArchivedTasks]);
470
+ }, [getTaskSummaryRevision, publishTaskDetailCache, setTasks, setArchivedTasks]);
266
471
  return {
267
472
  // Stable refs (for use inside closures)
268
473
  tasksRef,
269
474
  archivedTasksRef,
475
+ taskDetails,
476
+ staleTaskDetailRevisions,
270
477
  // Recently changed
271
478
  recentlyChangedTaskIds,
272
479
  markRecentlyChangedTasks,
273
480
  // Fetch functions
274
481
  fetchTasks,
275
482
  fetchArchive,
483
+ loadTaskDetail,
484
+ cancelTaskDetailLoad,
276
485
  refreshTaskCollections,
277
486
  refreshTaskCollectionsFromInvalidation,
278
487
  // Helpers
@@ -13,6 +13,7 @@ export interface UseTaskMutationsProps {
13
13
  tasks: TaskItem[];
14
14
  archivedTasks: TaskItem[];
15
15
  editingTaskId: string | null;
16
+ editingTaskDetailRevision?: string | null;
16
17
  setTasks: React.Dispatch<React.SetStateAction<TaskItem[]>>;
17
18
  setArchivedTasks: React.Dispatch<React.SetStateAction<TaskItem[]>>;
18
19
  setDeletedTasks: React.Dispatch<React.SetStateAction<DeletedTaskRecord[]>>;
@@ -44,6 +45,13 @@ export interface UseTaskMutationsProps {
44
45
  durableTaskHttpRootActivationEnabled?: boolean;
45
46
  workspaceId?: string;
46
47
  taskHttpMutationClient?: ReturnType<typeof createCloudTaskHttpMutationClient>;
48
+ onTaskRevisionConflict?: (taskId: string, currentDetailRevision: string | null) => TaskRevisionConflictResolution | void | Promise<TaskRevisionConflictResolution | void>;
49
+ onTaskMutationAccepted?: (task: TaskItem) => void;
50
+ }
51
+ export interface TaskRevisionConflictResolution {
52
+ handled: boolean;
53
+ retryDetailRevision?: string;
54
+ retryPatch?: Partial<TaskItem>;
47
55
  }
48
56
  export interface DeletedTaskRestoreFailure {
49
57
  deletedRecordId: string;
@@ -58,13 +66,13 @@ export interface DeletedTaskRestoreBatchResult {
58
66
  succeeded: DeletedTaskRecord[];
59
67
  failed: DeletedTaskRestoreFailure[];
60
68
  }
61
- export declare function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks, setArchivedTasks, setDeletedTasks, setError, resetForm, setActiveTab, fetchTasks, fetchArchive, fetchDeletedTasks, mergeTaskFromServer, pushNotice, cloudAuthConfigured, runtimeMode, isAuthenticated, workspaceCloudSyncEnabled, buildWorkspaceSyncSignature, pushWorkspaceChangesToCloud, workspacePendingSignatureRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, mutateTaskMetadata, mutateTaskStatus, mutateTaskLifecycle, mutateTaskDelete, mutateTaskRestore, durableTaskMetadataActivationEnabled, durableTaskStatusActivationEnabled, durableTaskDeleteActivationEnabled, durableTaskHttpRootActivationEnabled, workspaceId, taskHttpMutationClient, }: UseTaskMutationsProps): {
69
+ export declare function useTaskMutations({ tasks, archivedTasks, editingTaskId, editingTaskDetailRevision, setTasks, setArchivedTasks, setDeletedTasks, setError, resetForm, setActiveTab, fetchTasks, fetchArchive, fetchDeletedTasks, mergeTaskFromServer, pushNotice, cloudAuthConfigured, runtimeMode, isAuthenticated, workspaceCloudSyncEnabled, buildWorkspaceSyncSignature, pushWorkspaceChangesToCloud, workspacePendingSignatureRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, mutateTaskMetadata, mutateTaskStatus, mutateTaskLifecycle, mutateTaskDelete, mutateTaskRestore, durableTaskMetadataActivationEnabled, durableTaskStatusActivationEnabled, durableTaskDeleteActivationEnabled, durableTaskHttpRootActivationEnabled, workspaceId, taskHttpMutationClient, onTaskRevisionConflict, onTaskMutationAccepted, }: UseTaskMutationsProps): {
62
70
  copiedId: string | null;
63
71
  handleDelete: (id: string, archive?: boolean, options?: {
64
72
  skipConfirm?: boolean;
65
73
  }) => Promise<boolean>;
66
74
  handleUpdateTask: (id: string, updates: Partial<TaskItem>) => Promise<boolean | undefined>;
67
- handleSetStatus: (task: TaskItem, status: DurableTaskTargetStatus, updates?: Partial<TaskItem>) => Promise<void>;
75
+ handleSetStatus: (task: TaskItem, status: DurableTaskTargetStatus, updates?: Partial<TaskItem>, expectedDetailRevisionOverride?: string | null) => Promise<void>;
68
76
  handleToggleComplete: (task: TaskItem) => Promise<void>;
69
77
  handleToggleCancel: (task: TaskItem) => Promise<void>;
70
78
  handleToggleInProgress: (task: TaskItem) => Promise<void>;