@taskforcehq/taskforce 0.3.305 → 0.3.307

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 (440) hide show
  1. package/README.md +0 -2
  2. package/dist/Taskforce.module.css +603 -107
  3. package/dist/TaskforceCore.js +216 -43
  4. package/dist/TaskforceCore.test.js +611 -28
  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 +46 -2
  14. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  15. package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
  16. package/dist/components/activity/EntityActivityTimeline.js +81 -0
  17. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  18. package/dist/components/context/ContextAttachmentManager.js +561 -0
  19. package/dist/components/features/AgentsModule.d.ts +14 -1
  20. package/dist/components/features/AgentsModule.js +366 -18
  21. package/dist/components/features/AgentsModule.test.js +507 -8
  22. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
  23. package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
  24. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
  25. package/dist/components/features/DocumentIndex.d.ts +2 -2
  26. package/dist/components/features/DocumentIndex.js +3 -4
  27. package/dist/components/features/DocumentViewer.d.ts +7 -1
  28. package/dist/components/features/DocumentViewer.js +62 -4
  29. package/dist/components/features/DocumentWorkspace.d.ts +10 -2
  30. package/dist/components/features/DocumentWorkspace.js +15 -4
  31. package/dist/components/features/DocumentWorkspace.test.js +51 -1
  32. package/dist/components/features/TaskSettings.d.ts +2 -0
  33. package/dist/components/features/TaskSettings.js +83 -78
  34. package/dist/components/features/TaskSettings.test.js +171 -82
  35. package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
  36. package/dist/components/task/TaskActionHeader.js +11 -5
  37. package/dist/components/task/TaskActionHeader.test.js +10 -0
  38. package/dist/components/task/TaskCard.js +24 -29
  39. package/dist/components/task/TaskCard.test.js +38 -3
  40. package/dist/components/task/TaskContextUpload.js +4 -526
  41. package/dist/components/task/TaskContextUpload.test.js +3 -3
  42. package/dist/components/task/TaskForm.js +61 -116
  43. package/dist/components/task/TaskForm.test.js +110 -8
  44. package/dist/components/task/TaskKanban.d.ts +2 -1
  45. package/dist/components/task/TaskKanban.js +94 -21
  46. package/dist/components/task/TaskKanban.test.js +36 -0
  47. package/dist/components/task/TaskList.js +5 -1
  48. package/dist/components/task/TaskList.test.js +4 -0
  49. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  50. package/dist/components/ui/EditableAvatarButton.js +18 -0
  51. package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
  52. package/dist/components/ui/HierarchyActionBadge.js +5 -0
  53. package/dist/components/views/AppShellHeader.d.ts +3 -1
  54. package/dist/components/views/AppShellHeader.js +27 -11
  55. package/dist/components/views/AppShellHeader.test.js +18 -1
  56. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  57. package/dist/components/views/PlanComparisonPage.js +78 -14
  58. package/dist/components/views/PlanComparisonPage.test.js +257 -0
  59. package/dist/components/views/PlansPage.js +127 -36
  60. package/dist/components/views/PlansPage.test.js +294 -12
  61. package/dist/components/views/StandaloneLayout.js +529 -592
  62. package/dist/components/views/WidgetView.js +10 -8
  63. package/dist/components/views/panels/PlanningDrawer.d.ts +25 -5
  64. package/dist/components/views/panels/PlanningDrawer.js +270 -55
  65. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  66. package/dist/components/views/panels/PlanningDrawer.test.js +529 -0
  67. package/dist/components/views/planningScope.d.ts +20 -0
  68. package/dist/components/views/planningScope.js +25 -0
  69. package/dist/components/views/planningScope.test.d.ts +1 -0
  70. package/dist/components/views/planningScope.test.js +60 -0
  71. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +26 -1
  72. package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
  73. package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
  74. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  75. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  76. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  77. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  78. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  79. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  80. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  81. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  82. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  83. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  84. package/dist/config/envSchema.js +1 -0
  85. package/dist/core/AiProfileService.d.ts +46 -2
  86. package/dist/core/AiProfileService.js +1073 -82
  87. package/dist/core/AiProfileService.test.js +19 -1
  88. package/dist/core/AiProfiles.test.js +1249 -44
  89. package/dist/core/AttachmentLinkService.d.ts +3 -0
  90. package/dist/core/AttachmentLinkService.js +110 -23
  91. package/dist/core/AuthIdentityService.d.ts +107 -0
  92. package/dist/core/AuthIdentityService.js +284 -0
  93. package/dist/core/AuthTokenService.d.ts +4 -0
  94. package/dist/core/AuthTokenService.js +34 -6
  95. package/dist/core/AuthTokenService.test.js +3 -3
  96. package/dist/core/EntitlementsPolicy.test.js +2 -2
  97. package/dist/core/GlobalSettingsService.js +127 -6
  98. package/dist/core/McpTokenService.d.ts +12 -0
  99. package/dist/core/McpTokenService.js +44 -1
  100. package/dist/core/PlanEntitlementService.d.ts +23 -2
  101. package/dist/core/PlanEntitlementService.js +343 -57
  102. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  103. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  104. package/dist/core/PlanVersionPolicy.test.js +35 -2
  105. package/dist/core/PlanningEntities.test.js +418 -4
  106. package/dist/core/SignupMonetizationSettings.test.js +199 -27
  107. package/dist/core/SyncReconciliationService.js +2 -65
  108. package/dist/core/SystemAdmin.test.js +42 -42
  109. package/dist/core/TaskActivityService.d.ts +28 -1
  110. package/dist/core/TaskActivityService.js +227 -46
  111. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  112. package/dist/core/TaskActivitySync.test.js +90 -0
  113. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  114. package/dist/core/TaskAuditTimeline.test.js +96 -0
  115. package/dist/core/Taskforce.d.ts +415 -40
  116. package/dist/core/Taskforce.ids.test.js +58 -0
  117. package/dist/core/Taskforce.js +1543 -266
  118. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  119. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  120. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  121. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  122. package/dist/core/WorkspaceLifecycleService.js +0 -5
  123. package/dist/core/WorkspacePermissions.test.js +25 -1
  124. package/dist/core/WorkspacePlanMode.test.js +117 -16
  125. package/dist/core/shared.d.ts +1 -1
  126. package/dist/core/shared.js +5 -1
  127. package/dist/core/types.d.ts +75 -2
  128. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +28 -2
  129. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  130. package/dist/hooks/bootstrapTimeouts.js +8 -0
  131. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  132. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  133. package/dist/hooks/sync/bootstrap.js +2 -0
  134. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  135. package/dist/hooks/sync/controlPlane.js +78 -0
  136. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  137. package/dist/hooks/sync/controlPlane.test.js +121 -0
  138. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  139. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  140. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  141. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  142. package/dist/hooks/sync/orchestratorShared.d.ts +13 -4
  143. package/dist/hooks/sync/orchestratorShared.js +129 -48
  144. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  145. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  146. package/dist/hooks/sync/pullLifecycle.js +24 -0
  147. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  148. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  149. package/dist/hooks/sync/recovery.d.ts +16 -2
  150. package/dist/hooks/sync/recovery.js +179 -53
  151. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  152. package/dist/hooks/sync/recovery.test.js +292 -0
  153. package/dist/hooks/sync/transfers.d.ts +16 -3
  154. package/dist/hooks/sync/transfers.js +210 -66
  155. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  156. package/dist/hooks/sync/transfers.test.js +928 -0
  157. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  158. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  159. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  160. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  161. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  162. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  163. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  164. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  165. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  166. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  167. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  168. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  169. package/dist/hooks/useFilterSort.js +13 -13
  170. package/dist/hooks/useFilterSort.test.js +87 -1
  171. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  172. package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
  173. package/dist/hooks/useSyncOrchestrator.js +409 -183
  174. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
  175. package/dist/hooks/useTaskData.js +8 -3
  176. package/dist/hooks/useTaskData.test.js +45 -0
  177. package/dist/hooks/useTaskforce.d.ts +145 -52
  178. package/dist/hooks/useTaskforce.js +592 -253
  179. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  180. package/dist/hooks/useTaskforce.runtime-routing.test.js +246 -0
  181. package/dist/hooks/useTaskforce.sync-behavior.test.js +2468 -201
  182. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  183. package/dist/hooks/useWorkspaceSyncController.js +13 -2
  184. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  185. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  186. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +328 -40
  187. package/dist/localization/locales/en-US.d.ts +0 -1
  188. package/dist/localization/locales/en-US.js +0 -1
  189. package/dist/localization/locales/es-419.js +0 -1
  190. package/dist/localization/locales/pt-BR.js +0 -1
  191. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  192. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  193. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  194. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  195. package/dist/mcp/clientIntegrations.d.ts +1 -0
  196. package/dist/mcp/clientIntegrations.js +25 -0
  197. package/dist/mcp/clientIntegrations.test.js +14 -3
  198. package/dist/mcp/clientProfileDefaults.js +3 -3
  199. package/dist/mcp/collaborationRegistrar.js +7 -7
  200. package/dist/mcp/documentAssetRegistrar.js +116 -10
  201. package/dist/mcp/runtime.d.ts +3 -0
  202. package/dist/mcp/runtime.js +1099 -185
  203. package/dist/mcp/runtime.test.js +1491 -140
  204. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  205. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  206. package/dist/mcp/toolRegistry.d.ts +56 -0
  207. package/dist/mcp/toolRegistry.js +8 -0
  208. package/dist/mcp/toolRegistry.test.js +4 -0
  209. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  210. package/dist/migrations/taskSchemaMigrations.js +192 -61
  211. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  212. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  213. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  214. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  215. package/dist/resources/templates/workflows/plan.yaml +1 -1
  216. package/dist/resources/templates/workflows/review.yaml +2 -2
  217. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  218. package/dist/server/aiProfileAvatarAssets.js +319 -0
  219. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  220. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  221. package/dist/server/annotatedAttachmentsRoutes.test.js +69 -10
  222. package/dist/server/auth/providers/apple.d.ts +33 -0
  223. package/dist/server/auth/providers/apple.js +82 -0
  224. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  225. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  226. package/dist/server/auth/providers/github.d.ts +26 -0
  227. package/dist/server/auth/providers/github.js +86 -0
  228. package/dist/server/auth/providers/google.d.ts +21 -0
  229. package/dist/server/auth/providers/google.js +56 -0
  230. package/dist/server/auth/providers/types.d.ts +21 -0
  231. package/dist/server/auth/providers/types.js +1 -0
  232. package/dist/server/auth.d.ts +2 -0
  233. package/dist/server/auth.js +6 -3
  234. package/dist/server/documentReviewRoutes.test.js +36 -3
  235. package/dist/server/index.cookieProxy.test.js +1 -0
  236. package/dist/server/index.d.ts +12 -0
  237. package/dist/server/index.js +124 -9
  238. package/dist/server/index.rateLimit.test.js +3 -0
  239. package/dist/server/index.test.js +111 -3
  240. package/dist/server/routes/admin.d.ts +3 -0
  241. package/dist/server/routes/admin.js +459 -28
  242. package/dist/server/routes/annotatedAttachments.js +51 -0
  243. package/dist/server/routes/auth.d.ts +26 -2
  244. package/dist/server/routes/auth.js +1170 -71
  245. package/dist/server/routes/authSupport.d.ts +5 -1
  246. package/dist/server/routes/authSupport.js +13 -2
  247. package/dist/server/routes/billing.js +372 -35
  248. package/dist/server/routes/billing.test.js +595 -12
  249. package/dist/server/routes/documents.d.ts +1 -0
  250. package/dist/server/routes/documents.js +182 -70
  251. package/dist/server/routes/shared.d.ts +28 -2
  252. package/dist/server/routes/shared.js +69 -14
  253. package/dist/server/routes/sync.integration.test.js +658 -39
  254. package/dist/server/routes/sync.js +74 -608
  255. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  256. package/dist/server/routes/syncAuxRoutes.js +91 -0
  257. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  258. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  259. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  260. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  261. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  262. package/dist/server/routes/syncPushRoutes.js +212 -0
  263. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  264. package/dist/server/routes/syncRouteGuards.js +67 -0
  265. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  266. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  267. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  268. package/dist/server/routes/syncRouteTypes.js +1 -0
  269. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  270. package/dist/server/routes/syncSnapshotRoutes.js +182 -0
  271. package/dist/server/routes/tasks.js +55 -10
  272. package/dist/server/routes/workspaces.js +36 -5
  273. package/dist/server/routes.js +147 -11
  274. package/dist/server/routes.test.js +2704 -183
  275. package/dist/services/workflowExportSnapshots.test.js +2 -0
  276. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  277. package/dist/shared/aiProfileSeatScope.js +21 -0
  278. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  279. package/dist/shared/aiProfileSurfaceType.js +10 -8
  280. package/dist/shared/passwordPolicy.d.ts +13 -0
  281. package/dist/shared/passwordPolicy.js +84 -0
  282. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  283. package/dist/shared/passwordPolicy.test.js +34 -0
  284. package/dist/storage/objectStorage.d.ts +1 -0
  285. package/dist/storage/objectStorage.js +2 -1
  286. package/dist/storage/objectStorageClient.d.ts +1 -0
  287. package/dist/storage/objectStorageClient.js +41 -8
  288. package/dist/storage/objectStorageClient.test.js +26 -1
  289. package/dist/storage/objectStorageConfig.js +3 -1
  290. package/dist/storage/objectStorageConfig.test.js +13 -1
  291. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  292. package/dist/storage/workspaceAssetStore.js +132 -44
  293. package/dist/storage/workspaceAssetStore.test.js +93 -6
  294. package/dist/styles/fonts.css +70 -0
  295. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  296. package/dist/sync/collaborationSyncPayload.js +19 -11
  297. package/dist/sync/collaborationSyncPayload.test.js +14 -1
  298. package/dist/sync/collaborationSyncState.d.ts +3 -3
  299. package/dist/sync/collaborationSyncState.js +19 -12
  300. package/dist/sync/contentSyncPayload.d.ts +5 -1
  301. package/dist/sync/contentSyncPayload.js +9 -0
  302. package/dist/sync/contentSyncPayload.test.js +39 -0
  303. package/dist/sync/contentSyncState.d.ts +9 -9
  304. package/dist/sync/contentSyncState.js +248 -107
  305. package/dist/sync/planningSyncPayload.js +5 -0
  306. package/dist/sync/planningSyncPayload.test.js +24 -0
  307. package/dist/sync/syncApplyHandlers.d.ts +11 -4
  308. package/dist/sync/syncApplyHandlers.js +115 -42
  309. package/dist/sync/syncApplyHandlers.test.js +251 -4
  310. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  311. package/dist/sync/syncFieldSemantics.js +55 -0
  312. package/dist/sync/syncResourceAdapters.d.ts +46 -0
  313. package/dist/sync/syncResourceAdapters.js +79 -0
  314. package/dist/sync/syncService.d.ts +6 -0
  315. package/dist/sync/syncService.js +65 -1
  316. package/dist/sync/syncService.test.js +96 -3
  317. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  318. package/dist/sync/taskSyncChangeKey.js +143 -0
  319. package/dist/sync/taskSyncPayload.js +3 -8
  320. package/dist/sync/workspacePullFeed.d.ts +4 -0
  321. package/dist/sync/workspacePullFeed.js +132 -72
  322. package/dist/sync/workspacePullFeed.test.js +92 -0
  323. package/dist/sync/workspaceRepair.js +5 -2
  324. package/dist/sync/workspaceSyncModel.d.ts +73 -6
  325. package/dist/sync/workspaceSyncModel.js +342 -73
  326. package/dist/sync/workspaceSyncModel.test.js +559 -14
  327. package/dist/sync/workspaceSyncState.d.ts +17 -0
  328. package/dist/sync/workspaceSyncState.js +2 -1
  329. package/dist/sync/workspaceSyncSurface.js +3 -8
  330. package/dist/types.d.ts +50 -2
  331. package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
  332. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
  333. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  334. package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
  335. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  336. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  337. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  338. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  339. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  340. package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
  341. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  342. package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
  343. package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-D4XseTwW.js} +1 -1
  344. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  345. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  346. package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
  347. package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
  348. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  349. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  350. package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
  351. package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
  352. package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-oOA9bcdK.js} +4 -4
  353. package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
  354. package/dist/ui/assets/index-607WPo_X.js +4 -0
  355. package/dist/ui/assets/index-CYyodXsg.css +1 -0
  356. package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-B32hGuF2.js} +1 -1
  357. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  358. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  359. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  360. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  361. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  362. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  363. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  364. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  365. package/dist/ui/fonts/Noir_medium.otf +0 -0
  366. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  367. package/dist/ui/fonts/Noir_regular.otf +0 -0
  368. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  369. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  370. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  371. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  372. package/dist/ui/index.html +3 -3
  373. package/dist/ui/product/agent manager.png +0 -0
  374. package/dist/ui/product/agent_manager_02.png +0 -0
  375. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  376. package/dist/utils/aiProfileEvents.d.ts +7 -0
  377. package/dist/utils/aiProfileEvents.js +6 -0
  378. package/dist/utils/assignees.d.ts +8 -0
  379. package/dist/utils/assignees.js +10 -1
  380. package/dist/utils/assignees.test.d.ts +1 -0
  381. package/dist/utils/assignees.test.js +25 -0
  382. package/dist/utils/avatarDisplay.d.ts +1 -0
  383. package/dist/utils/avatarDisplay.js +15 -0
  384. package/dist/utils/avatarUpload.d.ts +27 -0
  385. package/dist/utils/avatarUpload.js +132 -0
  386. package/dist/utils/avatarUpload.test.d.ts +1 -0
  387. package/dist/utils/avatarUpload.test.js +40 -0
  388. package/dist/utils/bootstrapTrace.d.ts +7 -0
  389. package/dist/utils/bootstrapTrace.js +29 -0
  390. package/dist/utils/commercialLifecyclePresentation.js +3 -0
  391. package/dist/utils/contextAssetEvents.d.ts +2 -0
  392. package/dist/utils/contextFiles.d.ts +3 -1
  393. package/dist/utils/contextFiles.js +2 -1
  394. package/dist/utils/syncEventDetails.d.ts +3 -0
  395. package/dist/utils/syncEventDetails.js +64 -0
  396. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  397. package/dist/utils/syncEventDetails.test.js +23 -0
  398. package/dist/utils/syncEventPresentation.d.ts +15 -0
  399. package/dist/utils/syncEventPresentation.js +129 -0
  400. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  401. package/dist/utils/syncEventPresentation.test.js +64 -0
  402. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  403. package/dist/utils/syncStatusPresentation.js +217 -0
  404. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  405. package/dist/utils/syncStatusPresentation.test.js +164 -0
  406. package/dist/utils/taskActivity.d.ts +9 -1
  407. package/dist/utils/taskActivity.js +290 -22
  408. package/dist/utils/taskActivity.test.js +141 -8
  409. package/dist/utils/taskSearch.d.ts +22 -0
  410. package/dist/utils/taskSearch.js +87 -0
  411. package/dist/utils/taskSearch.test.d.ts +1 -0
  412. package/dist/utils/taskSearch.test.js +69 -0
  413. package/dist/utils/taskUtils.js +10 -3
  414. package/dist/utils/taskUtils.test.d.ts +1 -0
  415. package/dist/utils/taskUtils.test.js +19 -0
  416. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  417. package/dist/utils/workspaceSyncPresentation.js +68 -2
  418. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  419. package/package.json +3 -2
  420. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  421. package/scripts/run-e2e-realtime.sh +17 -3
  422. package/scripts/run-smoke.sh +17 -5
  423. package/scripts/run-soak.sh +17 -5
  424. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  425. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  426. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  427. package/src/resources/templates/workflows/plan.yaml +1 -1
  428. package/src/resources/templates/workflows/review.yaml +2 -2
  429. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +0 -1
  430. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BG6P_GVW.js +0 -3
  431. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  432. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
  433. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
  434. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  435. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
  436. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  437. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +0 -9
  438. package/dist/ui/assets/index-C2-OXZV7.css +0 -1
  439. package/dist/ui/assets/index-DcSI5F86.js +0 -6
  440. package/scripts/migrate-planning-model.ts +0 -233
@@ -1,8 +1,17 @@
1
1
  import { parseAiProfileSurfaceType } from '../shared/aiProfileSurfaceType.js';
2
+ import { parseAiProfileSeatScope } from '../shared/aiProfileSeatScope.js';
2
3
  import { serializeTaskForSync } from './taskSyncPayload.js';
4
+ import { aiProfileSyncResourceAdapter, taskSyncResourceAdapter, shouldSyncResource, } from './syncResourceAdapters.js';
5
+ import { normalizeSyncSameOriginAssetUrl } from './syncFieldSemantics.js';
3
6
  export function toWorkspaceSyncTaskSnapshot(task) {
4
7
  return serializeTaskForSync(task);
5
8
  }
9
+ function normalizeWorkspaceSyncPathSet(value, fallback) {
10
+ const source = value ?? fallback;
11
+ return new Set(Array.from(source)
12
+ .map((entry) => String(entry || '').trim())
13
+ .filter((entry) => entry.length > 0));
14
+ }
6
15
  function sortTaskEventsForSync(taskEvents) {
7
16
  return [...taskEvents].sort((left, right) => {
8
17
  const leftCreatedAt = String(left?.createdAt || '').trim();
@@ -20,6 +29,9 @@ function sortTasksForSync(tasks) {
20
29
  return leftId.localeCompare(rightId);
21
30
  });
22
31
  }
32
+ function clonePlanningAttachmentsForSync(value) {
33
+ return Array.isArray(value) ? value : undefined;
34
+ }
23
35
  export function buildWorkspaceSyncPayloadModel(input) {
24
36
  const payloadBuiltAt = new Date().toISOString();
25
37
  const pendingDeleteIds = new Set(Array.from(input.pendingDeletedTaskIds)
@@ -29,49 +41,62 @@ export function buildWorkspaceSyncPayloadModel(input) {
29
41
  .map(([taskId, deletedAt]) => [String(taskId || '').trim(), String(deletedAt || '').trim()])
30
42
  .filter(([taskId, deletedAt]) => taskId.length > 0 && Number.isFinite(Date.parse(deletedAt))));
31
43
  const lastWatermarks = input.lastPushedWatermarks;
32
- const isDirty = (task) => {
44
+ const lastTaskChangeKeys = input.lastPushedTaskChangeKeys;
45
+ const isDirty = (task, archived) => {
33
46
  if ((task?.referenceNumber === undefined || task?.referenceNumber === null) && Number.isFinite(Number(task?.localReferenceNumber))) {
34
47
  return true;
35
48
  }
36
- if (!lastWatermarks || lastWatermarks.size === 0)
37
- return true;
38
- const taskId = String(task?.id || '').trim();
39
- if (!taskId)
40
- return false;
41
- const currentWatermark = String(task?.updatedAt || task?.createdAt || '').trim();
42
- return lastWatermarks.get(taskId) !== currentWatermark;
49
+ return shouldSyncResource(taskSyncResourceAdapter, { ...task, isArchived: archived }, {
50
+ watermarks: lastWatermarks,
51
+ changeKeys: lastTaskChangeKeys,
52
+ });
43
53
  };
44
54
  const activeTasksOrdered = sortTasksForSync(input.tasks || []);
45
55
  const archivedTasksOrdered = sortTasksForSync(input.archivedTasks || []);
56
+ const currentTaskWatermarks = new Map();
57
+ const currentTaskChangeKeys = new Map();
46
58
  const pushedWatermarks = new Map();
59
+ const pushedTaskChangeKeys = new Map();
47
60
  const activeChanges = activeTasksOrdered
48
- .filter((task) => !pendingDeleteIds.has(String(task?.id || '').trim()) && isDirty(task))
61
+ .filter((task) => !pendingDeleteIds.has(String(task?.id || '').trim()) && isDirty(task, false))
49
62
  .map((task) => {
50
63
  const taskId = String(task?.id || '').trim();
51
- const wm = String(task?.updatedAt || task?.createdAt || '').trim();
52
- if (taskId)
64
+ const wm = taskSyncResourceAdapter.getWatermark(task);
65
+ const changeKey = taskSyncResourceAdapter.getChangeKey({ ...task, isArchived: false });
66
+ if (taskId) {
53
67
  pushedWatermarks.set(taskId, wm);
68
+ pushedTaskChangeKeys.set(taskId, changeKey);
69
+ }
54
70
  return { op: 'upsert', archived: false, task: toWorkspaceSyncTaskSnapshot(task) };
55
71
  });
56
72
  const archivedChanges = archivedTasksOrdered
57
- .filter((task) => !pendingDeleteIds.has(String(task?.id || '').trim()) && isDirty(task))
73
+ .filter((task) => !pendingDeleteIds.has(String(task?.id || '').trim()) && isDirty(task, true))
58
74
  .map((task) => {
59
75
  const taskId = String(task?.id || '').trim();
60
- const wm = String(task?.updatedAt || task?.createdAt || '').trim();
61
- if (taskId)
76
+ const wm = taskSyncResourceAdapter.getWatermark(task);
77
+ const changeKey = taskSyncResourceAdapter.getChangeKey({ ...task, isArchived: true });
78
+ if (taskId) {
62
79
  pushedWatermarks.set(taskId, wm);
80
+ pushedTaskChangeKeys.set(taskId, changeKey);
81
+ }
63
82
  return { op: 'upsert', archived: true, task: toWorkspaceSyncTaskSnapshot(task) };
64
83
  });
65
84
  const currentTaskIds = new Set();
66
85
  for (const task of input.tasks || []) {
67
86
  const taskId = String(task?.id || '').trim();
68
- if (taskId)
69
- currentTaskIds.add(taskId);
87
+ if (!taskId)
88
+ continue;
89
+ currentTaskIds.add(taskId);
90
+ currentTaskWatermarks.set(taskId, taskSyncResourceAdapter.getWatermark(task));
91
+ currentTaskChangeKeys.set(taskId, taskSyncResourceAdapter.getChangeKey({ ...task, isArchived: false }));
70
92
  }
71
93
  for (const task of input.archivedTasks || []) {
72
94
  const taskId = String(task?.id || '').trim();
73
- if (taskId)
74
- currentTaskIds.add(taskId);
95
+ if (!taskId)
96
+ continue;
97
+ currentTaskIds.add(taskId);
98
+ currentTaskWatermarks.set(taskId, taskSyncResourceAdapter.getWatermark(task));
99
+ currentTaskChangeKeys.set(taskId, taskSyncResourceAdapter.getChangeKey({ ...task, isArchived: true }));
75
100
  }
76
101
  const deleteChanges = Array.from(input.lastPushedTaskIds)
77
102
  .filter((taskId) => !currentTaskIds.has(taskId))
@@ -120,6 +145,7 @@ export function buildWorkspaceSyncPayloadModel(input) {
120
145
  createdAt: String(initiative?.createdAt || '').trim(),
121
146
  updatedAt: String(initiative?.updatedAt || initiative?.createdAt || '').trim(),
122
147
  order: Number.isFinite(Number(initiative?.order)) ? Math.floor(Number(initiative.order)) : null,
148
+ attachments: clonePlanningAttachmentsForSync(initiative?.attachments),
123
149
  isArchived: Boolean(initiative?.isArchived)
124
150
  }
125
151
  }))
@@ -162,6 +188,7 @@ export function buildWorkspaceSyncPayloadModel(input) {
162
188
  createdAt: String(workstream?.createdAt || '').trim(),
163
189
  updatedAt: String(workstream?.updatedAt || workstream?.createdAt || '').trim(),
164
190
  order: Number.isFinite(Number(workstream?.order)) ? Math.floor(Number(workstream.order)) : null,
191
+ attachments: clonePlanningAttachmentsForSync(workstream?.attachments),
165
192
  isArchived: Boolean(workstream?.isArchived)
166
193
  }
167
194
  }))
@@ -175,9 +202,13 @@ export function buildWorkspaceSyncPayloadModel(input) {
175
202
  .map((profile) => String(profile?.id || '').trim())
176
203
  .filter((profileId) => profileId.length > 0));
177
204
  const currentAiProfileWatermarks = new Map((Array.isArray(input.aiProfiles) ? input.aiProfiles : [])
178
- .map((profile) => [String(profile?.id || '').trim(), String(profile?.updatedAt || profile?.createdAt || '').trim()])
205
+ .map((profile) => [String(profile?.id || '').trim(), aiProfileSyncResourceAdapter.getWatermark(profile)])
206
+ .filter(([profileId]) => profileId.length > 0));
207
+ const currentAiProfileChangeKeys = new Map((Array.isArray(input.aiProfiles) ? input.aiProfiles : [])
208
+ .map((profile) => [String(profile?.id || '').trim(), aiProfileSyncResourceAdapter.getChangeKey(profile)])
179
209
  .filter(([profileId]) => profileId.length > 0));
180
210
  const previousAiProfileWatermarks = input.lastPushedAiProfileWatermarks;
211
+ const previousAiProfileChangeKeys = input.lastPushedAiProfileChangeKeys;
181
212
  const shouldSendAllAiProfiles = !previousAiProfileWatermarks || previousAiProfileWatermarks.size === 0;
182
213
  const aiProfileUpsertChanges = (Array.isArray(input.aiProfiles) ? input.aiProfiles : [])
183
214
  .map((profile) => ({
@@ -190,14 +221,34 @@ export function buildWorkspaceSyncPayloadModel(input) {
190
221
  username: String(profile.username || '').trim(),
191
222
  icon: String(profile.icon || 'Bot').trim() || 'Bot',
192
223
  color: String(profile.color || '#8b5cf6').trim() || '#8b5cf6',
224
+ avatarUrl: normalizeSyncSameOriginAssetUrl(profile.avatarUrl),
225
+ avatarSourceUrl: normalizeSyncSameOriginAssetUrl(profile.avatarSourceUrl),
226
+ avatarRevision: Number.isFinite(Number(profile.avatarRevision)) ? Math.max(0, Math.floor(Number(profile.avatarRevision))) : 0,
227
+ avatarUpdatedAt: typeof profile.avatarUpdatedAt === 'string' && profile.avatarUpdatedAt.trim().length > 0
228
+ ? profile.avatarUpdatedAt.trim()
229
+ : null,
193
230
  description: typeof profile.description === 'string' ? profile.description.trim() || null : null,
194
231
  role: typeof profile.role === 'string' ? profile.role.trim() || null : null,
195
232
  provider: typeof profile.provider === 'string' ? profile.provider.trim() || null : null,
196
233
  model: typeof profile.model === 'string' ? profile.model.trim() || null : null,
197
234
  surfaceType: parseAiProfileSurfaceType(profile.surfaceType),
235
+ seatScope: parseAiProfileSeatScope(profile.seatScope),
198
236
  providerMetadata: profile.providerMetadata && typeof profile.providerMetadata === 'object' && !Array.isArray(profile.providerMetadata)
199
237
  ? profile.providerMetadata
200
238
  : null,
239
+ archivedAt: typeof profile.archivedAt === 'string' && profile.archivedAt.trim().length > 0
240
+ ? profile.archivedAt.trim()
241
+ : null,
242
+ archivedBy: typeof profile.archivedBy === 'string' && profile.archivedBy.trim().length > 0
243
+ ? profile.archivedBy.trim()
244
+ : null,
245
+ archivedReason: typeof profile.archivedReason === 'string' ? profile.archivedReason.trim() || null : null,
246
+ mergedIntoProfileId: typeof profile.mergedIntoProfileId === 'string' && profile.mergedIntoProfileId.trim().length > 0
247
+ ? profile.mergedIntoProfileId.trim()
248
+ : null,
249
+ rosterStateUpdatedAt: typeof profile.rosterStateUpdatedAt === 'string' && profile.rosterStateUpdatedAt.trim().length > 0
250
+ ? profile.rosterStateUpdatedAt.trim()
251
+ : null,
201
252
  createdAt: String(profile.createdAt || '').trim(),
202
253
  updatedAt: String(profile.updatedAt || profile.createdAt || '').trim(),
203
254
  lastActiveAt: typeof profile.lastActiveAt === 'string' && profile.lastActiveAt.trim().length > 0
@@ -209,11 +260,13 @@ export function buildWorkspaceSyncPayloadModel(input) {
209
260
  .filter((change) => {
210
261
  if (shouldSendAllAiProfiles)
211
262
  return true;
212
- return previousAiProfileWatermarks.get(change.profile.id) !== change.profile.updatedAt;
263
+ return shouldSyncResource(aiProfileSyncResourceAdapter, change.profile, {
264
+ watermarks: previousAiProfileWatermarks,
265
+ changeKeys: previousAiProfileChangeKeys,
266
+ });
213
267
  });
214
- const currentDocumentPaths = new Set((Array.isArray(input.documents) ? input.documents : [])
215
- .map((doc) => String(doc?.path || '').trim())
216
- .filter((docPath) => docPath.length > 0));
268
+ const currentDocumentPaths = normalizeWorkspaceSyncPathSet(input.currentDocumentPaths, (Array.isArray(input.documents) ? input.documents : [])
269
+ .map((doc) => String(doc?.path || '').trim()));
217
270
  const deleteDocumentPaths = new Set(Array.from(input.lastPushedDocumentPaths || new Set())
218
271
  .map((docPath) => String(docPath || '').trim())
219
272
  .filter((docPath) => docPath.length > 0 && !currentDocumentPaths.has(docPath)));
@@ -233,6 +286,10 @@ export function buildWorkspaceSyncPayloadModel(input) {
233
286
  referenceNumber: Number.isFinite(Number(doc.referenceNumber)) ? Math.max(1, Math.floor(Number(doc.referenceNumber))) : null,
234
287
  version: Number.isFinite(Number(doc.version)) ? Math.max(1, Math.floor(Number(doc.version))) : null,
235
288
  taskId: typeof doc.taskId === 'string' && doc.taskId.trim().length > 0 ? doc.taskId.trim() : null,
289
+ linkTargetType: doc.linkTargetType === 'initiative' || doc.linkTargetType === 'workstream' || doc.linkTargetType === 'task'
290
+ ? doc.linkTargetType
291
+ : null,
292
+ linkTargetId: typeof doc.linkTargetId === 'string' && doc.linkTargetId.trim().length > 0 ? doc.linkTargetId.trim() : null,
236
293
  logicalName: typeof doc.logicalName === 'string' && doc.logicalName.trim().length > 0 ? doc.logicalName.trim() : null,
237
294
  caption: typeof doc.caption === 'string' && doc.caption.trim().length > 0 ? doc.caption.trim() : null,
238
295
  originalFilename: typeof doc.originalFilename === 'string' && doc.originalFilename.trim().length > 0 ? doc.originalFilename.trim() : null,
@@ -244,9 +301,8 @@ export function buildWorkspaceSyncPayloadModel(input) {
244
301
  return true;
245
302
  return previousDocumentWatermarks.get(doc.path) !== doc.updatedAt;
246
303
  });
247
- const currentAssetPaths = new Set((Array.isArray(input.assets) ? input.assets : [])
248
- .map((asset) => String(asset?.path || '').trim())
249
- .filter((assetPath) => assetPath.length > 0));
304
+ const currentAssetPaths = normalizeWorkspaceSyncPathSet(input.currentAssetPaths, (Array.isArray(input.assets) ? input.assets : [])
305
+ .map((asset) => String(asset?.path || '').trim()));
250
306
  const deleteAssetPaths = new Set(Array.from(input.lastPushedAssetPaths || new Set())
251
307
  .map((assetPath) => String(assetPath || '').trim())
252
308
  .filter((assetPath) => assetPath.length > 0 && !currentAssetPaths.has(assetPath)));
@@ -266,6 +322,10 @@ export function buildWorkspaceSyncPayloadModel(input) {
266
322
  mimeType: typeof asset.mimeType === 'string' && asset.mimeType.trim().length > 0 ? asset.mimeType.trim() : 'application/octet-stream',
267
323
  referenceNumber: Number.isFinite(Number(asset.referenceNumber)) ? Math.max(1, Math.floor(Number(asset.referenceNumber))) : null,
268
324
  taskId: typeof asset.taskId === 'string' && asset.taskId.trim().length > 0 ? asset.taskId.trim() : null,
325
+ linkTargetType: asset.linkTargetType === 'initiative' || asset.linkTargetType === 'workstream' || asset.linkTargetType === 'task'
326
+ ? asset.linkTargetType
327
+ : null,
328
+ linkTargetId: typeof asset.linkTargetId === 'string' && asset.linkTargetId.trim().length > 0 ? asset.linkTargetId.trim() : null,
269
329
  logicalName: typeof asset.logicalName === 'string' && asset.logicalName.trim().length > 0 ? asset.logicalName.trim() : null,
270
330
  caption: typeof asset.caption === 'string' && asset.caption.trim().length > 0 ? asset.caption.trim() : null,
271
331
  originalFilename: typeof asset.originalFilename === 'string' && asset.originalFilename.trim().length > 0 ? asset.originalFilename.trim() : null,
@@ -282,21 +342,19 @@ export function buildWorkspaceSyncPayloadModel(input) {
282
342
  return previousAssetWatermarks.get(asset.path) !== asset.updatedAt;
283
343
  });
284
344
  const reviewSessions = Array.isArray(input.documentReviewSessions) ? input.documentReviewSessions : [];
345
+ const allDocumentReviewSessionIds = new Set(reviewSessions
346
+ .map((session) => String(session?.id || '').trim())
347
+ .filter((sessionId) => sessionId.length > 0));
285
348
  const currentDocumentReviewSessionIds = new Set(reviewSessions
286
349
  .filter((session) => String(session?.deletedAt || '').trim().length === 0)
287
350
  .map((session) => String(session?.id || '').trim())
288
351
  .filter((sessionId) => sessionId.length > 0));
289
- const deleteDocumentReviewSessionIds = new Set([
290
- ...Array.from(input.lastPushedDocumentReviewSessionIds || new Set())
291
- .map((sessionId) => String(sessionId || '').trim())
292
- .filter((sessionId) => sessionId.length > 0 && !currentDocumentReviewSessionIds.has(sessionId)),
293
- ...reviewSessions
294
- .filter((session) => String(session?.deletedAt || '').trim().length > 0)
295
- .map((session) => String(session?.id || '').trim())
296
- .filter((sessionId) => sessionId.length > 0)
297
- ]);
352
+ const previousDocumentReviewSessionIds = input.lastPushedDocumentReviewSessionIds || new Set();
298
353
  const currentDocumentReviewSessionWatermarks = new Map(reviewSessions
299
- .map((session) => [String(session?.id || '').trim(), String(session?.updatedAt || session?.createdAt || '').trim()])
354
+ .map((session) => [
355
+ String(session?.id || '').trim(),
356
+ String(session?.deletedAt || session?.updatedAt || session?.createdAt || '').trim()
357
+ ])
300
358
  .filter(([sessionId]) => sessionId.length > 0));
301
359
  const previousDocumentReviewSessionWatermarks = input.lastPushedDocumentReviewSessionWatermarks;
302
360
  const shouldSendAllDocumentReviewSessions = !previousDocumentReviewSessionWatermarks || previousDocumentReviewSessionWatermarks.size === 0;
@@ -324,22 +382,46 @@ export function buildWorkspaceSyncPayloadModel(input) {
324
382
  return true;
325
383
  return previousDocumentReviewSessionWatermarks.get(change.session.id) !== change.session.updatedAt;
326
384
  });
385
+ const deletedReviewSessionChanges = reviewSessions
386
+ .filter((session) => String(session?.deletedAt || '').trim().length > 0)
387
+ .map((session) => ({
388
+ sessionId: String(session?.id || '').trim(),
389
+ deletedAt: String(session?.deletedAt || session?.updatedAt || session?.createdAt || payloadBuiltAt).trim() || payloadBuiltAt
390
+ }))
391
+ .filter((change) => change.sessionId.length > 0)
392
+ .filter((change) => {
393
+ if (shouldSendAllDocumentReviewSessions)
394
+ return true;
395
+ return previousDocumentReviewSessionWatermarks.get(change.sessionId) !== change.deletedAt;
396
+ });
397
+ const missingDocumentReviewSessionDeleteIds = Array.from(previousDocumentReviewSessionIds)
398
+ .map((sessionId) => String(sessionId || '').trim())
399
+ .filter((sessionId) => sessionId.length > 0 && !allDocumentReviewSessionIds.has(sessionId));
400
+ const documentReviewSessionDeleteChanges = [
401
+ ...missingDocumentReviewSessionDeleteIds.map((sessionId) => ({
402
+ sessionId,
403
+ deletedAt: payloadBuiltAt
404
+ })),
405
+ ...deletedReviewSessionChanges
406
+ ];
407
+ const deleteDocumentReviewSessionIds = new Set([
408
+ ...missingDocumentReviewSessionDeleteIds,
409
+ ...deletedReviewSessionChanges.map((change) => change.sessionId)
410
+ ]);
327
411
  const reviewComments = Array.isArray(input.documentReviewComments) ? input.documentReviewComments : [];
412
+ const allDocumentReviewCommentIds = new Set(reviewComments
413
+ .map((comment) => String(comment?.id || '').trim())
414
+ .filter((commentId) => commentId.length > 0));
328
415
  const currentDocumentReviewCommentIds = new Set(reviewComments
329
416
  .filter((comment) => String(comment?.deletedAt || '').trim().length === 0)
330
417
  .map((comment) => String(comment?.id || '').trim())
331
418
  .filter((commentId) => commentId.length > 0));
332
- const deleteDocumentReviewCommentIds = new Set([
333
- ...Array.from(input.lastPushedDocumentReviewCommentIds || new Set())
334
- .map((commentId) => String(commentId || '').trim())
335
- .filter((commentId) => commentId.length > 0 && !currentDocumentReviewCommentIds.has(commentId)),
336
- ...reviewComments
337
- .filter((comment) => String(comment?.deletedAt || '').trim().length > 0)
338
- .map((comment) => String(comment?.id || '').trim())
339
- .filter((commentId) => commentId.length > 0)
340
- ]);
419
+ const previousDocumentReviewCommentIds = input.lastPushedDocumentReviewCommentIds || new Set();
341
420
  const currentDocumentReviewCommentWatermarks = new Map(reviewComments
342
- .map((comment) => [String(comment?.id || '').trim(), String(comment?.updatedAt || comment?.createdAt || '').trim()])
421
+ .map((comment) => [
422
+ String(comment?.id || '').trim(),
423
+ String(comment?.deletedAt || comment?.updatedAt || comment?.createdAt || '').trim()
424
+ ])
343
425
  .filter(([commentId]) => commentId.length > 0));
344
426
  const previousDocumentReviewCommentWatermarks = input.lastPushedDocumentReviewCommentWatermarks;
345
427
  const shouldSendAllDocumentReviewComments = !previousDocumentReviewCommentWatermarks || previousDocumentReviewCommentWatermarks.size === 0;
@@ -364,22 +446,46 @@ export function buildWorkspaceSyncPayloadModel(input) {
364
446
  return true;
365
447
  return previousDocumentReviewCommentWatermarks.get(change.comment.id) !== change.comment.updatedAt;
366
448
  });
449
+ const deletedReviewCommentChanges = reviewComments
450
+ .filter((comment) => String(comment?.deletedAt || '').trim().length > 0)
451
+ .map((comment) => ({
452
+ commentId: String(comment?.id || '').trim(),
453
+ deletedAt: String(comment?.deletedAt || comment?.updatedAt || comment?.createdAt || payloadBuiltAt).trim() || payloadBuiltAt
454
+ }))
455
+ .filter((change) => change.commentId.length > 0)
456
+ .filter((change) => {
457
+ if (shouldSendAllDocumentReviewComments)
458
+ return true;
459
+ return previousDocumentReviewCommentWatermarks.get(change.commentId) !== change.deletedAt;
460
+ });
461
+ const missingDocumentReviewCommentDeleteIds = Array.from(previousDocumentReviewCommentIds)
462
+ .map((commentId) => String(commentId || '').trim())
463
+ .filter((commentId) => commentId.length > 0 && !allDocumentReviewCommentIds.has(commentId));
464
+ const documentReviewCommentDeleteChanges = [
465
+ ...missingDocumentReviewCommentDeleteIds.map((commentId) => ({
466
+ commentId,
467
+ deletedAt: payloadBuiltAt
468
+ })),
469
+ ...deletedReviewCommentChanges
470
+ ];
471
+ const deleteDocumentReviewCommentIds = new Set([
472
+ ...missingDocumentReviewCommentDeleteIds,
473
+ ...deletedReviewCommentChanges.map((change) => change.commentId)
474
+ ]);
367
475
  const annotatedSessions = Array.isArray(input.annotatedAttachmentSessions) ? input.annotatedAttachmentSessions : [];
476
+ const allAnnotatedAttachmentSessionIds = new Set(annotatedSessions
477
+ .map((session) => String(session?.id || '').trim())
478
+ .filter((sessionId) => sessionId.length > 0));
368
479
  const currentAnnotatedAttachmentSessionIds = new Set(annotatedSessions
369
480
  .filter((session) => String(session?.deletedAt || '').trim().length === 0)
370
481
  .map((session) => String(session?.id || '').trim())
371
482
  .filter((sessionId) => sessionId.length > 0));
372
- const deleteAnnotatedAttachmentSessionIds = new Set([
373
- ...Array.from(input.lastPushedAnnotatedAttachmentSessionIds || new Set())
374
- .map((sessionId) => String(sessionId || '').trim())
375
- .filter((sessionId) => sessionId.length > 0 && !currentAnnotatedAttachmentSessionIds.has(sessionId)),
376
- ...annotatedSessions
377
- .filter((session) => String(session?.deletedAt || '').trim().length > 0)
378
- .map((session) => String(session?.id || '').trim())
379
- .filter((sessionId) => sessionId.length > 0)
380
- ]);
483
+ const previousAnnotatedAttachmentSessionIds = input.lastPushedAnnotatedAttachmentSessionIds || new Set();
381
484
  const currentAnnotatedAttachmentSessionWatermarks = new Map(annotatedSessions
382
- .map((session) => [String(session?.id || '').trim(), String(session?.updatedAt || session?.createdAt || '').trim()])
485
+ .map((session) => [
486
+ String(session?.id || '').trim(),
487
+ String(session?.deletedAt || session?.updatedAt || session?.createdAt || '').trim()
488
+ ])
383
489
  .filter(([sessionId]) => sessionId.length > 0));
384
490
  const previousAnnotatedAttachmentSessionWatermarks = input.lastPushedAnnotatedAttachmentSessionWatermarks;
385
491
  const shouldSendAllAnnotatedAttachmentSessions = !previousAnnotatedAttachmentSessionWatermarks || previousAnnotatedAttachmentSessionWatermarks.size === 0;
@@ -410,6 +516,32 @@ export function buildWorkspaceSyncPayloadModel(input) {
410
516
  return true;
411
517
  return previousAnnotatedAttachmentSessionWatermarks.get(change.session.id) !== change.session.updatedAt;
412
518
  });
519
+ const deletedAnnotatedAttachmentSessionChanges = annotatedSessions
520
+ .filter((session) => String(session?.deletedAt || '').trim().length > 0)
521
+ .map((session) => ({
522
+ sessionId: String(session?.id || '').trim(),
523
+ deletedAt: String(session?.deletedAt || session?.updatedAt || session?.createdAt || payloadBuiltAt).trim() || payloadBuiltAt
524
+ }))
525
+ .filter((change) => change.sessionId.length > 0)
526
+ .filter((change) => {
527
+ if (shouldSendAllAnnotatedAttachmentSessions)
528
+ return true;
529
+ return previousAnnotatedAttachmentSessionWatermarks.get(change.sessionId) !== change.deletedAt;
530
+ });
531
+ const missingAnnotatedAttachmentSessionDeleteIds = Array.from(previousAnnotatedAttachmentSessionIds)
532
+ .map((sessionId) => String(sessionId || '').trim())
533
+ .filter((sessionId) => sessionId.length > 0 && !allAnnotatedAttachmentSessionIds.has(sessionId));
534
+ const annotatedAttachmentSessionDeleteChanges = [
535
+ ...missingAnnotatedAttachmentSessionDeleteIds.map((sessionId) => ({
536
+ sessionId,
537
+ deletedAt: payloadBuiltAt
538
+ })),
539
+ ...deletedAnnotatedAttachmentSessionChanges
540
+ ];
541
+ const deleteAnnotatedAttachmentSessionIds = new Set([
542
+ ...missingAnnotatedAttachmentSessionDeleteIds,
543
+ ...deletedAnnotatedAttachmentSessionChanges.map((change) => change.sessionId)
544
+ ]);
413
545
  const taskEvents = sortTaskEventsForSync(Array.isArray(input.taskEvents) ? input.taskEvents : []);
414
546
  const currentTaskEventWatermarks = new Map(taskEvents
415
547
  .map((event) => [String(event?.id || '').trim(), String(event?.createdAt || '').trim()])
@@ -417,12 +549,17 @@ export function buildWorkspaceSyncPayloadModel(input) {
417
549
  const previousTaskEventWatermarks = input.lastPushedTaskEventWatermarks;
418
550
  const shouldSendAllTaskEvents = !previousTaskEventWatermarks || previousTaskEventWatermarks.size === 0;
419
551
  const taskEventUpsertChanges = taskEvents
420
- .map((event) => ({
421
- op: 'task-event-upsert',
422
- event: {
552
+ .map((event) => {
553
+ const entityType = event.entityType === 'initiative' || event.entityType === 'workstream'
554
+ ? event.entityType
555
+ : 'task';
556
+ const entityId = String(event.entityId || event.taskId || '').trim();
557
+ const normalizedEvent = {
423
558
  id: String(event.id || '').trim(),
424
- taskId: String(event.taskId || '').trim(),
425
559
  workspaceId: String(event.workspaceId || '').trim(),
560
+ entityType,
561
+ entityId,
562
+ taskId: entityType === 'task' ? String(event.taskId || entityId).trim() : undefined,
426
563
  action: String(event.action || '').trim(),
427
564
  actor: String(event.actor || 'system').trim() || 'system',
428
565
  actorType: event.actorType === 'ai' || event.actorType === 'human' || event.actorType === 'system'
@@ -432,10 +569,28 @@ export function buildWorkspaceSyncPayloadModel(input) {
432
569
  ? event.details
433
570
  : {},
434
571
  createdAt: String(event.createdAt || '').trim(),
572
+ };
573
+ if (entityType === 'task') {
574
+ return {
575
+ op: 'task-event-upsert',
576
+ event: {
577
+ ...normalizedEvent,
578
+ entityType: 'task',
579
+ taskId: String(normalizedEvent.taskId || entityId).trim(),
580
+ }
581
+ };
435
582
  }
436
- }))
583
+ return {
584
+ op: 'entity-event-upsert',
585
+ event: {
586
+ ...normalizedEvent,
587
+ entityType,
588
+ taskId: undefined,
589
+ }
590
+ };
591
+ })
437
592
  .filter((change) => (change.event.id.length > 0
438
- && change.event.taskId.length > 0
593
+ && change.event.entityId.length > 0
439
594
  && change.event.workspaceId.length > 0
440
595
  && change.event.action.length > 0
441
596
  && Number.isFinite(Date.parse(change.event.createdAt))))
@@ -474,22 +629,22 @@ export function buildWorkspaceSyncPayloadModel(input) {
474
629
  deletedAt: payloadBuiltAt
475
630
  })),
476
631
  ...documentReviewSessionUpsertChanges,
477
- ...Array.from(deleteDocumentReviewSessionIds).map((sessionId) => ({
632
+ ...documentReviewSessionDeleteChanges.map((change) => ({
478
633
  op: 'document-review-session-delete',
479
- sessionId,
480
- deletedAt: payloadBuiltAt
634
+ sessionId: change.sessionId,
635
+ deletedAt: change.deletedAt
481
636
  })),
482
637
  ...documentReviewCommentUpsertChanges,
483
- ...Array.from(deleteDocumentReviewCommentIds).map((commentId) => ({
638
+ ...documentReviewCommentDeleteChanges.map((change) => ({
484
639
  op: 'document-review-comment-delete',
485
- commentId,
486
- deletedAt: payloadBuiltAt
640
+ commentId: change.commentId,
641
+ deletedAt: change.deletedAt
487
642
  })),
488
643
  ...annotatedAttachmentSessionUpsertChanges,
489
- ...Array.from(deleteAnnotatedAttachmentSessionIds).map((sessionId) => ({
644
+ ...annotatedAttachmentSessionDeleteChanges.map((change) => ({
490
645
  op: 'annotated-attachment-session-delete',
491
- sessionId,
492
- deletedAt: payloadBuiltAt
646
+ sessionId: change.sessionId,
647
+ deletedAt: change.deletedAt
493
648
  })),
494
649
  ...taskEventUpsertChanges,
495
650
  ...activeChanges,
@@ -500,9 +655,46 @@ export function buildWorkspaceSyncPayloadModel(input) {
500
655
  deletedAt: pendingDeleteWatermarks.get(taskId) || payloadBuiltAt
501
656
  }))
502
657
  ];
658
+ const baselineResetReason = input.baselineResetReason ?? null;
659
+ const resolveDomainDeltaDiagnostics = (args) => {
660
+ const changeCount = args.upsertCount + (args.deleteCount || 0);
661
+ if (args.currentCount === 0 && changeCount === 0) {
662
+ return {
663
+ mode: 'none',
664
+ reason: null,
665
+ currentCount: 0,
666
+ baselineCount: args.baselineCount,
667
+ changeCount
668
+ };
669
+ }
670
+ if (args.alwaysFull) {
671
+ return {
672
+ mode: changeCount > 0 ? 'send-all' : 'none',
673
+ reason: changeCount > 0 ? 'full-state-domain' : null,
674
+ currentCount: args.currentCount,
675
+ baselineCount: args.baselineCount,
676
+ changeCount
677
+ };
678
+ }
679
+ const shouldSendAll = args.baselineCount === 0 && args.currentCount > 0 && args.upsertCount >= args.currentCount;
680
+ return {
681
+ mode: shouldSendAll ? 'send-all' : 'delta',
682
+ reason: shouldSendAll ? (baselineResetReason || 'missing-baseline') : null,
683
+ currentCount: args.currentCount,
684
+ baselineCount: args.baselineCount,
685
+ changeCount
686
+ };
687
+ };
688
+ const byOp = changes.reduce((acc, change) => {
689
+ const op = String(change.op || '').trim();
690
+ acc[op] = (acc[op] || 0) + 1;
691
+ return acc;
692
+ }, {});
503
693
  return {
504
694
  changes,
505
695
  currentTaskIds: new Set(Array.from(currentTaskIds).filter((taskId) => !pendingDeleteIds.has(taskId))),
696
+ currentTaskWatermarks: new Map(Array.from(currentTaskWatermarks.entries()).filter(([taskId]) => !pendingDeleteIds.has(taskId))),
697
+ currentTaskChangeKeys: new Map(Array.from(currentTaskChangeKeys.entries()).filter(([taskId]) => !pendingDeleteIds.has(taskId))),
506
698
  deleteTaskIds,
507
699
  currentInitiativeIds,
508
700
  currentInitiativeWatermarks,
@@ -510,6 +702,7 @@ export function buildWorkspaceSyncPayloadModel(input) {
510
702
  currentWorkstreamWatermarks,
511
703
  currentAiProfileIds,
512
704
  currentAiProfileWatermarks,
705
+ currentAiProfileChangeKeys,
513
706
  currentDocumentPaths,
514
707
  deleteDocumentPaths,
515
708
  currentDocumentWatermarks,
@@ -526,7 +719,77 @@ export function buildWorkspaceSyncPayloadModel(input) {
526
719
  deleteAnnotatedAttachmentSessionIds,
527
720
  currentAnnotatedAttachmentSessionWatermarks,
528
721
  currentTaskEventWatermarks,
529
- pushedWatermarks
722
+ pushedWatermarks,
723
+ pushedTaskChangeKeys,
724
+ deltaDiagnostics: {
725
+ totalChanges: changes.length,
726
+ byOp,
727
+ baselineResetReason,
728
+ domains: {
729
+ tasks: resolveDomainDeltaDiagnostics({
730
+ currentCount: currentTaskIds.size,
731
+ baselineCount: lastWatermarks?.size || 0,
732
+ upsertCount: activeChanges.length + archivedChanges.length,
733
+ deleteCount: deleteTaskIds.size
734
+ }),
735
+ initiatives: resolveDomainDeltaDiagnostics({
736
+ currentCount: currentInitiativeIds.size,
737
+ baselineCount: previousInitiativeWatermarks?.size || 0,
738
+ upsertCount: initiativeUpsertChanges.length
739
+ }),
740
+ workstreams: resolveDomainDeltaDiagnostics({
741
+ currentCount: currentWorkstreamIds.size,
742
+ baselineCount: previousWorkstreamWatermarks?.size || 0,
743
+ upsertCount: workstreamUpsertChanges.length
744
+ }),
745
+ aiProfiles: resolveDomainDeltaDiagnostics({
746
+ currentCount: currentAiProfileIds.size,
747
+ baselineCount: previousAiProfileWatermarks?.size || 0,
748
+ upsertCount: aiProfileUpsertChanges.length
749
+ }),
750
+ documents: resolveDomainDeltaDiagnostics({
751
+ currentCount: currentDocumentPaths.size,
752
+ baselineCount: previousDocumentWatermarks?.size || 0,
753
+ upsertCount: documentUpsertChanges.length,
754
+ deleteCount: deleteDocumentPaths.size
755
+ }),
756
+ assets: resolveDomainDeltaDiagnostics({
757
+ currentCount: currentAssetPaths.size,
758
+ baselineCount: previousAssetWatermarks?.size || 0,
759
+ upsertCount: assetUpsertChanges.length,
760
+ deleteCount: deleteAssetPaths.size
761
+ }),
762
+ documentReviewSessions: resolveDomainDeltaDiagnostics({
763
+ currentCount: currentDocumentReviewSessionIds.size,
764
+ baselineCount: previousDocumentReviewSessionWatermarks?.size || 0,
765
+ upsertCount: documentReviewSessionUpsertChanges.length,
766
+ deleteCount: deleteDocumentReviewSessionIds.size
767
+ }),
768
+ documentReviewComments: resolveDomainDeltaDiagnostics({
769
+ currentCount: currentDocumentReviewCommentIds.size,
770
+ baselineCount: previousDocumentReviewCommentWatermarks?.size || 0,
771
+ upsertCount: documentReviewCommentUpsertChanges.length,
772
+ deleteCount: deleteDocumentReviewCommentIds.size
773
+ }),
774
+ annotatedAttachmentSessions: resolveDomainDeltaDiagnostics({
775
+ currentCount: currentAnnotatedAttachmentSessionIds.size,
776
+ baselineCount: previousAnnotatedAttachmentSessionWatermarks?.size || 0,
777
+ upsertCount: annotatedAttachmentSessionUpsertChanges.length,
778
+ deleteCount: deleteAnnotatedAttachmentSessionIds.size
779
+ }),
780
+ taskEvents: resolveDomainDeltaDiagnostics({
781
+ currentCount: currentTaskEventWatermarks.size,
782
+ baselineCount: previousTaskEventWatermarks?.size || 0,
783
+ upsertCount: taskEventUpsertChanges.length
784
+ }),
785
+ taxonomy: resolveDomainDeltaDiagnostics({
786
+ currentCount: (input.taxonomyState && typeof input.taxonomyState === 'object') || Array.isArray(input.taxonomies) ? 1 : 0,
787
+ baselineCount: 0,
788
+ upsertCount: byOp['taxonomy-upsert'] || 0,
789
+ alwaysFull: true
790
+ })
791
+ }
792
+ }
530
793
  };
531
794
  }
532
795
  export function buildWorkspaceSyncSignatureModel(input) {
@@ -543,6 +806,7 @@ export function buildWorkspaceSyncSignatureModel(input) {
543
806
  updatedAt: String(initiative.updatedAt || initiative.createdAt || '').trim(),
544
807
  title: String(initiative.title || '').trim(),
545
808
  ownerId: typeof initiative.ownerId === 'string' ? initiative.ownerId.trim() : '',
809
+ attachments: clonePlanningAttachmentsForSync(initiative.attachments) || [],
546
810
  isArchived: Boolean(initiative.isArchived)
547
811
  }))
548
812
  .filter((initiative) => initiative.id.length > 0)
@@ -554,6 +818,7 @@ export function buildWorkspaceSyncSignatureModel(input) {
554
818
  initiativeId: typeof workstream.initiativeId === 'string' ? workstream.initiativeId.trim() : '',
555
819
  title: String(workstream.title || '').trim(),
556
820
  ownerId: typeof workstream.ownerId === 'string' ? workstream.ownerId.trim() : '',
821
+ attachments: clonePlanningAttachmentsForSync(workstream.attachments) || [],
557
822
  isArchived: Boolean(workstream.isArchived)
558
823
  }))
559
824
  .filter((workstream) => workstream.id.length > 0)
@@ -570,7 +835,11 @@ export function buildWorkspaceSyncSignatureModel(input) {
570
835
  id: String(profile.id || '').trim(),
571
836
  updatedAt: String(profile.updatedAt || profile.createdAt || '').trim(),
572
837
  name: String(profile.name || '').trim(),
573
- username: String(profile.username || '').trim()
838
+ username: String(profile.username || '').trim(),
839
+ archivedAt: String(profile.archivedAt || '').trim(),
840
+ archivedReason: String(profile.archivedReason || '').trim(),
841
+ mergedIntoProfileId: String(profile.mergedIntoProfileId || '').trim(),
842
+ rosterStateUpdatedAt: String(profile.rosterStateUpdatedAt || '').trim()
574
843
  }))
575
844
  .filter((profile) => profile.id.length > 0)
576
845
  .sort((a, b) => a.id.localeCompare(b.id))