@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
@@ -41,6 +41,8 @@ import { resolveRuntimeModeContract } from '../shared/runtimeContract';
41
41
  import { DEFAULT_TYPE_VALUE } from '../shared/taxonomyDefaults.js';
42
42
  import { resolveClientDeploymentConfig } from '../config/clientDeployment';
43
43
  import { resolveTaskforceMcpBaseUrlFromBaseUrl } from '../config/cloudEnvironment';
44
+ import { applyEditorDraftToTask, formatTaskEditorConflictFields, preserveUnresolvedTaskEditorConflicts, reconcileTaskEditorDraft, resolveTaskEditorConflicts, taskToEditorDraft, } from '../utils/taskEditorReconciliation';
45
+ import { isResolvedFeatureAccessMap, } from '../runtime/appGates';
44
46
  import { buildBootstrapTraceId, withBootstrapTraceHeader } from '../utils/bootstrapTrace';
45
47
  import { buildTaskforceCredentialLoginPayload } from '../utils/authLogin';
46
48
  import { createDefaultAssigneeOptions } from '../utils/assignees';
@@ -59,6 +61,7 @@ import { useCurrentTaskRelations } from './tasks/useCurrentTaskRelations';
59
61
  import { useTaskEditorNavigation } from './tasks/useTaskEditorNavigation';
60
62
  import { useTaskFilters } from './tasks/useTaskFilters';
61
63
  import { useTaskFormActions } from './tasks/useTaskFormActions';
64
+ import { useTaskDescriptionImageDraftRecovery } from './tasks/useTaskDescriptionImageDraftRecovery';
62
65
  import { useTaskRelationships } from './tasks/useTaskRelationships';
63
66
  import { useTaskActions } from './tasks/useTaskActions';
64
67
  import { clearPersistedWorkspaceId, logBootstrapDebug, readPersistedWorkspaceId, writePersistedWorkspaceId } from './workspace/workspaceLocalState';
@@ -80,6 +83,19 @@ import { createTaskInvalidationScope, mergeTaskInvalidationScopes, resolveTaskIn
80
83
  import { createCloudWorkstreamHttpMutationClient, fetchWorkstreamHttpMutation, isConclusiveWorkstreamHttpResponse, selectCreatedWorkstreamAfterRefresh, } from './workstreams/workstreamHttpMutation';
81
84
  import { dispatchTaskforceWorkspaceResourcesInvalidated } from '../utils/workspaceResourceEvents';
82
85
  import { dispatchTaskforceAuthSessionChanged } from '../utils/authSessionEvents';
86
+ export function buildRelationshipTasks(tasks, archivedTasks, taskDetails, editingTaskId) {
87
+ const byId = new Map();
88
+ for (const task of archivedTasks)
89
+ byId.set(task.id, task);
90
+ for (const task of tasks)
91
+ byId.set(task.id, task);
92
+ if (editingTaskId && byId.has(editingTaskId)) {
93
+ const currentDetail = taskDetails.find((task) => task.id === editingTaskId);
94
+ if (currentDetail)
95
+ byId.set(editingTaskId, currentDetail);
96
+ }
97
+ return Array.from(byId.values());
98
+ }
83
99
  function upsertEntityById(items, nextItem) {
84
100
  const existingIndex = items.findIndex((item) => item.id === nextItem.id);
85
101
  if (existingIndex === -1) {
@@ -208,6 +224,7 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
208
224
  authSource: '',
209
225
  workspaceMode: '',
210
226
  workspaceSwitchingEnabled: null,
227
+ appGates: undefined,
211
228
  syncV3TaskMetadataActivation: false,
212
229
  syncV3TaskCreateActivation: false,
213
230
  syncV3TaskStatusActivation: false,
@@ -267,6 +284,9 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
267
284
  workspaceSwitchingEnabled: typeof cfg.workspaceSwitchingEnabled === 'boolean'
268
285
  ? Boolean(cfg.workspaceSwitchingEnabled)
269
286
  : (workspaceMode === 'multi-cloud'),
287
+ appGates: isResolvedFeatureAccessMap(cfg.appGates)
288
+ ? cfg.appGates
289
+ : undefined,
270
290
  syncV3TaskMetadataActivation: cfg.syncV3?.taskMetadataActivation === true,
271
291
  syncV3TaskCreateActivation: cfg.syncV3?.taskCreateActivation === true,
272
292
  syncV3TaskStatusActivation: cfg.syncV3?.taskStatusActivation === true,
@@ -619,6 +639,11 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
619
639
  const [manualComplexityEnabled, setManualComplexityEnabled] = useState(false);
620
640
  const [checklistDropdownEnabled, setChecklistDropdownEnabled] = useState(true);
621
641
  const [showTaskCardStatusLabel, setShowTaskCardStatusLabel] = useState(false);
642
+ const [showTaskCardDescription, setShowTaskCardDescription] = useState(true);
643
+ const [showTaskCardAttachments, setShowTaskCardAttachments] = useState(true);
644
+ const [showTaskCardChecklistProgress, setShowTaskCardChecklistProgress] = useState(true);
645
+ const [showTaskCardLatestActivity, setShowTaskCardLatestActivity] = useState(true);
646
+ const [tintTaskCardActivityBackground, setTintTaskCardActivityBackground] = useState(false);
622
647
  const [serverHostRoot, setServerHostRoot] = useState('');
623
648
  const [returnTab, setReturnTab] = useState(null);
624
649
  useTaskforceApiRouting({
@@ -666,6 +691,12 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
666
691
  const deletedHydrationRequestRef = useRef(null);
667
692
  const [loadingTasks, setLoadingTasks] = useState(false);
668
693
  const [editingTaskId, setEditingTaskId] = useState(null);
694
+ const editorBaselineRef = useRef(null);
695
+ const [editorBaseline, setEditorBaseline] = useState(null);
696
+ const [editingTaskRevisionConflict, setEditingTaskRevisionConflict] = useState(null);
697
+ const editingTaskRevisionConflictRef = useRef(null);
698
+ const reconcileOpenTaskWithRemoteRef = useRef(() => null);
699
+ const refreshingTaskRevisionRef = useRef(null);
669
700
  const [pendingOpenTaskId, setPendingOpenTaskId] = useState(null);
670
701
  const setPendingOpenTaskIdFromPersistedState = useCallback((taskId) => {
671
702
  if (String(initialTaskId || '').trim())
@@ -852,7 +883,7 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
852
883
  }, [AUTH_REQUIRED_ERROR, runtimeMode]);
853
884
  // === SECTION: Task Data ===
854
885
  // Delegated to useTaskData — see src/hooks/useTaskData.ts
855
- const { tasksRef, archivedTasksRef, recentlyChangedTaskIds, markRecentlyChangedTasks, fetchTasks: fetchTasksBase, fetchArchive: fetchArchiveBase, refreshTaskCollections: refreshTaskCollectionsBase, refreshTaskCollectionsFromInvalidation: refreshTaskCollectionsFromInvalidationBase, mergeTaskFromServer, getTaskRevisionKey } = useTaskData({
886
+ const { tasksRef, archivedTasksRef, taskDetails, staleTaskDetailRevisions, recentlyChangedTaskIds, markRecentlyChangedTasks, fetchTasks: fetchTasksBase, fetchArchive: fetchArchiveBase, loadTaskDetail, cancelTaskDetailLoad, refreshTaskCollections: refreshTaskCollectionsBase, refreshTaskCollectionsFromInvalidation: refreshTaskCollectionsFromInvalidationBase, mergeTaskFromServer, getTaskRevisionKey } = useTaskData({
856
887
  tasks,
857
888
  archivedTasks,
858
889
  setTasks,
@@ -863,7 +894,9 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
863
894
  shouldDeferProtectedApiCalls,
864
895
  shouldBlockProtectedApiCalls,
865
896
  handleUnauthorized,
866
- authRequiredForApi
897
+ authRequiredForApi,
898
+ setError,
899
+ setLoading
867
900
  });
868
901
  useEffect(() => {
869
902
  if (workspaceTransitionEpoch === 0)
@@ -1445,7 +1478,15 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
1445
1478
  configLoaded,
1446
1479
  referenceDataLoaded,
1447
1480
  assigneeOptionsLoaded,
1448
- assigneeOptions
1481
+ assigneeOptions,
1482
+ runtimeMode,
1483
+ workspaceId: currentWorkspaceId,
1484
+ cloudTaskSearchEnabled: runtimeMode === 'cloud'
1485
+ && isAuthenticated
1486
+ && taskScope !== 'deleted'
1487
+ && !shouldDeferProtectedApiCalls
1488
+ && !shouldBlockProtectedApiCalls,
1489
+ cloudTaskSearchScope: taskScope === 'archived' ? 'archive' : 'active',
1449
1490
  });
1450
1491
  setUserGlobalSyncStatusRef.current = setUserGlobalSyncStatus;
1451
1492
  setUserGlobalSyncErrorRef.current = setUserGlobalSyncError;
@@ -1549,7 +1590,7 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
1549
1590
  const referenceDataRequestRef = useRef(null);
1550
1591
  const workspaceListHydratedRef = useRef(false);
1551
1592
  const hasBootstrappedDataRef = useRef(false);
1552
- const { handleSaveSettings, handleSaveTheme, handleSaveGlobalTheme, handleJsonBackupEnabledChange, handleSaveGlobalJsonBackupEnabled, handleSaveGlobalWeekStartsOn, handleSaveLocale, handleManualComplexityEnabledChange, handleChecklistDropdownEnabledChange, handleShowTaskCardStatusLabelChange, handleResetProjectToGlobal } = useSettingsPersistence({
1593
+ const { handleSaveSettings, handleSaveTheme, handleSaveGlobalTheme, handleUseGlobalThemeDefault, handleJsonBackupEnabledChange, handleSaveGlobalJsonBackupEnabled, handleUseGlobalJsonBackupDefault, handleSaveGlobalWeekStartsOn, handleSaveLocale, handleManualComplexityEnabledChange, handleChecklistDropdownEnabledChange, handleShowTaskCardStatusLabelChange, handleShowTaskCardDescriptionChange, handleShowTaskCardAttachmentsChange, handleShowTaskCardChecklistProgressChange, handleShowTaskCardLatestActivityChange, handleTintTaskCardActivityBackgroundChange } = useSettingsPersistence({
1553
1594
  keyShortcut,
1554
1595
  themeUseGlobalDefault,
1555
1596
  runtimeMode,
@@ -1566,7 +1607,12 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
1566
1607
  setLocale,
1567
1608
  setManualComplexityEnabled,
1568
1609
  setChecklistDropdownEnabled,
1569
- setShowTaskCardStatusLabel
1610
+ setShowTaskCardStatusLabel,
1611
+ setShowTaskCardDescription,
1612
+ setShowTaskCardAttachments,
1613
+ setShowTaskCardChecklistProgress,
1614
+ setShowTaskCardLatestActivity,
1615
+ setTintTaskCardActivityBackground
1570
1616
  });
1571
1617
  useEffect(() => {
1572
1618
  if (runtimeMode !== 'cloud')
@@ -1767,6 +1813,36 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
1767
1813
  else {
1768
1814
  setShowTaskCardStatusLabel(false);
1769
1815
  }
1816
+ if (typeof data.showTaskCardDescription === 'boolean') {
1817
+ setShowTaskCardDescription(data.showTaskCardDescription);
1818
+ }
1819
+ else {
1820
+ setShowTaskCardDescription(true);
1821
+ }
1822
+ if (typeof data.showTaskCardAttachments === 'boolean') {
1823
+ setShowTaskCardAttachments(data.showTaskCardAttachments);
1824
+ }
1825
+ else {
1826
+ setShowTaskCardAttachments(true);
1827
+ }
1828
+ if (typeof data.showTaskCardChecklistProgress === 'boolean') {
1829
+ setShowTaskCardChecklistProgress(data.showTaskCardChecklistProgress);
1830
+ }
1831
+ else {
1832
+ setShowTaskCardChecklistProgress(true);
1833
+ }
1834
+ if (typeof data.showTaskCardLatestActivity === 'boolean') {
1835
+ setShowTaskCardLatestActivity(data.showTaskCardLatestActivity);
1836
+ }
1837
+ else {
1838
+ setShowTaskCardLatestActivity(true);
1839
+ }
1840
+ if (typeof data.tintTaskCardActivityBackground === 'boolean') {
1841
+ setTintTaskCardActivityBackground(data.tintTaskCardActivityBackground);
1842
+ }
1843
+ else {
1844
+ setTintTaskCardActivityBackground(false);
1845
+ }
1770
1846
  }, [applyResolvedWorkspaceId, commitCurrentWorkspaceId, configLoaded, currentWorkspaceIdRef]);
1771
1847
  const fetchBuildInfo = useCallback(async () => {
1772
1848
  const startedAt = typeof performance !== 'undefined' ? performance.now() : Date.now();
@@ -3301,13 +3377,8 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3301
3377
  coordinatorDataVersion: localCoordinatorDataVersion,
3302
3378
  });
3303
3379
  const relationshipTasks = useMemo(() => {
3304
- const byId = new Map();
3305
- for (const task of archivedTasks)
3306
- byId.set(task.id, task);
3307
- for (const task of tasks)
3308
- byId.set(task.id, task);
3309
- return Array.from(byId.values());
3310
- }, [tasks, archivedTasks]);
3380
+ return buildRelationshipTasks(tasks, archivedTasks, taskDetails, editingTaskId);
3381
+ }, [tasks, archivedTasks, taskDetails, editingTaskId]);
3311
3382
  const deletedRelationshipTasks = useMemo(() => deletedTasks.map((entry) => ({
3312
3383
  ...entry.taskSnapshot,
3313
3384
  isDeleted: true,
@@ -3463,13 +3534,25 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3463
3534
  setActiveTab__internal(tab);
3464
3535
  }, [activeTab]);
3465
3536
  const flushAutoSaveRef = useRef(async () => true);
3466
- const { handleNavigation, handleClose, resetForm, resolveTaskIdInput, resolveWorkstreamIdInput, handleEdit, handleOpenTaskById, returnToPreviousTask, clearReturnToParentTask } = useTaskEditorNavigation({
3537
+ const adoptTaskEditorBaseline = useCallback((task) => {
3538
+ const baseline = {
3539
+ taskId: task.id,
3540
+ task,
3541
+ draft: taskToEditorDraft(task),
3542
+ };
3543
+ editorBaselineRef.current = baseline;
3544
+ setEditorBaseline(baseline);
3545
+ editingTaskRevisionConflictRef.current = null;
3546
+ setEditingTaskRevisionConflict(null);
3547
+ }, []);
3548
+ const { handleNavigation, handleClose, resetForm, resolveTaskIdInput, resolveWorkstreamIdInput, handleEdit, applyTaskToEditor, handleOpenTaskById, returnToPreviousTask, clearReturnToParentTask } = useTaskEditorNavigation({
3467
3549
  activeCategories,
3468
3550
  activeTypes,
3469
3551
  activeTab,
3470
3552
  attachments,
3471
3553
  checklistItems,
3472
3554
  comments,
3555
+ cancelTaskDetailLoad,
3473
3556
  description,
3474
3557
  editingTaskId,
3475
3558
  flushPendingAutoSave: () => flushAutoSaveRef.current(),
@@ -3477,7 +3560,9 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3477
3560
  getPreferredCategoryValue,
3478
3561
  getTitleDraft,
3479
3562
  lastUsedCategory,
3563
+ loadTaskDetail,
3480
3564
  newCommentText,
3565
+ onTaskApplied: adoptTaskEditorBaseline,
3481
3566
  relationshipTasks,
3482
3567
  workstreams,
3483
3568
  showArchive,
@@ -3510,6 +3595,43 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3510
3595
  taskReturnTrail,
3511
3596
  title,
3512
3597
  });
3598
+ const readCurrentTaskEditorDraft = useCallback(() => {
3599
+ const resolvedWorkstream = workstreamInput.trim()
3600
+ ? resolveWorkstreamIdInput(workstreamInput)
3601
+ : null;
3602
+ return {
3603
+ title: getTitleDraft(),
3604
+ description: getDescriptionDraft(),
3605
+ category: typeof category === 'string' ? category : String(category.label || ''),
3606
+ type: typeof type === 'string' ? type : String(type.label || ''),
3607
+ priority,
3608
+ complexity: Number(complexity) || 3,
3609
+ status,
3610
+ assignee,
3611
+ scheduledDate,
3612
+ dueDate,
3613
+ workstreamId: resolvedWorkstream?.id || workstreamInput.trim(),
3614
+ taxonomies: { ...formTaxonomies },
3615
+ checklistItems: [...checklistItems],
3616
+ attachments: getAttachmentsDraft(),
3617
+ };
3618
+ }, [
3619
+ assignee,
3620
+ category,
3621
+ checklistItems,
3622
+ complexity,
3623
+ dueDate,
3624
+ formTaxonomies,
3625
+ getAttachmentsDraft,
3626
+ getDescriptionDraft,
3627
+ getTitleDraft,
3628
+ priority,
3629
+ resolveWorkstreamIdInput,
3630
+ scheduledDate,
3631
+ status,
3632
+ type,
3633
+ workstreamInput,
3634
+ ]);
3513
3635
  useEffect(() => {
3514
3636
  const normalizedTaskId = String(pendingOpenTaskId || '').trim();
3515
3637
  if (!uiStateReady || !normalizedTaskId)
@@ -3805,10 +3927,45 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3805
3927
  isAuthenticated,
3806
3928
  dispatchEnabled: workspaceCloudSyncEnabled,
3807
3929
  });
3930
+ const handleTaskRevisionConflict = useCallback(async (taskId, _currentDetailRevision) => {
3931
+ if (taskId !== editingTaskId)
3932
+ return { handled: false };
3933
+ const task = [...tasks, ...archivedTasks].find((candidate) => candidate.id === taskId);
3934
+ if (!task)
3935
+ return { handled: false };
3936
+ const detail = await loadTaskDetail(task, {
3937
+ force: true,
3938
+ shouldCommit: () => editorBaselineRef.current?.taskId === taskId,
3939
+ });
3940
+ if (!detail || editorBaselineRef.current?.taskId !== taskId)
3941
+ return { handled: false };
3942
+ const reconciliation = reconcileOpenTaskWithRemoteRef.current(detail, 'save');
3943
+ return {
3944
+ handled: true,
3945
+ retryDetailRevision: reconciliation?.conflicts.length === 0
3946
+ ? detail.detailRevision
3947
+ : undefined,
3948
+ retryPatch: reconciliation?.conflicts.length === 0
3949
+ ? applyEditorDraftToTask(detail, reconciliation.merged)
3950
+ : undefined,
3951
+ };
3952
+ }, [archivedTasks, editingTaskId, loadTaskDetail, tasks]);
3953
+ const handleTaskMutationAccepted = useCallback((task) => {
3954
+ if (task.id !== editingTaskId)
3955
+ return;
3956
+ if (editingTaskRevisionConflictRef.current?.taskId === task.id) {
3957
+ reconcileOpenTaskWithRemoteRef.current(task, 'refresh');
3958
+ return;
3959
+ }
3960
+ adoptTaskEditorBaseline(task);
3961
+ }, [adoptTaskEditorBaseline, editingTaskId]);
3808
3962
  const { copiedId, handleDelete, handleUpdateTask, handleSetStatus, handleToggleComplete, handleToggleCancel, handleToggleInProgress, handleToggleReview, handleArchiveTask, handleBulkArchive, handleUnarchive, handleRestoreDeletedTask, handleRestoreSelectedDeletedTasks, handlePermanentlyDeleteDeletedTask, handleEmptyDeletedTasks, handleCopyId, queueWorkspaceSyncFromAuthoritativeTaskState, } = useTaskActions({
3809
3963
  tasks,
3810
3964
  archivedTasks,
3811
3965
  editingTaskId,
3966
+ editingTaskDetailRevision: editorBaseline?.taskId === editingTaskId
3967
+ ? editorBaseline.task.detailRevision || null
3968
+ : null,
3812
3969
  setTasks,
3813
3970
  setArchivedTasks,
3814
3971
  setDeletedTasks,
@@ -3840,24 +3997,19 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3840
3997
  durableTaskDeleteActivationEnabled: taskDeleteActivationEnabled,
3841
3998
  durableTaskHttpRootActivationEnabled: runtimeConfigOverride.syncV3TaskCloudHttpRootActivation,
3842
3999
  workspaceId: currentWorkspaceId,
4000
+ onTaskRevisionConflict: handleTaskRevisionConflict,
4001
+ onTaskMutationAccepted: handleTaskMutationAccepted,
3843
4002
  });
4003
+ const { resolveDraft: resolveDescriptionImageDraft } = useTaskDescriptionImageDraftRecovery(currentWorkspaceId, authUserId);
3844
4004
  const promoteDescriptionImageDraft = useCallback(async () => {
3845
4005
  const draftId = descriptionImageDraftIdRef.current;
3846
- const response = await fetch('/api/taskforce/context-upload/draft/promote', {
3847
- method: 'POST',
3848
- headers: { 'Content-Type': 'application/json' },
3849
- body: JSON.stringify({
3850
- workspaceId: currentWorkspaceId,
3851
- draftUploadId: draftId,
3852
- uploadPurpose: 'task-description-draft',
3853
- }),
3854
- });
3855
- if (!response.ok) {
3856
- throw new Error('Task was created, but its draft upload records could not be finalized.');
3857
- }
3858
4006
  rotateDescriptionImageDraftId();
3859
- }, [currentWorkspaceId, rotateDescriptionImageDraftId]);
3860
- const { autoSaveState, flushAutoSave, scheduleWarningPrompt, handleSubmit, confirmScheduleWarning, cancelScheduleWarning, handleAddComment } = useTaskFormActions({
4007
+ await resolveDescriptionImageDraft({
4008
+ draftUploadId: draftId,
4009
+ resolution: 'promote',
4010
+ });
4011
+ }, [resolveDescriptionImageDraft, rotateDescriptionImageDraftId]);
4012
+ const { autoSaveState, hasUnsavedChanges, flushAutoSave, scheduleWarningPrompt, handleSubmit, confirmScheduleWarning, cancelScheduleWarning, handleAddComment } = useTaskFormActions({
3861
4013
  activeCategories,
3862
4014
  activeTab,
3863
4015
  apiEndpoint,
@@ -3875,6 +4027,8 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3875
4027
  workspaceId: currentWorkspaceId,
3876
4028
  updateTask: handleUpdateTask,
3877
4029
  editingTaskId,
4030
+ editorBaselineDraft: editorBaseline?.taskId === editingTaskId ? editorBaseline.draft : null,
4031
+ revisionConflict: editingTaskRevisionConflict?.taskId === editingTaskId,
3878
4032
  fetchTasks,
3879
4033
  formTaxonomies,
3880
4034
  getAttachmentsDraft,
@@ -3907,27 +4061,166 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3907
4061
  textDraftOccupied,
3908
4062
  type
3909
4063
  });
4064
+ const reconcileOpenTaskWithRemote = useCallback((remoteTask, source) => {
4065
+ if (!editingTaskId || remoteTask.id !== editingTaskId)
4066
+ return null;
4067
+ const previousBaseline = editorBaselineRef.current?.taskId === editingTaskId
4068
+ ? editorBaselineRef.current
4069
+ : { taskId: editingTaskId, task: remoteTask, draft: taskToEditorDraft(remoteTask) };
4070
+ const localDraft = readCurrentTaskEditorDraft();
4071
+ const remoteDraft = taskToEditorDraft(remoteTask);
4072
+ const nextReconciliation = reconcileTaskEditorDraft(previousBaseline.draft, localDraft, remoteDraft);
4073
+ const unresolvedConflicts = editingTaskRevisionConflictRef.current?.taskId === editingTaskId
4074
+ ? editingTaskRevisionConflictRef.current.reconciliation.conflicts
4075
+ : [];
4076
+ const reconciliation = {
4077
+ ...nextReconciliation,
4078
+ conflicts: preserveUnresolvedTaskEditorConflicts(unresolvedConflicts, nextReconciliation.conflicts, localDraft, remoteDraft),
4079
+ };
4080
+ const mergedTask = applyEditorDraftToTask(remoteTask, reconciliation.merged);
4081
+ const nextBaseline = { taskId: editingTaskId, task: remoteTask, draft: remoteDraft };
4082
+ editorBaselineRef.current = nextBaseline;
4083
+ setEditorBaseline(nextBaseline);
4084
+ applyTaskToEditor(mergedTask, { preserveReturnTrail: true, adoptAsBaseline: false });
4085
+ setAttachmentsDirty(JSON.stringify(reconciliation.merged.attachments) !== JSON.stringify(remoteDraft.attachments));
4086
+ setError('');
4087
+ if (reconciliation.conflicts.length > 0) {
4088
+ const nextConflict = {
4089
+ taskId: editingTaskId,
4090
+ detailRevision: remoteTask.detailRevision || 'unknown',
4091
+ source,
4092
+ reconciliation,
4093
+ remoteTask,
4094
+ remoteDraft,
4095
+ };
4096
+ editingTaskRevisionConflictRef.current = nextConflict;
4097
+ setEditingTaskRevisionConflict(nextConflict);
4098
+ }
4099
+ else {
4100
+ editingTaskRevisionConflictRef.current = null;
4101
+ setEditingTaskRevisionConflict(null);
4102
+ if (reconciliation.remoteChangedFields.length > 0) {
4103
+ pushNotice('Task updated', 'info', 2200);
4104
+ }
4105
+ }
4106
+ return reconciliation;
4107
+ }, [
4108
+ applyTaskToEditor,
4109
+ editingTaskId,
4110
+ pushNotice,
4111
+ readCurrentTaskEditorDraft,
4112
+ setAttachmentsDirty,
4113
+ setError,
4114
+ ]);
4115
+ reconcileOpenTaskWithRemoteRef.current = reconcileOpenTaskWithRemote;
4116
+ const resolveEditingTaskConflict = useCallback((resolution) => {
4117
+ const conflict = editingTaskRevisionConflict;
4118
+ if (!conflict || conflict.taskId !== editingTaskId)
4119
+ return;
4120
+ const resolvedDraft = resolveTaskEditorConflicts(conflict.reconciliation, conflict.remoteDraft, resolution, readCurrentTaskEditorDraft());
4121
+ const resolvedTask = applyEditorDraftToTask(conflict.remoteTask, resolvedDraft);
4122
+ applyTaskToEditor(resolvedTask, { preserveReturnTrail: true, adoptAsBaseline: false });
4123
+ setAttachmentsDirty(JSON.stringify(resolvedDraft.attachments) !== JSON.stringify(conflict.remoteDraft.attachments));
4124
+ editingTaskRevisionConflictRef.current = null;
4125
+ setEditingTaskRevisionConflict(null);
4126
+ setError('');
4127
+ clearNotice();
4128
+ const statusWasConflicted = conflict.reconciliation.conflicts.some((item) => item.field === 'status');
4129
+ if (resolution === 'local'
4130
+ && statusWasConflicted
4131
+ && resolvedDraft.status !== conflict.remoteDraft.status) {
4132
+ void handleSetStatus(conflict.remoteTask, resolvedDraft.status);
4133
+ }
4134
+ }, [
4135
+ applyTaskToEditor,
4136
+ clearNotice,
4137
+ editingTaskId,
4138
+ editingTaskRevisionConflict,
4139
+ handleSetStatus,
4140
+ readCurrentTaskEditorDraft,
4141
+ setAttachmentsDirty,
4142
+ setError,
4143
+ ]);
4144
+ useEffect(() => {
4145
+ const conflict = editingTaskRevisionConflict;
4146
+ if (!conflict || conflict.taskId !== editingTaskId)
4147
+ return;
4148
+ const fields = formatTaskEditorConflictFields(conflict.reconciliation.conflicts);
4149
+ const fieldSummary = fields.length > 0 ? fields.join(', ') : 'task fields';
4150
+ pushNotice(`${fieldSummary.charAt(0).toUpperCase()}${fieldSummary.slice(1)} also changed elsewhere.`, 'info', undefined, {
4151
+ label: 'Use latest',
4152
+ primary: true,
4153
+ onAction: () => resolveEditingTaskConflict('latest'),
4154
+ secondaryLabel: 'Keep mine',
4155
+ onSecondaryAction: () => resolveEditingTaskConflict('local'),
4156
+ dismissible: false,
4157
+ ownerKey: `task-editor-conflict:${conflict.taskId}:${conflict.detailRevision}`,
4158
+ });
4159
+ }, [editingTaskId, editingTaskRevisionConflict, pushNotice, resolveEditingTaskConflict]);
4160
+ useEffect(() => {
4161
+ if (!editingTaskId) {
4162
+ refreshingTaskRevisionRef.current = null;
4163
+ setEditingTaskRevisionConflict(null);
4164
+ editingTaskRevisionConflictRef.current = null;
4165
+ editorBaselineRef.current = null;
4166
+ setEditorBaseline(null);
4167
+ return;
4168
+ }
4169
+ const staleRevision = staleTaskDetailRevisions[editingTaskId];
4170
+ if (!staleRevision) {
4171
+ refreshingTaskRevisionRef.current = null;
4172
+ return;
4173
+ }
4174
+ const summary = [...tasks, ...archivedTasks].find((task) => task.id === editingTaskId);
4175
+ if (!summary)
4176
+ return;
4177
+ const refreshKey = `${editingTaskId}:${staleRevision}`;
4178
+ if (refreshingTaskRevisionRef.current === refreshKey)
4179
+ return;
4180
+ refreshingTaskRevisionRef.current = refreshKey;
4181
+ void loadTaskDetail(summary, {
4182
+ shouldCommit: () => editorBaselineRef.current?.taskId === editingTaskId,
4183
+ }).then((detail) => {
4184
+ if (editorBaselineRef.current?.taskId !== editingTaskId)
4185
+ return;
4186
+ if (!detail) {
4187
+ setError('The latest task update could not be loaded. Your local draft has been preserved.');
4188
+ return;
4189
+ }
4190
+ reconcileOpenTaskWithRemote(detail, 'refresh');
4191
+ }).finally(() => {
4192
+ if (refreshingTaskRevisionRef.current === refreshKey) {
4193
+ refreshingTaskRevisionRef.current = null;
4194
+ }
4195
+ });
4196
+ }, [
4197
+ archivedTasks,
4198
+ editingTaskId,
4199
+ loadTaskDetail,
4200
+ reconcileOpenTaskWithRemote,
4201
+ setError,
4202
+ staleTaskDetailRevisions,
4203
+ tasks,
4204
+ ]);
3910
4205
  const discardDescriptionImageDraft = useCallback(async () => {
3911
4206
  const draftId = descriptionImageDraftIdRef.current;
3912
4207
  rotateDescriptionImageDraftId();
3913
4208
  try {
3914
4209
  await awaitPendingDescriptionImageUpload();
3915
- const response = await fetch('/api/taskforce/context-upload/draft/discard', {
3916
- method: 'POST',
3917
- headers: { 'Content-Type': 'application/json' },
3918
- body: JSON.stringify({
3919
- workspaceId: currentWorkspaceId,
3920
- draftUploadId: draftId,
3921
- uploadPurpose: 'task-description-draft',
3922
- }),
4210
+ }
4211
+ catch (error) {
4212
+ console.warn('[Taskforce] Description image upload failed while the draft was being discarded.', error);
4213
+ }
4214
+ try {
4215
+ await resolveDescriptionImageDraft({
4216
+ draftUploadId: draftId,
4217
+ resolution: 'discard',
3923
4218
  });
3924
- if (!response.ok)
3925
- throw new Error('Draft upload cleanup was rejected.');
3926
4219
  }
3927
4220
  catch (error) {
3928
4221
  console.warn('[Taskforce] Failed to clean discarded Description image uploads.', error);
3929
4222
  }
3930
- }, [awaitPendingDescriptionImageUpload, currentWorkspaceId, rotateDescriptionImageDraftId]);
4223
+ }, [awaitPendingDescriptionImageUpload, resolveDescriptionImageDraft, rotateDescriptionImageDraftId]);
3931
4224
  useEffect(() => {
3932
4225
  flushAutoSaveRef.current = flushAutoSave;
3933
4226
  }, [flushAutoSave]);
@@ -3974,6 +4267,11 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3974
4267
  manualComplexityEnabled,
3975
4268
  checklistDropdownEnabled,
3976
4269
  showTaskCardStatusLabel,
4270
+ showTaskCardDescription,
4271
+ showTaskCardAttachments,
4272
+ showTaskCardChecklistProgress,
4273
+ showTaskCardLatestActivity,
4274
+ tintTaskCardActivityBackground,
3977
4275
  pathSaved,
3978
4276
  settingsSection,
3979
4277
  setupState,
@@ -3983,15 +4281,21 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
3983
4281
  setCurrentTheme,
3984
4282
  handleSaveTheme,
3985
4283
  handleSaveGlobalTheme,
4284
+ handleUseGlobalThemeDefault,
3986
4285
  setKeyShortcut,
3987
4286
  handleJsonBackupEnabledChange,
3988
4287
  handleSaveGlobalJsonBackupEnabled,
4288
+ handleUseGlobalJsonBackupDefault,
3989
4289
  handleSaveGlobalWeekStartsOn,
3990
4290
  handleSaveLocale,
3991
4291
  handleManualComplexityEnabledChange,
3992
4292
  handleChecklistDropdownEnabledChange,
3993
4293
  handleShowTaskCardStatusLabelChange,
3994
- handleResetProjectToGlobal,
4294
+ handleShowTaskCardDescriptionChange,
4295
+ handleShowTaskCardAttachmentsChange,
4296
+ handleShowTaskCardChecklistProgressChange,
4297
+ handleShowTaskCardLatestActivityChange,
4298
+ handleTintTaskCardActivityBackgroundChange,
3995
4299
  handleSaveSettings,
3996
4300
  initiativeTemplates,
3997
4301
  fetchInitiativeTemplates,
@@ -4091,6 +4395,7 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
4091
4395
  const effectiveConfig = {
4092
4396
  ...mergedConfig,
4093
4397
  cloudEnvironment: runtimeConfigOverride.cloudEnvironment || mergedConfig.cloudEnvironment || '',
4398
+ appGates: runtimeConfigOverride.appGates || mergedConfig.appGates,
4094
4399
  apiBaseUrl: normalizedApiBaseUrl || mergedConfig.apiBaseUrl,
4095
4400
  cloudAuthBaseUrl: normalizedCloudAuthBaseUrl || mergedConfig.cloudAuthBaseUrl,
4096
4401
  wsBaseUrl: effectiveWsBaseUrl || mergedConfig.wsBaseUrl
@@ -4121,6 +4426,11 @@ export function useTaskforce({ config = {}, initialTaskId, initialActivityEntryI
4121
4426
  manualComplexityEnabled,
4122
4427
  checklistDropdownEnabled,
4123
4428
  showTaskCardStatusLabel,
4429
+ showTaskCardDescription,
4430
+ showTaskCardAttachments,
4431
+ showTaskCardChecklistProgress,
4432
+ showTaskCardLatestActivity,
4433
+ tintTaskCardActivityBackground,
4124
4434
  setManualComplexityEnabled,
4125
4435
  mcpHostRoot,
4126
4436
  setMcpHostRoot,
@@ -14,6 +14,10 @@ export declare function useUiNotice(): {
14
14
  pushNotice: (message: string, tone?: UiNotice["tone"], ttlMs?: number, action?: {
15
15
  label: string;
16
16
  onAction: () => void;
17
+ primary?: boolean;
18
+ secondaryLabel?: string;
19
+ onSecondaryAction?: () => void;
20
+ dismissible?: boolean;
17
21
  ownerKey?: string;
18
22
  }) => void;
19
23
  clearNotice: () => void;
@@ -35,6 +35,10 @@ export function useUiNotice() {
35
35
  ttlMs,
36
36
  actionLabel: action?.label,
37
37
  onAction: action?.onAction,
38
+ actionPrimary: action?.primary,
39
+ secondaryActionLabel: action?.secondaryLabel,
40
+ onSecondaryAction: action?.onSecondaryAction,
41
+ dismissible: action?.dismissible,
38
42
  ownerKey: action?.ownerKey
39
43
  });
40
44
  // Actionable notices stay available until the action or explicit
@@ -78,11 +78,11 @@ export function registerAdminWorkspaceTools(registerTool, executeTool) {
78
78
  description: { type: 'string' },
79
79
  category: { type: 'string' },
80
80
  type: { type: 'string' },
81
- priority: { type: 'number', description: context.priorityDesc },
81
+ priority: { type: 'integer', description: context.priorityDesc, enum: context.priorityEnum },
82
82
  assignee: { type: 'string', description: `${context.assigneeHint} Call list_assignees to inspect the current eligible IDs.` },
83
83
  owner: { type: 'string', description: `${context.assigneeHint} Alias for assignee.` },
84
84
  status: { type: 'string', enum: ['task', 'on-hold', 'in-progress', 'review', 'done', 'cancelled'] },
85
- complexity: { type: 'number', description: context.complexityDesc },
85
+ complexity: { type: 'integer', minimum: 1, maximum: 5, description: context.complexityDesc },
86
86
  },
87
87
  required: ['id'],
88
88
  }),
@@ -101,7 +101,7 @@ export function registerAdminWorkspaceTools(registerTool, executeTool) {
101
101
  priorities: arrayProperty('Ordered priority definitions to save.', {
102
102
  type: 'object',
103
103
  properties: {
104
- value: { type: 'number' },
104
+ value: { type: 'integer', minimum: 1 },
105
105
  label: { type: 'string' },
106
106
  },
107
107
  }),