@taskforcehq/taskforce 0.3.304 → 0.3.306

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 (383) hide show
  1. package/README.md +140 -247
  2. package/dist/Taskforce.module.css +295 -89
  3. package/dist/TaskforceCore.js +378 -111
  4. package/dist/TaskforceCore.test.js +1221 -107
  5. package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
  6. package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  7. package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
  8. package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
  9. package/dist/assets/fonts/Noir_medium.woff2 +0 -0
  10. package/dist/assets/fonts/Noir_regular.woff2 +0 -0
  11. package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
  12. package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
  13. package/dist/compat/workspaceSyncCompat.js +52 -2
  14. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  15. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  16. package/dist/components/context/ContextAttachmentManager.js +549 -0
  17. package/dist/components/features/AgentsModule.d.ts +10 -1
  18. package/dist/components/features/AgentsModule.js +260 -15
  19. package/dist/components/features/AgentsModule.test.js +255 -5
  20. package/dist/components/features/DocumentIndex.d.ts +1 -1
  21. package/dist/components/features/DocumentIndex.js +1 -1
  22. package/dist/components/features/DocumentViewer.d.ts +6 -2
  23. package/dist/components/features/DocumentViewer.js +90 -10
  24. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  25. package/dist/components/features/DocumentWorkspace.js +12 -155
  26. package/dist/components/features/DocumentWorkspace.test.js +226 -6
  27. package/dist/components/features/TaskSettings.js +29 -3
  28. package/dist/components/features/TaskSettings.test.js +42 -1
  29. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  30. package/dist/components/features/documentWorkspaceModel.js +57 -0
  31. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  32. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  33. package/dist/components/task/TaskCard.js +19 -28
  34. package/dist/components/task/TaskCard.test.js +38 -3
  35. package/dist/components/task/TaskContextUpload.js +4 -526
  36. package/dist/components/task/TaskForm.d.ts +8 -8
  37. package/dist/components/task/TaskForm.js +362 -230
  38. package/dist/components/task/TaskForm.test.js +558 -381
  39. package/dist/components/task/TaskKanban.d.ts +2 -1
  40. package/dist/components/task/TaskKanban.js +96 -14
  41. package/dist/components/task/TaskKanban.test.js +36 -0
  42. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  43. package/dist/components/ui/EditableAvatarButton.js +18 -0
  44. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  45. package/dist/components/views/PlanComparisonPage.js +70 -17
  46. package/dist/components/views/PlanComparisonPage.test.js +289 -41
  47. package/dist/components/views/PlansPage.js +239 -106
  48. package/dist/components/views/PlansPage.test.js +593 -42
  49. package/dist/components/views/StandaloneLayout.js +310 -554
  50. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  51. package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
  52. package/dist/components/views/panels/PlanningDrawer.js +18 -11
  53. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  54. package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
  55. package/dist/components/views/planningScope.d.ts +20 -0
  56. package/dist/components/views/planningScope.js +25 -0
  57. package/dist/components/views/planningScope.test.d.ts +1 -0
  58. package/dist/components/views/planningScope.test.js +60 -0
  59. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +27 -14
  60. package/dist/components/views/standalone/modals/AccountHubModal.js +87 -15
  61. package/dist/components/views/standalone/modals/AccountHubModal.test.js +42 -2
  62. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  63. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  64. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  65. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  66. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  67. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  68. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  69. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  70. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  71. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  72. package/dist/config/envSchema.js +1 -1
  73. package/dist/core/AiProfileService.d.ts +45 -3
  74. package/dist/core/AiProfileService.js +774 -63
  75. package/dist/core/AiProfileService.test.js +22 -2
  76. package/dist/core/AiProfiles.test.js +761 -2
  77. package/dist/core/AttachmentLinkService.js +57 -15
  78. package/dist/core/AuthIdentityService.d.ts +107 -0
  79. package/dist/core/AuthIdentityService.js +284 -0
  80. package/dist/core/AuthTokenService.d.ts +4 -0
  81. package/dist/core/AuthTokenService.js +34 -6
  82. package/dist/core/AuthTokenService.test.d.ts +1 -0
  83. package/dist/core/AuthTokenService.test.js +78 -0
  84. package/dist/core/EntitlementsPolicy.test.js +77 -15
  85. package/dist/core/GlobalSettingsService.js +115 -6
  86. package/dist/core/PlanEntitlementService.d.ts +30 -3
  87. package/dist/core/PlanEntitlementService.js +454 -93
  88. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  89. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  90. package/dist/core/PlanVersionPolicy.test.js +54 -28
  91. package/dist/core/PlanningEntities.test.js +52 -0
  92. package/dist/core/SignupMonetizationSettings.test.js +241 -44
  93. package/dist/core/SyncReconciliationService.js +2 -65
  94. package/dist/core/SystemAdmin.test.js +244 -88
  95. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  96. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  97. package/dist/core/Taskforce.d.ts +228 -14
  98. package/dist/core/Taskforce.js +814 -201
  99. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  100. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  101. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  102. package/dist/core/WorkspaceLifecycleService.js +0 -5
  103. package/dist/core/WorkspacePermissions.test.js +25 -1
  104. package/dist/core/WorkspacePlanMode.test.js +128 -27
  105. package/dist/core/shared.d.ts +3 -1
  106. package/dist/core/shared.js +16 -1
  107. package/dist/core/types.d.ts +68 -2
  108. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
  109. package/dist/hooks/sync/auth.js +3 -1
  110. package/dist/hooks/sync/bootstrap.js +2 -0
  111. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  112. package/dist/hooks/sync/controlPlane.js +78 -0
  113. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  114. package/dist/hooks/sync/controlPlane.test.js +121 -0
  115. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  116. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  117. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  118. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  119. package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
  120. package/dist/hooks/sync/orchestratorShared.js +68 -47
  121. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  122. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  123. package/dist/hooks/sync/pullLifecycle.js +24 -0
  124. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  125. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  126. package/dist/hooks/sync/recovery.d.ts +16 -2
  127. package/dist/hooks/sync/recovery.js +171 -53
  128. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  129. package/dist/hooks/sync/recovery.test.js +292 -0
  130. package/dist/hooks/sync/transfers.d.ts +16 -3
  131. package/dist/hooks/sync/transfers.js +186 -65
  132. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  133. package/dist/hooks/sync/transfers.test.js +396 -0
  134. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  135. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  136. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  137. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  138. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  139. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  140. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  141. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  142. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  143. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  144. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  145. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  146. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  147. package/dist/hooks/useSyncOrchestrator.d.ts +16 -6
  148. package/dist/hooks/useSyncOrchestrator.js +424 -175
  149. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +487 -10
  150. package/dist/hooks/useTaskData.js +8 -3
  151. package/dist/hooks/useTaskData.test.js +45 -0
  152. package/dist/hooks/useTaskMutations.d.ts +2 -1
  153. package/dist/hooks/useTaskMutations.js +12 -1
  154. package/dist/hooks/useTaskMutations.test.js +30 -1
  155. package/dist/hooks/useTaskforce.d.ts +65 -4
  156. package/dist/hooks/useTaskforce.js +564 -243
  157. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  158. package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
  159. package/dist/hooks/useTaskforce.sync-behavior.test.js +2454 -208
  160. package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
  161. package/dist/hooks/useWorkspaceSyncController.js +17 -2
  162. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  163. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  164. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
  165. package/dist/localization/locales/en-US.d.ts +0 -1
  166. package/dist/localization/locales/en-US.js +0 -1
  167. package/dist/localization/locales/es-419.js +0 -1
  168. package/dist/localization/locales/pt-BR.js +0 -1
  169. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  170. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  171. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  172. package/dist/mcp/collaborationRegistrar.js +71 -0
  173. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  174. package/dist/mcp/documentAssetRegistrar.js +346 -0
  175. package/dist/mcp/runtime.d.ts +2 -0
  176. package/dist/mcp/runtime.js +2433 -3534
  177. package/dist/mcp/runtime.test.js +440 -2
  178. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  179. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  180. package/dist/mcp/toolCatalog.d.ts +35 -0
  181. package/dist/mcp/toolCatalog.js +3 -0
  182. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  183. package/dist/migrations/taskSchemaMigrations.js +171 -61
  184. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  185. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  186. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  187. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  188. package/dist/resources/templates/workflows/plan.yaml +1 -1
  189. package/dist/resources/templates/workflows/review.yaml +2 -2
  190. package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
  191. package/dist/server/auth/providers/apple.d.ts +33 -0
  192. package/dist/server/auth/providers/apple.js +82 -0
  193. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  194. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  195. package/dist/server/auth/providers/github.d.ts +26 -0
  196. package/dist/server/auth/providers/github.js +86 -0
  197. package/dist/server/auth/providers/google.d.ts +21 -0
  198. package/dist/server/auth/providers/google.js +56 -0
  199. package/dist/server/auth/providers/types.d.ts +21 -0
  200. package/dist/server/auth/providers/types.js +1 -0
  201. package/dist/server/auth.d.ts +2 -0
  202. package/dist/server/auth.js +6 -3
  203. package/dist/server/documentReviewRoutes.test.js +36 -3
  204. package/dist/server/index.cookieProxy.test.js +1 -0
  205. package/dist/server/index.d.ts +12 -0
  206. package/dist/server/index.js +120 -9
  207. package/dist/server/index.rateLimit.test.js +3 -0
  208. package/dist/server/index.test.js +106 -3
  209. package/dist/server/routes/admin.d.ts +4 -5
  210. package/dist/server/routes/admin.js +410 -80
  211. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  212. package/dist/server/routes/annotatedAttachments.js +1 -1
  213. package/dist/server/routes/auth.d.ts +16 -6
  214. package/dist/server/routes/auth.js +1015 -118
  215. package/dist/server/routes/authSupport.d.ts +30 -0
  216. package/dist/server/routes/authSupport.js +81 -0
  217. package/dist/server/routes/billing.d.ts +1 -1
  218. package/dist/server/routes/billing.js +276 -34
  219. package/dist/server/routes/billing.test.js +760 -52
  220. package/dist/server/routes/documentReviews.d.ts +1 -1
  221. package/dist/server/routes/documentReviews.js +1 -1
  222. package/dist/server/routes/documents.d.ts +5 -2
  223. package/dist/server/routes/documents.js +242 -74
  224. package/dist/server/routes/primitives.d.ts +11 -0
  225. package/dist/server/routes/primitives.js +73 -0
  226. package/dist/server/routes/resources.d.ts +1 -1
  227. package/dist/server/routes/resources.js +1 -1
  228. package/dist/server/routes/shared.d.ts +29 -3
  229. package/dist/server/routes/shared.js +69 -4
  230. package/dist/server/routes/sync.d.ts +1 -1
  231. package/dist/server/routes/sync.integration.test.js +437 -39
  232. package/dist/server/routes/sync.js +62 -606
  233. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  234. package/dist/server/routes/syncAuxRoutes.js +91 -0
  235. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  236. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  237. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  238. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  239. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  240. package/dist/server/routes/syncPushRoutes.js +212 -0
  241. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  242. package/dist/server/routes/syncRouteGuards.js +67 -0
  243. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  244. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  245. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  246. package/dist/server/routes/syncRouteTypes.js +1 -0
  247. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  248. package/dist/server/routes/syncSnapshotRoutes.js +180 -0
  249. package/dist/server/routes/tasks.d.ts +1 -1
  250. package/dist/server/routes/tasks.js +44 -2
  251. package/dist/server/routes/workspaces.d.ts +1 -1
  252. package/dist/server/routes/workspaces.js +37 -6
  253. package/dist/server/routes.d.ts +2 -9
  254. package/dist/server/routes.js +136 -165
  255. package/dist/server/routes.test.js +2457 -162
  256. package/dist/services/workflowExportSnapshots.test.js +2 -0
  257. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  258. package/dist/shared/aiProfileSeatScope.js +21 -0
  259. package/dist/shared/passwordPolicy.d.ts +13 -0
  260. package/dist/shared/passwordPolicy.js +84 -0
  261. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  262. package/dist/shared/passwordPolicy.test.js +34 -0
  263. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  264. package/dist/storage/workspaceAssetStore.js +132 -44
  265. package/dist/storage/workspaceAssetStore.test.js +93 -6
  266. package/dist/styles/fonts.css +70 -0
  267. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  268. package/dist/sync/collaborationSyncPayload.js +17 -11
  269. package/dist/sync/collaborationSyncPayload.test.js +13 -1
  270. package/dist/sync/contentSyncState.d.ts +4 -8
  271. package/dist/sync/contentSyncState.js +33 -77
  272. package/dist/sync/syncApplyHandlers.d.ts +7 -0
  273. package/dist/sync/syncApplyHandlers.js +28 -13
  274. package/dist/sync/syncApplyHandlers.test.js +72 -3
  275. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  276. package/dist/sync/syncFieldSemantics.js +55 -0
  277. package/dist/sync/syncResourceAdapters.d.ts +44 -0
  278. package/dist/sync/syncResourceAdapters.js +77 -0
  279. package/dist/sync/syncService.d.ts +6 -0
  280. package/dist/sync/syncService.js +60 -0
  281. package/dist/sync/syncService.test.js +71 -3
  282. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  283. package/dist/sync/taskSyncChangeKey.js +143 -0
  284. package/dist/sync/taskSyncPayload.js +3 -8
  285. package/dist/sync/workspacePullFeed.js +4 -3
  286. package/dist/sync/workspacePullFeed.test.js +43 -0
  287. package/dist/sync/workspaceRepair.js +5 -2
  288. package/dist/sync/workspaceSyncModel.d.ts +48 -0
  289. package/dist/sync/workspaceSyncModel.js +195 -31
  290. package/dist/sync/workspaceSyncModel.test.js +325 -12
  291. package/dist/sync/workspaceSyncState.d.ts +19 -0
  292. package/dist/sync/workspaceSyncState.js +3 -1
  293. package/dist/sync/workspaceSyncSurface.js +3 -8
  294. package/dist/types.d.ts +17 -0
  295. package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
  296. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
  297. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  298. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  299. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  300. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  301. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
  302. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
  303. package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-9E6ParrY.js} +1 -1
  304. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  305. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  306. package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
  307. package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
  308. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  309. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  310. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +9 -0
  311. package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BA7jcEpH.js} +1 -1
  312. package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
  313. package/dist/ui/assets/index-C6k75jr8.js +6 -0
  314. package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-DuEd_65c.js} +1 -1
  315. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  316. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  317. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  318. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  319. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  320. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  321. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  322. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  323. package/dist/ui/fonts/Noir_medium.otf +0 -0
  324. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  325. package/dist/ui/fonts/Noir_regular.otf +0 -0
  326. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  327. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  328. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  329. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  330. package/dist/ui/index.html +3 -3
  331. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  332. package/dist/utils/assignees.d.ts +2 -0
  333. package/dist/utils/assignees.js +2 -0
  334. package/dist/utils/avatarUpload.d.ts +27 -0
  335. package/dist/utils/avatarUpload.js +132 -0
  336. package/dist/utils/avatarUpload.test.d.ts +1 -0
  337. package/dist/utils/avatarUpload.test.js +40 -0
  338. package/dist/utils/bootstrapTrace.d.ts +7 -0
  339. package/dist/utils/bootstrapTrace.js +29 -0
  340. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  341. package/dist/utils/commercialLifecyclePresentation.js +199 -0
  342. package/dist/utils/contextAssetEvents.d.ts +2 -0
  343. package/dist/utils/syncEventDetails.d.ts +3 -0
  344. package/dist/utils/syncEventDetails.js +64 -0
  345. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  346. package/dist/utils/syncEventDetails.test.js +23 -0
  347. package/dist/utils/syncEventPresentation.d.ts +15 -0
  348. package/dist/utils/syncEventPresentation.js +129 -0
  349. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  350. package/dist/utils/syncEventPresentation.test.js +64 -0
  351. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  352. package/dist/utils/syncStatusPresentation.js +217 -0
  353. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  354. package/dist/utils/syncStatusPresentation.test.js +164 -0
  355. package/dist/utils/taskActivity.d.ts +2 -0
  356. package/dist/utils/taskActivity.js +228 -22
  357. package/dist/utils/taskActivity.test.js +119 -8
  358. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  359. package/dist/utils/workspaceSyncPresentation.js +68 -2
  360. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  361. package/package.json +10 -2
  362. package/scripts/check-cloud-mcp.mjs +83 -0
  363. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  364. package/scripts/playwright-auth.sh +5 -1
  365. package/scripts/run-billing-performance-smoke.sh +24 -0
  366. package/scripts/run-e2e-realtime.sh +17 -3
  367. package/scripts/run-smoke.sh +17 -5
  368. package/scripts/run-soak.sh +17 -5
  369. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  370. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  371. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  372. package/src/resources/templates/workflows/plan.yaml +1 -1
  373. package/src/resources/templates/workflows/review.yaml +2 -2
  374. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +0 -1
  375. package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
  376. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  377. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  378. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
  379. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  380. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
  381. package/dist/ui/assets/index-DXUdtH1B.js +0 -6
  382. package/dist/ui/assets/index-Ii0B0rTO.css +0 -1
  383. package/scripts/migrate-planning-model.ts +0 -233
@@ -0,0 +1,396 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { createWorkspaceTransferCoordinator } from './transfers';
3
+ const { runWorkspacePullSyncServiceMock, runWorkspacePushSyncServiceMock } = vi.hoisted(() => ({
4
+ runWorkspacePullSyncServiceMock: vi.fn(),
5
+ runWorkspacePushSyncServiceMock: vi.fn()
6
+ }));
7
+ vi.mock('../../sync/syncService', async () => {
8
+ const actual = await vi.importActual('../../sync/syncService');
9
+ return {
10
+ ...actual,
11
+ runWorkspacePullSyncService: runWorkspacePullSyncServiceMock,
12
+ runWorkspacePushSyncService: runWorkspacePushSyncServiceMock
13
+ };
14
+ });
15
+ function ref(current) {
16
+ return { current };
17
+ }
18
+ function createCoordinator(overrides) {
19
+ const persistWorkspaceSyncPatchSafely = (overrides?.persistWorkspaceSyncPatchSafely ?? vi.fn());
20
+ const scheduleWorkspaceSyncRetry = (overrides?.scheduleWorkspaceSyncRetry ?? vi.fn());
21
+ const reportSyncEvent = vi.fn();
22
+ const workspacePendingPushReplayRef = ref(null);
23
+ const coordinator = createWorkspaceTransferCoordinator({
24
+ cloudAuthConfigured: true,
25
+ runtimeMode: 'local',
26
+ workspaceCloudSyncEnabled: true,
27
+ currentWorkspaceId: 'workspace-local-active',
28
+ workspaceSyncPhase: 'active',
29
+ isAuthenticated: true,
30
+ checkAuthSession: vi.fn(async () => true),
31
+ resolveCloudAuthUrl: (path) => `https://cloud.example.com${path}`,
32
+ ensureCloudWorkspaceReadyForSync: vi.fn(async () => ({ success: true })),
33
+ ensureWorkspaceSyncReady: vi.fn(async () => true),
34
+ handleSyncAuthFailure: vi.fn(async () => { }),
35
+ clearWorkspaceRetry: vi.fn(),
36
+ clearWorkspaceIssue: vi.fn(),
37
+ isWorkspaceRetryPending: vi.fn(() => false),
38
+ scheduleWorkspaceSyncRetry,
39
+ persistWorkspaceSyncPatchSafely,
40
+ persistWorkspaceSyncPatchBestEffort: vi.fn(async () => { }),
41
+ persistWorkspaceSyncWatermarksSnapshotBestEffort: vi.fn(() => true),
42
+ reportSyncEvent,
43
+ acquireWorkspaceSyncLease: vi.fn(async () => true),
44
+ releaseWorkspaceSyncLease: vi.fn(),
45
+ reportWorkspaceWindowContention: vi.fn(),
46
+ refreshWorkspaceSyncMarkdownDocumentsSnapshot: vi.fn(async () => { }),
47
+ refreshWorkspaceSyncAiProfilesSnapshot: vi.fn(async () => { }),
48
+ refreshWorkspaceSyncAssetsSnapshot: vi.fn(async () => { }),
49
+ refreshWorkspaceSyncDocumentReviewSessionsSnapshot: vi.fn(async () => { }),
50
+ refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: vi.fn(async () => { }),
51
+ refreshLocalWorkspaceTaskCollections: vi.fn(async () => { }),
52
+ buildWorkspaceSyncPayload: (overrides?.buildWorkspaceSyncPayload ?? vi.fn(() => ({
53
+ changes: [],
54
+ currentTaskIds: new Set(),
55
+ deleteTaskIds: new Set(),
56
+ currentInitiativeIds: new Set(),
57
+ currentInitiativeWatermarks: new Map(),
58
+ currentWorkstreamIds: new Set(),
59
+ currentWorkstreamWatermarks: new Map(),
60
+ currentAiProfileIds: new Set(),
61
+ currentAiProfileWatermarks: new Map(),
62
+ currentDocumentPaths: new Set(),
63
+ deleteDocumentPaths: new Set(),
64
+ currentDocumentWatermarks: new Map(),
65
+ currentAssetPaths: new Set(),
66
+ deleteAssetPaths: new Set(),
67
+ currentAssetWatermarks: new Map(),
68
+ currentDocumentReviewSessionIds: new Set(),
69
+ deleteDocumentReviewSessionIds: new Set(),
70
+ currentDocumentReviewSessionWatermarks: new Map(),
71
+ currentDocumentReviewCommentIds: new Set(),
72
+ deleteDocumentReviewCommentIds: new Set(),
73
+ currentDocumentReviewCommentWatermarks: new Map(),
74
+ currentAnnotatedAttachmentSessionIds: new Set(),
75
+ deleteAnnotatedAttachmentSessionIds: new Set(),
76
+ currentAnnotatedAttachmentSessionWatermarks: new Map(),
77
+ currentTaskEventWatermarks: new Map(),
78
+ pushedWatermarks: new Map()
79
+ }))),
80
+ buildWorkspaceSyncSignature: vi.fn(() => 'sync-signature'),
81
+ seedWorkspaceSnapshotPushBaseline: vi.fn(),
82
+ shouldSuppressAttachBootstrapPush: vi.fn(() => false),
83
+ recordSuppressedEventIds: vi.fn(),
84
+ workspaceRepairModeRef: ref(false),
85
+ workspaceForceFullAiProfilePushRef: ref(false),
86
+ workspacePushInFlightRef: ref(false),
87
+ workspacePullInFlightRef: ref(false),
88
+ workspacePendingPushReplayRef,
89
+ workspaceLastPushedSignatureRef: ref(''),
90
+ workspacePendingSignatureRef: ref(''),
91
+ workspaceLastPushedTaskIdsRef: ref(new Set()),
92
+ workspaceDeletedTaskIdsRef: ref(new Set()),
93
+ workspaceDeletedTaskWatermarksRef: ref(new Map()),
94
+ workspacePullCursorRef: ref('stale-cursor'),
95
+ workspaceLastPushedWatermarksRef: ref(new Map()),
96
+ workspaceLastPushedTaskChangeKeysRef: ref(new Map()),
97
+ workspaceLastPushedInitiativeIdsRef: ref(new Set()),
98
+ workspaceLastPushedInitiativeWatermarksRef: ref(new Map()),
99
+ workspaceLastPushedWorkstreamIdsRef: ref(new Set()),
100
+ workspaceLastPushedWorkstreamWatermarksRef: ref(new Map()),
101
+ workspaceLastPushedAiProfileIdsRef: ref(new Set()),
102
+ workspaceLastPushedAiProfileWatermarksRef: ref(new Map()),
103
+ workspaceLastPushedAiProfileChangeKeysRef: ref(new Map()),
104
+ workspaceLastPushedDocumentPathsRef: ref(new Set()),
105
+ workspaceLastPushedDocumentWatermarksRef: ref(new Map()),
106
+ workspaceLastPushedAssetPathsRef: ref(new Set()),
107
+ workspaceLastPushedAssetWatermarksRef: ref(new Map()),
108
+ workspaceLastPushedDocumentReviewSessionIdsRef: ref(new Set()),
109
+ workspaceLastPushedDocumentReviewSessionWatermarksRef: ref(new Map()),
110
+ workspaceLastPushedDocumentReviewCommentIdsRef: ref(new Set()),
111
+ workspaceLastPushedDocumentReviewCommentWatermarksRef: ref(new Map()),
112
+ workspaceLastPushedAnnotatedAttachmentSessionIdsRef: ref(new Set()),
113
+ workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef: ref(new Map()),
114
+ workspaceLastPushedTaskEventWatermarksRef: ref(new Map()),
115
+ workspaceBaselineResetReasonRef: ref(null),
116
+ workspaceCaptureAttachBootstrapBaselineRef: ref(false),
117
+ workspaceStartupFullReconcileRanRef: ref(false),
118
+ workspaceFullReconcileInFlightRef: ref(false),
119
+ bootstrapLastProgressAtRef: ref(null),
120
+ realtimeSuppressedEventQueueRef: ref([]),
121
+ setWorkspaceSyncBusy: vi.fn(),
122
+ setUserGlobalSyncStatus: vi.fn(),
123
+ setUserGlobalSyncError: vi.fn(),
124
+ setWorkspaceLastErrorMessage: vi.fn(),
125
+ setWorkspaceLastErrorAt: vi.fn(),
126
+ setWorkspaceLastPullAt: vi.fn(),
127
+ setWorkspaceLastPushAt: vi.fn(),
128
+ setWorkspaceSyncPendingChanges: vi.fn(),
129
+ setTasks: vi.fn(),
130
+ setArchivedTasks: vi.fn()
131
+ });
132
+ return { coordinator, persistWorkspaceSyncPatchSafely, scheduleWorkspaceSyncRetry, reportSyncEvent, workspacePendingPushReplayRef };
133
+ }
134
+ describe('createWorkspaceTransferCoordinator', () => {
135
+ beforeEach(() => {
136
+ runWorkspacePullSyncServiceMock.mockReset();
137
+ runWorkspacePushSyncServiceMock.mockReset();
138
+ });
139
+ it('persists a cleared cursor before scheduling an invalid-cursor retry', async () => {
140
+ runWorkspacePullSyncServiceMock.mockResolvedValueOnce({
141
+ success: false,
142
+ kind: 'pull_http',
143
+ status: 400,
144
+ errorCode: 'INVALID_SYNC_CURSOR',
145
+ error: 'Invalid sync cursor.',
146
+ cursor: 'stale-cursor',
147
+ pages: 0,
148
+ appliedChanges: 0,
149
+ pullRequestMs: 1,
150
+ applyMs: 0,
151
+ pulledDeleteTaskIds: new Set(),
152
+ pulledActiveUpserts: false,
153
+ pulledArchivedUpserts: false,
154
+ documentDecryptFailures: []
155
+ });
156
+ const { coordinator, persistWorkspaceSyncPatchSafely, scheduleWorkspaceSyncRetry } = createCoordinator();
157
+ const result = await coordinator.pullWorkspaceChangesFromCloud();
158
+ expect(result).toBe(false);
159
+ expect(runWorkspacePullSyncServiceMock).toHaveBeenCalledWith(expect.objectContaining({
160
+ cursor: 'stale-cursor'
161
+ }));
162
+ expect(persistWorkspaceSyncPatchSafely).toHaveBeenCalledWith({ pullCursor: null });
163
+ expect(scheduleWorkspaceSyncRetry).toHaveBeenCalledWith(150);
164
+ });
165
+ it('reports push payload diagnostics when a push fails', async () => {
166
+ runWorkspacePushSyncServiceMock.mockResolvedValueOnce({
167
+ success: false,
168
+ status: 524,
169
+ error: '524 unknown',
170
+ transient: true,
171
+ requestMs: 125000,
172
+ changeCount: 3,
173
+ deleteCount: 0,
174
+ currentTaskIds: new Set(),
175
+ deleteTaskIds: new Set(),
176
+ currentInitiativeIds: new Set(),
177
+ currentInitiativeWatermarks: new Map(),
178
+ currentWorkstreamIds: new Set(),
179
+ currentWorkstreamWatermarks: new Map(),
180
+ currentAiProfileIds: new Set(),
181
+ currentAiProfileWatermarks: new Map(),
182
+ currentDocumentPaths: new Set(),
183
+ deleteDocumentPaths: new Set(),
184
+ currentDocumentWatermarks: new Map(),
185
+ currentAssetPaths: new Set(),
186
+ deleteAssetPaths: new Set(),
187
+ currentAssetWatermarks: new Map(),
188
+ currentDocumentReviewSessionIds: new Set(),
189
+ deleteDocumentReviewSessionIds: new Set(),
190
+ currentDocumentReviewSessionWatermarks: new Map(),
191
+ currentDocumentReviewCommentIds: new Set(),
192
+ deleteDocumentReviewCommentIds: new Set(),
193
+ currentDocumentReviewCommentWatermarks: new Map(),
194
+ currentAnnotatedAttachmentSessionIds: new Set(),
195
+ deleteAnnotatedAttachmentSessionIds: new Set(),
196
+ currentAnnotatedAttachmentSessionWatermarks: new Map(),
197
+ currentTaskEventWatermarks: new Map(),
198
+ pushedWatermarks: new Map(),
199
+ emittedEventIds: [],
200
+ appliedTaskUpserts: []
201
+ });
202
+ const payloadDiagnostics = {
203
+ totalChanges: 3,
204
+ byOp: { 'document-upsert': 2, 'asset-upsert': 1 },
205
+ baselineResetReason: 'repair-reset',
206
+ domains: {
207
+ tasks: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
208
+ initiatives: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
209
+ workstreams: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
210
+ aiProfiles: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
211
+ documents: { mode: 'send-all', reason: 'repair-reset', currentCount: 2, baselineCount: 0, changeCount: 2 },
212
+ assets: { mode: 'send-all', reason: 'repair-reset', currentCount: 1, baselineCount: 0, changeCount: 1 },
213
+ documentReviewSessions: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
214
+ documentReviewComments: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
215
+ annotatedAttachmentSessions: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
216
+ taskEvents: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
217
+ taxonomy: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 }
218
+ }
219
+ };
220
+ const buildWorkspaceSyncPayload = vi.fn(() => ({
221
+ changes: [],
222
+ currentTaskIds: new Set(),
223
+ deleteTaskIds: new Set(),
224
+ currentInitiativeIds: new Set(),
225
+ currentInitiativeWatermarks: new Map(),
226
+ currentWorkstreamIds: new Set(),
227
+ currentWorkstreamWatermarks: new Map(),
228
+ currentAiProfileIds: new Set(),
229
+ currentAiProfileWatermarks: new Map(),
230
+ currentDocumentPaths: new Set(),
231
+ deleteDocumentPaths: new Set(),
232
+ currentDocumentWatermarks: new Map(),
233
+ currentAssetPaths: new Set(),
234
+ deleteAssetPaths: new Set(),
235
+ currentAssetWatermarks: new Map(),
236
+ currentDocumentReviewSessionIds: new Set(),
237
+ deleteDocumentReviewSessionIds: new Set(),
238
+ currentDocumentReviewSessionWatermarks: new Map(),
239
+ currentDocumentReviewCommentIds: new Set(),
240
+ deleteDocumentReviewCommentIds: new Set(),
241
+ currentDocumentReviewCommentWatermarks: new Map(),
242
+ currentAnnotatedAttachmentSessionIds: new Set(),
243
+ deleteAnnotatedAttachmentSessionIds: new Set(),
244
+ currentAnnotatedAttachmentSessionWatermarks: new Map(),
245
+ currentTaskEventWatermarks: new Map(),
246
+ pushedWatermarks: new Map(),
247
+ deltaDiagnostics: payloadDiagnostics
248
+ }));
249
+ const { coordinator, reportSyncEvent } = createCoordinator({ buildWorkspaceSyncPayload });
250
+ await coordinator.pushWorkspaceChangesToCloud('signature-1');
251
+ expect(buildWorkspaceSyncPayload).toHaveBeenCalled();
252
+ expect(reportSyncEvent).toHaveBeenCalledWith('workspace-local-active', expect.objectContaining({
253
+ eventType: 'push',
254
+ status: 'error',
255
+ details: expect.objectContaining({
256
+ payloadDiagnostics
257
+ })
258
+ }));
259
+ });
260
+ it('replays the same payload after a transient push failure before rebuilding a fresh one', async () => {
261
+ const firstPayload = {
262
+ changes: [],
263
+ currentTaskIds: new Set(),
264
+ deleteTaskIds: new Set(),
265
+ currentInitiativeIds: new Set(),
266
+ currentInitiativeWatermarks: new Map(),
267
+ currentWorkstreamIds: new Set(),
268
+ currentWorkstreamWatermarks: new Map(),
269
+ currentAiProfileIds: new Set(),
270
+ currentAiProfileWatermarks: new Map(),
271
+ currentDocumentPaths: new Set(),
272
+ deleteDocumentPaths: new Set(),
273
+ currentDocumentWatermarks: new Map(),
274
+ currentAssetPaths: new Set(),
275
+ deleteAssetPaths: new Set(),
276
+ currentAssetWatermarks: new Map(),
277
+ currentDocumentReviewSessionIds: new Set(),
278
+ deleteDocumentReviewSessionIds: new Set(),
279
+ currentDocumentReviewSessionWatermarks: new Map(),
280
+ currentDocumentReviewCommentIds: new Set(),
281
+ deleteDocumentReviewCommentIds: new Set(),
282
+ currentDocumentReviewCommentWatermarks: new Map(),
283
+ currentAnnotatedAttachmentSessionIds: new Set(),
284
+ deleteAnnotatedAttachmentSessionIds: new Set(),
285
+ currentAnnotatedAttachmentSessionWatermarks: new Map(),
286
+ currentTaskEventWatermarks: new Map(),
287
+ pushedWatermarks: new Map()
288
+ };
289
+ const secondPayload = {
290
+ ...firstPayload,
291
+ deltaDiagnostics: {
292
+ totalChanges: 9,
293
+ byOp: { 'task-upsert': 9 },
294
+ baselineResetReason: 'missing-baseline',
295
+ domains: {
296
+ tasks: { mode: 'delta', reason: null, currentCount: 9, baselineCount: 8, changeCount: 1 },
297
+ initiatives: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
298
+ workstreams: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
299
+ aiProfiles: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
300
+ documents: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
301
+ assets: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
302
+ documentReviewSessions: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
303
+ documentReviewComments: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
304
+ annotatedAttachmentSessions: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
305
+ taskEvents: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 },
306
+ taxonomy: { mode: 'none', reason: null, currentCount: 0, baselineCount: 0, changeCount: 0 }
307
+ }
308
+ }
309
+ };
310
+ const buildWorkspaceSyncPayload = vi.fn()
311
+ .mockReturnValueOnce(firstPayload)
312
+ .mockReturnValueOnce(secondPayload);
313
+ runWorkspacePushSyncServiceMock
314
+ .mockResolvedValueOnce({
315
+ success: false,
316
+ status: 524,
317
+ error: '524 unknown',
318
+ transient: true,
319
+ requestMs: 125000,
320
+ changeCount: 1,
321
+ deleteCount: 0,
322
+ currentTaskIds: new Set(),
323
+ deleteTaskIds: new Set(),
324
+ currentInitiativeIds: new Set(),
325
+ currentInitiativeWatermarks: new Map(),
326
+ currentWorkstreamIds: new Set(),
327
+ currentWorkstreamWatermarks: new Map(),
328
+ currentAiProfileIds: new Set(),
329
+ currentAiProfileWatermarks: new Map(),
330
+ currentDocumentPaths: new Set(),
331
+ deleteDocumentPaths: new Set(),
332
+ currentDocumentWatermarks: new Map(),
333
+ currentAssetPaths: new Set(),
334
+ deleteAssetPaths: new Set(),
335
+ currentAssetWatermarks: new Map(),
336
+ currentDocumentReviewSessionIds: new Set(),
337
+ deleteDocumentReviewSessionIds: new Set(),
338
+ currentDocumentReviewSessionWatermarks: new Map(),
339
+ currentDocumentReviewCommentIds: new Set(),
340
+ deleteDocumentReviewCommentIds: new Set(),
341
+ currentDocumentReviewCommentWatermarks: new Map(),
342
+ currentAnnotatedAttachmentSessionIds: new Set(),
343
+ deleteAnnotatedAttachmentSessionIds: new Set(),
344
+ currentAnnotatedAttachmentSessionWatermarks: new Map(),
345
+ currentTaskEventWatermarks: new Map(),
346
+ pushedWatermarks: new Map(),
347
+ emittedEventIds: [],
348
+ appliedTaskUpserts: []
349
+ })
350
+ .mockResolvedValueOnce({
351
+ success: true,
352
+ syncedAt: '2026-04-21T12:00:00.000Z',
353
+ requestMs: 200,
354
+ changeCount: 1,
355
+ deleteCount: 0,
356
+ currentTaskIds: new Set(['task-1']),
357
+ deleteTaskIds: new Set(),
358
+ currentInitiativeIds: new Set(),
359
+ currentInitiativeWatermarks: new Map(),
360
+ currentWorkstreamIds: new Set(),
361
+ currentWorkstreamWatermarks: new Map(),
362
+ currentAiProfileIds: new Set(),
363
+ currentAiProfileWatermarks: new Map(),
364
+ currentDocumentPaths: new Set(),
365
+ currentDocumentWatermarks: new Map(),
366
+ currentAssetPaths: new Set(),
367
+ currentAssetWatermarks: new Map(),
368
+ currentDocumentReviewSessionIds: new Set(),
369
+ currentDocumentReviewSessionWatermarks: new Map(),
370
+ currentDocumentReviewCommentIds: new Set(),
371
+ currentDocumentReviewCommentWatermarks: new Map(),
372
+ currentAnnotatedAttachmentSessionIds: new Set(),
373
+ currentAnnotatedAttachmentSessionWatermarks: new Map(),
374
+ currentTaskEventWatermarks: new Map(),
375
+ pushedWatermarks: new Map([['task-1', '2026-04-21T12:00:00.000Z']]),
376
+ emittedEventIds: [],
377
+ appliedTaskUpserts: []
378
+ });
379
+ const { coordinator, workspacePendingPushReplayRef, reportSyncEvent } = createCoordinator({ buildWorkspaceSyncPayload });
380
+ const firstResult = await coordinator.pushWorkspaceChangesToCloud('signature-1');
381
+ const secondResult = await coordinator.pushWorkspaceChangesToCloud('signature-1');
382
+ expect(firstResult).toBe(false);
383
+ expect(secondResult).toBe(true);
384
+ expect(buildWorkspaceSyncPayload).toHaveBeenCalledTimes(1);
385
+ expect(runWorkspacePushSyncServiceMock.mock.calls[0]?.[0]?.payload).toBe(firstPayload);
386
+ expect(runWorkspacePushSyncServiceMock.mock.calls[1]?.[0]?.payload).toBe(firstPayload);
387
+ expect(workspacePendingPushReplayRef.current).toBeNull();
388
+ expect(reportSyncEvent).toHaveBeenLastCalledWith('workspace-local-active', expect.objectContaining({
389
+ eventType: 'push',
390
+ status: 'success',
391
+ details: expect.objectContaining({
392
+ replayedPayload: true
393
+ })
394
+ }));
395
+ });
396
+ });
@@ -0,0 +1,21 @@
1
+ import { type SyncEventRecord } from '../../utils/syncEventPresentation';
2
+ interface UseRecentSyncEventsInput {
3
+ isOpen: boolean;
4
+ workspaceId: string;
5
+ refreshKeys: ReadonlyArray<string | null | undefined>;
6
+ }
7
+ export interface PendingSyncEventsScrollRestore {
8
+ scrollTop: number;
9
+ scrollHeight: number;
10
+ }
11
+ interface UseRecentSyncEventsResult {
12
+ syncRecentEvents: SyncEventRecord[];
13
+ syncRecentEventsLoading: boolean;
14
+ syncRecentEventsError: string | null;
15
+ syncEventsListRef: React.RefObject<HTMLDivElement | null>;
16
+ loadRecentSyncEvents: () => Promise<SyncEventRecord[]>;
17
+ }
18
+ export declare function useRecentSyncEvents({ isOpen, workspaceId, refreshKeys, }: UseRecentSyncEventsInput): UseRecentSyncEventsResult;
19
+ export declare function capturePendingSyncEventsScrollRestore(listEl: HTMLDivElement | null): PendingSyncEventsScrollRestore | null;
20
+ export declare function restorePendingSyncEventsScrollRestore(listEl: HTMLDivElement | null, pendingRestore: PendingSyncEventsScrollRestore | null): null;
21
+ export {};
@@ -0,0 +1,92 @@
1
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
2
+ import { areSyncEventListsEqual, mergeRecentSyncEvents, } from '../../utils/syncEventPresentation';
3
+ export function useRecentSyncEvents({ isOpen, workspaceId, refreshKeys, }) {
4
+ const [syncRecentEvents, setSyncRecentEvents] = useState([]);
5
+ const [syncRecentEventsLoading, setSyncRecentEventsLoading] = useState(false);
6
+ const [syncRecentEventsError, setSyncRecentEventsError] = useState(null);
7
+ const syncEventsListRef = useRef(null);
8
+ const pendingSyncEventsScrollRestoreRef = useRef(null);
9
+ useLayoutEffect(() => {
10
+ pendingSyncEventsScrollRestoreRef.current = restorePendingSyncEventsScrollRestore(syncEventsListRef.current, pendingSyncEventsScrollRestoreRef.current);
11
+ }, [syncRecentEvents]);
12
+ const loadRecentSyncEvents = useCallback(async () => {
13
+ const res = await fetch(`/api/taskforce/sync/events?workspace_id=${encodeURIComponent(workspaceId)}&limit=15`, {
14
+ method: 'GET',
15
+ credentials: 'include'
16
+ });
17
+ if (!res.ok) {
18
+ throw new Error(`Failed to load sync events (${res.status})`);
19
+ }
20
+ const data = await res.json().catch(() => ({}));
21
+ return Array.isArray(data?.events)
22
+ ? data.events.filter((event) => event && typeof event === 'object')
23
+ : [];
24
+ }, [workspaceId]);
25
+ useEffect(() => {
26
+ if (!isOpen)
27
+ return;
28
+ let cancelled = false;
29
+ const isInitialLoad = syncRecentEvents.length === 0;
30
+ if (isInitialLoad) {
31
+ setSyncRecentEventsLoading(true);
32
+ }
33
+ void loadRecentSyncEvents()
34
+ .then((events) => {
35
+ if (cancelled)
36
+ return;
37
+ pendingSyncEventsScrollRestoreRef.current = capturePendingSyncEventsScrollRestore(syncEventsListRef.current);
38
+ setSyncRecentEvents((current) => {
39
+ const next = current.length === 0 ? events : mergeRecentSyncEvents(current, events, 15);
40
+ return areSyncEventListsEqual(current, next) ? current : next;
41
+ });
42
+ setSyncRecentEventsError(null);
43
+ })
44
+ .catch((error) => {
45
+ if (cancelled)
46
+ return;
47
+ const message = error instanceof Error && error.message.trim().length > 0
48
+ ? error.message.trim()
49
+ : 'Unable to load recent sync events.';
50
+ setSyncRecentEventsError(message);
51
+ if (isInitialLoad) {
52
+ setSyncRecentEvents([]);
53
+ }
54
+ })
55
+ .finally(() => {
56
+ if (cancelled)
57
+ return;
58
+ if (isInitialLoad) {
59
+ setSyncRecentEventsLoading(false);
60
+ }
61
+ });
62
+ return () => {
63
+ cancelled = true;
64
+ };
65
+ }, [
66
+ isOpen,
67
+ loadRecentSyncEvents,
68
+ ...refreshKeys,
69
+ ]);
70
+ return {
71
+ syncRecentEvents,
72
+ syncRecentEventsLoading,
73
+ syncRecentEventsError,
74
+ syncEventsListRef,
75
+ loadRecentSyncEvents,
76
+ };
77
+ }
78
+ export function capturePendingSyncEventsScrollRestore(listEl) {
79
+ return listEl && listEl.scrollTop > 8
80
+ ? {
81
+ scrollTop: listEl.scrollTop,
82
+ scrollHeight: listEl.scrollHeight
83
+ }
84
+ : null;
85
+ }
86
+ export function restorePendingSyncEventsScrollRestore(listEl, pendingRestore) {
87
+ if (!pendingRestore || !listEl)
88
+ return null;
89
+ const delta = listEl.scrollHeight - pendingRestore.scrollHeight;
90
+ listEl.scrollTop = pendingRestore.scrollTop + Math.max(0, delta);
91
+ return null;
92
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,124 @@
1
+ import { renderHook, waitFor } from '@testing-library/react';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { capturePendingSyncEventsScrollRestore, restorePendingSyncEventsScrollRestore, useRecentSyncEvents, } from './useRecentSyncEvents';
4
+ describe('useRecentSyncEvents', () => {
5
+ const fetchMock = vi.fn();
6
+ beforeEach(() => {
7
+ fetchMock.mockReset();
8
+ vi.stubGlobal('fetch', fetchMock);
9
+ });
10
+ afterEach(() => {
11
+ vi.unstubAllGlobals();
12
+ });
13
+ it('does not fetch recent sync events while the modal is closed', () => {
14
+ renderHook(() => useRecentSyncEvents({
15
+ isOpen: false,
16
+ workspaceId: 'workspace-1',
17
+ refreshKeys: ['a'],
18
+ }));
19
+ expect(fetchMock).not.toHaveBeenCalled();
20
+ });
21
+ it('loads recent sync events when the modal opens', async () => {
22
+ fetchMock.mockResolvedValue({
23
+ ok: true,
24
+ json: async () => ({
25
+ events: [{
26
+ id: 1,
27
+ occurredAt: '2026-04-18T12:00:00.000Z',
28
+ eventType: 'pull',
29
+ status: 'success',
30
+ }],
31
+ }),
32
+ });
33
+ const { result } = renderHook(() => useRecentSyncEvents({
34
+ isOpen: true,
35
+ workspaceId: 'workspace-1',
36
+ refreshKeys: ['2026-04-18T12:00:00.000Z'],
37
+ }));
38
+ await waitFor(() => {
39
+ expect(result.current.syncRecentEventsLoading).toBe(false);
40
+ expect(result.current.syncRecentEvents).toHaveLength(1);
41
+ });
42
+ expect(fetchMock).toHaveBeenCalledWith('/api/taskforce/sync/events?workspace_id=workspace-1&limit=15', expect.objectContaining({
43
+ method: 'GET',
44
+ credentials: 'include',
45
+ }));
46
+ expect(result.current.syncRecentEventsError).toBeNull();
47
+ });
48
+ it('stores a readable error when the fetch fails', async () => {
49
+ fetchMock.mockResolvedValue({
50
+ ok: false,
51
+ status: 503,
52
+ json: async () => ({}),
53
+ });
54
+ const { result } = renderHook(() => useRecentSyncEvents({
55
+ isOpen: true,
56
+ workspaceId: 'workspace-1',
57
+ refreshKeys: ['2026-04-18T12:00:00.000Z'],
58
+ }));
59
+ await waitFor(() => {
60
+ expect(result.current.syncRecentEventsLoading).toBe(false);
61
+ expect(result.current.syncRecentEventsError).toBe('Failed to load sync events (503)');
62
+ });
63
+ expect(result.current.syncRecentEvents).toEqual([]);
64
+ });
65
+ it('merges refreshed events into the recent event list', async () => {
66
+ fetchMock
67
+ .mockResolvedValueOnce({
68
+ ok: true,
69
+ json: async () => ({
70
+ events: [{
71
+ id: 1,
72
+ occurredAt: '2026-04-18T12:00:00.000Z',
73
+ eventType: 'pull',
74
+ status: 'success',
75
+ }],
76
+ }),
77
+ })
78
+ .mockResolvedValueOnce({
79
+ ok: true,
80
+ json: async () => ({
81
+ events: [{
82
+ id: 2,
83
+ occurredAt: '2026-04-18T12:01:00.000Z',
84
+ eventType: 'push',
85
+ status: 'success',
86
+ }],
87
+ }),
88
+ });
89
+ const { result, rerender } = renderHook(({ refreshKey }) => useRecentSyncEvents({
90
+ isOpen: true,
91
+ workspaceId: 'workspace-1',
92
+ refreshKeys: [refreshKey],
93
+ }), {
94
+ initialProps: { refreshKey: 'first' },
95
+ });
96
+ await waitFor(() => {
97
+ expect(result.current.syncRecentEvents).toHaveLength(1);
98
+ });
99
+ rerender({ refreshKey: 'second' });
100
+ await waitFor(() => {
101
+ expect(result.current.syncRecentEvents).toHaveLength(2);
102
+ });
103
+ });
104
+ it('captures and restores list scroll offset when the event list grows', () => {
105
+ let scrollHeight = 100;
106
+ const listEl = document.createElement('div');
107
+ Object.defineProperty(listEl, 'scrollTop', {
108
+ value: 24,
109
+ writable: true,
110
+ });
111
+ Object.defineProperty(listEl, 'scrollHeight', {
112
+ get: () => scrollHeight,
113
+ });
114
+ const pendingRestore = capturePendingSyncEventsScrollRestore(listEl);
115
+ expect(pendingRestore).toEqual({
116
+ scrollTop: 24,
117
+ scrollHeight: 100,
118
+ });
119
+ scrollHeight = 140;
120
+ const result = restorePendingSyncEventsScrollRestore(listEl, pendingRestore);
121
+ expect(result).toBeNull();
122
+ expect(listEl.scrollTop).toBe(64);
123
+ });
124
+ });
@@ -0,0 +1,30 @@
1
+ import { type WorkspaceSyncDiagnosticsPresentation } from '../../utils/syncStatusPresentation';
2
+ import type { SyncEventRecord } from '../../utils/syncEventPresentation';
3
+ interface UseSyncStatusActionsInput {
4
+ currentWorkspaceId: string;
5
+ syncStatusLabel: string;
6
+ workspaceSyncSummary: string;
7
+ workspaceSyncRecommendedAction: string;
8
+ formattedLastSyncTime: string;
9
+ formattedLastPullTime: string;
10
+ formattedLastPushTime: string;
11
+ workspaceSyncDiagnostics: WorkspaceSyncDiagnosticsPresentation;
12
+ workspaceSyncPendingChanges: string | number;
13
+ syncLastError: string;
14
+ workspaceSyncPhase: string;
15
+ referenceMismatchCount: number;
16
+ syncRecentEvents: SyncEventRecord[];
17
+ loadRecentSyncEvents: () => Promise<SyncEventRecord[]>;
18
+ pushNotice: (message: string, tone: 'success' | 'error') => void;
19
+ resetWorkspaceSyncCursorAndPull: () => Promise<void>;
20
+ workspaceSyncBusy: boolean;
21
+ }
22
+ interface UseSyncStatusActionsResult {
23
+ workspaceSyncRepairBusy: boolean;
24
+ workspaceSyncRepairQueued: boolean;
25
+ workspaceSyncCopied: boolean;
26
+ handleCopySyncDetails: () => Promise<void>;
27
+ handleQueueOrRunRepairSync: () => void;
28
+ }
29
+ export declare function useSyncStatusActions({ currentWorkspaceId, syncStatusLabel, workspaceSyncSummary, workspaceSyncRecommendedAction, formattedLastSyncTime, formattedLastPullTime, formattedLastPushTime, workspaceSyncDiagnostics, workspaceSyncPendingChanges, syncLastError, workspaceSyncPhase, referenceMismatchCount, syncRecentEvents, loadRecentSyncEvents, pushNotice, resetWorkspaceSyncCursorAndPull, workspaceSyncBusy, }: UseSyncStatusActionsInput): UseSyncStatusActionsResult;
30
+ export {};