@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
@@ -28,55 +28,16 @@ import { useRealtimeSync } from './useRealtimeSync';
28
28
  import { TASKFORCE_CONTEXT_ASSETS_MUTATED_EVENT } from '../utils/contextAssetEvents';
29
29
  import { setLocale as setAppLocale } from '../localization';
30
30
  import { normalizeTaskFromApi } from '../utils/taskNormalization.js';
31
+ import { normalizeSyncEventDetails } from '../utils/syncEventDetails';
31
32
  import { createSyncAuthCoordinator } from './sync/auth';
32
33
  import { createBootstrapTimeoutMonitor, syncBootstrapPhaseTracking } from './sync/bootstrap';
33
- import { buildTaxonomyStateFingerprint, flattenDocumentReviewComments, flattenTaskEvents, isValidWorkspaceKey, logSyncDebug, normalizeWorkspaceSyncAiProfileSnapshotEntry, readPersistedSyncWatermarks, readUserGlobalSyncMeta, WORKSPACE_CONCURRENT_WINDOW_MESSAGE, WORKSPACE_FULL_RECONCILE_INTERVAL_MS, writeUserGlobalSyncMeta } from './sync/orchestratorShared';
34
+ import { resolvePullSyncReadiness, resolvePushSyncReadiness, resolveRetrySyncReadiness } from './sync/controlPlane';
35
+ import { resolveWorkspaceAutomaticPullPolicy, resolveWorkspaceSyncPresentation, shouldRunWorkspaceStartupRepair, shouldScheduleWorkspaceReactivePush, shouldStartWorkspacePeriodicFullReconcile, shouldStartWorkspacePushWatchdog, } from './sync/lifecyclePolicy';
36
+ import { buildPersistedSyncWatermarksSnapshot, buildTaxonomyStateFingerprint, flattenDocumentReviewComments, flattenTaskEvents, isValidWorkspaceKey, logSyncDebug, normalizeWorkspaceSyncAiProfileSnapshotEntry, parsePersistedSyncWatermarksSnapshot, readPersistedSyncWatermarks, readUserGlobalSyncMeta, savePersistedSyncWatermarks, WORKSPACE_CONCURRENT_WINDOW_MESSAGE, WORKSPACE_FULL_RECONCILE_INTERVAL_MS, writeUserGlobalSyncMeta } from './sync/orchestratorShared';
34
37
  import { createRealtimeSyncBridge } from './sync/realtime';
35
38
  import { createSyncRecoveryCoordinator } from './sync/recovery';
36
39
  import { createWorkspaceTransferCoordinator } from './sync/transfers';
37
- // === SECTION: Pure utility functions ===
38
- function summarizeSyncPhase(enabled, phase, busy, retryAt, lastErrorMessage) {
39
- if (!enabled)
40
- return 'Sync is turned off for this workspace.';
41
- if (phase === 'provision-local') {
42
- return busy
43
- ? 'Uploading this workspace to cloud for the first time.'
44
- : 'Preparing the first upload to cloud.';
45
- }
46
- if (phase === 'attach-cloud') {
47
- return busy
48
- ? 'Pulling the cloud workspace into this device.'
49
- : 'Waiting for the first cloud pull into this device.';
50
- }
51
- if (retryAt) {
52
- return 'Sync hit a temporary problem and will retry automatically.';
53
- }
54
- if (lastErrorMessage) {
55
- return 'Sync needs attention before it can continue.';
56
- }
57
- if (busy) {
58
- return 'Sync is currently running.';
59
- }
60
- return 'Sync is healthy.';
61
- }
62
- function recommendSyncAction(enabled, phase, retryAt, lastErrorMessage) {
63
- if (!enabled) {
64
- return 'Turn on sync when you are ready to back up this workspace.';
65
- }
66
- if (phase === 'provision-local') {
67
- return 'Keep this window open while the first upload finishes. If it seems stalled, press Sync.';
68
- }
69
- if (phase === 'attach-cloud') {
70
- return 'Keep this window open for the first cloud pull. If it does not move, press Repair.';
71
- }
72
- if (retryAt) {
73
- return 'Wait for the automatic retry, or press Sync to retry now.';
74
- }
75
- if (lastErrorMessage) {
76
- return 'Press Sync to retry. If the same error keeps returning, press Repair.';
77
- }
78
- return 'No action needed.';
79
- }
40
+ import { aiProfileSyncResourceAdapter, taskSyncResourceAdapter } from '../sync/syncResourceAdapters.js';
80
41
  const EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS = {
81
42
  documents: false,
82
43
  aiProfiles: false,
@@ -86,7 +47,7 @@ const EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS = {
86
47
  };
87
48
  export { normalizeWorkspaceSyncAiProfileSnapshotEntry };
88
49
  export function useSyncOrchestrator(input) {
89
- const { currentWorkspaceId, cloudAuthConfigured, runtimeMode, authSessionResolved, isAuthenticated, authUserId, projectName, resolveCloudAuthUrl, resolveWebSocketUrl, realtimeSyncEnabled, tasks, archivedTasks, initiatives, workstreams, taxonomies, taxonomyState, setupState, globalTheme, locale, globalWeekStartsOn, themeUseGlobalDefault, setTasks, setArchivedTasks, setAuthBlocked, setIsAuthenticated, checkAuthSession, setGlobalTheme, setCurrentTheme, setSetupState, setLocale, setGlobalWeekStartsOn, fetchPlanningEntities } = input;
50
+ const { currentWorkspaceId, cloudAuthConfigured, runtimeMode, authSessionResolved, isAuthenticated, authUserId, projectName, resolveCloudAuthUrl, resolveWebSocketUrl, realtimeSyncEnabled, tasks, archivedTasks, deletedTasks = [], initiatives, workstreams, taxonomies, taxonomyState, setupState, globalTheme, locale, globalWeekStartsOn, themeUseGlobalDefault, setTasks, setArchivedTasks, setAuthBlocked, setIsAuthenticated, checkAuthSession, setGlobalTheme, setCurrentTheme, setSetupState, setLocale, setGlobalWeekStartsOn, fetchPlanningEntities } = input;
90
51
  const taxonomyStateFingerprint = useMemo(() => buildTaxonomyStateFingerprint(taxonomyState), [taxonomyState]);
91
52
  // === SECTION: State initialization ===
92
53
  const [userGlobalSyncStatus, setUserGlobalSyncStatus] = useState('disconnected');
@@ -97,6 +58,7 @@ export function useSyncOrchestrator(input) {
97
58
  const [workspaceLastErrorMessage, setWorkspaceLastErrorMessage] = useState(null);
98
59
  const [workspaceCloudSyncEnabled, setWorkspaceCloudSyncEnabled] = useState(false);
99
60
  const [workspaceSyncPhase, setWorkspaceSyncPhase] = useState('idle');
61
+ const [workspaceSyncSetupIntent, setWorkspaceSyncSetupIntent] = useState(null);
100
62
  const [workspaceSyncBusy, setWorkspaceSyncBusy] = useState(false);
101
63
  const [workspaceSyncPendingChanges, setWorkspaceSyncPendingChanges] = useState(0);
102
64
  const [workspaceSyncMarkdownDocuments, setWorkspaceSyncMarkdownDocuments] = useState([]);
@@ -108,6 +70,7 @@ export function useSyncOrchestrator(input) {
108
70
  const workspaceInitiativesRef = useRef(initiatives || []);
109
71
  const workspaceWorkstreamsRef = useRef(workstreams || []);
110
72
  const workspaceSyncMarkdownDocumentsRef = useRef([]);
73
+ const workspaceSyncKnownDocumentPathsRef = useRef(new Set());
111
74
  const [workspaceSyncAiProfiles, setWorkspaceSyncAiProfiles] = useState([]);
112
75
  const [workspaceSyncAiProfilesFingerprint, setWorkspaceSyncAiProfilesFingerprint] = useState('');
113
76
  const [workspaceSyncAiProfilesLastFetchAt, setWorkspaceSyncAiProfilesLastFetchAt] = useState(null);
@@ -118,6 +81,7 @@ export function useSyncOrchestrator(input) {
118
81
  const [workspaceSyncAssets, setWorkspaceSyncAssets] = useState([]);
119
82
  const [workspaceSyncAssetsFingerprint, setWorkspaceSyncAssetsFingerprint] = useState('');
120
83
  const workspaceSyncAssetsRef = useRef([]);
84
+ const workspaceSyncKnownAssetPathsRef = useRef(new Set());
121
85
  const [workspaceSyncDocumentReviewSessions, setWorkspaceSyncDocumentReviewSessions] = useState([]);
122
86
  const [workspaceSyncDocumentReviewSessionsFingerprint, setWorkspaceSyncDocumentReviewSessionsFingerprint] = useState('');
123
87
  const workspaceSyncDocumentReviewSessionsRef = useRef([]);
@@ -136,6 +100,7 @@ export function useSyncOrchestrator(input) {
136
100
  });
137
101
  const userSettingsSyncInFlightRef = useRef(false);
138
102
  const userSettingsRetryTimeoutRef = useRef(null);
103
+ const workspaceBaselineResetReasonRef = useRef('missing-baseline');
139
104
  const userSettingsRetryAttemptRef = useRef(0);
140
105
  const applyingRemoteUserSettingsRef = useRef(false);
141
106
  const initialUserSettingsSyncCompletedRef = useRef(new Set());
@@ -149,6 +114,7 @@ export function useSyncOrchestrator(input) {
149
114
  const realtimePullDebounceRef = useRef(null);
150
115
  const realtimeSuppressedEventIdsRef = useRef(new Set());
151
116
  const realtimeSuppressedEventQueueRef = useRef([]);
117
+ const workspacePendingPushReplayRef = useRef(null);
152
118
  const retryWorkspaceSyncRef = useRef(() => Promise.resolve(false));
153
119
  const syncUserGlobalSettingsRef = useRef(async () => { });
154
120
  const bootstrapPhaseStartedAtRef = useRef(null);
@@ -177,73 +143,6 @@ export function useSyncOrchestrator(input) {
177
143
  void syncUserGlobalSettingsRef.current({ preferCloudOnFirstSync: false });
178
144
  }, delayMs);
179
145
  }, [clearUserSettingsRetry]);
180
- useEffect(() => {
181
- workspaceSyncMarkdownDocumentsFingerprintRef.current = '';
182
- workspaceSyncAiProfilesFingerprintRef.current = '';
183
- workspaceSyncAssetsFingerprintRef.current = '';
184
- workspaceSyncDocumentReviewSessionsFingerprintRef.current = '';
185
- workspaceSyncAnnotatedAttachmentSessionsFingerprintRef.current = '';
186
- workspaceAttachBootstrapBaselineSignatureRef.current = '';
187
- workspaceCaptureAttachBootstrapBaselineRef.current = false;
188
- setWorkspaceSyncMarkdownDocuments([]);
189
- setWorkspaceSyncMarkdownDocumentsFingerprint('');
190
- workspaceSyncAiProfilesRef.current = [];
191
- setWorkspaceSyncAiProfiles([]);
192
- setWorkspaceSyncAiProfilesFingerprint('');
193
- setWorkspaceSyncAssets([]);
194
- setWorkspaceSyncAssetsFingerprint('');
195
- workspaceSyncDocumentReviewSessionsRef.current = [];
196
- workspaceSyncDocumentReviewCommentsRef.current = [];
197
- setWorkspaceSyncDocumentReviewSessions([]);
198
- setWorkspaceSyncDocumentReviewSessionsFingerprint('');
199
- workspaceSyncAnnotatedAttachmentSessionsRef.current = [];
200
- setWorkspaceSyncAnnotatedAttachmentSessions([]);
201
- setWorkspaceSyncAnnotatedAttachmentSessionsFingerprint('');
202
- setWorkspaceSyncReferenceSnapshotStatus(EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS);
203
- setWorkspaceLastErrorMessage(null);
204
- setWorkspaceLastErrorAt(null);
205
- setWorkspaceSyncPendingChanges(0);
206
- realtimeSuppressedEventIdsRef.current = new Set();
207
- realtimeSuppressedEventQueueRef.current = [];
208
- // Reset watermarks for the new workspace, then restore from localStorage if recent
209
- // enough. This prevents a full resync on every page reload while still catching any
210
- // changes that happened while the app was closed.
211
- workspaceLastPushedWatermarksRef.current = new Map();
212
- workspaceLastPushedInitiativeIdsRef.current = new Set();
213
- workspaceLastPushedInitiativeWatermarksRef.current = new Map();
214
- workspaceLastPushedWorkstreamIdsRef.current = new Set();
215
- workspaceLastPushedWorkstreamWatermarksRef.current = new Map();
216
- workspaceLastPushedAiProfileIdsRef.current = new Set();
217
- workspaceLastPushedAiProfileWatermarksRef.current = new Map();
218
- workspaceLastPushedDocumentPathsRef.current = new Set();
219
- workspaceLastPushedDocumentWatermarksRef.current = new Map();
220
- workspaceLastPushedAssetPathsRef.current = new Set();
221
- workspaceLastPushedAssetWatermarksRef.current = new Map();
222
- workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set();
223
- workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map();
224
- workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set();
225
- workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map();
226
- workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
227
- workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
228
- workspaceLastPushedTaskEventWatermarksRef.current = new Map();
229
- workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
230
- workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
231
- workspaceLastPushedTaskEventWatermarksRef.current = new Map();
232
- workspaceStartupFullReconcileRanRef.current = false;
233
- workspaceFullReconcileInFlightRef.current = false;
234
- const persisted = readPersistedSyncWatermarks(currentWorkspaceId, taxonomyStateFingerprint);
235
- if (persisted) {
236
- workspaceLastPushedWatermarksRef.current = persisted.taskWatermarks;
237
- workspaceLastPushedInitiativeWatermarksRef.current = persisted.initiativeWatermarks;
238
- workspaceLastPushedWorkstreamWatermarksRef.current = persisted.workstreamWatermarks;
239
- workspaceLastPushedDocumentWatermarksRef.current = persisted.documentWatermarks;
240
- workspaceLastPushedAssetWatermarksRef.current = persisted.assetWatermarks;
241
- workspaceLastPushedDocumentReviewSessionWatermarksRef.current = persisted.documentReviewSessionWatermarks;
242
- workspaceLastPushedDocumentReviewCommentWatermarksRef.current = persisted.documentReviewCommentWatermarks;
243
- workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = persisted.annotatedAttachmentSessionWatermarks;
244
- workspaceLastPushedTaskEventWatermarksRef.current = persisted.taskEventWatermarks;
245
- }
246
- }, [currentWorkspaceId, taxonomyStateFingerprint]);
247
146
  const markWorkspaceSyncReferenceSnapshotReady = useCallback((key) => {
248
147
  setWorkspaceSyncReferenceSnapshotStatus((current) => (current[key]
249
148
  ? current
@@ -253,21 +152,34 @@ export function useSyncOrchestrator(input) {
253
152
  }));
254
153
  }, []);
255
154
  const workspaceSyncReferenceSnapshotsReady = Object.values(workspaceSyncReferenceSnapshotStatus).every(Boolean);
256
- const { workspaceRetryAt, isWorkspaceRetryPending, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspacePullCursorRef, clearWorkspaceRetry, scheduleWorkspaceRetry, persistWorkspaceSyncPatch, applyWorkspaceSyncPatchLocally, loadWorkspaceSyncState, applyWorkspaceSyncStateSnapshot, saveWorkspaceCloudSyncSettings: saveWorkspaceCloudSyncSettingsBase, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, forceClearWorkspaceSyncLease } = useWorkspaceSyncController({
155
+ const { workspaceRetryAt, isWorkspaceRetryPending, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, workspacePullCursorRef, clearWorkspaceRetry, scheduleWorkspaceRetry, persistWorkspaceSyncPatch, applyWorkspaceSyncPatchLocally, readWorkspaceSyncStateSnapshot, loadWorkspaceSyncState, applyWorkspaceSyncStateSnapshot: applyWorkspaceSyncStateSnapshotBase, saveWorkspaceCloudSyncSettings: saveWorkspaceCloudSyncSettingsBase, acquireWorkspaceSyncLease, releaseWorkspaceSyncLease, forceClearWorkspaceSyncLease } = useWorkspaceSyncController({
257
156
  currentWorkspaceId,
258
157
  setWorkspaceCloudSyncEnabled,
259
158
  setWorkspaceSyncPhase,
159
+ setWorkspaceSyncSetupIntent,
260
160
  setWorkspaceLastPullAt,
261
161
  setWorkspaceLastPushAt,
262
162
  setWorkspaceLastErrorMessage
263
163
  });
164
+ const applyWorkspaceSyncStateSnapshot = useCallback((nextState) => {
165
+ if (nextState.phase === 'attach-cloud' || nextState.phase === 'provision-local') {
166
+ lastBootstrapPhaseRef.current = nextState.phase;
167
+ if (bootstrapPhaseStartedAtRef.current === null) {
168
+ bootstrapPhaseStartedAtRef.current = Date.now();
169
+ }
170
+ bootstrapLastProgressAtRef.current = null;
171
+ }
172
+ applyWorkspaceSyncStateSnapshotBase(nextState);
173
+ }, [applyWorkspaceSyncStateSnapshotBase]);
264
174
  const workspaceLastPushedWatermarksRef = useRef(new Map());
175
+ const workspaceLastPushedTaskChangeKeysRef = useRef(new Map());
265
176
  const workspaceLastPushedInitiativeIdsRef = useRef(new Set());
266
177
  const workspaceLastPushedInitiativeWatermarksRef = useRef(new Map());
267
178
  const workspaceLastPushedWorkstreamIdsRef = useRef(new Set());
268
179
  const workspaceLastPushedWorkstreamWatermarksRef = useRef(new Map());
269
180
  const workspaceLastPushedAiProfileIdsRef = useRef(new Set());
270
181
  const workspaceLastPushedAiProfileWatermarksRef = useRef(new Map());
182
+ const workspaceLastPushedAiProfileChangeKeysRef = useRef(new Map());
271
183
  const workspaceLastPushedDocumentPathsRef = useRef(new Set());
272
184
  const workspaceLastPushedDocumentWatermarksRef = useRef(new Map());
273
185
  const workspaceLastPushedAssetPathsRef = useRef(new Set());
@@ -279,6 +191,48 @@ export function useSyncOrchestrator(input) {
279
191
  const workspaceLastPushedAnnotatedAttachmentSessionIdsRef = useRef(new Set());
280
192
  const workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef = useRef(new Map());
281
193
  const workspaceLastPushedTaskEventWatermarksRef = useRef(new Map());
194
+ const hydrateWorkspacePushBaselines = useCallback((persisted) => {
195
+ if (!persisted)
196
+ return false;
197
+ workspaceLastPushedTaskIdsRef.current = new Set(persisted.taskIds);
198
+ workspaceLastPushedWatermarksRef.current = persisted.taskWatermarks;
199
+ workspaceLastPushedTaskChangeKeysRef.current = persisted.taskChangeKeys || new Map();
200
+ workspaceLastPushedInitiativeIdsRef.current = new Set(Array.from(persisted.initiativeWatermarks.keys()));
201
+ workspaceLastPushedInitiativeWatermarksRef.current = persisted.initiativeWatermarks;
202
+ workspaceLastPushedWorkstreamIdsRef.current = new Set(Array.from(persisted.workstreamWatermarks.keys()));
203
+ workspaceLastPushedWorkstreamWatermarksRef.current = persisted.workstreamWatermarks;
204
+ workspaceLastPushedAiProfileIdsRef.current = new Set(Array.from(persisted.aiProfileWatermarks.keys()));
205
+ workspaceLastPushedAiProfileWatermarksRef.current = persisted.aiProfileWatermarks;
206
+ workspaceLastPushedAiProfileChangeKeysRef.current = persisted.aiProfileChangeKeys || new Map();
207
+ workspaceLastPushedDocumentPathsRef.current = new Set(Array.from(persisted.documentWatermarks.keys()));
208
+ workspaceLastPushedDocumentWatermarksRef.current = persisted.documentWatermarks;
209
+ workspaceLastPushedAssetPathsRef.current = new Set(Array.from(persisted.assetWatermarks.keys()));
210
+ workspaceLastPushedAssetWatermarksRef.current = persisted.assetWatermarks;
211
+ workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set(Array.from(persisted.documentReviewSessionWatermarks.keys()));
212
+ workspaceLastPushedDocumentReviewSessionWatermarksRef.current = persisted.documentReviewSessionWatermarks;
213
+ workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set(Array.from(persisted.documentReviewCommentWatermarks.keys()));
214
+ workspaceLastPushedDocumentReviewCommentWatermarksRef.current = persisted.documentReviewCommentWatermarks;
215
+ workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set(Array.from(persisted.annotatedAttachmentSessionWatermarks.keys()));
216
+ workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = persisted.annotatedAttachmentSessionWatermarks;
217
+ workspaceLastPushedTaskEventWatermarksRef.current = persisted.taskEventWatermarks;
218
+ workspaceBaselineResetReasonRef.current = null;
219
+ return true;
220
+ }, [workspaceLastPushedTaskIdsRef]);
221
+ const buildCurrentPersistedWorkspaceSyncWatermarks = useCallback(() => ({
222
+ taskIds: workspaceLastPushedTaskIdsRef.current,
223
+ taskWatermarks: workspaceLastPushedWatermarksRef.current,
224
+ taskChangeKeys: workspaceLastPushedTaskChangeKeysRef.current,
225
+ initiativeWatermarks: workspaceLastPushedInitiativeWatermarksRef.current,
226
+ workstreamWatermarks: workspaceLastPushedWorkstreamWatermarksRef.current,
227
+ aiProfileWatermarks: workspaceLastPushedAiProfileWatermarksRef.current,
228
+ aiProfileChangeKeys: workspaceLastPushedAiProfileChangeKeysRef.current,
229
+ documentWatermarks: workspaceLastPushedDocumentWatermarksRef.current,
230
+ assetWatermarks: workspaceLastPushedAssetWatermarksRef.current,
231
+ documentReviewSessionWatermarks: workspaceLastPushedDocumentReviewSessionWatermarksRef.current,
232
+ documentReviewCommentWatermarks: workspaceLastPushedDocumentReviewCommentWatermarksRef.current,
233
+ annotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
234
+ taskEventWatermarks: workspaceLastPushedTaskEventWatermarksRef.current,
235
+ }), []);
282
236
  const workspaceStartupRepairRanRef = useRef(false);
283
237
  const workspaceStartupFullReconcileRanRef = useRef(false);
284
238
  const workspaceRepairModeRef = useRef(false);
@@ -286,6 +240,7 @@ export function useSyncOrchestrator(input) {
286
240
  const workspaceCaptureAttachBootstrapBaselineRef = useRef(false);
287
241
  const workspaceDocumentsRefreshIntervalMs = 30_000;
288
242
  const workspacePullIntervalMs = realtimeSyncEnabled ? 30_000 : 15_000;
243
+ const workspacePushWatchdogIntervalMs = Math.max(30_000, workspacePullIntervalMs);
289
244
  const workspaceLastSuccessfulSyncAt = useMemo(() => {
290
245
  const pullMs = workspaceLastPullAt ? Date.parse(workspaceLastPullAt) : NaN;
291
246
  const pushMs = workspaceLastPushAt ? Date.parse(workspaceLastPushAt) : NaN;
@@ -298,36 +253,22 @@ export function useSyncOrchestrator(input) {
298
253
  return workspaceLastPushAt;
299
254
  return null;
300
255
  }, [workspaceLastPullAt, workspaceLastPushAt]);
301
- const workspaceSyncStatus = useMemo(() => {
302
- if (!workspaceCloudSyncEnabled)
303
- return 'off';
304
- if (workspaceSyncBusy || workspaceSyncPhase === 'provision-local' || workspaceSyncPhase === 'attach-cloud') {
305
- return 'syncing';
306
- }
307
- if (workspaceRetryAt || workspaceLastErrorMessage || workspaceSyncPhase === 'error') {
308
- return 'attention';
309
- }
310
- return 'healthy';
311
- }, [
312
- workspaceCloudSyncEnabled,
313
- workspaceSyncBusy,
314
- workspaceSyncPhase,
315
- workspaceRetryAt,
316
- workspaceLastErrorMessage
317
- ]);
318
- const workspaceSyncSummary = useMemo(() => summarizeSyncPhase(workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncBusy, workspaceRetryAt, workspaceLastErrorMessage), [
256
+ const workspaceSyncPresentation = useMemo(() => resolveWorkspaceSyncPresentation({
257
+ enabled: workspaceCloudSyncEnabled,
258
+ phase: workspaceSyncPhase,
259
+ busy: workspaceSyncBusy,
260
+ retryAt: workspaceRetryAt,
261
+ lastErrorMessage: workspaceLastErrorMessage
262
+ }), [
319
263
  workspaceCloudSyncEnabled,
320
264
  workspaceSyncPhase,
321
265
  workspaceSyncBusy,
322
266
  workspaceRetryAt,
323
267
  workspaceLastErrorMessage
324
268
  ]);
325
- const workspaceSyncRecommendedAction = useMemo(() => recommendSyncAction(workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceRetryAt, workspaceLastErrorMessage), [
326
- workspaceCloudSyncEnabled,
327
- workspaceSyncPhase,
328
- workspaceRetryAt,
329
- workspaceLastErrorMessage
330
- ]);
269
+ const workspaceSyncStatus = workspaceSyncPresentation.status;
270
+ const workspaceSyncSummary = workspaceSyncPresentation.summary;
271
+ const workspaceSyncRecommendedAction = workspaceSyncPresentation.recommendedAction;
331
272
  const getLocalUserSyncUpdatedAt = useCallback((userId) => {
332
273
  const key = String(userId || '').trim();
333
274
  if (!key)
@@ -518,6 +459,102 @@ export function useSyncOrchestrator(input) {
518
459
  const persistWorkspaceSyncPatchSafely = useCallback((patch) => {
519
460
  void persistWorkspaceSyncPatchBestEffort(patch).catch(() => { });
520
461
  }, [persistWorkspaceSyncPatchBestEffort]);
462
+ const persistWorkspaceSyncWatermarksSnapshotBestEffort = useCallback(() => {
463
+ const snapshotData = {
464
+ ...buildCurrentPersistedWorkspaceSyncWatermarks(),
465
+ taxonomyStateFingerprint
466
+ };
467
+ const persisted = savePersistedSyncWatermarks(currentWorkspaceId, snapshotData);
468
+ void persistWorkspaceSyncPatchBestEffort({
469
+ pushBaseline: buildPersistedSyncWatermarksSnapshot(snapshotData)
470
+ }).catch(() => { });
471
+ return persisted;
472
+ }, [
473
+ buildCurrentPersistedWorkspaceSyncWatermarks,
474
+ currentWorkspaceId,
475
+ persistWorkspaceSyncPatchBestEffort,
476
+ taxonomyStateFingerprint
477
+ ]);
478
+ useEffect(() => {
479
+ workspaceSyncMarkdownDocumentsFingerprintRef.current = '';
480
+ workspaceSyncAiProfilesFingerprintRef.current = '';
481
+ workspaceSyncAssetsFingerprintRef.current = '';
482
+ workspaceSyncDocumentReviewSessionsFingerprintRef.current = '';
483
+ workspaceSyncAnnotatedAttachmentSessionsFingerprintRef.current = '';
484
+ workspaceAttachBootstrapBaselineSignatureRef.current = '';
485
+ workspaceCaptureAttachBootstrapBaselineRef.current = false;
486
+ setWorkspaceSyncMarkdownDocuments([]);
487
+ setWorkspaceSyncMarkdownDocumentsFingerprint('');
488
+ workspaceSyncAiProfilesRef.current = [];
489
+ setWorkspaceSyncAiProfiles([]);
490
+ setWorkspaceSyncAiProfilesFingerprint('');
491
+ setWorkspaceSyncAssets([]);
492
+ setWorkspaceSyncAssetsFingerprint('');
493
+ workspaceSyncDocumentReviewSessionsRef.current = [];
494
+ workspaceSyncDocumentReviewCommentsRef.current = [];
495
+ setWorkspaceSyncDocumentReviewSessions([]);
496
+ setWorkspaceSyncDocumentReviewSessionsFingerprint('');
497
+ workspaceSyncAnnotatedAttachmentSessionsRef.current = [];
498
+ setWorkspaceSyncAnnotatedAttachmentSessions([]);
499
+ setWorkspaceSyncAnnotatedAttachmentSessionsFingerprint('');
500
+ setWorkspaceSyncReferenceSnapshotStatus(EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS);
501
+ setWorkspaceLastErrorMessage(null);
502
+ setWorkspaceLastErrorAt(null);
503
+ setWorkspaceSyncPendingChanges(0);
504
+ realtimeSuppressedEventIdsRef.current = new Set();
505
+ realtimeSuppressedEventQueueRef.current = [];
506
+ // Reset watermarks for the new workspace, then restore from localStorage if recent
507
+ // enough. This prevents a full resync on every page reload while still catching any
508
+ // changes that happened while the app was closed.
509
+ workspaceLastPushedWatermarksRef.current = new Map();
510
+ workspaceLastPushedTaskChangeKeysRef.current = new Map();
511
+ workspaceLastPushedInitiativeIdsRef.current = new Set();
512
+ workspaceLastPushedInitiativeWatermarksRef.current = new Map();
513
+ workspaceLastPushedWorkstreamIdsRef.current = new Set();
514
+ workspaceLastPushedWorkstreamWatermarksRef.current = new Map();
515
+ workspaceLastPushedAiProfileIdsRef.current = new Set();
516
+ workspaceLastPushedAiProfileWatermarksRef.current = new Map();
517
+ workspaceLastPushedAiProfileChangeKeysRef.current = new Map();
518
+ workspaceLastPushedDocumentPathsRef.current = new Set();
519
+ workspaceLastPushedDocumentWatermarksRef.current = new Map();
520
+ workspaceLastPushedAssetPathsRef.current = new Set();
521
+ workspaceLastPushedAssetWatermarksRef.current = new Map();
522
+ workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set();
523
+ workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map();
524
+ workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set();
525
+ workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map();
526
+ workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
527
+ workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
528
+ workspaceLastPushedTaskEventWatermarksRef.current = new Map();
529
+ workspaceStartupFullReconcileRanRef.current = false;
530
+ workspaceFullReconcileInFlightRef.current = false;
531
+ const persisted = readPersistedSyncWatermarks(currentWorkspaceId, taxonomyStateFingerprint);
532
+ if (!hydrateWorkspacePushBaselines(persisted)) {
533
+ workspaceBaselineResetReasonRef.current = 'missing-baseline';
534
+ }
535
+ }, [currentWorkspaceId, hydrateWorkspacePushBaselines, taxonomyStateFingerprint]);
536
+ useEffect(() => {
537
+ if (workspaceBaselineResetReasonRef.current !== 'missing-baseline')
538
+ return;
539
+ const persistedLocal = readPersistedSyncWatermarks(currentWorkspaceId, taxonomyStateFingerprint);
540
+ if (hydrateWorkspacePushBaselines(persistedLocal))
541
+ return;
542
+ const persistedRemote = parsePersistedSyncWatermarksSnapshot(readWorkspaceSyncStateSnapshot().pushBaseline, taxonomyStateFingerprint);
543
+ if (!hydrateWorkspacePushBaselines(persistedRemote))
544
+ return;
545
+ savePersistedSyncWatermarks(currentWorkspaceId, {
546
+ ...buildCurrentPersistedWorkspaceSyncWatermarks(),
547
+ taxonomyStateFingerprint
548
+ });
549
+ }, [
550
+ buildCurrentPersistedWorkspaceSyncWatermarks,
551
+ currentWorkspaceId,
552
+ hydrateWorkspacePushBaselines,
553
+ readWorkspaceSyncStateSnapshot,
554
+ taxonomyStateFingerprint,
555
+ workspaceCloudSyncEnabled,
556
+ workspaceSyncPhase
557
+ ]);
521
558
  const scheduleWorkspaceSyncRetry = useCallback((minDelayMs) => {
522
559
  scheduleWorkspaceRetry(() => retryWorkspaceSyncRef.current(), { minDelayMs });
523
560
  }, [scheduleWorkspaceRetry]);
@@ -560,7 +597,8 @@ export function useSyncOrchestrator(input) {
560
597
  isAuthenticated,
561
598
  resolveCloudAuthUrl,
562
599
  workspaceId: currentWorkspaceId,
563
- workspaceName: projectName || currentWorkspaceId
600
+ workspaceName: projectName || currentWorkspaceId,
601
+ allowProvisionOnMismatch: workspaceSyncPhase !== 'attach-cloud'
564
602
  });
565
603
  }, [
566
604
  cloudAuthConfigured,
@@ -568,7 +606,8 @@ export function useSyncOrchestrator(input) {
568
606
  isAuthenticated,
569
607
  resolveCloudAuthUrl,
570
608
  currentWorkspaceId,
571
- projectName
609
+ projectName,
610
+ workspaceSyncPhase
572
611
  ]);
573
612
  useEffect(() => {
574
613
  workspaceTasksRef.current = tasks || [];
@@ -576,6 +615,17 @@ export function useSyncOrchestrator(input) {
576
615
  useEffect(() => {
577
616
  workspaceArchivedTasksRef.current = archivedTasks || [];
578
617
  }, [archivedTasks]);
618
+ useEffect(() => {
619
+ workspaceDeletedTaskIdsRef.current = new Set((deletedTasks || [])
620
+ .map((entry) => String(entry?.taskId || '').trim())
621
+ .filter((taskId) => taskId.length > 0));
622
+ workspaceDeletedTaskWatermarksRef.current = new Map((deletedTasks || [])
623
+ .map((entry) => [
624
+ String(entry?.taskId || '').trim(),
625
+ String(entry?.deletedAt || '').trim()
626
+ ])
627
+ .filter(([taskId, deletedAt]) => taskId.length > 0 && Number.isFinite(Date.parse(deletedAt))));
628
+ }, [deletedTasks, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef]);
579
629
  useEffect(() => {
580
630
  workspaceInitiativesRef.current = initiatives || [];
581
631
  }, [initiatives]);
@@ -595,6 +645,7 @@ export function useSyncOrchestrator(input) {
595
645
  taskEvents: flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current),
596
646
  lastPushedTaskIds: workspaceLastPushedTaskIdsRef.current,
597
647
  pendingDeletedTaskIds: workspaceDeletedTaskIdsRef.current,
648
+ pendingDeletedTaskWatermarks: workspaceDeletedTaskWatermarksRef.current,
598
649
  initiatives: workspaceInitiativesRef.current,
599
650
  workstreams: workspaceWorkstreamsRef.current,
600
651
  taxonomies: taxonomies || [],
@@ -606,8 +657,11 @@ export function useSyncOrchestrator(input) {
606
657
  aiProfiles: workspaceSyncAiProfilesRef.current,
607
658
  lastPushedAiProfileIds: forceAllAiProfiles ? new Set() : workspaceLastPushedAiProfileIdsRef.current,
608
659
  lastPushedAiProfileWatermarks: forceAllAiProfiles ? new Map() : workspaceLastPushedAiProfileWatermarksRef.current,
660
+ lastPushedAiProfileChangeKeys: forceAllAiProfiles ? new Map() : workspaceLastPushedAiProfileChangeKeysRef.current,
609
661
  documents: workspaceSyncMarkdownDocumentsRef.current,
610
662
  assets: workspaceSyncAssetsRef.current,
663
+ currentDocumentPaths: workspaceSyncKnownDocumentPathsRef.current,
664
+ currentAssetPaths: workspaceSyncKnownAssetPathsRef.current,
611
665
  documentReviewSessions: workspaceSyncDocumentReviewSessionsRef.current,
612
666
  documentReviewComments: workspaceSyncDocumentReviewCommentsRef.current,
613
667
  annotatedAttachmentSessions: workspaceSyncAnnotatedAttachmentSessionsRef.current,
@@ -622,25 +676,42 @@ export function useSyncOrchestrator(input) {
622
676
  lastPushedAnnotatedAttachmentSessionIds: workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current,
623
677
  lastPushedAnnotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
624
678
  lastPushedTaskEventWatermarks: workspaceLastPushedTaskEventWatermarksRef.current,
625
- lastPushedWatermarks: workspaceLastPushedWatermarksRef.current
679
+ baselineResetReason: workspaceBaselineResetReasonRef.current,
680
+ lastPushedWatermarks: workspaceLastPushedWatermarksRef.current,
681
+ lastPushedTaskChangeKeys: workspaceLastPushedTaskChangeKeysRef.current
626
682
  });
627
683
  }, [taxonomies, taxonomyState]);
628
684
  const reportSyncEvent = useCallback((workspaceId, event) => {
629
685
  if (runtimeMode !== 'local')
630
686
  return;
631
- fetch('/api/taskforce/sync/events', {
632
- method: 'POST',
633
- credentials: 'include',
634
- headers: { 'Content-Type': 'application/json' },
635
- body: JSON.stringify({ ...event, workspaceId })
636
- }).catch((error) => {
687
+ try {
688
+ const normalizedEvent = {
689
+ ...event,
690
+ workspaceId,
691
+ details: normalizeSyncEventDetails(event.details)
692
+ };
693
+ void fetch('/api/taskforce/sync/events', {
694
+ method: 'POST',
695
+ credentials: 'include',
696
+ headers: { 'Content-Type': 'application/json' },
697
+ body: JSON.stringify(normalizedEvent)
698
+ }).catch((error) => {
699
+ logSyncDebug('sync_event_post_failed', {
700
+ workspaceId,
701
+ eventType: event.eventType,
702
+ status: event.status,
703
+ error: String(error?.message || error || '')
704
+ });
705
+ });
706
+ }
707
+ catch (error) {
637
708
  logSyncDebug('sync_event_post_failed', {
638
709
  workspaceId,
639
710
  eventType: event.eventType,
640
711
  status: event.status,
641
712
  error: String(error?.message || error || '')
642
713
  });
643
- });
714
+ }
644
715
  }, [runtimeMode]);
645
716
  const buildWorkspaceSyncSignature = useCallback(() => {
646
717
  // Read documents and assets from refs for the same stale-closure reason as
@@ -704,9 +775,16 @@ export function useSyncOrchestrator(input) {
704
775
  action: String(event.action || '').trim(),
705
776
  }))
706
777
  .sort((left, right) => left.id.localeCompare(right.id)),
778
+ deletedTasks: (deletedTasks || [])
779
+ .map((entry) => ({
780
+ taskId: String(entry?.taskId || '').trim(),
781
+ deletedAt: String(entry?.deletedAt || '').trim()
782
+ }))
783
+ .filter((entry) => entry.taskId.length > 0 && Number.isFinite(Date.parse(entry.deletedAt)))
784
+ .sort((left, right) => left.taskId.localeCompare(right.taskId)),
707
785
  taxonomies: Array.isArray(taxonomies) ? taxonomies : [],
708
786
  taxonomyState: taxonomyState && typeof taxonomyState === 'object' ? taxonomyState : null
709
- }), [tasks, archivedTasks, initiatives, workstreams, taxonomies, taxonomyState]);
787
+ }), [tasks, archivedTasks, deletedTasks, initiatives, workstreams, taxonomies, taxonomyState]);
710
788
  // Fingerprint of per-task attachment counts — changes when the MCP tool saves a new
711
789
  // attachment and the React app polls the updated task. Used to trigger an immediate
712
790
  // asset snapshot refresh without requiring a cross-process DOM event.
@@ -773,6 +851,11 @@ export function useSyncOrchestrator(input) {
773
851
  }))
774
852
  .filter((entry) => entry.path.length > 0)
775
853
  : [];
854
+ const knownDocumentPaths = new Set(Array.isArray(data?.knownPaths)
855
+ ? data.knownPaths
856
+ .map((entry) => String(entry || '').trim())
857
+ .filter((entry) => entry.length > 0)
858
+ : docs.map((entry) => entry.path));
776
859
  const fingerprint = typeof data?.fingerprint === 'string'
777
860
  ? data.fingerprint
778
861
  : JSON.stringify(docs.map((entry) => `${entry.path}:${entry.updatedAt}:${entry.content.length}`).sort());
@@ -780,6 +863,7 @@ export function useSyncOrchestrator(input) {
780
863
  return;
781
864
  workspaceSyncMarkdownDocumentsFingerprintRef.current = fingerprint;
782
865
  workspaceSyncMarkdownDocumentsRef.current = docs;
866
+ workspaceSyncKnownDocumentPathsRef.current = knownDocumentPaths;
783
867
  setWorkspaceSyncMarkdownDocuments(docs);
784
868
  setWorkspaceSyncMarkdownDocumentsFingerprint(fingerprint);
785
869
  }
@@ -829,7 +913,16 @@ export function useSyncOrchestrator(input) {
829
913
  setWorkspaceSyncAiProfilesLastFetchAt(new Date().toISOString());
830
914
  const fingerprint = typeof data?.fingerprint === 'string'
831
915
  ? data.fingerprint
832
- : JSON.stringify(aiProfiles.map((entry) => `${entry.id}:${entry.updatedAt}:${entry.name}:${entry.username}`).sort());
916
+ : JSON.stringify(aiProfiles.map((entry) => [
917
+ entry.id,
918
+ entry.updatedAt,
919
+ entry.name,
920
+ entry.username,
921
+ entry.archivedAt ?? '',
922
+ entry.archivedReason ?? '',
923
+ entry.mergedIntoProfileId ?? '',
924
+ entry.rosterStateUpdatedAt ?? ''
925
+ ].join(':')).sort());
833
926
  if (fingerprint === workspaceSyncAiProfilesFingerprintRef.current)
834
927
  return;
835
928
  workspaceSyncAiProfilesFingerprintRef.current = fingerprint;
@@ -886,6 +979,11 @@ export function useSyncOrchestrator(input) {
886
979
  }))
887
980
  .filter((entry) => entry.path.length > 0 && entry.contentBase64.length > 0)
888
981
  : [];
982
+ const knownAssetPaths = new Set(Array.isArray(data?.knownPaths)
983
+ ? data.knownPaths
984
+ .map((entry) => String(entry || '').trim())
985
+ .filter((entry) => entry.length > 0)
986
+ : assets.map((entry) => entry.path));
889
987
  const fingerprint = typeof data?.fingerprint === 'string'
890
988
  ? data.fingerprint
891
989
  : JSON.stringify(assets.map((entry) => `${entry.path}:${entry.updatedAt}:${entry.contentBase64.length}`).sort());
@@ -893,6 +991,7 @@ export function useSyncOrchestrator(input) {
893
991
  return;
894
992
  workspaceSyncAssetsFingerprintRef.current = fingerprint;
895
993
  workspaceSyncAssetsRef.current = assets;
994
+ workspaceSyncKnownAssetPathsRef.current = knownAssetPaths;
896
995
  setWorkspaceSyncAssets(assets);
897
996
  setWorkspaceSyncAssetsFingerprint(fingerprint);
898
997
  }
@@ -1035,6 +1134,38 @@ export function useSyncOrchestrator(input) {
1035
1134
  }
1036
1135
  }, [runtimeMode, currentWorkspaceId, cloudAuthConfigured, authSessionResolved, isAuthenticated, markWorkspaceSyncReferenceSnapshotReady]);
1037
1136
  const seedWorkspaceSnapshotPushBaseline = useCallback(() => {
1137
+ workspaceLastPushedTaskIdsRef.current = new Set([...workspaceTasksRef.current, ...workspaceArchivedTasksRef.current]
1138
+ .map((entry) => String(entry?.id || '').trim())
1139
+ .filter((id) => id.length > 0));
1140
+ workspaceLastPushedWatermarksRef.current = new Map([...workspaceTasksRef.current, ...workspaceArchivedTasksRef.current]
1141
+ .map((entry) => [String(entry?.id || '').trim(), taskSyncResourceAdapter.getWatermark(entry)])
1142
+ .filter(([id]) => id.length > 0));
1143
+ workspaceLastPushedTaskChangeKeysRef.current = new Map([
1144
+ ...workspaceTasksRef.current.map((entry) => [String(entry?.id || '').trim(), taskSyncResourceAdapter.getChangeKey({ ...entry, isArchived: false })]),
1145
+ ...workspaceArchivedTasksRef.current.map((entry) => [String(entry?.id || '').trim(), taskSyncResourceAdapter.getChangeKey({ ...entry, isArchived: true })])
1146
+ ]
1147
+ .filter(([id]) => id.length > 0));
1148
+ workspaceLastPushedInitiativeIdsRef.current = new Set(workspaceInitiativesRef.current
1149
+ .map((entry) => String(entry?.id || '').trim())
1150
+ .filter((id) => id.length > 0));
1151
+ workspaceLastPushedInitiativeWatermarksRef.current = new Map(workspaceInitiativesRef.current
1152
+ .map((entry) => [String(entry?.id || '').trim(), String(entry?.updatedAt || entry?.createdAt || '').trim()])
1153
+ .filter(([id]) => id.length > 0));
1154
+ workspaceLastPushedWorkstreamIdsRef.current = new Set(workspaceWorkstreamsRef.current
1155
+ .map((entry) => String(entry?.id || '').trim())
1156
+ .filter((id) => id.length > 0));
1157
+ workspaceLastPushedWorkstreamWatermarksRef.current = new Map(workspaceWorkstreamsRef.current
1158
+ .map((entry) => [String(entry?.id || '').trim(), String(entry?.updatedAt || entry?.createdAt || '').trim()])
1159
+ .filter(([id]) => id.length > 0));
1160
+ workspaceLastPushedAiProfileIdsRef.current = new Set(workspaceSyncAiProfilesRef.current
1161
+ .map((entry) => String(entry?.id || '').trim())
1162
+ .filter((id) => id.length > 0));
1163
+ workspaceLastPushedAiProfileWatermarksRef.current = new Map(workspaceSyncAiProfilesRef.current
1164
+ .map((entry) => [String(entry?.id || '').trim(), aiProfileSyncResourceAdapter.getWatermark(entry)])
1165
+ .filter(([id]) => id.length > 0));
1166
+ workspaceLastPushedAiProfileChangeKeysRef.current = new Map(workspaceSyncAiProfilesRef.current
1167
+ .map((entry) => [String(entry?.id || '').trim(), aiProfileSyncResourceAdapter.getChangeKey(entry)])
1168
+ .filter(([id]) => id.length > 0));
1038
1169
  workspaceLastPushedDocumentPathsRef.current = new Set(workspaceSyncMarkdownDocumentsRef.current
1039
1170
  .map((entry) => String(entry?.path || '').trim())
1040
1171
  .filter((path) => path.length > 0));
@@ -1068,7 +1199,9 @@ export function useSyncOrchestrator(input) {
1068
1199
  workspaceLastPushedTaskEventWatermarksRef.current = new Map(flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current)
1069
1200
  .map((entry) => [String(entry?.id || '').trim(), String(entry?.createdAt || '').trim()])
1070
1201
  .filter(([id]) => id.length > 0));
1071
- }, []);
1202
+ const persisted = persistWorkspaceSyncWatermarksSnapshotBestEffort();
1203
+ workspaceBaselineResetReasonRef.current = persisted ? null : 'storage-write-failed';
1204
+ }, [persistWorkspaceSyncWatermarksSnapshotBestEffort]);
1072
1205
  const refreshLocalWorkspaceTaskCollections = useCallback(async () => {
1073
1206
  const [tasksRes, archiveRes] = await Promise.all([
1074
1207
  fetch('/api/taskforce/tasks', {
@@ -1139,7 +1272,6 @@ export function useSyncOrchestrator(input) {
1139
1272
  workspaceCloudSyncEnabled,
1140
1273
  currentWorkspaceId,
1141
1274
  workspaceSyncPhase,
1142
- taxonomyStateFingerprint,
1143
1275
  isAuthenticated,
1144
1276
  checkAuthSession,
1145
1277
  resolveCloudAuthUrl,
@@ -1152,6 +1284,7 @@ export function useSyncOrchestrator(input) {
1152
1284
  scheduleWorkspaceSyncRetry,
1153
1285
  persistWorkspaceSyncPatchSafely,
1154
1286
  persistWorkspaceSyncPatchBestEffort,
1287
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1155
1288
  reportSyncEvent,
1156
1289
  acquireWorkspaceSyncLease,
1157
1290
  releaseWorkspaceSyncLease,
@@ -1171,18 +1304,22 @@ export function useSyncOrchestrator(input) {
1171
1304
  workspaceForceFullAiProfilePushRef,
1172
1305
  workspacePushInFlightRef,
1173
1306
  workspacePullInFlightRef,
1307
+ workspacePendingPushReplayRef,
1174
1308
  workspaceLastPushedSignatureRef,
1175
1309
  workspacePendingSignatureRef,
1176
1310
  workspaceLastPushedTaskIdsRef,
1177
1311
  workspaceDeletedTaskIdsRef,
1312
+ workspaceDeletedTaskWatermarksRef,
1178
1313
  workspacePullCursorRef,
1179
1314
  workspaceLastPushedWatermarksRef,
1315
+ workspaceLastPushedTaskChangeKeysRef,
1180
1316
  workspaceLastPushedInitiativeIdsRef,
1181
1317
  workspaceLastPushedInitiativeWatermarksRef,
1182
1318
  workspaceLastPushedWorkstreamIdsRef,
1183
1319
  workspaceLastPushedWorkstreamWatermarksRef,
1184
1320
  workspaceLastPushedAiProfileIdsRef,
1185
1321
  workspaceLastPushedAiProfileWatermarksRef,
1322
+ workspaceLastPushedAiProfileChangeKeysRef,
1186
1323
  workspaceLastPushedDocumentPathsRef,
1187
1324
  workspaceLastPushedDocumentWatermarksRef,
1188
1325
  workspaceLastPushedAssetPathsRef,
@@ -1194,6 +1331,7 @@ export function useSyncOrchestrator(input) {
1194
1331
  workspaceLastPushedAnnotatedAttachmentSessionIdsRef,
1195
1332
  workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef,
1196
1333
  workspaceLastPushedTaskEventWatermarksRef,
1334
+ workspaceBaselineResetReasonRef,
1197
1335
  workspaceCaptureAttachBootstrapBaselineRef,
1198
1336
  workspaceStartupFullReconcileRanRef,
1199
1337
  workspaceFullReconcileInFlightRef,
@@ -1227,6 +1365,7 @@ export function useSyncOrchestrator(input) {
1227
1365
  scheduleWorkspaceSyncRetry,
1228
1366
  persistWorkspaceSyncPatchSafely,
1229
1367
  persistWorkspaceSyncPatchBestEffort,
1368
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1230
1369
  reportSyncEvent,
1231
1370
  acquireWorkspaceSyncLease,
1232
1371
  releaseWorkspaceSyncLease,
@@ -1314,7 +1453,6 @@ export function useSyncOrchestrator(input) {
1314
1453
  }, [syncUserGlobalSettings]);
1315
1454
  const recoveryCoordinator = useMemo(() => createSyncRecoveryCoordinator({
1316
1455
  currentWorkspaceId,
1317
- taxonomyStateFingerprint,
1318
1456
  workspaceCloudSyncEnabled,
1319
1457
  workspaceSyncPhase,
1320
1458
  workspaceLastPullAt,
@@ -1330,12 +1468,14 @@ export function useSyncOrchestrator(input) {
1330
1468
  workspacePendingSignatureRef,
1331
1469
  workspaceLastPushedSignatureRef,
1332
1470
  workspaceLastPushedWatermarksRef,
1471
+ workspaceLastPushedTaskChangeKeysRef,
1333
1472
  workspaceLastPushedInitiativeIdsRef,
1334
1473
  workspaceLastPushedInitiativeWatermarksRef,
1335
1474
  workspaceLastPushedWorkstreamIdsRef,
1336
1475
  workspaceLastPushedWorkstreamWatermarksRef,
1337
1476
  workspaceLastPushedAiProfileIdsRef,
1338
1477
  workspaceLastPushedAiProfileWatermarksRef,
1478
+ workspaceLastPushedAiProfileChangeKeysRef,
1339
1479
  workspaceLastPushedDocumentPathsRef,
1340
1480
  workspaceLastPushedDocumentWatermarksRef,
1341
1481
  workspaceLastPushedAssetPathsRef,
@@ -1347,6 +1487,7 @@ export function useSyncOrchestrator(input) {
1347
1487
  workspaceLastPushedAnnotatedAttachmentSessionIdsRef,
1348
1488
  workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef,
1349
1489
  workspaceLastPushedTaskEventWatermarksRef,
1490
+ workspaceBaselineResetReasonRef,
1350
1491
  bootstrapPhaseStartedAtRef,
1351
1492
  bootstrapLastProgressAtRef,
1352
1493
  acquireWorkspaceSyncLease,
@@ -1355,7 +1496,9 @@ export function useSyncOrchestrator(input) {
1355
1496
  clearWorkspaceRetry,
1356
1497
  clearWorkspaceIssue,
1357
1498
  reportWorkspaceWindowContention,
1499
+ reportSyncEvent,
1358
1500
  persistWorkspaceSyncPatch,
1501
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1359
1502
  buildWorkspaceSyncSignature,
1360
1503
  pullWorkspaceChangesFromCloud,
1361
1504
  pushWorkspaceChangesToCloud,
@@ -1375,7 +1518,9 @@ export function useSyncOrchestrator(input) {
1375
1518
  clearWorkspaceRetry,
1376
1519
  clearWorkspaceIssue,
1377
1520
  reportWorkspaceWindowContention,
1521
+ reportSyncEvent,
1378
1522
  persistWorkspaceSyncPatch,
1523
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1379
1524
  buildWorkspaceSyncSignature,
1380
1525
  pullWorkspaceChangesFromCloud,
1381
1526
  pushWorkspaceChangesToCloud,
@@ -1387,6 +1532,39 @@ export function useSyncOrchestrator(input) {
1387
1532
  const { retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull } = recoveryCoordinator;
1388
1533
  // === SECTION: Diagnostics & reactive effects ===
1389
1534
  const getWorkspaceSyncDiagnostics = useCallback(() => {
1535
+ const pushReadiness = resolvePushSyncReadiness({
1536
+ cloudAuthConfigured,
1537
+ runtimeMode,
1538
+ workspaceCloudSyncEnabled,
1539
+ currentWorkspaceId,
1540
+ workspaceSyncPhase,
1541
+ isAuthenticated,
1542
+ repairModeActive: workspaceRepairModeRef.current === true,
1543
+ repairModeBypass: false,
1544
+ pushInFlight: workspacePushInFlightRef.current,
1545
+ pullInFlight: workspacePullInFlightRef.current
1546
+ });
1547
+ const retryPending = isWorkspaceRetryPending();
1548
+ const pullReadiness = resolvePullSyncReadiness({
1549
+ cloudAuthConfigured,
1550
+ runtimeMode,
1551
+ workspaceCloudSyncEnabled,
1552
+ currentWorkspaceId,
1553
+ workspaceSyncPhase,
1554
+ isAuthenticated,
1555
+ repairModeActive: workspaceRepairModeRef.current === true,
1556
+ repairModeBypass: false,
1557
+ retryPending,
1558
+ pushInFlight: workspacePushInFlightRef.current,
1559
+ pullInFlight: workspacePullInFlightRef.current
1560
+ });
1561
+ const retryReadiness = resolveRetrySyncReadiness({
1562
+ cloudAuthConfigured,
1563
+ runtimeMode,
1564
+ workspaceCloudSyncEnabled,
1565
+ currentWorkspaceId,
1566
+ isAuthenticated
1567
+ });
1390
1568
  return {
1391
1569
  workspaceId: currentWorkspaceId,
1392
1570
  enabled: workspaceCloudSyncEnabled,
@@ -1400,6 +1578,13 @@ export function useSyncOrchestrator(input) {
1400
1578
  pendingChanges: workspaceSyncPendingChanges,
1401
1579
  lastErrorMessage: workspaceLastErrorMessage,
1402
1580
  recommendedAction: workspaceSyncRecommendedAction,
1581
+ pushBlockedReason: pushReadiness.allowed ? null : pushReadiness.reason,
1582
+ pullBlockedReason: pullReadiness.allowed ? null : pullReadiness.reason,
1583
+ retryBlockedReason: retryReadiness.allowed ? null : retryReadiness.reason,
1584
+ repairActive: workspaceRepairModeRef.current === true,
1585
+ pushInFlight: workspacePushInFlightRef.current === true,
1586
+ pullInFlight: workspacePullInFlightRef.current === true,
1587
+ retryPending,
1403
1588
  documentSnapshotCount: workspaceSyncMarkdownDocuments.length,
1404
1589
  aiProfileSnapshotCount: workspaceSyncAiProfiles.length,
1405
1590
  aiProfileSnapshotRawCount: workspaceSyncAiProfilesRawCount,
@@ -1416,9 +1601,12 @@ export function useSyncOrchestrator(input) {
1416
1601
  forceFullAiProfilePushQueued: workspaceForceFullAiProfilePushRef.current === true
1417
1602
  };
1418
1603
  }, [
1604
+ cloudAuthConfigured,
1419
1605
  currentWorkspaceId,
1606
+ runtimeMode,
1420
1607
  workspaceCloudSyncEnabled,
1421
1608
  workspaceSyncPhase,
1609
+ isAuthenticated,
1422
1610
  workspaceSyncStatus,
1423
1611
  workspaceSyncSummary,
1424
1612
  workspaceLastSuccessfulSyncAt,
@@ -1438,7 +1626,8 @@ export function useSyncOrchestrator(input) {
1438
1626
  workspaceSyncDocumentReviewSessions.length,
1439
1627
  workspaceSyncDocumentReviewSessionsFingerprint,
1440
1628
  workspaceSyncAnnotatedAttachmentSessions.length,
1441
- workspaceSyncAnnotatedAttachmentSessionsFingerprint
1629
+ workspaceSyncAnnotatedAttachmentSessionsFingerprint,
1630
+ isWorkspaceRetryPending
1442
1631
  ]);
1443
1632
  useEffect(() => {
1444
1633
  retryWorkspaceSyncRef.current = retryWorkspaceCloudSync;
@@ -1520,11 +1709,14 @@ export function useSyncOrchestrator(input) {
1520
1709
  refreshWorkspaceSyncContextAssetSnapshots();
1521
1710
  }, [cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, authSessionResolved, isAuthenticated, taskAttachmentsFingerprint, refreshWorkspaceSyncContextAssetSnapshots]);
1522
1711
  useEffect(() => {
1523
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
1524
- return;
1525
- if (workspaceSyncPhase === 'attach-cloud')
1526
- return;
1527
- if (!workspaceSyncReferenceSnapshotsReady)
1712
+ if (!shouldScheduleWorkspaceReactivePush({
1713
+ cloudAuthConfigured,
1714
+ runtimeMode,
1715
+ workspaceCloudSyncEnabled,
1716
+ authSessionResolved,
1717
+ workspaceSyncPhase,
1718
+ workspaceSyncReferenceSnapshotsReady
1719
+ }))
1528
1720
  return;
1529
1721
  const signature = buildWorkspaceSyncSignature();
1530
1722
  if (shouldSuppressAttachBootstrapPush(signature))
@@ -1561,6 +1753,44 @@ export function useSyncOrchestrator(input) {
1561
1753
  workspacePushInFlightRef,
1562
1754
  workspacePullInFlightRef
1563
1755
  ]);
1756
+ useEffect(() => {
1757
+ if (!shouldStartWorkspacePushWatchdog({
1758
+ cloudAuthConfigured,
1759
+ runtimeMode,
1760
+ workspaceCloudSyncEnabled,
1761
+ authSessionResolved,
1762
+ workspaceSyncPhase,
1763
+ workspaceSyncReferenceSnapshotsReady
1764
+ }))
1765
+ return;
1766
+ const intervalId = window.setInterval(() => {
1767
+ if (workspacePushInFlightRef.current || workspacePullInFlightRef.current)
1768
+ return;
1769
+ if (isWorkspaceRetryPending())
1770
+ return;
1771
+ const signature = workspacePendingSignatureRef.current || buildWorkspaceSyncSignature();
1772
+ if (!signature || signature === workspaceLastPushedSignatureRef.current)
1773
+ return;
1774
+ const pendingPayload = buildWorkspaceSyncPayload();
1775
+ setWorkspaceSyncPendingChanges(pendingPayload.changes.length);
1776
+ if (pendingPayload.changes.length === 0)
1777
+ return;
1778
+ void pushWorkspaceChangesToCloud(signature);
1779
+ }, workspacePushWatchdogIntervalMs);
1780
+ return () => window.clearInterval(intervalId);
1781
+ }, [
1782
+ cloudAuthConfigured,
1783
+ runtimeMode,
1784
+ workspaceCloudSyncEnabled,
1785
+ authSessionResolved,
1786
+ workspaceSyncPhase,
1787
+ workspaceSyncReferenceSnapshotsReady,
1788
+ buildWorkspaceSyncSignature,
1789
+ buildWorkspaceSyncPayload,
1790
+ pushWorkspaceChangesToCloud,
1791
+ isWorkspaceRetryPending,
1792
+ workspacePushWatchdogIntervalMs
1793
+ ]);
1564
1794
  useEffect(() => {
1565
1795
  if (cloudAuthConfigured && runtimeMode === 'local' && workspaceCloudSyncEnabled)
1566
1796
  return;
@@ -1573,31 +1803,46 @@ export function useSyncOrchestrator(input) {
1573
1803
  clearWorkspaceRetry
1574
1804
  ]);
1575
1805
  useEffect(() => {
1576
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
1577
- return;
1578
- if (workspaceSyncPhase === 'provision-local')
1579
- return;
1580
- if (isWorkspaceRetryPending())
1806
+ const pullPolicy = resolveWorkspaceAutomaticPullPolicy({
1807
+ cloudAuthConfigured,
1808
+ runtimeMode,
1809
+ workspaceCloudSyncEnabled,
1810
+ authSessionResolved,
1811
+ workspaceSyncPhase,
1812
+ lastPullAt: workspaceLastPullAt,
1813
+ retryPending: isWorkspaceRetryPending()
1814
+ });
1815
+ if (!pullPolicy.shouldStart)
1581
1816
  return;
1582
- void pullWorkspaceChangesFromCloud();
1817
+ if (pullPolicy.shouldKickOffImmediately) {
1818
+ void pullWorkspaceChangesFromCloud();
1819
+ }
1583
1820
  const intervalId = window.setInterval(() => {
1584
1821
  void pullWorkspaceChangesFromCloud();
1585
1822
  }, workspacePullIntervalMs);
1586
- return () => window.clearInterval(intervalId);
1823
+ return () => {
1824
+ window.clearInterval(intervalId);
1825
+ };
1587
1826
  }, [
1588
1827
  cloudAuthConfigured,
1589
1828
  runtimeMode,
1590
1829
  workspaceCloudSyncEnabled,
1591
1830
  authSessionResolved,
1592
1831
  workspaceSyncPhase,
1832
+ workspaceLastPullAt,
1593
1833
  isWorkspaceRetryPending,
1594
1834
  pullWorkspaceChangesFromCloud,
1595
1835
  workspacePullIntervalMs
1596
1836
  ]);
1597
1837
  useEffect(() => {
1598
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved || !isAuthenticated)
1599
- return;
1600
- if (workspaceSyncPhase !== 'active')
1838
+ if (!shouldStartWorkspacePeriodicFullReconcile({
1839
+ cloudAuthConfigured,
1840
+ runtimeMode,
1841
+ workspaceCloudSyncEnabled,
1842
+ authSessionResolved,
1843
+ isAuthenticated,
1844
+ workspaceSyncPhase
1845
+ }))
1601
1846
  return;
1602
1847
  const intervalId = window.setInterval(() => {
1603
1848
  if (workspaceFullReconcileInFlightRef.current)
@@ -1660,13 +1905,15 @@ export function useSyncOrchestrator(input) {
1660
1905
  // missing or stale-tombstoned metadata — a common result of a crash between file write
1661
1906
  // and DB upsert. The repair only adds missing records; it never deletes files.
1662
1907
  useEffect(() => {
1663
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
1664
- return;
1665
- if (!authSessionResolved || !isAuthenticated)
1666
- return;
1667
- if (workspaceSyncPhase === 'provision-local' || workspaceSyncPhase === 'attach-cloud')
1668
- return;
1669
- if (workspaceStartupRepairRanRef.current)
1908
+ if (!shouldRunWorkspaceStartupRepair({
1909
+ cloudAuthConfigured,
1910
+ runtimeMode,
1911
+ workspaceCloudSyncEnabled,
1912
+ authSessionResolved,
1913
+ isAuthenticated,
1914
+ workspaceSyncPhase,
1915
+ alreadyRan: workspaceStartupRepairRanRef.current
1916
+ }))
1670
1917
  return;
1671
1918
  workspaceStartupRepairRanRef.current = true;
1672
1919
  fetch('/api/taskforce/sync/workspace/repair-startup', {
@@ -1718,6 +1965,7 @@ export function useSyncOrchestrator(input) {
1718
1965
  workspaceLastSuccessfulSyncAt,
1719
1966
  workspaceCloudSyncEnabled,
1720
1967
  workspaceSyncPhase,
1968
+ workspaceSyncSetupIntent,
1721
1969
  workspaceSyncStatus,
1722
1970
  workspaceSyncSummary,
1723
1971
  workspaceSyncRecommendedAction,
@@ -1732,6 +1980,7 @@ export function useSyncOrchestrator(input) {
1732
1980
  workspacePendingSignatureRef,
1733
1981
  workspaceLastPushedTaskIdsRef,
1734
1982
  workspaceDeletedTaskIdsRef,
1983
+ workspaceDeletedTaskWatermarksRef,
1735
1984
  workspacePullCursorRef,
1736
1985
  clearWorkspaceRetry,
1737
1986
  persistWorkspaceSyncPatch,