@taskforcehq/taskforce 0.3.305 → 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 (337) hide show
  1. package/dist/Taskforce.module.css +295 -89
  2. package/dist/TaskforceCore.js +215 -43
  3. package/dist/TaskforceCore.test.js +433 -28
  4. package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
  5. package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  6. package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
  7. package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
  8. package/dist/assets/fonts/Noir_medium.woff2 +0 -0
  9. package/dist/assets/fonts/Noir_regular.woff2 +0 -0
  10. package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
  11. package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
  12. package/dist/compat/workspaceSyncCompat.js +46 -2
  13. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  14. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  15. package/dist/components/context/ContextAttachmentManager.js +549 -0
  16. package/dist/components/features/AgentsModule.d.ts +10 -1
  17. package/dist/components/features/AgentsModule.js +260 -15
  18. package/dist/components/features/AgentsModule.test.js +255 -5
  19. package/dist/components/features/DocumentViewer.d.ts +5 -1
  20. package/dist/components/features/DocumentViewer.js +60 -3
  21. package/dist/components/features/DocumentWorkspace.js +1 -1
  22. package/dist/components/features/DocumentWorkspace.test.js +33 -0
  23. package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
  24. package/dist/components/task/TaskCard.js +19 -28
  25. package/dist/components/task/TaskCard.test.js +38 -3
  26. package/dist/components/task/TaskContextUpload.js +4 -526
  27. package/dist/components/task/TaskForm.js +85 -90
  28. package/dist/components/task/TaskForm.test.js +110 -8
  29. package/dist/components/task/TaskKanban.d.ts +2 -1
  30. package/dist/components/task/TaskKanban.js +94 -13
  31. package/dist/components/task/TaskKanban.test.js +36 -0
  32. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  33. package/dist/components/ui/EditableAvatarButton.js +18 -0
  34. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  35. package/dist/components/views/PlanComparisonPage.js +70 -14
  36. package/dist/components/views/PlanComparisonPage.test.js +238 -0
  37. package/dist/components/views/PlansPage.js +127 -36
  38. package/dist/components/views/PlansPage.test.js +294 -12
  39. package/dist/components/views/StandaloneLayout.js +283 -533
  40. package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
  41. package/dist/components/views/panels/PlanningDrawer.js +18 -11
  42. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  43. package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
  44. package/dist/components/views/planningScope.d.ts +20 -0
  45. package/dist/components/views/planningScope.js +25 -0
  46. package/dist/components/views/planningScope.test.d.ts +1 -0
  47. package/dist/components/views/planningScope.test.js +60 -0
  48. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +26 -1
  49. package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
  50. package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
  51. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  52. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  53. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  54. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  55. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  56. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  57. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  58. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  59. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  60. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  61. package/dist/core/AiProfileService.d.ts +39 -2
  62. package/dist/core/AiProfileService.js +639 -73
  63. package/dist/core/AiProfileService.test.js +19 -1
  64. package/dist/core/AiProfiles.test.js +566 -7
  65. package/dist/core/AttachmentLinkService.js +57 -15
  66. package/dist/core/AuthIdentityService.d.ts +107 -0
  67. package/dist/core/AuthIdentityService.js +284 -0
  68. package/dist/core/AuthTokenService.d.ts +4 -0
  69. package/dist/core/AuthTokenService.js +34 -6
  70. package/dist/core/AuthTokenService.test.js +3 -3
  71. package/dist/core/EntitlementsPolicy.test.js +2 -2
  72. package/dist/core/GlobalSettingsService.js +115 -6
  73. package/dist/core/PlanEntitlementService.d.ts +23 -2
  74. package/dist/core/PlanEntitlementService.js +343 -57
  75. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  76. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  77. package/dist/core/PlanVersionPolicy.test.js +35 -2
  78. package/dist/core/PlanningEntities.test.js +52 -0
  79. package/dist/core/SignupMonetizationSettings.test.js +199 -27
  80. package/dist/core/SyncReconciliationService.js +2 -65
  81. package/dist/core/SystemAdmin.test.js +42 -42
  82. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  83. package/dist/core/Taskforce.d.ts +225 -14
  84. package/dist/core/Taskforce.js +765 -192
  85. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  86. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  87. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  88. package/dist/core/WorkspaceLifecycleService.js +0 -5
  89. package/dist/core/WorkspacePermissions.test.js +25 -1
  90. package/dist/core/WorkspacePlanMode.test.js +117 -16
  91. package/dist/core/shared.d.ts +1 -1
  92. package/dist/core/shared.js +5 -1
  93. package/dist/core/types.d.ts +68 -2
  94. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
  95. package/dist/hooks/sync/bootstrap.js +2 -0
  96. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  97. package/dist/hooks/sync/controlPlane.js +78 -0
  98. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  99. package/dist/hooks/sync/controlPlane.test.js +121 -0
  100. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  101. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  102. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  103. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  104. package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
  105. package/dist/hooks/sync/orchestratorShared.js +68 -47
  106. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  107. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  108. package/dist/hooks/sync/pullLifecycle.js +24 -0
  109. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  110. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  111. package/dist/hooks/sync/recovery.d.ts +16 -2
  112. package/dist/hooks/sync/recovery.js +171 -53
  113. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  114. package/dist/hooks/sync/recovery.test.js +292 -0
  115. package/dist/hooks/sync/transfers.d.ts +15 -3
  116. package/dist/hooks/sync/transfers.js +179 -62
  117. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  118. package/dist/hooks/sync/transfers.test.js +396 -0
  119. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  120. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  121. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  122. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  123. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  124. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  125. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  126. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  127. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  128. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  129. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  130. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  131. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  132. package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
  133. package/dist/hooks/useSyncOrchestrator.js +359 -178
  134. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
  135. package/dist/hooks/useTaskData.js +8 -3
  136. package/dist/hooks/useTaskData.test.js +45 -0
  137. package/dist/hooks/useTaskforce.d.ts +60 -2
  138. package/dist/hooks/useTaskforce.js +549 -240
  139. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  140. package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
  141. package/dist/hooks/useTaskforce.sync-behavior.test.js +2380 -212
  142. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  143. package/dist/hooks/useWorkspaceSyncController.js +6 -1
  144. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  145. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
  146. package/dist/localization/locales/en-US.d.ts +0 -1
  147. package/dist/localization/locales/en-US.js +0 -1
  148. package/dist/localization/locales/es-419.js +0 -1
  149. package/dist/localization/locales/pt-BR.js +0 -1
  150. package/dist/mcp/documentAssetRegistrar.js +3 -3
  151. package/dist/mcp/runtime.d.ts +2 -0
  152. package/dist/mcp/runtime.js +131 -64
  153. package/dist/mcp/runtime.test.js +291 -12
  154. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  155. package/dist/migrations/taskSchemaMigrations.js +171 -61
  156. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  157. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  158. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  159. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  160. package/dist/resources/templates/workflows/plan.yaml +1 -1
  161. package/dist/resources/templates/workflows/review.yaml +2 -2
  162. package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
  163. package/dist/server/auth/providers/apple.d.ts +33 -0
  164. package/dist/server/auth/providers/apple.js +82 -0
  165. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  166. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  167. package/dist/server/auth/providers/github.d.ts +26 -0
  168. package/dist/server/auth/providers/github.js +86 -0
  169. package/dist/server/auth/providers/google.d.ts +21 -0
  170. package/dist/server/auth/providers/google.js +56 -0
  171. package/dist/server/auth/providers/types.d.ts +21 -0
  172. package/dist/server/auth/providers/types.js +1 -0
  173. package/dist/server/auth.d.ts +2 -0
  174. package/dist/server/auth.js +6 -3
  175. package/dist/server/documentReviewRoutes.test.js +36 -3
  176. package/dist/server/index.cookieProxy.test.js +1 -0
  177. package/dist/server/index.d.ts +12 -0
  178. package/dist/server/index.js +120 -9
  179. package/dist/server/index.rateLimit.test.js +3 -0
  180. package/dist/server/index.test.js +106 -3
  181. package/dist/server/routes/admin.d.ts +3 -0
  182. package/dist/server/routes/admin.js +272 -19
  183. package/dist/server/routes/auth.d.ts +15 -2
  184. package/dist/server/routes/auth.js +994 -57
  185. package/dist/server/routes/authSupport.d.ts +1 -1
  186. package/dist/server/routes/authSupport.js +1 -1
  187. package/dist/server/routes/billing.js +361 -35
  188. package/dist/server/routes/billing.test.js +592 -12
  189. package/dist/server/routes/documents.d.ts +1 -0
  190. package/dist/server/routes/documents.js +172 -70
  191. package/dist/server/routes/shared.d.ts +28 -2
  192. package/dist/server/routes/shared.js +69 -4
  193. package/dist/server/routes/sync.integration.test.js +437 -39
  194. package/dist/server/routes/sync.js +61 -605
  195. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  196. package/dist/server/routes/syncAuxRoutes.js +91 -0
  197. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  198. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  199. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  200. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  201. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  202. package/dist/server/routes/syncPushRoutes.js +212 -0
  203. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  204. package/dist/server/routes/syncRouteGuards.js +67 -0
  205. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  206. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  207. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  208. package/dist/server/routes/syncRouteTypes.js +1 -0
  209. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  210. package/dist/server/routes/syncSnapshotRoutes.js +180 -0
  211. package/dist/server/routes/tasks.js +43 -1
  212. package/dist/server/routes/workspaces.js +36 -5
  213. package/dist/server/routes.js +60 -3
  214. package/dist/server/routes.test.js +1824 -171
  215. package/dist/services/workflowExportSnapshots.test.js +2 -0
  216. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  217. package/dist/shared/aiProfileSeatScope.js +21 -0
  218. package/dist/shared/passwordPolicy.d.ts +13 -0
  219. package/dist/shared/passwordPolicy.js +84 -0
  220. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  221. package/dist/shared/passwordPolicy.test.js +34 -0
  222. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  223. package/dist/storage/workspaceAssetStore.js +132 -44
  224. package/dist/storage/workspaceAssetStore.test.js +93 -6
  225. package/dist/styles/fonts.css +70 -0
  226. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  227. package/dist/sync/collaborationSyncPayload.js +17 -11
  228. package/dist/sync/collaborationSyncPayload.test.js +13 -1
  229. package/dist/sync/contentSyncState.d.ts +4 -8
  230. package/dist/sync/contentSyncState.js +17 -70
  231. package/dist/sync/syncApplyHandlers.d.ts +7 -0
  232. package/dist/sync/syncApplyHandlers.js +28 -13
  233. package/dist/sync/syncApplyHandlers.test.js +70 -3
  234. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  235. package/dist/sync/syncFieldSemantics.js +55 -0
  236. package/dist/sync/syncResourceAdapters.d.ts +44 -0
  237. package/dist/sync/syncResourceAdapters.js +77 -0
  238. package/dist/sync/syncService.d.ts +5 -0
  239. package/dist/sync/syncService.js +52 -0
  240. package/dist/sync/syncService.test.js +57 -3
  241. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  242. package/dist/sync/taskSyncChangeKey.js +143 -0
  243. package/dist/sync/taskSyncPayload.js +3 -8
  244. package/dist/sync/workspacePullFeed.js +4 -3
  245. package/dist/sync/workspacePullFeed.test.js +43 -0
  246. package/dist/sync/workspaceRepair.js +5 -2
  247. package/dist/sync/workspaceSyncModel.d.ts +45 -0
  248. package/dist/sync/workspaceSyncModel.js +183 -28
  249. package/dist/sync/workspaceSyncModel.test.js +291 -12
  250. package/dist/sync/workspaceSyncState.d.ts +17 -0
  251. package/dist/sync/workspaceSyncState.js +2 -1
  252. package/dist/sync/workspaceSyncSurface.js +3 -8
  253. package/dist/types.d.ts +17 -0
  254. package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
  255. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BG6P_GVW.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
  256. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  257. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  258. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  259. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  260. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
  261. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
  262. package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-9E6ParrY.js} +1 -1
  263. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  264. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  265. package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
  266. package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
  267. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  268. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  269. package/dist/ui/assets/{TaskSettings-ln0aF7xc.js → TaskSettings-DZX4jk7e.js} +1 -1
  270. package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-BA7jcEpH.js} +1 -1
  271. package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
  272. package/dist/ui/assets/index-C6k75jr8.js +6 -0
  273. package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-DuEd_65c.js} +1 -1
  274. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  275. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  276. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  277. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  278. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  279. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  280. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  281. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  282. package/dist/ui/fonts/Noir_medium.otf +0 -0
  283. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  284. package/dist/ui/fonts/Noir_regular.otf +0 -0
  285. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  286. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  287. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  288. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  289. package/dist/ui/index.html +3 -3
  290. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  291. package/dist/utils/assignees.d.ts +2 -0
  292. package/dist/utils/assignees.js +2 -0
  293. package/dist/utils/avatarUpload.d.ts +27 -0
  294. package/dist/utils/avatarUpload.js +132 -0
  295. package/dist/utils/avatarUpload.test.d.ts +1 -0
  296. package/dist/utils/avatarUpload.test.js +40 -0
  297. package/dist/utils/bootstrapTrace.d.ts +7 -0
  298. package/dist/utils/bootstrapTrace.js +29 -0
  299. package/dist/utils/commercialLifecyclePresentation.js +3 -0
  300. package/dist/utils/contextAssetEvents.d.ts +2 -0
  301. package/dist/utils/syncEventDetails.d.ts +3 -0
  302. package/dist/utils/syncEventDetails.js +64 -0
  303. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  304. package/dist/utils/syncEventDetails.test.js +23 -0
  305. package/dist/utils/syncEventPresentation.d.ts +15 -0
  306. package/dist/utils/syncEventPresentation.js +129 -0
  307. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  308. package/dist/utils/syncEventPresentation.test.js +64 -0
  309. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  310. package/dist/utils/syncStatusPresentation.js +217 -0
  311. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  312. package/dist/utils/syncStatusPresentation.test.js +164 -0
  313. package/dist/utils/taskActivity.d.ts +2 -0
  314. package/dist/utils/taskActivity.js +228 -22
  315. package/dist/utils/taskActivity.test.js +119 -8
  316. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  317. package/dist/utils/workspaceSyncPresentation.js +68 -2
  318. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  319. package/package.json +3 -2
  320. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  321. package/scripts/run-e2e-realtime.sh +17 -3
  322. package/scripts/run-smoke.sh +17 -5
  323. package/scripts/run-soak.sh +17 -5
  324. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  325. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  326. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  327. package/src/resources/templates/workflows/plan.yaml +1 -1
  328. package/src/resources/templates/workflows/review.yaml +2 -2
  329. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +0 -1
  330. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
  331. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
  332. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  333. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
  334. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  335. package/dist/ui/assets/index-C2-OXZV7.css +0 -1
  336. package/dist/ui/assets/index-DcSI5F86.js +0 -6
  337. 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,
@@ -109,6 +70,7 @@ export function useSyncOrchestrator(input) {
109
70
  const workspaceInitiativesRef = useRef(initiatives || []);
110
71
  const workspaceWorkstreamsRef = useRef(workstreams || []);
111
72
  const workspaceSyncMarkdownDocumentsRef = useRef([]);
73
+ const workspaceSyncKnownDocumentPathsRef = useRef(new Set());
112
74
  const [workspaceSyncAiProfiles, setWorkspaceSyncAiProfiles] = useState([]);
113
75
  const [workspaceSyncAiProfilesFingerprint, setWorkspaceSyncAiProfilesFingerprint] = useState('');
114
76
  const [workspaceSyncAiProfilesLastFetchAt, setWorkspaceSyncAiProfilesLastFetchAt] = useState(null);
@@ -119,6 +81,7 @@ export function useSyncOrchestrator(input) {
119
81
  const [workspaceSyncAssets, setWorkspaceSyncAssets] = useState([]);
120
82
  const [workspaceSyncAssetsFingerprint, setWorkspaceSyncAssetsFingerprint] = useState('');
121
83
  const workspaceSyncAssetsRef = useRef([]);
84
+ const workspaceSyncKnownAssetPathsRef = useRef(new Set());
122
85
  const [workspaceSyncDocumentReviewSessions, setWorkspaceSyncDocumentReviewSessions] = useState([]);
123
86
  const [workspaceSyncDocumentReviewSessionsFingerprint, setWorkspaceSyncDocumentReviewSessionsFingerprint] = useState('');
124
87
  const workspaceSyncDocumentReviewSessionsRef = useRef([]);
@@ -137,6 +100,7 @@ export function useSyncOrchestrator(input) {
137
100
  });
138
101
  const userSettingsSyncInFlightRef = useRef(false);
139
102
  const userSettingsRetryTimeoutRef = useRef(null);
103
+ const workspaceBaselineResetReasonRef = useRef('missing-baseline');
140
104
  const userSettingsRetryAttemptRef = useRef(0);
141
105
  const applyingRemoteUserSettingsRef = useRef(false);
142
106
  const initialUserSettingsSyncCompletedRef = useRef(new Set());
@@ -150,6 +114,7 @@ export function useSyncOrchestrator(input) {
150
114
  const realtimePullDebounceRef = useRef(null);
151
115
  const realtimeSuppressedEventIdsRef = useRef(new Set());
152
116
  const realtimeSuppressedEventQueueRef = useRef([]);
117
+ const workspacePendingPushReplayRef = useRef(null);
153
118
  const retryWorkspaceSyncRef = useRef(() => Promise.resolve(false));
154
119
  const syncUserGlobalSettingsRef = useRef(async () => { });
155
120
  const bootstrapPhaseStartedAtRef = useRef(null);
@@ -178,73 +143,6 @@ export function useSyncOrchestrator(input) {
178
143
  void syncUserGlobalSettingsRef.current({ preferCloudOnFirstSync: false });
179
144
  }, delayMs);
180
145
  }, [clearUserSettingsRetry]);
181
- useEffect(() => {
182
- workspaceSyncMarkdownDocumentsFingerprintRef.current = '';
183
- workspaceSyncAiProfilesFingerprintRef.current = '';
184
- workspaceSyncAssetsFingerprintRef.current = '';
185
- workspaceSyncDocumentReviewSessionsFingerprintRef.current = '';
186
- workspaceSyncAnnotatedAttachmentSessionsFingerprintRef.current = '';
187
- workspaceAttachBootstrapBaselineSignatureRef.current = '';
188
- workspaceCaptureAttachBootstrapBaselineRef.current = false;
189
- setWorkspaceSyncMarkdownDocuments([]);
190
- setWorkspaceSyncMarkdownDocumentsFingerprint('');
191
- workspaceSyncAiProfilesRef.current = [];
192
- setWorkspaceSyncAiProfiles([]);
193
- setWorkspaceSyncAiProfilesFingerprint('');
194
- setWorkspaceSyncAssets([]);
195
- setWorkspaceSyncAssetsFingerprint('');
196
- workspaceSyncDocumentReviewSessionsRef.current = [];
197
- workspaceSyncDocumentReviewCommentsRef.current = [];
198
- setWorkspaceSyncDocumentReviewSessions([]);
199
- setWorkspaceSyncDocumentReviewSessionsFingerprint('');
200
- workspaceSyncAnnotatedAttachmentSessionsRef.current = [];
201
- setWorkspaceSyncAnnotatedAttachmentSessions([]);
202
- setWorkspaceSyncAnnotatedAttachmentSessionsFingerprint('');
203
- setWorkspaceSyncReferenceSnapshotStatus(EMPTY_WORKSPACE_SYNC_REFERENCE_SNAPSHOT_STATUS);
204
- setWorkspaceLastErrorMessage(null);
205
- setWorkspaceLastErrorAt(null);
206
- setWorkspaceSyncPendingChanges(0);
207
- realtimeSuppressedEventIdsRef.current = new Set();
208
- realtimeSuppressedEventQueueRef.current = [];
209
- // Reset watermarks for the new workspace, then restore from localStorage if recent
210
- // enough. This prevents a full resync on every page reload while still catching any
211
- // changes that happened while the app was closed.
212
- workspaceLastPushedWatermarksRef.current = new Map();
213
- workspaceLastPushedInitiativeIdsRef.current = new Set();
214
- workspaceLastPushedInitiativeWatermarksRef.current = new Map();
215
- workspaceLastPushedWorkstreamIdsRef.current = new Set();
216
- workspaceLastPushedWorkstreamWatermarksRef.current = new Map();
217
- workspaceLastPushedAiProfileIdsRef.current = new Set();
218
- workspaceLastPushedAiProfileWatermarksRef.current = new Map();
219
- workspaceLastPushedDocumentPathsRef.current = new Set();
220
- workspaceLastPushedDocumentWatermarksRef.current = new Map();
221
- workspaceLastPushedAssetPathsRef.current = new Set();
222
- workspaceLastPushedAssetWatermarksRef.current = new Map();
223
- workspaceLastPushedDocumentReviewSessionIdsRef.current = new Set();
224
- workspaceLastPushedDocumentReviewSessionWatermarksRef.current = new Map();
225
- workspaceLastPushedDocumentReviewCommentIdsRef.current = new Set();
226
- workspaceLastPushedDocumentReviewCommentWatermarksRef.current = new Map();
227
- workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
228
- workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
229
- workspaceLastPushedTaskEventWatermarksRef.current = new Map();
230
- workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current = new Set();
231
- workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = new Map();
232
- workspaceLastPushedTaskEventWatermarksRef.current = new Map();
233
- workspaceStartupFullReconcileRanRef.current = false;
234
- workspaceFullReconcileInFlightRef.current = false;
235
- const persisted = readPersistedSyncWatermarks(currentWorkspaceId, taxonomyStateFingerprint);
236
- if (persisted) {
237
- workspaceLastPushedWatermarksRef.current = persisted.taskWatermarks;
238
- workspaceLastPushedInitiativeWatermarksRef.current = persisted.initiativeWatermarks;
239
- workspaceLastPushedWorkstreamWatermarksRef.current = persisted.workstreamWatermarks;
240
- workspaceLastPushedDocumentWatermarksRef.current = persisted.documentWatermarks;
241
- workspaceLastPushedAssetWatermarksRef.current = persisted.assetWatermarks;
242
- workspaceLastPushedDocumentReviewSessionWatermarksRef.current = persisted.documentReviewSessionWatermarks;
243
- workspaceLastPushedDocumentReviewCommentWatermarksRef.current = persisted.documentReviewCommentWatermarks;
244
- workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current = persisted.annotatedAttachmentSessionWatermarks;
245
- workspaceLastPushedTaskEventWatermarksRef.current = persisted.taskEventWatermarks;
246
- }
247
- }, [currentWorkspaceId, taxonomyStateFingerprint]);
248
146
  const markWorkspaceSyncReferenceSnapshotReady = useCallback((key) => {
249
147
  setWorkspaceSyncReferenceSnapshotStatus((current) => (current[key]
250
148
  ? current
@@ -254,7 +152,7 @@ export function useSyncOrchestrator(input) {
254
152
  }));
255
153
  }, []);
256
154
  const workspaceSyncReferenceSnapshotsReady = Object.values(workspaceSyncReferenceSnapshotStatus).every(Boolean);
257
- const { workspaceRetryAt, isWorkspaceRetryPending, workspacePushInFlightRef, workspacePullInFlightRef, workspaceLastPushedSignatureRef, workspacePendingSignatureRef, workspaceLastPushedTaskIdsRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, workspacePullCursorRef, clearWorkspaceRetry, scheduleWorkspaceRetry, persistWorkspaceSyncPatch, applyWorkspaceSyncPatchLocally, loadWorkspaceSyncState, applyWorkspaceSyncStateSnapshot: applyWorkspaceSyncStateSnapshotBase, 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({
258
156
  currentWorkspaceId,
259
157
  setWorkspaceCloudSyncEnabled,
260
158
  setWorkspaceSyncPhase,
@@ -274,12 +172,14 @@ export function useSyncOrchestrator(input) {
274
172
  applyWorkspaceSyncStateSnapshotBase(nextState);
275
173
  }, [applyWorkspaceSyncStateSnapshotBase]);
276
174
  const workspaceLastPushedWatermarksRef = useRef(new Map());
175
+ const workspaceLastPushedTaskChangeKeysRef = useRef(new Map());
277
176
  const workspaceLastPushedInitiativeIdsRef = useRef(new Set());
278
177
  const workspaceLastPushedInitiativeWatermarksRef = useRef(new Map());
279
178
  const workspaceLastPushedWorkstreamIdsRef = useRef(new Set());
280
179
  const workspaceLastPushedWorkstreamWatermarksRef = useRef(new Map());
281
180
  const workspaceLastPushedAiProfileIdsRef = useRef(new Set());
282
181
  const workspaceLastPushedAiProfileWatermarksRef = useRef(new Map());
182
+ const workspaceLastPushedAiProfileChangeKeysRef = useRef(new Map());
283
183
  const workspaceLastPushedDocumentPathsRef = useRef(new Set());
284
184
  const workspaceLastPushedDocumentWatermarksRef = useRef(new Map());
285
185
  const workspaceLastPushedAssetPathsRef = useRef(new Set());
@@ -291,6 +191,48 @@ export function useSyncOrchestrator(input) {
291
191
  const workspaceLastPushedAnnotatedAttachmentSessionIdsRef = useRef(new Set());
292
192
  const workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef = useRef(new Map());
293
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
+ }), []);
294
236
  const workspaceStartupRepairRanRef = useRef(false);
295
237
  const workspaceStartupFullReconcileRanRef = useRef(false);
296
238
  const workspaceRepairModeRef = useRef(false);
@@ -311,36 +253,22 @@ export function useSyncOrchestrator(input) {
311
253
  return workspaceLastPushAt;
312
254
  return null;
313
255
  }, [workspaceLastPullAt, workspaceLastPushAt]);
314
- const workspaceSyncStatus = useMemo(() => {
315
- if (!workspaceCloudSyncEnabled)
316
- return 'off';
317
- if (workspaceSyncBusy || workspaceSyncPhase === 'provision-local' || workspaceSyncPhase === 'attach-cloud') {
318
- return 'syncing';
319
- }
320
- if (workspaceRetryAt || workspaceLastErrorMessage || workspaceSyncPhase === 'error') {
321
- return 'attention';
322
- }
323
- return 'healthy';
324
- }, [
325
- workspaceCloudSyncEnabled,
326
- workspaceSyncBusy,
327
- workspaceSyncPhase,
328
- workspaceRetryAt,
329
- workspaceLastErrorMessage
330
- ]);
331
- 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
+ }), [
332
263
  workspaceCloudSyncEnabled,
333
264
  workspaceSyncPhase,
334
265
  workspaceSyncBusy,
335
266
  workspaceRetryAt,
336
267
  workspaceLastErrorMessage
337
268
  ]);
338
- const workspaceSyncRecommendedAction = useMemo(() => recommendSyncAction(workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceRetryAt, workspaceLastErrorMessage), [
339
- workspaceCloudSyncEnabled,
340
- workspaceSyncPhase,
341
- workspaceRetryAt,
342
- workspaceLastErrorMessage
343
- ]);
269
+ const workspaceSyncStatus = workspaceSyncPresentation.status;
270
+ const workspaceSyncSummary = workspaceSyncPresentation.summary;
271
+ const workspaceSyncRecommendedAction = workspaceSyncPresentation.recommendedAction;
344
272
  const getLocalUserSyncUpdatedAt = useCallback((userId) => {
345
273
  const key = String(userId || '').trim();
346
274
  if (!key)
@@ -531,6 +459,102 @@ export function useSyncOrchestrator(input) {
531
459
  const persistWorkspaceSyncPatchSafely = useCallback((patch) => {
532
460
  void persistWorkspaceSyncPatchBestEffort(patch).catch(() => { });
533
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
+ ]);
534
558
  const scheduleWorkspaceSyncRetry = useCallback((minDelayMs) => {
535
559
  scheduleWorkspaceRetry(() => retryWorkspaceSyncRef.current(), { minDelayMs });
536
560
  }, [scheduleWorkspaceRetry]);
@@ -592,13 +616,16 @@ export function useSyncOrchestrator(input) {
592
616
  workspaceArchivedTasksRef.current = archivedTasks || [];
593
617
  }, [archivedTasks]);
594
618
  useEffect(() => {
619
+ workspaceDeletedTaskIdsRef.current = new Set((deletedTasks || [])
620
+ .map((entry) => String(entry?.taskId || '').trim())
621
+ .filter((taskId) => taskId.length > 0));
595
622
  workspaceDeletedTaskWatermarksRef.current = new Map((deletedTasks || [])
596
623
  .map((entry) => [
597
624
  String(entry?.taskId || '').trim(),
598
625
  String(entry?.deletedAt || '').trim()
599
626
  ])
600
627
  .filter(([taskId, deletedAt]) => taskId.length > 0 && Number.isFinite(Date.parse(deletedAt))));
601
- }, [deletedTasks, workspaceDeletedTaskWatermarksRef]);
628
+ }, [deletedTasks, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef]);
602
629
  useEffect(() => {
603
630
  workspaceInitiativesRef.current = initiatives || [];
604
631
  }, [initiatives]);
@@ -630,8 +657,11 @@ export function useSyncOrchestrator(input) {
630
657
  aiProfiles: workspaceSyncAiProfilesRef.current,
631
658
  lastPushedAiProfileIds: forceAllAiProfiles ? new Set() : workspaceLastPushedAiProfileIdsRef.current,
632
659
  lastPushedAiProfileWatermarks: forceAllAiProfiles ? new Map() : workspaceLastPushedAiProfileWatermarksRef.current,
660
+ lastPushedAiProfileChangeKeys: forceAllAiProfiles ? new Map() : workspaceLastPushedAiProfileChangeKeysRef.current,
633
661
  documents: workspaceSyncMarkdownDocumentsRef.current,
634
662
  assets: workspaceSyncAssetsRef.current,
663
+ currentDocumentPaths: workspaceSyncKnownDocumentPathsRef.current,
664
+ currentAssetPaths: workspaceSyncKnownAssetPathsRef.current,
635
665
  documentReviewSessions: workspaceSyncDocumentReviewSessionsRef.current,
636
666
  documentReviewComments: workspaceSyncDocumentReviewCommentsRef.current,
637
667
  annotatedAttachmentSessions: workspaceSyncAnnotatedAttachmentSessionsRef.current,
@@ -646,25 +676,42 @@ export function useSyncOrchestrator(input) {
646
676
  lastPushedAnnotatedAttachmentSessionIds: workspaceLastPushedAnnotatedAttachmentSessionIdsRef.current,
647
677
  lastPushedAnnotatedAttachmentSessionWatermarks: workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef.current,
648
678
  lastPushedTaskEventWatermarks: workspaceLastPushedTaskEventWatermarksRef.current,
649
- lastPushedWatermarks: workspaceLastPushedWatermarksRef.current
679
+ baselineResetReason: workspaceBaselineResetReasonRef.current,
680
+ lastPushedWatermarks: workspaceLastPushedWatermarksRef.current,
681
+ lastPushedTaskChangeKeys: workspaceLastPushedTaskChangeKeysRef.current
650
682
  });
651
683
  }, [taxonomies, taxonomyState]);
652
684
  const reportSyncEvent = useCallback((workspaceId, event) => {
653
685
  if (runtimeMode !== 'local')
654
686
  return;
655
- fetch('/api/taskforce/sync/events', {
656
- method: 'POST',
657
- credentials: 'include',
658
- headers: { 'Content-Type': 'application/json' },
659
- body: JSON.stringify({ ...event, workspaceId })
660
- }).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) {
661
708
  logSyncDebug('sync_event_post_failed', {
662
709
  workspaceId,
663
710
  eventType: event.eventType,
664
711
  status: event.status,
665
712
  error: String(error?.message || error || '')
666
713
  });
667
- });
714
+ }
668
715
  }, [runtimeMode]);
669
716
  const buildWorkspaceSyncSignature = useCallback(() => {
670
717
  // Read documents and assets from refs for the same stale-closure reason as
@@ -728,9 +775,16 @@ export function useSyncOrchestrator(input) {
728
775
  action: String(event.action || '').trim(),
729
776
  }))
730
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)),
731
785
  taxonomies: Array.isArray(taxonomies) ? taxonomies : [],
732
786
  taxonomyState: taxonomyState && typeof taxonomyState === 'object' ? taxonomyState : null
733
- }), [tasks, archivedTasks, initiatives, workstreams, taxonomies, taxonomyState]);
787
+ }), [tasks, archivedTasks, deletedTasks, initiatives, workstreams, taxonomies, taxonomyState]);
734
788
  // Fingerprint of per-task attachment counts — changes when the MCP tool saves a new
735
789
  // attachment and the React app polls the updated task. Used to trigger an immediate
736
790
  // asset snapshot refresh without requiring a cross-process DOM event.
@@ -797,6 +851,11 @@ export function useSyncOrchestrator(input) {
797
851
  }))
798
852
  .filter((entry) => entry.path.length > 0)
799
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));
800
859
  const fingerprint = typeof data?.fingerprint === 'string'
801
860
  ? data.fingerprint
802
861
  : JSON.stringify(docs.map((entry) => `${entry.path}:${entry.updatedAt}:${entry.content.length}`).sort());
@@ -804,6 +863,7 @@ export function useSyncOrchestrator(input) {
804
863
  return;
805
864
  workspaceSyncMarkdownDocumentsFingerprintRef.current = fingerprint;
806
865
  workspaceSyncMarkdownDocumentsRef.current = docs;
866
+ workspaceSyncKnownDocumentPathsRef.current = knownDocumentPaths;
807
867
  setWorkspaceSyncMarkdownDocuments(docs);
808
868
  setWorkspaceSyncMarkdownDocumentsFingerprint(fingerprint);
809
869
  }
@@ -853,7 +913,16 @@ export function useSyncOrchestrator(input) {
853
913
  setWorkspaceSyncAiProfilesLastFetchAt(new Date().toISOString());
854
914
  const fingerprint = typeof data?.fingerprint === 'string'
855
915
  ? data.fingerprint
856
- : 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());
857
926
  if (fingerprint === workspaceSyncAiProfilesFingerprintRef.current)
858
927
  return;
859
928
  workspaceSyncAiProfilesFingerprintRef.current = fingerprint;
@@ -910,6 +979,11 @@ export function useSyncOrchestrator(input) {
910
979
  }))
911
980
  .filter((entry) => entry.path.length > 0 && entry.contentBase64.length > 0)
912
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));
913
987
  const fingerprint = typeof data?.fingerprint === 'string'
914
988
  ? data.fingerprint
915
989
  : JSON.stringify(assets.map((entry) => `${entry.path}:${entry.updatedAt}:${entry.contentBase64.length}`).sort());
@@ -917,6 +991,7 @@ export function useSyncOrchestrator(input) {
917
991
  return;
918
992
  workspaceSyncAssetsFingerprintRef.current = fingerprint;
919
993
  workspaceSyncAssetsRef.current = assets;
994
+ workspaceSyncKnownAssetPathsRef.current = knownAssetPaths;
920
995
  setWorkspaceSyncAssets(assets);
921
996
  setWorkspaceSyncAssetsFingerprint(fingerprint);
922
997
  }
@@ -1059,6 +1134,38 @@ export function useSyncOrchestrator(input) {
1059
1134
  }
1060
1135
  }, [runtimeMode, currentWorkspaceId, cloudAuthConfigured, authSessionResolved, isAuthenticated, markWorkspaceSyncReferenceSnapshotReady]);
1061
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));
1062
1169
  workspaceLastPushedDocumentPathsRef.current = new Set(workspaceSyncMarkdownDocumentsRef.current
1063
1170
  .map((entry) => String(entry?.path || '').trim())
1064
1171
  .filter((path) => path.length > 0));
@@ -1092,7 +1199,9 @@ export function useSyncOrchestrator(input) {
1092
1199
  workspaceLastPushedTaskEventWatermarksRef.current = new Map(flattenTaskEvents(workspaceTasksRef.current, workspaceArchivedTasksRef.current)
1093
1200
  .map((entry) => [String(entry?.id || '').trim(), String(entry?.createdAt || '').trim()])
1094
1201
  .filter(([id]) => id.length > 0));
1095
- }, []);
1202
+ const persisted = persistWorkspaceSyncWatermarksSnapshotBestEffort();
1203
+ workspaceBaselineResetReasonRef.current = persisted ? null : 'storage-write-failed';
1204
+ }, [persistWorkspaceSyncWatermarksSnapshotBestEffort]);
1096
1205
  const refreshLocalWorkspaceTaskCollections = useCallback(async () => {
1097
1206
  const [tasksRes, archiveRes] = await Promise.all([
1098
1207
  fetch('/api/taskforce/tasks', {
@@ -1163,7 +1272,6 @@ export function useSyncOrchestrator(input) {
1163
1272
  workspaceCloudSyncEnabled,
1164
1273
  currentWorkspaceId,
1165
1274
  workspaceSyncPhase,
1166
- taxonomyStateFingerprint,
1167
1275
  isAuthenticated,
1168
1276
  checkAuthSession,
1169
1277
  resolveCloudAuthUrl,
@@ -1176,6 +1284,7 @@ export function useSyncOrchestrator(input) {
1176
1284
  scheduleWorkspaceSyncRetry,
1177
1285
  persistWorkspaceSyncPatchSafely,
1178
1286
  persistWorkspaceSyncPatchBestEffort,
1287
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1179
1288
  reportSyncEvent,
1180
1289
  acquireWorkspaceSyncLease,
1181
1290
  releaseWorkspaceSyncLease,
@@ -1195,6 +1304,7 @@ export function useSyncOrchestrator(input) {
1195
1304
  workspaceForceFullAiProfilePushRef,
1196
1305
  workspacePushInFlightRef,
1197
1306
  workspacePullInFlightRef,
1307
+ workspacePendingPushReplayRef,
1198
1308
  workspaceLastPushedSignatureRef,
1199
1309
  workspacePendingSignatureRef,
1200
1310
  workspaceLastPushedTaskIdsRef,
@@ -1202,12 +1312,14 @@ export function useSyncOrchestrator(input) {
1202
1312
  workspaceDeletedTaskWatermarksRef,
1203
1313
  workspacePullCursorRef,
1204
1314
  workspaceLastPushedWatermarksRef,
1315
+ workspaceLastPushedTaskChangeKeysRef,
1205
1316
  workspaceLastPushedInitiativeIdsRef,
1206
1317
  workspaceLastPushedInitiativeWatermarksRef,
1207
1318
  workspaceLastPushedWorkstreamIdsRef,
1208
1319
  workspaceLastPushedWorkstreamWatermarksRef,
1209
1320
  workspaceLastPushedAiProfileIdsRef,
1210
1321
  workspaceLastPushedAiProfileWatermarksRef,
1322
+ workspaceLastPushedAiProfileChangeKeysRef,
1211
1323
  workspaceLastPushedDocumentPathsRef,
1212
1324
  workspaceLastPushedDocumentWatermarksRef,
1213
1325
  workspaceLastPushedAssetPathsRef,
@@ -1219,6 +1331,7 @@ export function useSyncOrchestrator(input) {
1219
1331
  workspaceLastPushedAnnotatedAttachmentSessionIdsRef,
1220
1332
  workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef,
1221
1333
  workspaceLastPushedTaskEventWatermarksRef,
1334
+ workspaceBaselineResetReasonRef,
1222
1335
  workspaceCaptureAttachBootstrapBaselineRef,
1223
1336
  workspaceStartupFullReconcileRanRef,
1224
1337
  workspaceFullReconcileInFlightRef,
@@ -1252,6 +1365,7 @@ export function useSyncOrchestrator(input) {
1252
1365
  scheduleWorkspaceSyncRetry,
1253
1366
  persistWorkspaceSyncPatchSafely,
1254
1367
  persistWorkspaceSyncPatchBestEffort,
1368
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1255
1369
  reportSyncEvent,
1256
1370
  acquireWorkspaceSyncLease,
1257
1371
  releaseWorkspaceSyncLease,
@@ -1339,7 +1453,6 @@ export function useSyncOrchestrator(input) {
1339
1453
  }, [syncUserGlobalSettings]);
1340
1454
  const recoveryCoordinator = useMemo(() => createSyncRecoveryCoordinator({
1341
1455
  currentWorkspaceId,
1342
- taxonomyStateFingerprint,
1343
1456
  workspaceCloudSyncEnabled,
1344
1457
  workspaceSyncPhase,
1345
1458
  workspaceLastPullAt,
@@ -1355,12 +1468,14 @@ export function useSyncOrchestrator(input) {
1355
1468
  workspacePendingSignatureRef,
1356
1469
  workspaceLastPushedSignatureRef,
1357
1470
  workspaceLastPushedWatermarksRef,
1471
+ workspaceLastPushedTaskChangeKeysRef,
1358
1472
  workspaceLastPushedInitiativeIdsRef,
1359
1473
  workspaceLastPushedInitiativeWatermarksRef,
1360
1474
  workspaceLastPushedWorkstreamIdsRef,
1361
1475
  workspaceLastPushedWorkstreamWatermarksRef,
1362
1476
  workspaceLastPushedAiProfileIdsRef,
1363
1477
  workspaceLastPushedAiProfileWatermarksRef,
1478
+ workspaceLastPushedAiProfileChangeKeysRef,
1364
1479
  workspaceLastPushedDocumentPathsRef,
1365
1480
  workspaceLastPushedDocumentWatermarksRef,
1366
1481
  workspaceLastPushedAssetPathsRef,
@@ -1372,6 +1487,7 @@ export function useSyncOrchestrator(input) {
1372
1487
  workspaceLastPushedAnnotatedAttachmentSessionIdsRef,
1373
1488
  workspaceLastPushedAnnotatedAttachmentSessionWatermarksRef,
1374
1489
  workspaceLastPushedTaskEventWatermarksRef,
1490
+ workspaceBaselineResetReasonRef,
1375
1491
  bootstrapPhaseStartedAtRef,
1376
1492
  bootstrapLastProgressAtRef,
1377
1493
  acquireWorkspaceSyncLease,
@@ -1380,7 +1496,9 @@ export function useSyncOrchestrator(input) {
1380
1496
  clearWorkspaceRetry,
1381
1497
  clearWorkspaceIssue,
1382
1498
  reportWorkspaceWindowContention,
1499
+ reportSyncEvent,
1383
1500
  persistWorkspaceSyncPatch,
1501
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1384
1502
  buildWorkspaceSyncSignature,
1385
1503
  pullWorkspaceChangesFromCloud,
1386
1504
  pushWorkspaceChangesToCloud,
@@ -1400,7 +1518,9 @@ export function useSyncOrchestrator(input) {
1400
1518
  clearWorkspaceRetry,
1401
1519
  clearWorkspaceIssue,
1402
1520
  reportWorkspaceWindowContention,
1521
+ reportSyncEvent,
1403
1522
  persistWorkspaceSyncPatch,
1523
+ persistWorkspaceSyncWatermarksSnapshotBestEffort,
1404
1524
  buildWorkspaceSyncSignature,
1405
1525
  pullWorkspaceChangesFromCloud,
1406
1526
  pushWorkspaceChangesToCloud,
@@ -1412,6 +1532,39 @@ export function useSyncOrchestrator(input) {
1412
1532
  const { retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull } = recoveryCoordinator;
1413
1533
  // === SECTION: Diagnostics & reactive effects ===
1414
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
+ });
1415
1568
  return {
1416
1569
  workspaceId: currentWorkspaceId,
1417
1570
  enabled: workspaceCloudSyncEnabled,
@@ -1425,6 +1578,13 @@ export function useSyncOrchestrator(input) {
1425
1578
  pendingChanges: workspaceSyncPendingChanges,
1426
1579
  lastErrorMessage: workspaceLastErrorMessage,
1427
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,
1428
1588
  documentSnapshotCount: workspaceSyncMarkdownDocuments.length,
1429
1589
  aiProfileSnapshotCount: workspaceSyncAiProfiles.length,
1430
1590
  aiProfileSnapshotRawCount: workspaceSyncAiProfilesRawCount,
@@ -1441,9 +1601,12 @@ export function useSyncOrchestrator(input) {
1441
1601
  forceFullAiProfilePushQueued: workspaceForceFullAiProfilePushRef.current === true
1442
1602
  };
1443
1603
  }, [
1604
+ cloudAuthConfigured,
1444
1605
  currentWorkspaceId,
1606
+ runtimeMode,
1445
1607
  workspaceCloudSyncEnabled,
1446
1608
  workspaceSyncPhase,
1609
+ isAuthenticated,
1447
1610
  workspaceSyncStatus,
1448
1611
  workspaceSyncSummary,
1449
1612
  workspaceLastSuccessfulSyncAt,
@@ -1463,7 +1626,8 @@ export function useSyncOrchestrator(input) {
1463
1626
  workspaceSyncDocumentReviewSessions.length,
1464
1627
  workspaceSyncDocumentReviewSessionsFingerprint,
1465
1628
  workspaceSyncAnnotatedAttachmentSessions.length,
1466
- workspaceSyncAnnotatedAttachmentSessionsFingerprint
1629
+ workspaceSyncAnnotatedAttachmentSessionsFingerprint,
1630
+ isWorkspaceRetryPending
1467
1631
  ]);
1468
1632
  useEffect(() => {
1469
1633
  retryWorkspaceSyncRef.current = retryWorkspaceCloudSync;
@@ -1545,11 +1709,14 @@ export function useSyncOrchestrator(input) {
1545
1709
  refreshWorkspaceSyncContextAssetSnapshots();
1546
1710
  }, [cloudAuthConfigured, runtimeMode, workspaceCloudSyncEnabled, authSessionResolved, isAuthenticated, taskAttachmentsFingerprint, refreshWorkspaceSyncContextAssetSnapshots]);
1547
1711
  useEffect(() => {
1548
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
1549
- return;
1550
- if (workspaceSyncPhase === 'attach-cloud')
1551
- return;
1552
- if (!workspaceSyncReferenceSnapshotsReady)
1712
+ if (!shouldScheduleWorkspaceReactivePush({
1713
+ cloudAuthConfigured,
1714
+ runtimeMode,
1715
+ workspaceCloudSyncEnabled,
1716
+ authSessionResolved,
1717
+ workspaceSyncPhase,
1718
+ workspaceSyncReferenceSnapshotsReady
1719
+ }))
1553
1720
  return;
1554
1721
  const signature = buildWorkspaceSyncSignature();
1555
1722
  if (shouldSuppressAttachBootstrapPush(signature))
@@ -1587,11 +1754,14 @@ export function useSyncOrchestrator(input) {
1587
1754
  workspacePullInFlightRef
1588
1755
  ]);
1589
1756
  useEffect(() => {
1590
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
1591
- return;
1592
- if (workspaceSyncPhase !== 'active')
1593
- return;
1594
- if (!workspaceSyncReferenceSnapshotsReady)
1757
+ if (!shouldStartWorkspacePushWatchdog({
1758
+ cloudAuthConfigured,
1759
+ runtimeMode,
1760
+ workspaceCloudSyncEnabled,
1761
+ authSessionResolved,
1762
+ workspaceSyncPhase,
1763
+ workspaceSyncReferenceSnapshotsReady
1764
+ }))
1595
1765
  return;
1596
1766
  const intervalId = window.setInterval(() => {
1597
1767
  if (workspacePushInFlightRef.current || workspacePullInFlightRef.current)
@@ -1633,14 +1803,18 @@ export function useSyncOrchestrator(input) {
1633
1803
  clearWorkspaceRetry
1634
1804
  ]);
1635
1805
  useEffect(() => {
1636
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved)
1637
- return;
1638
- if (workspaceSyncPhase === 'provision-local' || workspaceSyncPhase === 'error')
1639
- return;
1640
- 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)
1641
1816
  return;
1642
- const shouldKickOffPull = workspaceSyncPhase === 'attach-cloud' || !workspaceLastPullAt;
1643
- if (shouldKickOffPull) {
1817
+ if (pullPolicy.shouldKickOffImmediately) {
1644
1818
  void pullWorkspaceChangesFromCloud();
1645
1819
  }
1646
1820
  const intervalId = window.setInterval(() => {
@@ -1661,9 +1835,14 @@ export function useSyncOrchestrator(input) {
1661
1835
  workspacePullIntervalMs
1662
1836
  ]);
1663
1837
  useEffect(() => {
1664
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled || !authSessionResolved || !isAuthenticated)
1665
- return;
1666
- if (workspaceSyncPhase !== 'active')
1838
+ if (!shouldStartWorkspacePeriodicFullReconcile({
1839
+ cloudAuthConfigured,
1840
+ runtimeMode,
1841
+ workspaceCloudSyncEnabled,
1842
+ authSessionResolved,
1843
+ isAuthenticated,
1844
+ workspaceSyncPhase
1845
+ }))
1667
1846
  return;
1668
1847
  const intervalId = window.setInterval(() => {
1669
1848
  if (workspaceFullReconcileInFlightRef.current)
@@ -1726,13 +1905,15 @@ export function useSyncOrchestrator(input) {
1726
1905
  // missing or stale-tombstoned metadata — a common result of a crash between file write
1727
1906
  // and DB upsert. The repair only adds missing records; it never deletes files.
1728
1907
  useEffect(() => {
1729
- if (!cloudAuthConfigured || runtimeMode !== 'local' || !workspaceCloudSyncEnabled)
1730
- return;
1731
- if (!authSessionResolved || !isAuthenticated)
1732
- return;
1733
- if (workspaceSyncPhase === 'provision-local' || workspaceSyncPhase === 'attach-cloud')
1734
- return;
1735
- if (workspaceStartupRepairRanRef.current)
1908
+ if (!shouldRunWorkspaceStartupRepair({
1909
+ cloudAuthConfigured,
1910
+ runtimeMode,
1911
+ workspaceCloudSyncEnabled,
1912
+ authSessionResolved,
1913
+ isAuthenticated,
1914
+ workspaceSyncPhase,
1915
+ alreadyRan: workspaceStartupRepairRanRef.current
1916
+ }))
1736
1917
  return;
1737
1918
  workspaceStartupRepairRanRef.current = true;
1738
1919
  fetch('/api/taskforce/sync/workspace/repair-startup', {