@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
@@ -1,16 +1,31 @@
1
1
  function getActivityFieldLabel(field) {
2
2
  if (field === 'title')
3
- return 'TITLE';
3
+ return 'Title';
4
4
  if (field === 'description')
5
- return 'DESCRIPTION';
5
+ return 'Description';
6
6
  if (field === 'assignee')
7
- return 'ASSIGNED TO';
7
+ return 'Assigned to';
8
+ if (field === 'status')
9
+ return 'Status';
10
+ if (field === 'scheduledDate')
11
+ return 'Scheduled date';
12
+ if (field === 'dueDate')
13
+ return 'Due date';
8
14
  if (field === 'workstreamId')
9
- return 'WORKSTREAM';
15
+ return 'Workstream';
10
16
  if (field === 'initiativeId')
11
- return 'INITIATIVE';
17
+ return 'Initiative';
12
18
  return field;
13
19
  }
20
+ function formatStatusValue(value) {
21
+ if (value === 'task')
22
+ return 'Task';
23
+ return value
24
+ .replace(/[-_]+/g, ' ')
25
+ .replace(/\s+/g, ' ')
26
+ .trim()
27
+ .replace(/\b\w/g, (letter) => letter.toUpperCase());
28
+ }
14
29
  function formatValue(value, field, formatter) {
15
30
  const formatted = formatter?.(field, value);
16
31
  if (typeof formatted === 'string')
@@ -23,6 +38,8 @@ function formatValue(value, field, formatter) {
23
38
  }
24
39
  if (typeof value === 'object')
25
40
  return 'updated';
41
+ if (field === 'status')
42
+ return formatStatusValue(String(value));
26
43
  return String(value);
27
44
  }
28
45
  function normalizeChecklistItems(value) {
@@ -75,10 +92,131 @@ function summarizeChecklistChange(change) {
75
92
  }
76
93
  return 'Checklist updated';
77
94
  }
95
+ function stripQueryAndHash(value) {
96
+ return value.split(/[?#]/, 1)[0] || value;
97
+ }
98
+ function basenameFromPath(value) {
99
+ const normalized = stripQueryAndHash(value).replace(/\\/g, '/');
100
+ const segments = normalized.split('/').filter(Boolean);
101
+ return segments[segments.length - 1] || normalized;
102
+ }
103
+ function isImageAttachmentCandidate(value) {
104
+ return /\.(png|jpe?g|webp|gif)$/i.test(stripQueryAndHash(value));
105
+ }
106
+ function isDocumentAttachmentCandidate(value) {
107
+ if (value.includes('/api/taskforce/documents/'))
108
+ return true;
109
+ return /\.(pdf|txt|md|markdown|csv|json|docx?|html?|js|jsx|ts|tsx|css|py|java|go|rs|sh)$/i.test(stripQueryAndHash(value));
110
+ }
111
+ function normalizeAttachmentList(value) {
112
+ if (!Array.isArray(value))
113
+ return [];
114
+ return value.flatMap((entry, index) => {
115
+ const attachment = (entry && typeof entry === 'object' ? entry : null);
116
+ const path = typeof entry === 'string'
117
+ ? entry
118
+ : String(attachment?.path || '').trim();
119
+ const fsPath = String(attachment?.fsPath || '').trim();
120
+ const assetId = String(attachment?.assetId || '').trim();
121
+ const referenceLabel = String(attachment?.referenceLabel || '').trim();
122
+ const label = [
123
+ attachment?.displayName,
124
+ attachment?.originalFilename,
125
+ attachment?.caption,
126
+ referenceLabel,
127
+ fsPath ? basenameFromPath(fsPath) : '',
128
+ path ? basenameFromPath(path) : '',
129
+ ]
130
+ .map((candidate) => String(candidate || '').trim())
131
+ .find(Boolean) || `Attachment ${index + 1}`;
132
+ const key = assetId || fsPath || path || `${label}:${index}`;
133
+ const kindCandidates = [
134
+ path,
135
+ fsPath,
136
+ String(attachment?.originalFilename || '').trim(),
137
+ String(attachment?.displayName || '').trim(),
138
+ referenceLabel,
139
+ ].filter(Boolean);
140
+ const kind = kindCandidates.some((candidate) => isImageAttachmentCandidate(candidate))
141
+ ? 'image'
142
+ : (referenceLabel.startsWith('D-') || kindCandidates.some((candidate) => isDocumentAttachmentCandidate(candidate)))
143
+ ? 'document'
144
+ : 'attachment';
145
+ return [{ key, label, kind }];
146
+ });
147
+ }
148
+ function pluralizeAttachmentKind(kind, count) {
149
+ if (count === 1)
150
+ return kind;
151
+ if (kind === 'image')
152
+ return 'images';
153
+ if (kind === 'document')
154
+ return 'documents';
155
+ return 'attachments';
156
+ }
157
+ function describeAttachmentCollection(entries) {
158
+ if (entries.length === 0)
159
+ return 'attachments';
160
+ const counts = entries.reduce((acc, entry) => {
161
+ acc[entry.kind] += 1;
162
+ return acc;
163
+ }, { image: 0, document: 0, attachment: 0 });
164
+ const nonZeroParts = Object.entries(counts)
165
+ .filter(([, count]) => count > 0)
166
+ .map(([kind, count]) => count === 1 ? kind : `${count} ${pluralizeAttachmentKind(kind, count)}`);
167
+ if (nonZeroParts.length === 1)
168
+ return nonZeroParts[0];
169
+ if (entries.length <= 3 && nonZeroParts.length <= 2)
170
+ return nonZeroParts.join(' and ');
171
+ return `${entries.length} attachments`;
172
+ }
173
+ function capitalizeLeading(value) {
174
+ if (!value)
175
+ return value;
176
+ return value.charAt(0).toUpperCase() + value.slice(1);
177
+ }
178
+ function summarizeAttachmentChange(change) {
179
+ const before = normalizeAttachmentList(change?.from);
180
+ const after = normalizeAttachmentList(change?.to);
181
+ const beforeByKey = new Map(before.map((entry) => [entry.key, entry]));
182
+ const afterByKey = new Map(after.map((entry) => [entry.key, entry]));
183
+ const added = after.filter((entry) => !beforeByKey.has(entry.key));
184
+ const removed = before.filter((entry) => !afterByKey.has(entry.key));
185
+ if (added.length === 1 && removed.length === 0) {
186
+ return `${capitalizeLeading(added[0].kind)} attached: ${added[0].label}`;
187
+ }
188
+ if (removed.length === 1 && added.length === 0) {
189
+ return `${capitalizeLeading(removed[0].kind)} removed: ${removed[0].label}`;
190
+ }
191
+ if (added.length > 0 && removed.length === 0) {
192
+ return `${capitalizeLeading(describeAttachmentCollection(added))} attached`;
193
+ }
194
+ if (removed.length > 0 && added.length === 0) {
195
+ return `${capitalizeLeading(describeAttachmentCollection(removed))} removed`;
196
+ }
197
+ if (added.length === 1 && removed.length === 1) {
198
+ return `${capitalizeLeading(added[0].kind)} replaced: ${removed[0].label} -> ${added[0].label}`;
199
+ }
200
+ if (added.length > 0 || removed.length > 0) {
201
+ return `${capitalizeLeading(describeAttachmentCollection(added))} attached, ${describeAttachmentCollection(removed)} removed`;
202
+ }
203
+ const relabeled = after.filter((entry) => {
204
+ const previous = beforeByKey.get(entry.key);
205
+ return previous && previous.label !== entry.label;
206
+ });
207
+ if (relabeled.length === 1 && before.length === 1 && after.length === 1) {
208
+ const previous = beforeByKey.get(relabeled[0].key);
209
+ return `${capitalizeLeading(relabeled[0].kind)} renamed: ${previous?.label || 'Attachment'} -> ${relabeled[0].label}`;
210
+ }
211
+ return 'Attachment details updated';
212
+ }
78
213
  export function summarizeTaskChange(field, change, formatter) {
79
214
  if (field === 'checklistItems') {
80
215
  return summarizeChecklistChange(change);
81
216
  }
217
+ if (field === 'attachments') {
218
+ return summarizeAttachmentChange(change);
219
+ }
82
220
  const label = getActivityFieldLabel(field);
83
221
  if (field === 'title') {
84
222
  return `${label}: updated`;
@@ -108,22 +246,28 @@ export function summarizeTaskEvent(event, formatter) {
108
246
  if (event.action === 'task-unarchived')
109
247
  return 'Task restored';
110
248
  if (event.action === 'task-status-changed' && changes.status) {
111
- return `Status: ${formatValue(changes.status.from, 'status', formatter)} -> ${formatValue(changes.status.to, 'status', formatter)}`;
249
+ return `Status changed: ${formatValue(changes.status.from, 'status', formatter)} -> ${formatValue(changes.status.to, 'status', formatter)}`;
112
250
  }
113
251
  if (event.action === 'task-schedule-changed') {
114
252
  if (changes.scheduledDate) {
115
- return `Scheduled: ${formatValue(changes.scheduledDate.from, 'scheduledDate', formatter)} -> ${formatValue(changes.scheduledDate.to, 'scheduledDate', formatter)}`;
253
+ return `Scheduled date changed: ${formatValue(changes.scheduledDate.from, 'scheduledDate', formatter)} -> ${formatValue(changes.scheduledDate.to, 'scheduledDate', formatter)}`;
116
254
  }
117
255
  if (changes.dueDate) {
118
- return `Due: ${formatValue(changes.dueDate.from, 'dueDate', formatter)} -> ${formatValue(changes.dueDate.to, 'dueDate', formatter)}`;
256
+ return `Due date changed: ${formatValue(changes.dueDate.from, 'dueDate', formatter)} -> ${formatValue(changes.dueDate.to, 'dueDate', formatter)}`;
119
257
  }
120
258
  return 'Schedule updated';
121
259
  }
122
260
  if (event.action === 'task-relationship-changed' && changes.workstreamId) {
123
- return `Workstream: ${formatValue(changes.workstreamId.from, 'workstreamId', formatter)} -> ${formatValue(changes.workstreamId.to, 'workstreamId', formatter)}`;
261
+ return `Workstream changed: ${formatValue(changes.workstreamId.from, 'workstreamId', formatter)} -> ${formatValue(changes.workstreamId.to, 'workstreamId', formatter)}`;
262
+ }
263
+ if (event.action === 'task-relationship-changed' && changes.initiativeId) {
264
+ return `Initiative changed: ${formatValue(changes.initiativeId.from, 'initiativeId', formatter)} -> ${formatValue(changes.initiativeId.to, 'initiativeId', formatter)}`;
265
+ }
266
+ if (event.action === 'task-attachments-changed') {
267
+ return changes.attachments
268
+ ? summarizeTaskChange('attachments', changes.attachments, formatter)
269
+ : 'Attachments updated';
124
270
  }
125
- if (event.action === 'task-attachments-changed')
126
- return 'Attachments updated';
127
271
  if (fields.length === 1) {
128
272
  const field = fields[0];
129
273
  const change = changes[field];
@@ -133,25 +277,87 @@ export function summarizeTaskEvent(event, formatter) {
133
277
  return `${fields.length} fields updated`;
134
278
  return 'Task updated';
135
279
  }
136
- export function getLatestTaskActivity(task) {
137
- const items = Array.isArray(task.activity) && task.activity.length > 0
138
- ? task.activity
139
- : Array.isArray(task.comments)
140
- ? task.comments.map((comment) => ({
141
- id: `comment:${comment.id}`,
280
+ function activityTimestamp(item) {
281
+ const parsed = Date.parse(String(item.timestamp || ''));
282
+ return Number.isFinite(parsed) ? parsed : null;
283
+ }
284
+ export function getTaskEventPrimaryChangeField(event) {
285
+ const changes = event.details?.changes || {};
286
+ const fields = Object.keys(changes);
287
+ if (event.action === 'task-status-changed' && changes.status)
288
+ return 'status';
289
+ if (event.action === 'task-schedule-changed') {
290
+ if (changes.scheduledDate)
291
+ return 'scheduledDate';
292
+ if (changes.dueDate)
293
+ return 'dueDate';
294
+ }
295
+ if (event.action === 'task-relationship-changed') {
296
+ if (changes.workstreamId)
297
+ return 'workstreamId';
298
+ if (changes.initiativeId)
299
+ return 'initiativeId';
300
+ }
301
+ if (event.action === 'task-attachments-changed' && changes.attachments)
302
+ return 'attachments';
303
+ return fields.length === 1 ? fields[0] : null;
304
+ }
305
+ export function getTaskActivityItems(task) {
306
+ const deduped = new Map();
307
+ const commentIds = new Set();
308
+ const addItem = (item) => {
309
+ if (item.type === 'event' && item.event.action === 'task-comment-added')
310
+ return;
311
+ const key = item.type === 'comment'
312
+ ? `comment:${String(item.comment.id || item.id).replace(/^comment:/, '')}`
313
+ : `event:${String(item.event.id || item.id).replace(/^event:/, '')}`;
314
+ deduped.set(key, { ...item, id: key });
315
+ if (item.type === 'comment') {
316
+ commentIds.add(String(item.comment.id || item.id).replace(/^comment:/, ''));
317
+ }
318
+ };
319
+ if (Array.isArray(task.activity)) {
320
+ task.activity.forEach(addItem);
321
+ }
322
+ if (Array.isArray(task.comments)) {
323
+ task.comments.forEach((comment) => {
324
+ const commentId = String(comment.id || '').trim();
325
+ if (!commentId || commentIds.has(commentId))
326
+ return;
327
+ addItem({
328
+ id: `comment:${commentId}`,
142
329
  type: 'comment',
143
330
  timestamp: comment.timestamp,
144
331
  comment
145
- }))
146
- : [];
332
+ });
333
+ });
334
+ }
335
+ return [...deduped.values()].sort((left, right) => {
336
+ const leftTs = activityTimestamp(left);
337
+ const rightTs = activityTimestamp(right);
338
+ if (leftTs !== null && rightTs !== null && leftTs !== rightTs)
339
+ return leftTs - rightTs;
340
+ if (leftTs !== null && rightTs === null)
341
+ return -1;
342
+ if (leftTs === null && rightTs !== null)
343
+ return 1;
344
+ return String(left.id || '').localeCompare(String(right.id || ''));
345
+ });
346
+ }
347
+ export function getLatestTaskActivity(task) {
348
+ const items = getTaskActivityItems(task);
147
349
  if (items.length === 0)
148
350
  return null;
149
351
  return [...items].sort((left, right) => {
150
- const leftTs = Date.parse(String(left.timestamp || ''));
151
- const rightTs = Date.parse(String(right.timestamp || ''));
152
- if (Number.isFinite(leftTs) && Number.isFinite(rightTs) && leftTs !== rightTs) {
352
+ const leftTs = activityTimestamp(left);
353
+ const rightTs = activityTimestamp(right);
354
+ if (leftTs !== null && rightTs !== null && leftTs !== rightTs) {
153
355
  return rightTs - leftTs;
154
356
  }
357
+ if (leftTs !== null && rightTs === null)
358
+ return -1;
359
+ if (leftTs === null && rightTs !== null)
360
+ return 1;
155
361
  return String(right.id || '').localeCompare(String(left.id || ''));
156
362
  })[0] || null;
157
363
  }
@@ -1,32 +1,32 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { summarizeTaskChange, summarizeTaskEvent } from './taskActivity';
2
+ import { getTaskActivityItems, summarizeTaskChange, summarizeTaskEvent } from './taskActivity';
3
3
  describe('summarizeTaskChange', () => {
4
4
  it('summarizes title edits without dumping before and after text', () => {
5
5
  expect(summarizeTaskChange('title', {
6
6
  from: 'Old title',
7
7
  to: 'New title',
8
- })).toBe('TITLE: updated');
8
+ })).toBe('Title: updated');
9
9
  });
10
- it('summarizes assignee changes with uppercase labels and formatted values', () => {
10
+ it('summarizes assignee changes with sentence-case labels and formatted values', () => {
11
11
  expect(summarizeTaskChange('assignee', { from: 'unassigned', to: 'member-1' }, (_field, value) => {
12
12
  if (value === 'unassigned')
13
13
  return 'Unassigned';
14
14
  if (value === 'member-1')
15
15
  return 'Emma Peel';
16
16
  return undefined;
17
- })).toBe('ASSIGNED TO: Unassigned -> Emma Peel');
17
+ })).toBe('Assigned to: Unassigned -> Emma Peel');
18
18
  });
19
19
  it('summarizes description additions without rendering the body text', () => {
20
20
  expect(summarizeTaskChange('description', {
21
21
  from: '',
22
22
  to: 'Line one\n\nLine two',
23
- })).toBe('DESCRIPTION: added');
23
+ })).toBe('Description: added');
24
24
  });
25
25
  it('summarizes description clears without rendering the old body text', () => {
26
26
  expect(summarizeTaskChange('description', {
27
27
  from: 'Old body',
28
28
  to: '',
29
- })).toBe('DESCRIPTION: cleared');
29
+ })).toBe('Description: cleared');
30
30
  });
31
31
  it('summarizes checklist completion clearly', () => {
32
32
  expect(summarizeTaskChange('checklistItems', {
@@ -59,7 +59,49 @@ describe('summarizeTaskChange', () => {
59
59
  })).toBe('Checklist reordered');
60
60
  });
61
61
  it('uses readable labels for workstream relationship changes', () => {
62
- expect(summarizeTaskChange('workstreamId', { from: null, to: 'workstream-1' }, (_field, value) => value === 'workstream-1' ? 'WS-12' : undefined)).toBe('WORKSTREAM: none -> WS-12');
62
+ expect(summarizeTaskChange('workstreamId', { from: null, to: 'workstream-1' }, (_field, value) => value === 'workstream-1' ? 'WS-12' : undefined)).toBe('Workstream: none -> WS-12');
63
+ });
64
+ it('summarizes image attachment additions clearly', () => {
65
+ expect(summarizeTaskChange('attachments', {
66
+ from: [],
67
+ to: [{
68
+ path: '/api/taskforce/context/workspaces/default/assets/images/homepage-proof.png',
69
+ caption: 'Homepage proof',
70
+ }],
71
+ })).toBe('Image attached: Homepage proof');
72
+ });
73
+ it('summarizes document attachment removals clearly', () => {
74
+ expect(summarizeTaskChange('attachments', {
75
+ from: [{
76
+ path: '/api/taskforce/documents/asset-doc-1/content',
77
+ originalFilename: 'spec.md',
78
+ assetId: 'asset-doc-1',
79
+ }],
80
+ to: [],
81
+ })).toBe('Document removed: spec.md');
82
+ });
83
+ it('summarizes multiple document attachments concisely', () => {
84
+ expect(summarizeTaskChange('attachments', {
85
+ from: [],
86
+ to: [
87
+ { path: '/api/taskforce/documents/asset-doc-1/content', originalFilename: 'spec.md', assetId: 'asset-doc-1' },
88
+ { path: '/api/taskforce/documents/asset-doc-2/content', originalFilename: 'notes.pdf', assetId: 'asset-doc-2' },
89
+ ],
90
+ })).toBe('2 documents attached');
91
+ });
92
+ it('summarizes attachment renames when the same asset remains attached', () => {
93
+ expect(summarizeTaskChange('attachments', {
94
+ from: [{
95
+ path: '/api/taskforce/documents/asset-doc-1/content',
96
+ assetId: 'asset-doc-1',
97
+ caption: 'Draft spec',
98
+ }],
99
+ to: [{
100
+ path: '/api/taskforce/documents/asset-doc-1/content',
101
+ assetId: 'asset-doc-1',
102
+ caption: 'Final spec',
103
+ }],
104
+ })).toBe('Document renamed: Draft spec -> Final spec');
63
105
  });
64
106
  });
65
107
  describe('summarizeTaskEvent', () => {
@@ -80,6 +122,75 @@ describe('summarizeTaskEvent', () => {
80
122
  },
81
123
  },
82
124
  },
83
- }, (_field, value) => value === 'workstream-1' ? 'WS-12' : undefined)).toBe('Workstream: none -> WS-12');
125
+ }, (_field, value) => value === 'workstream-1' ? 'WS-12' : undefined)).toBe('Workstream changed: none -> WS-12');
126
+ });
127
+ it('uses attachment-specific summaries for attachment activity events', () => {
128
+ expect(summarizeTaskEvent({
129
+ id: 'event-2',
130
+ taskId: 'task-1',
131
+ workspaceId: 'default',
132
+ action: 'task-attachments-changed',
133
+ actor: 'user',
134
+ actorType: 'human',
135
+ createdAt: '2026-04-12T21:42:00.000Z',
136
+ details: {
137
+ changes: {
138
+ attachments: {
139
+ from: [{
140
+ path: '/api/taskforce/context/workspaces/default/assets/images/old-proof.png',
141
+ caption: 'Old proof',
142
+ }],
143
+ to: [{
144
+ path: '/api/taskforce/context/workspaces/default/assets/images/new-proof.png',
145
+ caption: 'New proof',
146
+ }],
147
+ },
148
+ },
149
+ },
150
+ })).toBe('Image replaced: Old proof -> New proof');
151
+ });
152
+ });
153
+ describe('getTaskActivityItems', () => {
154
+ it('combines comments and audit events while hiding duplicate comment-added events', () => {
155
+ const items = getTaskActivityItems({
156
+ comments: [{
157
+ id: 'comment-1',
158
+ author: 'user',
159
+ text: 'Captured comment',
160
+ timestamp: '2026-03-22T10:00:00.000Z',
161
+ }],
162
+ activity: [
163
+ {
164
+ id: 'event:event-1',
165
+ type: 'event',
166
+ timestamp: '2026-03-22T09:00:00.000Z',
167
+ event: {
168
+ id: 'event-1',
169
+ taskId: 'task-1',
170
+ workspaceId: 'default',
171
+ action: 'task-status-changed',
172
+ actor: 'user',
173
+ actorType: 'human',
174
+ createdAt: '2026-03-22T09:00:00.000Z',
175
+ details: { changes: { status: { from: 'task', to: 'in-progress' } } },
176
+ },
177
+ },
178
+ {
179
+ id: 'event:comment-event-1',
180
+ type: 'event',
181
+ timestamp: '2026-03-22T10:00:00.000Z',
182
+ event: {
183
+ id: 'comment-event-1',
184
+ taskId: 'task-1',
185
+ workspaceId: 'default',
186
+ action: 'task-comment-added',
187
+ actor: 'user',
188
+ actorType: 'human',
189
+ createdAt: '2026-03-22T10:00:00.000Z',
190
+ },
191
+ },
192
+ ],
193
+ });
194
+ expect(items.map((item) => item.id)).toEqual(['event:event-1', 'comment:comment-1']);
84
195
  });
85
196
  });
@@ -1,4 +1,5 @@
1
1
  export type HeaderWorkspaceSyncStatus = 'off' | 'syncing' | 'attention' | 'healthy';
2
+ type WorkspaceSyncBlockedReason = 'cloud-auth-unconfigured' | 'runtime-not-local' | 'sync-disabled' | 'invalid-workspace' | 'repair-active' | 'attach-cloud' | 'provision-local' | 'auth-required' | 'push-in-flight' | 'pull-in-flight' | 'retry-pending' | 'lease-held';
2
3
  interface ResolveHeaderWorkspaceSyncPresentationInput {
3
4
  runtimeMode: 'local' | 'cloud';
4
5
  isAuthenticated: boolean;
@@ -7,6 +8,9 @@ interface ResolveHeaderWorkspaceSyncPresentationInput {
7
8
  workspaceSyncSummary: string;
8
9
  workspaceSyncRecommendedAction: string;
9
10
  workspaceSyncError: string;
11
+ pushBlockedReason?: WorkspaceSyncBlockedReason | null;
12
+ pullBlockedReason?: WorkspaceSyncBlockedReason | null;
13
+ retryBlockedReason?: WorkspaceSyncBlockedReason | null;
10
14
  }
11
15
  export interface HeaderWorkspaceSyncPresentation {
12
16
  actionable: boolean;
@@ -1,12 +1,78 @@
1
+ function resolveBlockedReasonPresentation(reason) {
2
+ switch (reason) {
3
+ case 'auth-required':
4
+ return {
5
+ summary: 'Sync is paused until you sign in again.',
6
+ recommendedAction: 'Sign in, then press Sync to retry.'
7
+ };
8
+ case 'attach-cloud':
9
+ return {
10
+ summary: 'This workspace still needs its first cloud pull before local pushes can run.',
11
+ recommendedAction: 'Keep this window open for the first cloud pull. If it seems stuck, press Repair.'
12
+ };
13
+ case 'provision-local':
14
+ return {
15
+ summary: 'This workspace is still doing its first cloud upload before normal pulls can run.',
16
+ recommendedAction: 'Keep this window open until the first cloud upload finishes.'
17
+ };
18
+ case 'repair-active':
19
+ return {
20
+ summary: 'Repair is already running for this workspace.',
21
+ recommendedAction: 'Wait for repair to finish before retrying sync.'
22
+ };
23
+ case 'retry-pending':
24
+ return {
25
+ summary: 'Sync hit a temporary problem and is waiting for its scheduled retry.',
26
+ recommendedAction: 'Wait for the automatic retry, or press Sync to retry now.'
27
+ };
28
+ case 'push-in-flight':
29
+ case 'pull-in-flight':
30
+ return {
31
+ summary: 'A sync request is already running for this workspace.',
32
+ recommendedAction: 'Wait for the current sync request to finish.'
33
+ };
34
+ case 'lease-held':
35
+ return {
36
+ summary: 'Another local window is already syncing this workspace.',
37
+ recommendedAction: 'Use a single local window for sync, or close the other syncing window.'
38
+ };
39
+ case 'invalid-workspace':
40
+ return {
41
+ summary: 'Workspace setup is required before sync can run.',
42
+ recommendedAction: 'Open or create a named workspace before retrying sync.'
43
+ };
44
+ case 'cloud-auth-unconfigured':
45
+ return {
46
+ summary: 'Cloud sync is unavailable because cloud authentication is not configured.',
47
+ recommendedAction: 'Configure cloud authentication before turning on sync.'
48
+ };
49
+ case 'runtime-not-local':
50
+ return {
51
+ summary: 'Workspace sync controls are only available in the local runtime.',
52
+ recommendedAction: 'Open this workspace in the local app to manage sync.'
53
+ };
54
+ case 'sync-disabled':
55
+ return {
56
+ summary: 'Workspace sync is currently turned off.',
57
+ recommendedAction: 'Turn on sync when you are ready to connect this workspace to cloud.'
58
+ };
59
+ default:
60
+ return null;
61
+ }
62
+ }
1
63
  export function resolveHeaderWorkspaceSyncPresentation(input) {
2
64
  const actionable = input.runtimeMode === 'local' && input.isAuthenticated;
3
65
  if (actionable) {
66
+ const blockedReasonPresentation = resolveBlockedReasonPresentation(input.retryBlockedReason
67
+ || input.pullBlockedReason
68
+ || input.pushBlockedReason
69
+ || null);
4
70
  return {
5
71
  actionable,
6
72
  status: input.workspaceSyncStatus,
7
73
  syncEnabledLabel: input.workspaceCloudSyncEnabled ? 'yes' : 'no',
8
- summary: input.workspaceSyncSummary,
9
- recommendedAction: input.workspaceSyncRecommendedAction,
74
+ summary: blockedReasonPresentation?.summary || input.workspaceSyncSummary,
75
+ recommendedAction: blockedReasonPresentation?.recommendedAction || input.workspaceSyncRecommendedAction,
10
76
  lastError: input.workspaceSyncError
11
77
  };
12
78
  }
@@ -25,7 +25,10 @@ describe('resolveHeaderWorkspaceSyncPresentation', () => {
25
25
  workspaceCloudSyncEnabled: true,
26
26
  workspaceSyncSummary: 'Sync needs attention before it can continue.',
27
27
  workspaceSyncRecommendedAction: 'Press Sync to retry.',
28
- workspaceSyncError: 'Retry later'
28
+ workspaceSyncError: 'Retry later',
29
+ pushBlockedReason: null,
30
+ pullBlockedReason: null,
31
+ retryBlockedReason: null
29
32
  });
30
33
  expect(result.actionable).toBe(true);
31
34
  expect(result.status).toBe('attention');
@@ -33,4 +36,36 @@ describe('resolveHeaderWorkspaceSyncPresentation', () => {
33
36
  expect(result.summary).toBe('Sync needs attention before it can continue.');
34
37
  expect(result.lastError).toBe('Retry later');
35
38
  });
39
+ it('surfaces the highest-priority blocked sync reason in the visible summary', () => {
40
+ const result = resolveHeaderWorkspaceSyncPresentation({
41
+ runtimeMode: 'local',
42
+ isAuthenticated: true,
43
+ workspaceSyncStatus: 'attention',
44
+ workspaceCloudSyncEnabled: true,
45
+ workspaceSyncSummary: 'Sync needs attention before it can continue.',
46
+ workspaceSyncRecommendedAction: 'Press Sync to retry.',
47
+ workspaceSyncError: 'Retry later',
48
+ pushBlockedReason: 'attach-cloud',
49
+ pullBlockedReason: 'retry-pending',
50
+ retryBlockedReason: 'auth-required'
51
+ });
52
+ expect(result.summary).toBe('Sync is paused until you sign in again.');
53
+ expect(result.recommendedAction).toBe('Sign in, then press Sync to retry.');
54
+ });
55
+ it('explains when another local window holds the sync lease', () => {
56
+ const result = resolveHeaderWorkspaceSyncPresentation({
57
+ runtimeMode: 'local',
58
+ isAuthenticated: true,
59
+ workspaceSyncStatus: 'attention',
60
+ workspaceCloudSyncEnabled: true,
61
+ workspaceSyncSummary: 'Sync needs attention before it can continue.',
62
+ workspaceSyncRecommendedAction: 'Press Sync to retry.',
63
+ workspaceSyncError: 'Retry later',
64
+ pushBlockedReason: 'lease-held',
65
+ pullBlockedReason: null,
66
+ retryBlockedReason: null
67
+ });
68
+ expect(result.summary).toBe('Another local window is already syncing this workspace.');
69
+ expect(result.recommendedAction).toBe('Use a single local window for sync, or close the other syncing window.');
70
+ });
36
71
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taskforcehq/taskforce",
3
- "version": "0.3.305",
3
+ "version": "0.3.306",
4
4
  "description": "Mission Command center for AI-powered task management (beta - under active development)",
5
5
  "author": "Taskforce HQ <hello@taskforcehq.ai> (https://taskforcehq.ai)",
6
6
  "license": "MIT",
@@ -54,7 +54,7 @@
54
54
  "build:ui": "npm run build:app",
55
55
  "check:deploy-preflight": "npm run build",
56
56
  "hooks:install": "git config core.hooksPath .githooks",
57
- "build": "shx rm -rf dist && tsc && shx cp src/Taskforce.module.css dist/Taskforce.module.css && shx mkdir -p dist/resources && shx cp -r src/resources/* dist/resources/ && shx rm -f dist/resources/templates/workflows/release.yaml && shx mkdir -p dist/public && shx cp -r src/assets/images/* dist/public/ && shx cp -r src/assets/favicon/* dist/public/ && shx chmod +x dist/cli.js && npm run build:ui",
57
+ "build": "shx rm -rf dist && tsc && shx cp src/Taskforce.module.css dist/Taskforce.module.css && shx mkdir -p dist/styles && shx cp src/styles/fonts.css dist/styles/fonts.css && shx mkdir -p dist/assets/fonts && shx cp src/assets/fonts/*.woff2 dist/assets/fonts/ && shx mkdir -p dist/resources && shx cp -r src/resources/* dist/resources/ && shx rm -f dist/resources/templates/workflows/release.yaml && shx mkdir -p dist/public && shx cp -r src/assets/images/* dist/public/ && shx cp -r src/assets/favicon/* dist/public/ && shx chmod +x dist/cli.js && npm run build:ui",
58
58
  "mcp": "node dist/mcp/server.js",
59
59
  "gen:workflows": "node scripts/generate-workflow-templates.mjs",
60
60
  "check:workflows": "node scripts/generate-workflow-templates.mjs --check",
@@ -111,6 +111,7 @@
111
111
  "js-yaml": "^4.1.1",
112
112
  "nodemailer": "^6.10.1",
113
113
  "pg": "^8.18.0",
114
+ "react-avatar-editor": "^15.1.0",
114
115
  "rehype-sanitize": "^6.0.0",
115
116
  "stripe": "^20.3.1",
116
117
  "ws": "^8.18.0"