@taskforcehq/taskforce 0.3.304 → 0.3.306

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. package/README.md +140 -247
  2. package/dist/Taskforce.module.css +295 -89
  3. package/dist/TaskforceCore.js +378 -111
  4. package/dist/TaskforceCore.test.js +1221 -107
  5. package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
  6. package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  7. package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
  8. package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
  9. package/dist/assets/fonts/Noir_medium.woff2 +0 -0
  10. package/dist/assets/fonts/Noir_regular.woff2 +0 -0
  11. package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
  12. package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
  13. package/dist/compat/workspaceSyncCompat.js +52 -2
  14. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  15. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  16. package/dist/components/context/ContextAttachmentManager.js +549 -0
  17. package/dist/components/features/AgentsModule.d.ts +10 -1
  18. package/dist/components/features/AgentsModule.js +260 -15
  19. package/dist/components/features/AgentsModule.test.js +255 -5
  20. package/dist/components/features/DocumentIndex.d.ts +1 -1
  21. package/dist/components/features/DocumentIndex.js +1 -1
  22. package/dist/components/features/DocumentViewer.d.ts +6 -2
  23. package/dist/components/features/DocumentViewer.js +90 -10
  24. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  25. package/dist/components/features/DocumentWorkspace.js +12 -155
  26. package/dist/components/features/DocumentWorkspace.test.js +226 -6
  27. package/dist/components/features/TaskSettings.js +29 -3
  28. package/dist/components/features/TaskSettings.test.js +42 -1
  29. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  30. package/dist/components/features/documentWorkspaceModel.js +57 -0
  31. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  32. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  33. package/dist/components/task/TaskCard.js +19 -28
  34. package/dist/components/task/TaskCard.test.js +38 -3
  35. package/dist/components/task/TaskContextUpload.js +4 -526
  36. package/dist/components/task/TaskForm.d.ts +8 -8
  37. package/dist/components/task/TaskForm.js +362 -230
  38. package/dist/components/task/TaskForm.test.js +558 -381
  39. package/dist/components/task/TaskKanban.d.ts +2 -1
  40. package/dist/components/task/TaskKanban.js +96 -14
  41. package/dist/components/task/TaskKanban.test.js +36 -0
  42. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  43. package/dist/components/ui/EditableAvatarButton.js +18 -0
  44. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  45. package/dist/components/views/PlanComparisonPage.js +70 -17
  46. package/dist/components/views/PlanComparisonPage.test.js +289 -41
  47. package/dist/components/views/PlansPage.js +239 -106
  48. package/dist/components/views/PlansPage.test.js +593 -42
  49. package/dist/components/views/StandaloneLayout.js +310 -554
  50. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  51. package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
  52. package/dist/components/views/panels/PlanningDrawer.js +18 -11
  53. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  54. package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
  55. package/dist/components/views/planningScope.d.ts +20 -0
  56. package/dist/components/views/planningScope.js +25 -0
  57. package/dist/components/views/planningScope.test.d.ts +1 -0
  58. package/dist/components/views/planningScope.test.js +60 -0
  59. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +27 -14
  60. package/dist/components/views/standalone/modals/AccountHubModal.js +87 -15
  61. package/dist/components/views/standalone/modals/AccountHubModal.test.js +42 -2
  62. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  63. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  64. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  65. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  66. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  67. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  68. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  69. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  70. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  71. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  72. package/dist/config/envSchema.js +1 -1
  73. package/dist/core/AiProfileService.d.ts +45 -3
  74. package/dist/core/AiProfileService.js +774 -63
  75. package/dist/core/AiProfileService.test.js +22 -2
  76. package/dist/core/AiProfiles.test.js +761 -2
  77. package/dist/core/AttachmentLinkService.js +57 -15
  78. package/dist/core/AuthIdentityService.d.ts +107 -0
  79. package/dist/core/AuthIdentityService.js +284 -0
  80. package/dist/core/AuthTokenService.d.ts +4 -0
  81. package/dist/core/AuthTokenService.js +34 -6
  82. package/dist/core/AuthTokenService.test.d.ts +1 -0
  83. package/dist/core/AuthTokenService.test.js +78 -0
  84. package/dist/core/EntitlementsPolicy.test.js +77 -15
  85. package/dist/core/GlobalSettingsService.js +115 -6
  86. package/dist/core/PlanEntitlementService.d.ts +30 -3
  87. package/dist/core/PlanEntitlementService.js +454 -93
  88. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  89. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  90. package/dist/core/PlanVersionPolicy.test.js +54 -28
  91. package/dist/core/PlanningEntities.test.js +52 -0
  92. package/dist/core/SignupMonetizationSettings.test.js +241 -44
  93. package/dist/core/SyncReconciliationService.js +2 -65
  94. package/dist/core/SystemAdmin.test.js +244 -88
  95. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  96. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  97. package/dist/core/Taskforce.d.ts +228 -14
  98. package/dist/core/Taskforce.js +814 -201
  99. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  100. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  101. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  102. package/dist/core/WorkspaceLifecycleService.js +0 -5
  103. package/dist/core/WorkspacePermissions.test.js +25 -1
  104. package/dist/core/WorkspacePlanMode.test.js +128 -27
  105. package/dist/core/shared.d.ts +3 -1
  106. package/dist/core/shared.js +16 -1
  107. package/dist/core/types.d.ts +68 -2
  108. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
  109. package/dist/hooks/sync/auth.js +3 -1
  110. package/dist/hooks/sync/bootstrap.js +2 -0
  111. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  112. package/dist/hooks/sync/controlPlane.js +78 -0
  113. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  114. package/dist/hooks/sync/controlPlane.test.js +121 -0
  115. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  116. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  117. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  118. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  119. package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
  120. package/dist/hooks/sync/orchestratorShared.js +68 -47
  121. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  122. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  123. package/dist/hooks/sync/pullLifecycle.js +24 -0
  124. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  125. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  126. package/dist/hooks/sync/recovery.d.ts +16 -2
  127. package/dist/hooks/sync/recovery.js +171 -53
  128. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  129. package/dist/hooks/sync/recovery.test.js +292 -0
  130. package/dist/hooks/sync/transfers.d.ts +16 -3
  131. package/dist/hooks/sync/transfers.js +186 -65
  132. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  133. package/dist/hooks/sync/transfers.test.js +396 -0
  134. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  135. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  136. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  137. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  138. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  139. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  140. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  141. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  142. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  143. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  144. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  145. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  146. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  147. package/dist/hooks/useSyncOrchestrator.d.ts +16 -6
  148. package/dist/hooks/useSyncOrchestrator.js +424 -175
  149. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +487 -10
  150. package/dist/hooks/useTaskData.js +8 -3
  151. package/dist/hooks/useTaskData.test.js +45 -0
  152. package/dist/hooks/useTaskMutations.d.ts +2 -1
  153. package/dist/hooks/useTaskMutations.js +12 -1
  154. package/dist/hooks/useTaskMutations.test.js +30 -1
  155. package/dist/hooks/useTaskforce.d.ts +65 -4
  156. package/dist/hooks/useTaskforce.js +564 -243
  157. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  158. package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
  159. package/dist/hooks/useTaskforce.sync-behavior.test.js +2454 -208
  160. package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
  161. package/dist/hooks/useWorkspaceSyncController.js +17 -2
  162. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  163. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  164. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
  165. package/dist/localization/locales/en-US.d.ts +0 -1
  166. package/dist/localization/locales/en-US.js +0 -1
  167. package/dist/localization/locales/es-419.js +0 -1
  168. package/dist/localization/locales/pt-BR.js +0 -1
  169. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  170. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  171. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  172. package/dist/mcp/collaborationRegistrar.js +71 -0
  173. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  174. package/dist/mcp/documentAssetRegistrar.js +346 -0
  175. package/dist/mcp/runtime.d.ts +2 -0
  176. package/dist/mcp/runtime.js +2433 -3534
  177. package/dist/mcp/runtime.test.js +440 -2
  178. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  179. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  180. package/dist/mcp/toolCatalog.d.ts +35 -0
  181. package/dist/mcp/toolCatalog.js +3 -0
  182. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  183. package/dist/migrations/taskSchemaMigrations.js +171 -61
  184. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  185. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  186. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  187. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  188. package/dist/resources/templates/workflows/plan.yaml +1 -1
  189. package/dist/resources/templates/workflows/review.yaml +2 -2
  190. package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
  191. package/dist/server/auth/providers/apple.d.ts +33 -0
  192. package/dist/server/auth/providers/apple.js +82 -0
  193. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  194. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  195. package/dist/server/auth/providers/github.d.ts +26 -0
  196. package/dist/server/auth/providers/github.js +86 -0
  197. package/dist/server/auth/providers/google.d.ts +21 -0
  198. package/dist/server/auth/providers/google.js +56 -0
  199. package/dist/server/auth/providers/types.d.ts +21 -0
  200. package/dist/server/auth/providers/types.js +1 -0
  201. package/dist/server/auth.d.ts +2 -0
  202. package/dist/server/auth.js +6 -3
  203. package/dist/server/documentReviewRoutes.test.js +36 -3
  204. package/dist/server/index.cookieProxy.test.js +1 -0
  205. package/dist/server/index.d.ts +12 -0
  206. package/dist/server/index.js +120 -9
  207. package/dist/server/index.rateLimit.test.js +3 -0
  208. package/dist/server/index.test.js +106 -3
  209. package/dist/server/routes/admin.d.ts +4 -5
  210. package/dist/server/routes/admin.js +410 -80
  211. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  212. package/dist/server/routes/annotatedAttachments.js +1 -1
  213. package/dist/server/routes/auth.d.ts +16 -6
  214. package/dist/server/routes/auth.js +1015 -118
  215. package/dist/server/routes/authSupport.d.ts +30 -0
  216. package/dist/server/routes/authSupport.js +81 -0
  217. package/dist/server/routes/billing.d.ts +1 -1
  218. package/dist/server/routes/billing.js +276 -34
  219. package/dist/server/routes/billing.test.js +760 -52
  220. package/dist/server/routes/documentReviews.d.ts +1 -1
  221. package/dist/server/routes/documentReviews.js +1 -1
  222. package/dist/server/routes/documents.d.ts +5 -2
  223. package/dist/server/routes/documents.js +242 -74
  224. package/dist/server/routes/primitives.d.ts +11 -0
  225. package/dist/server/routes/primitives.js +73 -0
  226. package/dist/server/routes/resources.d.ts +1 -1
  227. package/dist/server/routes/resources.js +1 -1
  228. package/dist/server/routes/shared.d.ts +29 -3
  229. package/dist/server/routes/shared.js +69 -4
  230. package/dist/server/routes/sync.d.ts +1 -1
  231. package/dist/server/routes/sync.integration.test.js +437 -39
  232. package/dist/server/routes/sync.js +62 -606
  233. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  234. package/dist/server/routes/syncAuxRoutes.js +91 -0
  235. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  236. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  237. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  238. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  239. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  240. package/dist/server/routes/syncPushRoutes.js +212 -0
  241. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  242. package/dist/server/routes/syncRouteGuards.js +67 -0
  243. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  244. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  245. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  246. package/dist/server/routes/syncRouteTypes.js +1 -0
  247. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  248. package/dist/server/routes/syncSnapshotRoutes.js +180 -0
  249. package/dist/server/routes/tasks.d.ts +1 -1
  250. package/dist/server/routes/tasks.js +44 -2
  251. package/dist/server/routes/workspaces.d.ts +1 -1
  252. package/dist/server/routes/workspaces.js +37 -6
  253. package/dist/server/routes.d.ts +2 -9
  254. package/dist/server/routes.js +136 -165
  255. package/dist/server/routes.test.js +2457 -162
  256. package/dist/services/workflowExportSnapshots.test.js +2 -0
  257. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  258. package/dist/shared/aiProfileSeatScope.js +21 -0
  259. package/dist/shared/passwordPolicy.d.ts +13 -0
  260. package/dist/shared/passwordPolicy.js +84 -0
  261. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  262. package/dist/shared/passwordPolicy.test.js +34 -0
  263. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  264. package/dist/storage/workspaceAssetStore.js +132 -44
  265. package/dist/storage/workspaceAssetStore.test.js +93 -6
  266. package/dist/styles/fonts.css +70 -0
  267. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  268. package/dist/sync/collaborationSyncPayload.js +17 -11
  269. package/dist/sync/collaborationSyncPayload.test.js +13 -1
  270. package/dist/sync/contentSyncState.d.ts +4 -8
  271. package/dist/sync/contentSyncState.js +33 -77
  272. package/dist/sync/syncApplyHandlers.d.ts +7 -0
  273. package/dist/sync/syncApplyHandlers.js +28 -13
  274. package/dist/sync/syncApplyHandlers.test.js +72 -3
  275. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  276. package/dist/sync/syncFieldSemantics.js +55 -0
  277. package/dist/sync/syncResourceAdapters.d.ts +44 -0
  278. package/dist/sync/syncResourceAdapters.js +77 -0
  279. package/dist/sync/syncService.d.ts +6 -0
  280. package/dist/sync/syncService.js +60 -0
  281. package/dist/sync/syncService.test.js +71 -3
  282. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  283. package/dist/sync/taskSyncChangeKey.js +143 -0
  284. package/dist/sync/taskSyncPayload.js +3 -8
  285. package/dist/sync/workspacePullFeed.js +4 -3
  286. package/dist/sync/workspacePullFeed.test.js +43 -0
  287. package/dist/sync/workspaceRepair.js +5 -2
  288. package/dist/sync/workspaceSyncModel.d.ts +48 -0
  289. package/dist/sync/workspaceSyncModel.js +195 -31
  290. package/dist/sync/workspaceSyncModel.test.js +325 -12
  291. package/dist/sync/workspaceSyncState.d.ts +19 -0
  292. package/dist/sync/workspaceSyncState.js +3 -1
  293. package/dist/sync/workspaceSyncSurface.js +3 -8
  294. package/dist/types.d.ts +17 -0
  295. package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
  296. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
  297. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  298. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  299. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  300. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  301. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
  302. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
  303. package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-9E6ParrY.js} +1 -1
  304. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  305. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  306. package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
  307. package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
  308. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  309. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  310. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +9 -0
  311. package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BA7jcEpH.js} +1 -1
  312. package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
  313. package/dist/ui/assets/index-C6k75jr8.js +6 -0
  314. package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-DuEd_65c.js} +1 -1
  315. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  316. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  317. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  318. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  319. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  320. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  321. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  322. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  323. package/dist/ui/fonts/Noir_medium.otf +0 -0
  324. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  325. package/dist/ui/fonts/Noir_regular.otf +0 -0
  326. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  327. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  328. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  329. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  330. package/dist/ui/index.html +3 -3
  331. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  332. package/dist/utils/assignees.d.ts +2 -0
  333. package/dist/utils/assignees.js +2 -0
  334. package/dist/utils/avatarUpload.d.ts +27 -0
  335. package/dist/utils/avatarUpload.js +132 -0
  336. package/dist/utils/avatarUpload.test.d.ts +1 -0
  337. package/dist/utils/avatarUpload.test.js +40 -0
  338. package/dist/utils/bootstrapTrace.d.ts +7 -0
  339. package/dist/utils/bootstrapTrace.js +29 -0
  340. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  341. package/dist/utils/commercialLifecyclePresentation.js +199 -0
  342. package/dist/utils/contextAssetEvents.d.ts +2 -0
  343. package/dist/utils/syncEventDetails.d.ts +3 -0
  344. package/dist/utils/syncEventDetails.js +64 -0
  345. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  346. package/dist/utils/syncEventDetails.test.js +23 -0
  347. package/dist/utils/syncEventPresentation.d.ts +15 -0
  348. package/dist/utils/syncEventPresentation.js +129 -0
  349. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  350. package/dist/utils/syncEventPresentation.test.js +64 -0
  351. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  352. package/dist/utils/syncStatusPresentation.js +217 -0
  353. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  354. package/dist/utils/syncStatusPresentation.test.js +164 -0
  355. package/dist/utils/taskActivity.d.ts +2 -0
  356. package/dist/utils/taskActivity.js +228 -22
  357. package/dist/utils/taskActivity.test.js +119 -8
  358. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  359. package/dist/utils/workspaceSyncPresentation.js +68 -2
  360. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  361. package/package.json +10 -2
  362. package/scripts/check-cloud-mcp.mjs +83 -0
  363. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  364. package/scripts/playwright-auth.sh +5 -1
  365. package/scripts/run-billing-performance-smoke.sh +24 -0
  366. package/scripts/run-e2e-realtime.sh +17 -3
  367. package/scripts/run-smoke.sh +17 -5
  368. package/scripts/run-soak.sh +17 -5
  369. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  370. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  371. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  372. package/src/resources/templates/workflows/plan.yaml +1 -1
  373. package/src/resources/templates/workflows/review.yaml +2 -2
  374. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +0 -1
  375. package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
  376. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  377. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  378. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
  379. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  380. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
  381. package/dist/ui/assets/index-DXUdtH1B.js +0 -6
  382. package/dist/ui/assets/index-Ii0B0rTO.css +0 -1
  383. package/scripts/migrate-planning-model.ts +0 -233
@@ -1,26 +1,26 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { render, screen, fireEvent } from '@testing-library/react';
3
- import userEvent from '@testing-library/user-event';
4
- import { describe, it, expect, vi } from 'vitest';
5
- import { TaskForm } from './TaskForm';
6
- describe('TaskForm Component', () => {
2
+ import { render, screen, fireEvent } from "@testing-library/react";
3
+ import userEvent from "@testing-library/user-event";
4
+ import { describe, it, expect, vi } from "vitest";
5
+ import { TaskForm } from "./TaskForm";
6
+ describe("TaskForm Component", () => {
7
7
  const defaultProps = {
8
8
  editingTaskId: null,
9
- error: '',
10
- title: '',
11
- description: '',
12
- category: 'general',
13
- type: 'feature',
9
+ error: "",
10
+ title: "",
11
+ description: "",
12
+ category: "general",
13
+ type: "feature",
14
14
  priority: 2,
15
15
  complexity: 3,
16
- approach: '',
17
- assignee: 'unassigned',
18
- scheduledDate: '',
19
- dueDate: '',
20
- workstreamInput: '',
16
+ approach: "",
17
+ assignee: "unassigned",
18
+ scheduledDate: "",
19
+ dueDate: "",
20
+ workstreamInput: "",
21
21
  checklistItems: [],
22
22
  comments: [],
23
- newCommentText: '',
23
+ newCommentText: "",
24
24
  contextFiles: [],
25
25
  descriptionFocused: false,
26
26
  showMarkdownHelp: false,
@@ -32,41 +32,59 @@ describe('TaskForm Component', () => {
32
32
  taxonomies: [],
33
33
  approaches: [],
34
34
  assigneeOptions: [
35
- { value: 'unassigned', label: 'Unassigned', icon: 'HelpCircle', color: 'var(--text-secondary)', kind: 'unassigned' },
36
- { value: 'member-1', label: 'Alex Example', icon: 'User', color: '#22c55e', kind: 'member' },
35
+ {
36
+ value: "unassigned",
37
+ label: "Unassigned",
38
+ icon: "HelpCircle",
39
+ color: "var(--text-secondary)",
40
+ kind: "unassigned",
41
+ },
42
+ {
43
+ value: "member-1",
44
+ label: "Alex Example",
45
+ icon: "User",
46
+ color: "#22c55e",
47
+ kind: "member",
48
+ },
37
49
  ],
38
50
  categories: [
39
- { value: 'general', label: 'General', disabled: false },
40
- { value: 'backend', label: 'Backend', disabled: false }
51
+ { value: "general", label: "General", disabled: false },
52
+ { value: "backend", label: "Backend", disabled: false },
53
+ ],
54
+ types: [
55
+ { value: "feature", label: "Feature" },
56
+ { value: "bug", label: "Bug" },
57
+ ],
58
+ priorities: [
59
+ { value: 3, label: "High" },
60
+ { value: 2, label: "Medium" },
41
61
  ],
42
- types: [{ value: 'feature', label: 'Feature' }, { value: 'bug', label: 'Bug' }],
43
- priorities: [{ value: 3, label: 'High' }, { value: 2, label: 'Medium' }],
44
62
  workstreams: [
45
63
  {
46
- id: 'workstream-1',
64
+ id: "workstream-1",
47
65
  referenceNumber: 12,
48
- referenceLabel: 'WS-12',
49
- title: 'Platform Refinement',
66
+ referenceLabel: "WS-12",
67
+ title: "Platform Refinement",
50
68
  description: null,
51
- createdAt: '2026-04-03T00:00:00.000Z',
69
+ createdAt: "2026-04-03T00:00:00.000Z",
52
70
  },
53
71
  {
54
- id: 'workstream-2',
72
+ id: "workstream-2",
55
73
  referenceNumber: 44,
56
- referenceLabel: 'WS-44',
57
- title: 'Growth Experiments',
74
+ referenceLabel: "WS-44",
75
+ title: "Growth Experiments",
58
76
  description: null,
59
- createdAt: '2026-04-03T00:00:00.000Z',
77
+ createdAt: "2026-04-03T00:00:00.000Z",
60
78
  },
61
79
  ],
62
80
  initiatives: [
63
81
  {
64
- id: 'initiative-1',
82
+ id: "initiative-1",
65
83
  referenceNumber: 3,
66
- referenceLabel: 'IN-3',
67
- title: 'Execution model',
84
+ referenceLabel: "IN-3",
85
+ title: "Execution model",
68
86
  description: null,
69
- createdAt: '2026-04-03T00:00:00.000Z',
87
+ createdAt: "2026-04-03T00:00:00.000Z",
70
88
  },
71
89
  ],
72
90
  copiedId: null,
@@ -90,7 +108,7 @@ describe('TaskForm Component', () => {
90
108
  onShowCategoryManagerChange: vi.fn(),
91
109
  onShowTypeManagerChange: vi.fn(),
92
110
  onConfiguringCategoryChange: vi.fn(),
93
- onSubmit: vi.fn(e => e.preventDefault()),
111
+ onSubmit: vi.fn((e) => e.preventDefault()),
94
112
  onAddComment: vi.fn(),
95
113
  onAddContextFile: vi.fn(),
96
114
  onRemoveContextFile: vi.fn(),
@@ -110,295 +128,325 @@ describe('TaskForm Component', () => {
110
128
  return render(_jsx(TaskForm, { ...defaultProps, ...props }));
111
129
  };
112
130
  // --- Create Task Tests ---
113
- it('3.1 Open add form - Renders correctly', () => {
131
+ it("3.1 Open add form - Renders correctly", () => {
114
132
  renderForm();
115
- expect(screen.getByPlaceholderText('What needs to be done?')).toBeInTheDocument();
116
- expect(screen.getByTitle('Markdown Help')).toBeInTheDocument();
133
+ expect(screen.getByPlaceholderText("What needs to be done?")).toBeInTheDocument();
134
+ expect(screen.getByTitle("Markdown Help")).toBeInTheDocument();
117
135
  });
118
- it('3.2 Title required - Submit disabled if empty', () => {
119
- renderForm({ title: '' });
136
+ it("3.2 Title required - Submit disabled if empty", () => {
137
+ renderForm({ title: "" });
120
138
  // In the implementation, the form submit handles validation, but let's check input attribute
121
- const input = screen.getByPlaceholderText('What needs to be done?');
139
+ const input = screen.getByPlaceholderText("What needs to be done?");
122
140
  expect(input).toBeRequired();
123
141
  });
124
- it('3.3 Create minimal - Title input updates', async () => {
142
+ it("3.3 Create minimal - Title input updates", async () => {
125
143
  const user = userEvent.setup();
126
144
  renderForm();
127
- const input = screen.getByPlaceholderText('What needs to be done?');
128
- await user.type(input, 'New Task');
145
+ const input = screen.getByPlaceholderText("What needs to be done?");
146
+ await user.type(input, "New Task");
129
147
  expect(defaultProps.onTitleChange).toHaveBeenCalled();
130
148
  });
131
- it('3.4 Create with all fields - Description updates', async () => {
149
+ it("3.4 Create with all fields - Description updates", async () => {
132
150
  const user = userEvent.setup();
133
151
  renderForm({ descriptionFocused: true }); // Ensure textarea is rendered
134
- const textarea = screen.getByPlaceholderText('What needs to be done? (Markdown supported)');
135
- await user.type(textarea, 'Details');
152
+ const textarea = screen.getByPlaceholderText("What needs to be done? (Markdown supported)");
153
+ await user.type(textarea, "Details");
136
154
  expect(defaultProps.onDescriptionChange).toHaveBeenCalled();
137
155
  });
138
- it('renders actual newline paragraphs in description preview without escaped text', () => {
156
+ it("renders actual newline paragraphs in description preview without escaped text", () => {
139
157
  const { container } = renderForm({
140
- description: 'Phase 1\n\nPhase 2',
141
- descriptionFocused: false
158
+ description: "Phase 1\n\nPhase 2",
159
+ descriptionFocused: false,
142
160
  });
143
- expect(screen.getByText('Phase 1')).toBeInTheDocument();
144
- expect(screen.getByText('Phase 2')).toBeInTheDocument();
145
- expect(container.textContent).not.toContain('\\n\\n');
161
+ expect(screen.getByText("Phase 1")).toBeInTheDocument();
162
+ expect(screen.getByText("Phase 2")).toBeInTheDocument();
163
+ expect(container.textContent).not.toContain("\\n\\n");
146
164
  });
147
- it('3.5 Category selection', async () => {
165
+ it("3.5 Category selection", async () => {
148
166
  const user = userEvent.setup();
149
167
  renderForm();
150
- const dropdown = screen.getByRole('combobox', { name: /category/i });
168
+ const dropdown = screen.getByRole("combobox", { name: /category/i });
151
169
  await user.click(dropdown);
152
- const option = screen.getByText('Backend');
170
+ const option = screen.getByText("Backend");
153
171
  await user.click(option);
154
- expect(defaultProps.onCategoryChange).toHaveBeenCalledWith('backend');
172
+ expect(defaultProps.onCategoryChange).toHaveBeenCalledWith("backend");
155
173
  });
156
- it('keeps a legacy category selectable when the current task already uses it', async () => {
174
+ it("keeps a legacy category selectable when the current task already uses it", async () => {
157
175
  const user = userEvent.setup();
158
176
  renderForm({
159
- category: 'backend',
177
+ category: "backend",
160
178
  categories: [
161
- { value: 'default', label: 'General', disabled: false },
162
- { value: 'backend', label: 'Backend', disabled: true }
163
- ]
179
+ { value: "default", label: "General", disabled: false },
180
+ { value: "backend", label: "Backend", disabled: true },
181
+ ],
164
182
  });
165
- const dropdown = screen.getByRole('combobox', { name: /category/i });
183
+ const dropdown = screen.getByRole("combobox", { name: /category/i });
166
184
  await user.click(dropdown);
167
- expect(screen.getAllByText('Backend (Legacy)').length).toBeGreaterThan(0);
185
+ expect(screen.getAllByText("Backend (Legacy)").length).toBeGreaterThan(0);
168
186
  });
169
- it('uses the configured default category label in the category picker', async () => {
187
+ it("uses the configured default category label in the category picker", async () => {
170
188
  const user = userEvent.setup();
171
189
  renderForm({
172
190
  categories: [
173
- { value: 'default', label: 'Inbox Work', icon: 'Star', color: 'teal-500', disabled: false },
174
- { value: 'backend', label: 'Backend', disabled: false }
175
- ]
191
+ {
192
+ value: "default",
193
+ label: "Inbox Work",
194
+ icon: "Star",
195
+ color: "teal-500",
196
+ disabled: false,
197
+ },
198
+ { value: "backend", label: "Backend", disabled: false },
199
+ ],
176
200
  });
177
- const dropdown = screen.getByRole('combobox', { name: /category/i });
201
+ const dropdown = screen.getByRole("combobox", { name: /category/i });
178
202
  await user.click(dropdown);
179
- expect(screen.getByText('Inbox Work')).toBeInTheDocument();
180
- expect(screen.queryByText('General')).not.toBeInTheDocument();
203
+ expect(screen.getByText("Inbox Work")).toBeInTheDocument();
204
+ expect(screen.queryByText("General")).not.toBeInTheDocument();
181
205
  });
182
- it('3.6 Type selection', async () => {
206
+ it("3.6 Type selection", async () => {
183
207
  const user = userEvent.setup();
184
208
  renderForm();
185
- const dropdown = screen.getByRole('combobox', { name: /type/i });
209
+ const dropdown = screen.getByRole("combobox", { name: /type/i });
186
210
  await user.click(dropdown);
187
- const option = screen.getByText('Bug');
211
+ const option = screen.getByText("Bug");
188
212
  await user.click(option);
189
- expect(defaultProps.onTypeChange).toHaveBeenCalledWith('bug');
213
+ expect(defaultProps.onTypeChange).toHaveBeenCalledWith("bug");
190
214
  });
191
- it('keeps a retired type selectable when the current task already uses it', async () => {
215
+ it("keeps a retired type selectable when the current task already uses it", async () => {
192
216
  const user = userEvent.setup();
193
217
  renderForm({
194
- type: 'issue',
218
+ type: "issue",
195
219
  types: [
196
- { value: 'task', label: 'Task', status: 'active' },
197
- { value: 'issue', label: 'Issue', status: 'retired' }
198
- ]
220
+ { value: "task", label: "Task", status: "active" },
221
+ { value: "issue", label: "Issue", status: "retired" },
222
+ ],
199
223
  });
200
- const dropdown = screen.getByRole('combobox', { name: /type/i });
224
+ const dropdown = screen.getByRole("combobox", { name: /type/i });
201
225
  await user.click(dropdown);
202
- expect(screen.getAllByText('Issue (Retired)').length).toBeGreaterThan(0);
226
+ expect(screen.getAllByText("Issue (Retired)").length).toBeGreaterThan(0);
203
227
  });
204
- it('3.7 Priority selection', () => {
228
+ it("3.7 Priority selection", () => {
205
229
  renderForm();
206
230
  // LevelSelector uses divs/buttons. Looking at LevelSelector implementation (implied):
207
231
  // It renders options. Let's click one.
208
232
  // Priority and Complexity both have 'High' options.
209
- const priorityOption = screen.getAllByTitle('High')[0];
233
+ const priorityOption = screen.getAllByTitle("High")[0];
210
234
  fireEvent.click(priorityOption);
211
235
  expect(defaultProps.onPriorityChange).toHaveBeenCalledWith(3);
212
236
  });
213
- it('renders workspace member assignee options', async () => {
237
+ it("renders workspace member assignee options", async () => {
214
238
  const user = userEvent.setup();
215
239
  renderForm();
216
- const dropdown = screen.getByRole('combobox', { name: /assigned to/i });
240
+ const dropdown = screen.getByRole("combobox", { name: /assigned to/i });
217
241
  await user.click(dropdown);
218
- expect(screen.getByText('Alex Example')).toBeInTheDocument();
242
+ expect(screen.getByText("Alex Example")).toBeInTheDocument();
219
243
  });
220
- it('shows attach to workstream only on the new task form', () => {
244
+ it("shows attach to workstream only on the new task form", () => {
221
245
  renderForm();
222
- expect(screen.getByText('Attach to workstream')).toBeInTheDocument();
223
- expect(screen.getByRole('combobox', { name: /attach to workstream/i })).toBeInTheDocument();
246
+ expect(screen.getByText("Attach to workstream")).toBeInTheDocument();
247
+ expect(screen.getByRole("combobox", { name: /attach to workstream/i })).toBeInTheDocument();
224
248
  });
225
- it('does not show attach to workstream on the edit task form', () => {
249
+ it("does not show attach to workstream on the edit task form", () => {
226
250
  renderForm({
227
- editingTaskId: 'task-1',
251
+ editingTaskId: "task-1",
228
252
  currentTask: {
229
- id: 'task-1',
230
- title: 'Existing task',
231
- description: '',
232
- category: 'general',
233
- type: 'feature',
253
+ id: "task-1",
254
+ title: "Existing task",
255
+ description: "",
256
+ category: "general",
257
+ type: "feature",
234
258
  priority: 2,
235
- status: 'task',
236
- createdAt: '2026-04-03T00:00:00.000Z',
237
- }
259
+ status: "task",
260
+ createdAt: "2026-04-03T00:00:00.000Z",
261
+ },
238
262
  });
239
- expect(screen.queryByText('Attach to workstream')).not.toBeInTheDocument();
263
+ expect(screen.queryByText("Attach to workstream")).not.toBeInTheDocument();
240
264
  });
241
- it('switches the create-workstream control into manual entry when plus is clicked', async () => {
265
+ it("switches the create-workstream control into manual entry when plus is clicked", async () => {
242
266
  const user = userEvent.setup();
243
267
  renderForm();
244
- await user.click(screen.getByRole('button', { name: /enter workstream by name/i }));
245
- const manualInput = screen.getByRole('textbox', { name: /attach to workstream/i });
268
+ await user.click(screen.getByRole("button", { name: /enter workstream by name/i }));
269
+ const manualInput = screen.getByRole("textbox", {
270
+ name: /attach to workstream/i,
271
+ });
246
272
  expect(manualInput).toBeInTheDocument();
247
- await user.type(manualInput, 'Platform Refinement');
273
+ await user.type(manualInput, "Platform Refinement");
248
274
  expect(defaultProps.onWorkstreamInputChange).toHaveBeenCalled();
249
275
  });
250
- it('renders custom taxonomy descriptions as helper text', () => {
276
+ it("renders custom taxonomy descriptions as helper text", () => {
251
277
  renderForm({
252
- taxonomies: [{
253
- id: 'review-status',
254
- label: 'Review Status',
255
- description: 'Use this to track review routing.',
256
- widgetType: 'select',
278
+ taxonomies: [
279
+ {
280
+ id: "review-status",
281
+ label: "Review Status",
282
+ description: "Use this to track review routing.",
283
+ widgetType: "select",
257
284
  isSystem: false,
258
285
  multiSelect: false,
259
- options: [{ value: 'pending', label: 'Pending', status: 'active' }]
260
- }]
286
+ options: [{ value: "pending", label: "Pending", status: "active" }],
287
+ },
288
+ ],
261
289
  });
262
- expect(screen.getByText('Use this to track review routing.')).toBeInTheDocument();
290
+ expect(screen.getByText("Use this to track review routing.")).toBeInTheDocument();
263
291
  });
264
- it('hides custom taxonomies from the task form when form visibility is disabled', () => {
292
+ it("hides custom taxonomies from the task form when form visibility is disabled", () => {
265
293
  renderForm({
266
- taxonomies: [{
267
- id: 'review-status',
268
- label: 'Review Status',
294
+ taxonomies: [
295
+ {
296
+ id: "review-status",
297
+ label: "Review Status",
269
298
  formEnabled: false,
270
- widgetType: 'select',
299
+ widgetType: "select",
271
300
  isSystem: false,
272
301
  multiSelect: false,
273
- options: [{ value: 'pending', label: 'Pending', status: 'active' }]
274
- }]
302
+ options: [{ value: "pending", label: "Pending", status: "active" }],
303
+ },
304
+ ],
275
305
  });
276
- expect(screen.queryByText('Review Status')).not.toBeInTheDocument();
306
+ expect(screen.queryByText("Review Status")).not.toBeInTheDocument();
277
307
  });
278
- it('keeps a hidden custom taxonomy visible when the current task already has a value', () => {
308
+ it("keeps a hidden custom taxonomy visible when the current task already has a value", () => {
279
309
  renderForm({
280
- formTaxonomies: { 'review-status': 'pending' },
281
- taxonomies: [{
282
- id: 'review-status',
283
- label: 'Review Status',
310
+ formTaxonomies: { "review-status": "pending" },
311
+ taxonomies: [
312
+ {
313
+ id: "review-status",
314
+ label: "Review Status",
284
315
  formEnabled: false,
285
- widgetType: 'select',
316
+ widgetType: "select",
286
317
  isSystem: false,
287
318
  multiSelect: false,
288
- options: [{ value: 'pending', label: 'Pending', status: 'active' }]
289
- }]
319
+ options: [{ value: "pending", label: "Pending", status: "active" }],
320
+ },
321
+ ],
290
322
  });
291
- expect(screen.getByText('Review Status')).toBeInTheDocument();
323
+ expect(screen.getByText("Review Status")).toBeInTheDocument();
292
324
  });
293
- it('renders multi-select custom taxonomies as selectable chips', async () => {
325
+ it("renders multi-select custom taxonomies as selectable chips", async () => {
294
326
  const user = userEvent.setup();
295
327
  renderForm({
296
- taxonomies: [{
297
- id: 'channels',
298
- label: 'Channels',
299
- widgetType: 'select',
328
+ taxonomies: [
329
+ {
330
+ id: "channels",
331
+ label: "Channels",
332
+ widgetType: "select",
300
333
  isSystem: false,
301
334
  multiSelect: true,
302
335
  options: [
303
- { value: 'email', label: 'Email', status: 'active' },
304
- { value: 'social', label: 'Social', status: 'active' }
305
- ]
306
- }]
336
+ { value: "email", label: "Email", status: "active" },
337
+ { value: "social", label: "Social", status: "active" },
338
+ ],
339
+ },
340
+ ],
307
341
  });
308
- await user.click(screen.getByRole('button', { name: 'Email' }));
309
- expect(defaultProps.onTaxonomyChange).toHaveBeenCalledWith('channels', ['email']);
342
+ await user.click(screen.getByRole("button", { name: "Email" }));
343
+ expect(defaultProps.onTaxonomyChange).toHaveBeenCalledWith("channels", [
344
+ "email",
345
+ ]);
310
346
  });
311
- it('keeps selected retired multi-select custom taxonomy options available', () => {
347
+ it("keeps selected retired multi-select custom taxonomy options available", () => {
312
348
  renderForm({
313
- formTaxonomies: { channels: ['email'] },
314
- taxonomies: [{
315
- id: 'channels',
316
- label: 'Channels',
317
- widgetType: 'select',
349
+ formTaxonomies: { channels: ["email"] },
350
+ taxonomies: [
351
+ {
352
+ id: "channels",
353
+ label: "Channels",
354
+ widgetType: "select",
318
355
  isSystem: false,
319
356
  multiSelect: true,
320
357
  options: [
321
- { value: 'email', label: 'Email', status: 'retired' },
322
- { value: 'social', label: 'Social', status: 'active' }
323
- ]
324
- }]
358
+ { value: "email", label: "Email", status: "retired" },
359
+ { value: "social", label: "Social", status: "active" },
360
+ ],
361
+ },
362
+ ],
325
363
  });
326
- expect(screen.getByRole('button', { name: /Email \(Retired\)/i })).toBeInTheDocument();
327
- expect(screen.getByRole('button', { name: 'Social' })).toBeInTheDocument();
364
+ expect(screen.getByRole("button", { name: /Email \(Retired\)/i })).toBeInTheDocument();
365
+ expect(screen.getByRole("button", { name: "Social" })).toBeInTheDocument();
328
366
  });
329
- it('hides retired custom taxonomies from new-task entry when there is no selected value', () => {
367
+ it("hides retired custom taxonomies from new-task entry when there is no selected value", () => {
330
368
  renderForm({
331
- taxonomies: [{
332
- id: 'channels',
333
- label: 'Channels',
334
- status: 'retired',
335
- widgetType: 'select',
369
+ taxonomies: [
370
+ {
371
+ id: "channels",
372
+ label: "Channels",
373
+ status: "retired",
374
+ widgetType: "select",
336
375
  isSystem: false,
337
376
  multiSelect: false,
338
- options: [{ value: 'email', label: 'Email', status: 'active' }]
339
- }]
377
+ options: [{ value: "email", label: "Email", status: "active" }],
378
+ },
379
+ ],
340
380
  });
341
- expect(screen.queryByText('Channels')).not.toBeInTheDocument();
381
+ expect(screen.queryByText("Channels")).not.toBeInTheDocument();
342
382
  });
343
- it('keeps retired custom taxonomies visible when the current task already has a value', () => {
383
+ it("keeps retired custom taxonomies visible when the current task already has a value", () => {
344
384
  renderForm({
345
- formTaxonomies: { channels: 'email' },
346
- taxonomies: [{
347
- id: 'channels',
348
- label: 'Channels',
349
- status: 'retired',
350
- widgetType: 'select',
385
+ formTaxonomies: { channels: "email" },
386
+ taxonomies: [
387
+ {
388
+ id: "channels",
389
+ label: "Channels",
390
+ status: "retired",
391
+ widgetType: "select",
351
392
  isSystem: false,
352
393
  multiSelect: false,
353
- options: [{ value: 'email', label: 'Email', status: 'active' }]
354
- }]
394
+ options: [{ value: "email", label: "Email", status: "active" }],
395
+ },
396
+ ],
355
397
  });
356
- expect(screen.getByText('Channels')).toBeInTheDocument();
398
+ expect(screen.getByText("Channels")).toBeInTheDocument();
357
399
  });
358
400
  // --- Edit Task Tests ---
359
- it('3.10 Load existing task', () => {
360
- renderForm({ editingTaskId: '123', title: 'Existing Task' });
361
- expect(screen.getByDisplayValue('Existing Task')).toBeInTheDocument();
401
+ it("3.10 Load existing task", () => {
402
+ renderForm({ editingTaskId: "123", title: "Existing Task" });
403
+ expect(screen.getByDisplayValue("Existing Task")).toBeInTheDocument();
362
404
  });
363
- it('3.11 ID badge visible in edit mode', () => {
405
+ it("3.11 ID badge visible in edit mode", () => {
364
406
  // Form-level edit mode validation.
365
- renderForm({ editingTaskId: '123' });
407
+ renderForm({ editingTaskId: "123" });
366
408
  expect(screen.getByText(/Activity & Comments/)).toBeInTheDocument();
367
409
  });
368
- it('3.12 Copy ID', async () => {
410
+ it("3.12 Copy ID", async () => {
369
411
  // Header copy action is outside TaskForm; keep form render sanity check.
370
412
  renderForm();
371
- expect(screen.getByPlaceholderText('What needs to be done?')).toBeInTheDocument();
413
+ expect(screen.getByPlaceholderText("What needs to be done?")).toBeInTheDocument();
372
414
  });
373
- it('renders assignee audit activity with readable labels instead of raw ids', () => {
415
+ it("renders assignee audit activity with readable labels instead of raw ids", () => {
374
416
  renderForm({
375
- editingTaskId: '123',
417
+ editingTaskId: "123",
376
418
  showComments: true,
377
419
  assigneeOptions: [
378
420
  ...defaultProps.assigneeOptions,
379
- { value: 'ai-profile-1', label: 'Planner Bot', icon: 'Bot', color: '#8b5cf6', kind: 'agent' },
421
+ {
422
+ value: "ai-profile-1",
423
+ label: "Planner Bot",
424
+ icon: "Bot",
425
+ color: "#8b5cf6",
426
+ kind: "agent",
427
+ },
380
428
  ],
381
429
  currentTask: {
382
- id: '123',
383
- title: 'Existing Task',
430
+ id: "123",
431
+ title: "Existing Task",
384
432
  activity: [
385
433
  {
386
- id: 'event:task-event-1',
387
- type: 'event',
388
- timestamp: '2026-03-23T12:00:00.000Z',
434
+ id: "event:task-event-1",
435
+ type: "event",
436
+ timestamp: "2026-03-23T12:00:00.000Z",
389
437
  event: {
390
- id: 'task-event-1',
391
- taskId: '123',
392
- workspaceId: 'workspace-1',
393
- action: 'task-updated',
394
- actor: 'member-1',
395
- actorType: 'human',
396
- createdAt: '2026-03-23T12:00:00.000Z',
438
+ id: "task-event-1",
439
+ taskId: "123",
440
+ workspaceId: "workspace-1",
441
+ action: "task-updated",
442
+ actor: "member-1",
443
+ actorType: "human",
444
+ createdAt: "2026-03-23T12:00:00.000Z",
397
445
  details: {
398
446
  changes: {
399
447
  assignee: {
400
- from: 'unassigned',
401
- to: 'ai-profile-1',
448
+ from: "unassigned",
449
+ to: "ai-profile-1",
402
450
  },
403
451
  },
404
452
  },
@@ -407,34 +455,34 @@ describe('TaskForm Component', () => {
407
455
  ],
408
456
  },
409
457
  });
410
- expect(screen.getByText('ASSIGNED TO: Unassigned -> Planner Bot')).toBeInTheDocument();
458
+ expect(screen.getByText("Assigned to: Unassigned -> Planner Bot")).toBeInTheDocument();
411
459
  expect(screen.queryByText(/ai-profile-1/i)).not.toBeInTheDocument();
412
460
  });
413
- it('renders planning relationship audit activity with reference labels instead of raw ids', () => {
461
+ it("renders planning relationship audit activity with reference labels instead of raw ids", () => {
414
462
  renderForm({
415
- editingTaskId: '123',
463
+ editingTaskId: "123",
416
464
  showComments: true,
417
465
  currentTask: {
418
- id: '123',
419
- title: 'Existing Task',
466
+ id: "123",
467
+ title: "Existing Task",
420
468
  activity: [
421
469
  {
422
- id: 'event:task-event-relationship',
423
- type: 'event',
424
- timestamp: '2026-03-23T12:00:00.000Z',
470
+ id: "event:task-event-relationship",
471
+ type: "event",
472
+ timestamp: "2026-03-23T12:00:00.000Z",
425
473
  event: {
426
- id: 'task-event-relationship',
427
- taskId: '123',
428
- workspaceId: 'workspace-1',
429
- action: 'task-relationship-changed',
430
- actor: 'member-1',
431
- actorType: 'human',
432
- createdAt: '2026-03-23T12:00:00.000Z',
474
+ id: "task-event-relationship",
475
+ taskId: "123",
476
+ workspaceId: "workspace-1",
477
+ action: "task-relationship-changed",
478
+ actor: "member-1",
479
+ actorType: "human",
480
+ createdAt: "2026-03-23T12:00:00.000Z",
433
481
  details: {
434
482
  changes: {
435
483
  workstreamId: {
436
484
  from: null,
437
- to: 'workstream-1',
485
+ to: "workstream-1",
438
486
  },
439
487
  },
440
488
  },
@@ -443,52 +491,51 @@ describe('TaskForm Component', () => {
443
491
  ],
444
492
  },
445
493
  });
446
- expect(screen.getByText('Workstream: none -> WS-12')).toBeInTheDocument();
494
+ expect(screen.getByText("Workstream changed: none -> WS-12")).toBeInTheDocument();
447
495
  expect(screen.queryByText(/workstream-1/i)).not.toBeInTheDocument();
448
496
  });
449
- it('3.13 Update title', async () => {
497
+ it("3.13 Update title", async () => {
450
498
  const user = userEvent.setup();
451
- renderForm({ editingTaskId: '123', title: 'Old Title' });
452
- const input = screen.getByDisplayValue('Old Title');
499
+ renderForm({ editingTaskId: "123", title: "Old Title" });
500
+ const input = screen.getByDisplayValue("Old Title");
453
501
  await user.clear(input);
454
- await user.type(input, 'New Title');
502
+ await user.type(input, "New Title");
455
503
  expect(defaultProps.onTitleChange).toHaveBeenCalled();
456
504
  });
457
- it('keeps scheduled date editable in the task form', () => {
505
+ it("keeps scheduled date editable in the task form", () => {
458
506
  renderForm({
459
- editingTaskId: 'parent-1',
460
- currentTask: { id: 'parent-1', title: 'Parent Task' },
507
+ editingTaskId: "task-1",
508
+ currentTask: { id: "task-1", title: "Schedulable task" },
461
509
  });
462
- const scheduledLabel = screen.getByText('Scheduled:');
510
+ const scheduledLabel = screen.getByText("Scheduled:");
463
511
  const scheduledInput = scheduledLabel.nextElementSibling;
464
512
  expect(scheduledInput).toBeTruthy();
465
513
  if (!scheduledInput)
466
- throw new Error('Scheduled date input missing');
514
+ throw new Error("Scheduled date input missing");
467
515
  expect(scheduledInput.disabled).toBe(false);
468
- expect(screen.queryByText('Parent tasks cannot be scheduled. Schedule leaf tasks instead.')).not.toBeInTheDocument();
469
516
  });
470
- it('3.14 Context Documents section renders', async () => {
517
+ it("3.14 Context Documents section renders", async () => {
471
518
  const user = userEvent.setup();
472
519
  renderForm();
473
- expect(screen.getByText('Context Documents')).toBeInTheDocument();
474
- await user.click(screen.getByTitle('Show context documents'));
475
- expect(screen.getByRole('button', { name: /upload/i })).toBeInTheDocument();
520
+ expect(screen.getByText("Context Documents")).toBeInTheDocument();
521
+ await user.click(screen.getByTitle("Show context documents"));
522
+ expect(screen.getByRole("button", { name: /upload/i })).toBeInTheDocument();
476
523
  });
477
- it('3.15 Markdown Help Toggle', async () => {
524
+ it("3.15 Markdown Help Toggle", async () => {
478
525
  const user = userEvent.setup();
479
526
  renderForm();
480
- const helpBtn = screen.getByTitle('Markdown Help');
527
+ const helpBtn = screen.getByTitle("Markdown Help");
481
528
  await user.click(helpBtn);
482
529
  expect(defaultProps.onShowMarkdownHelpChange).toHaveBeenCalledWith(true);
483
530
  });
484
- it('3.16 Description Focus', () => {
531
+ it("3.16 Description Focus", () => {
485
532
  renderForm();
486
- renderForm({ description: 'Test Desc', descriptionFocused: false });
487
- const previewDiv = screen.getByText('Test Desc');
533
+ renderForm({ description: "Test Desc", descriptionFocused: false });
534
+ const previewDiv = screen.getByText("Test Desc");
488
535
  fireEvent.click(previewDiv);
489
536
  expect(defaultProps.onDescriptionFocusedChange).toHaveBeenCalledWith(true);
490
537
  });
491
- it('3.18 Category Manager Toggle', async () => {
538
+ it("3.18 Category Manager Toggle", async () => {
492
539
  const user = userEvent.setup();
493
540
  renderForm();
494
541
  // The + button next to Category label
@@ -517,252 +564,382 @@ describe('TaskForm Component', () => {
517
564
  const { container } = renderForm();
518
565
  // Select the first button with class manageLink (assuming style imports work or we use classname)
519
566
  // Styles are imported as object `styles`. In test, classes are mocked or stringified?
520
- // Vitest CSS handling usually preserves class names if using `identity-obj-proxy` or similar,
567
+ // Vitest CSS handling usually preserves class names if using `identity-obj-proxy` or similar,
521
568
  // but here we have `css: true` in vitest config.
522
569
  // It might be hashed.
523
570
  // Fallback: The button contains an SVG.
524
571
  // Let's try to find the button by its visual role.
525
572
  // It is `tabIndex={-1}`.
526
- const buttons = screen.getAllByRole('button', { hidden: true }); // tabIndex -1 might hide it from accessibility tree? No.
573
+ const buttons = screen.getAllByRole("button", { hidden: true }); // tabIndex -1 might hide it from accessibility tree? No.
527
574
  // Let's skip this specific test for now or try to match by association.
528
575
  expect(true).toBe(true);
529
576
  });
530
577
  // --- Unsaved Changes Tests ---
531
578
  // These logic reside in the Parent (Taskforce), checking state vs original.
532
- // TaskForm just calls `onChange`.
579
+ // TaskForm just calls `onChange`.
533
580
  // So "Test 3.19 Trigger warning" is an INTEGRATION test for Taskforce, not Unit test for TaskForm.
534
581
  // But we CAN verify that TaskForm calls the change handlers correctly, which we did in 3.3, 3.4.
535
582
  // --- Comments ---
536
- it('3.23 Expand comments', async () => {
537
- const user = userEvent.setup();
538
- renderForm({ editingTaskId: '123' });
539
- const toggle = screen.getByTitle('Show Comments');
540
- await user.click(toggle);
541
- expect(defaultProps.onShowCommentsChange).toHaveBeenCalledWith(true);
583
+ it("3.23 Shows the activity sidebar in edit mode", () => {
584
+ renderForm({ editingTaskId: "123" });
585
+ expect(screen.getByText("Activity & Comments")).toBeInTheDocument();
586
+ expect(screen.getByPlaceholderText("Type a comment...")).toBeInTheDocument();
587
+ });
588
+ it("scrolls the activity thread to the latest item on open", () => {
589
+ const originalScrollIntoView = HTMLElement.prototype.scrollIntoView;
590
+ const scrollIntoView = vi.fn();
591
+ Object.defineProperty(HTMLElement.prototype, "scrollIntoView", {
592
+ configurable: true,
593
+ value: scrollIntoView,
594
+ });
595
+ try {
596
+ renderForm({
597
+ editingTaskId: "123",
598
+ comments: [
599
+ {
600
+ id: "comment-scroll-1",
601
+ author: "member-1",
602
+ text: "Latest comment",
603
+ timestamp: "2026-03-22T10:10:00.000Z",
604
+ },
605
+ ],
606
+ });
607
+ expect(scrollIntoView).toHaveBeenCalled();
608
+ }
609
+ finally {
610
+ if (originalScrollIntoView) {
611
+ Object.defineProperty(HTMLElement.prototype, "scrollIntoView", {
612
+ configurable: true,
613
+ value: originalScrollIntoView,
614
+ });
615
+ }
616
+ else {
617
+ delete HTMLElement.prototype.scrollIntoView;
618
+ }
619
+ }
542
620
  });
543
- it('3.24 Add comment', async () => {
621
+ it("3.24 Add comment", async () => {
544
622
  const user = userEvent.setup();
545
- renderForm({ editingTaskId: '123', showComments: true, newCommentText: 'Nice work' });
546
- const sendBtn = screen.getByRole('button', { name: '' }); // Send icon has no text?
547
- // The button has <Send /> icon.
548
- // We can find by class or hierarchy.
549
- // Or find the textarea and the button next to it.
550
- const textarea = screen.getByPlaceholderText('Type a comment...');
551
- const button = textarea.nextSibling; // Assuming button is next sibling
552
- await user.click(button);
623
+ renderForm({ editingTaskId: "123", newCommentText: "Nice work" });
624
+ await user.click(screen.getByRole("button", { name: /send comment/i }));
553
625
  expect(defaultProps.onAddComment).toHaveBeenCalled();
554
626
  });
555
- it('renders named AI comment authors from resolved actor data', () => {
627
+ it("renders named AI comment authors from resolved actor data", () => {
556
628
  renderForm({
557
- editingTaskId: '123',
629
+ editingTaskId: "123",
558
630
  showComments: true,
559
631
  comments: [
560
632
  {
561
- id: 'comment-1',
562
- author: 'ai-profile-123',
563
- text: 'I reviewed the task details.',
564
- timestamp: '2026-03-22T10:00:00.000Z',
633
+ id: "comment-1",
634
+ author: "ai-profile-123",
635
+ text: "I reviewed the task details.",
636
+ timestamp: "2026-03-22T10:00:00.000Z",
565
637
  actor: {
566
- id: 'ai-profile-123',
567
- kind: 'ai',
568
- label: 'Codex',
569
- username: 'codex',
570
- icon: 'Bot',
571
- color: '#0ea5e9',
638
+ id: "ai-profile-123",
639
+ kind: "ai",
640
+ label: "Codex",
641
+ username: "codex",
642
+ icon: "Bot",
643
+ color: "#0ea5e9",
572
644
  isNamed: true,
573
645
  },
574
646
  },
575
647
  ],
576
648
  });
577
- expect(screen.getByText('Codex')).toBeInTheDocument();
578
- expect(screen.queryByText('AI Agent')).not.toBeInTheDocument();
649
+ expect(screen.getByText("Codex")).toBeInTheDocument();
650
+ expect(screen.queryByText("AI Agent")).not.toBeInTheDocument();
579
651
  });
580
- it('renders task audit events in the activity feed', () => {
652
+ it("renders task audit events in the activity feed", () => {
581
653
  renderForm({
582
- editingTaskId: '123',
654
+ editingTaskId: "123",
583
655
  showComments: true,
584
656
  currentTask: {
585
- id: 'task-activity-1',
586
- title: 'Activity Task',
587
- status: 'in-progress',
588
- createdAt: '2026-03-22T09:00:00.000Z',
657
+ id: "task-activity-1",
658
+ title: "Activity Task",
659
+ status: "in-progress",
660
+ createdAt: "2026-03-22T09:00:00.000Z",
661
+ activity: [
662
+ {
663
+ id: "event:1",
664
+ type: "event",
665
+ timestamp: "2026-03-22T10:00:00.000Z",
666
+ event: {
667
+ id: "event-1",
668
+ taskId: "task-activity-1",
669
+ workspaceId: "default",
670
+ action: "task-status-changed",
671
+ actor: "user",
672
+ actorType: "human",
673
+ createdAt: "2026-03-22T10:00:00.000Z",
674
+ details: {
675
+ changes: {
676
+ status: { from: "task", to: "in-progress" },
677
+ },
678
+ },
679
+ },
680
+ },
681
+ ],
682
+ },
683
+ });
684
+ expect(screen.getByText("Status changed: Task -> In Progress")).toBeInTheDocument();
685
+ expect(screen.queryByText("System")).not.toBeInTheDocument();
686
+ });
687
+ it("captures comments alongside audit events and avoids duplicate comment event rows", () => {
688
+ renderForm({
689
+ editingTaskId: "123",
690
+ comments: [
691
+ {
692
+ id: "comment-activity-1",
693
+ author: "user",
694
+ text: "This comment should stay visible.",
695
+ timestamp: "2026-03-22T10:05:00.000Z",
696
+ },
697
+ ],
698
+ currentTask: {
699
+ id: "task-activity-with-comment",
700
+ title: "Activity with comment",
701
+ status: "task",
702
+ createdAt: "2026-03-22T09:00:00.000Z",
703
+ activity: [
704
+ {
705
+ id: "event:status-1",
706
+ type: "event",
707
+ timestamp: "2026-03-22T10:00:00.000Z",
708
+ event: {
709
+ id: "status-1",
710
+ taskId: "task-activity-with-comment",
711
+ workspaceId: "default",
712
+ action: "task-status-changed",
713
+ actor: "user",
714
+ actorType: "human",
715
+ createdAt: "2026-03-22T10:00:00.000Z",
716
+ details: {
717
+ changes: {
718
+ status: { from: "task", to: "in-progress" },
719
+ },
720
+ },
721
+ },
722
+ },
723
+ {
724
+ id: "event:comment-added-1",
725
+ type: "event",
726
+ timestamp: "2026-03-22T10:05:00.000Z",
727
+ event: {
728
+ id: "comment-added-1",
729
+ taskId: "task-activity-with-comment",
730
+ workspaceId: "default",
731
+ action: "task-comment-added",
732
+ actor: "user",
733
+ actorType: "human",
734
+ createdAt: "2026-03-22T10:05:00.000Z",
735
+ },
736
+ },
737
+ ],
738
+ },
739
+ });
740
+ expect(screen.getByText("This comment should stay visible.")).toBeInTheDocument();
741
+ expect(screen.getByText("Status changed: Task -> In Progress")).toBeInTheDocument();
742
+ expect(screen.queryByText("Comment added")).not.toBeInTheDocument();
743
+ });
744
+ it("does not repeat the primary activity change in multi-change event details", () => {
745
+ renderForm({
746
+ editingTaskId: "123",
747
+ assigneeOptions: [
748
+ ...defaultProps.assigneeOptions,
749
+ {
750
+ value: "member-2",
751
+ label: "Tiffany Case",
752
+ icon: "User",
753
+ color: "#22c55e",
754
+ kind: "member",
755
+ },
756
+ ],
757
+ currentTask: {
758
+ id: "task-multi-change",
759
+ title: "Multi change task",
760
+ status: "in-progress",
761
+ createdAt: "2026-03-22T09:00:00.000Z",
589
762
  activity: [
590
763
  {
591
- id: 'event:1',
592
- type: 'event',
593
- timestamp: '2026-03-22T10:00:00.000Z',
764
+ id: "event:multi-change-1",
765
+ type: "event",
766
+ timestamp: "2026-03-22T10:00:00.000Z",
594
767
  event: {
595
- id: 'event-1',
596
- taskId: 'task-activity-1',
597
- workspaceId: 'default',
598
- action: 'task-status-changed',
599
- actor: 'user',
600
- actorType: 'human',
601
- createdAt: '2026-03-22T10:00:00.000Z',
768
+ id: "multi-change-1",
769
+ taskId: "task-multi-change",
770
+ workspaceId: "default",
771
+ action: "task-status-changed",
772
+ actor: "user",
773
+ actorType: "human",
774
+ createdAt: "2026-03-22T10:00:00.000Z",
602
775
  details: {
603
776
  changes: {
604
- status: { from: 'task', to: 'in-progress' }
605
- }
606
- }
607
- }
608
- }
609
- ]
777
+ status: { from: "task", to: "in-progress" },
778
+ assignee: { from: "unassigned", to: "member-2" },
779
+ },
780
+ },
781
+ },
782
+ },
783
+ ],
610
784
  },
611
785
  });
612
- expect(screen.getByText('Status: task -> in-progress')).toBeInTheDocument();
613
- expect(screen.queryByText('System')).not.toBeInTheDocument();
786
+ expect(screen.getByText("Status changed: Task -> In Progress")).toBeInTheDocument();
787
+ expect(screen.getByText("Assigned to: Unassigned -> Tiffany Case")).toBeInTheDocument();
788
+ expect(screen.queryByText("Status: Task -> In Progress")).not.toBeInTheDocument();
614
789
  });
615
- it('summarizes description audit events without rendering the full description body', () => {
790
+ it("summarizes description audit events without rendering the full description body", () => {
616
791
  renderForm({
617
- editingTaskId: '123',
792
+ editingTaskId: "123",
618
793
  showComments: true,
619
794
  currentTask: {
620
- id: 'task-activity-2',
621
- title: 'Description Audit Task',
622
- status: 'task',
623
- createdAt: '2026-03-22T09:00:00.000Z',
795
+ id: "task-activity-2",
796
+ title: "Description Audit Task",
797
+ status: "task",
798
+ createdAt: "2026-03-22T09:00:00.000Z",
624
799
  activity: [
625
800
  {
626
- id: 'event:2',
627
- type: 'event',
628
- timestamp: '2026-03-22T10:00:00.000Z',
801
+ id: "event:2",
802
+ type: "event",
803
+ timestamp: "2026-03-22T10:00:00.000Z",
629
804
  event: {
630
- id: 'event-2',
631
- taskId: 'task-activity-2',
632
- workspaceId: 'default',
633
- action: 'task-updated',
634
- actor: 'user',
635
- actorType: 'human',
636
- createdAt: '2026-03-22T10:00:00.000Z',
805
+ id: "event-2",
806
+ taskId: "task-activity-2",
807
+ workspaceId: "default",
808
+ action: "task-updated",
809
+ actor: "user",
810
+ actorType: "human",
811
+ createdAt: "2026-03-22T10:00:00.000Z",
637
812
  details: {
638
813
  changes: {
639
814
  description: {
640
- from: '',
641
- to: 'Line one\n\nLine two\n\nLine three'
642
- }
643
- }
644
- }
645
- }
646
- }
647
- ]
815
+ from: "",
816
+ to: "Line one\n\nLine two\n\nLine three",
817
+ },
818
+ },
819
+ },
820
+ },
821
+ },
822
+ ],
648
823
  },
649
824
  });
650
- expect(screen.getByText('DESCRIPTION: added')).toBeInTheDocument();
825
+ expect(screen.getByText("Description: added")).toBeInTheDocument();
651
826
  expect(screen.queryByText(/Line one/)).not.toBeInTheDocument();
652
827
  expect(screen.queryByText(/Line two/)).not.toBeInTheDocument();
653
828
  expect(screen.queryByText(/Line three/)).not.toBeInTheDocument();
654
829
  });
655
- it('summarizes title audit events without rendering before and after title text', () => {
830
+ it("summarizes title audit events without rendering before and after title text", () => {
656
831
  renderForm({
657
- editingTaskId: '123',
832
+ editingTaskId: "123",
658
833
  showComments: true,
659
834
  currentTask: {
660
- id: 'task-activity-3',
661
- title: 'Renamed task',
662
- status: 'task',
663
- createdAt: '2026-03-22T09:00:00.000Z',
835
+ id: "task-activity-3",
836
+ title: "Renamed task",
837
+ status: "task",
838
+ createdAt: "2026-03-22T09:00:00.000Z",
664
839
  activity: [
665
840
  {
666
- id: 'event:3',
667
- type: 'event',
668
- timestamp: '2026-03-22T10:00:00.000Z',
841
+ id: "event:3",
842
+ type: "event",
843
+ timestamp: "2026-03-22T10:00:00.000Z",
669
844
  event: {
670
- id: 'event-3',
671
- taskId: 'task-activity-3',
672
- workspaceId: 'default',
673
- action: 'task-updated',
674
- actor: 'user',
675
- actorType: 'human',
676
- createdAt: '2026-03-22T10:00:00.000Z',
845
+ id: "event-3",
846
+ taskId: "task-activity-3",
847
+ workspaceId: "default",
848
+ action: "task-updated",
849
+ actor: "user",
850
+ actorType: "human",
851
+ createdAt: "2026-03-22T10:00:00.000Z",
677
852
  details: {
678
853
  changes: {
679
854
  title: {
680
- from: 'Old title',
681
- to: 'New title'
682
- }
683
- }
684
- }
685
- }
686
- }
687
- ]
855
+ from: "Old title",
856
+ to: "New title",
857
+ },
858
+ },
859
+ },
860
+ },
861
+ },
862
+ ],
688
863
  },
689
864
  });
690
- expect(screen.getByText('TITLE: updated')).toBeInTheDocument();
691
- expect(screen.queryByText('Old title')).not.toBeInTheDocument();
692
- expect(screen.queryByText('New title')).not.toBeInTheDocument();
865
+ expect(screen.getByText("Title: updated")).toBeInTheDocument();
866
+ expect(screen.queryByText("Old title")).not.toBeInTheDocument();
867
+ expect(screen.queryByText("New title")).not.toBeInTheDocument();
693
868
  });
694
- it('falls back to the current task actor label for AI comments when only the id is present', () => {
869
+ it("falls back to the current task actor label for AI comments when only the id is present", () => {
695
870
  renderForm({
696
- editingTaskId: '123',
871
+ editingTaskId: "123",
697
872
  showComments: true,
698
873
  currentTask: {
699
- id: '123',
700
- title: 'Codex-owned task',
701
- assignee: 'ai-profile-123',
874
+ id: "123",
875
+ title: "Codex-owned task",
876
+ assignee: "ai-profile-123",
702
877
  assigneeActor: {
703
- id: 'ai-profile-123',
704
- kind: 'ai',
705
- label: 'Codex',
706
- username: 'codex',
707
- icon: 'Bot',
708
- color: '#0ea5e9',
878
+ id: "ai-profile-123",
879
+ kind: "ai",
880
+ label: "Codex",
881
+ username: "codex",
882
+ icon: "Bot",
883
+ color: "#0ea5e9",
709
884
  isNamed: true,
710
885
  },
711
886
  },
712
887
  comments: [
713
888
  {
714
- id: 'comment-2',
715
- author: 'ai-profile-123',
716
- text: 'Rendered from task actor fallback.',
717
- timestamp: '2026-03-22T10:05:00.000Z',
889
+ id: "comment-2",
890
+ author: "ai-profile-123",
891
+ text: "Rendered from task actor fallback.",
892
+ timestamp: "2026-03-22T10:05:00.000Z",
718
893
  },
719
894
  ],
720
895
  });
721
- expect(screen.getByText('Codex')).toBeInTheDocument();
896
+ expect(screen.getByText("Codex")).toBeInTheDocument();
722
897
  expect(screen.queryByText(/019d|ai-profile/i)).not.toBeInTheDocument();
723
898
  });
724
- it('uses the resolved actor icon and color tint for named AI comments', () => {
899
+ it("uses the resolved actor icon and color tint for named AI comments", () => {
725
900
  const { container } = renderForm({
726
- editingTaskId: '123',
901
+ editingTaskId: "123",
727
902
  showComments: true,
728
903
  comments: [
729
904
  {
730
- id: 'comment-3',
731
- author: 'claude',
732
- text: 'Resolved actor presentation.',
733
- timestamp: '2026-03-22T10:10:00.000Z',
905
+ id: "comment-3",
906
+ author: "claude",
907
+ text: "Resolved actor presentation.",
908
+ timestamp: "2026-03-22T10:10:00.000Z",
734
909
  actor: {
735
- id: 'ai-profile-claude',
736
- kind: 'ai',
737
- label: 'Claude',
738
- username: 'claude',
739
- icon: 'Bot',
740
- color: '#f59e0b',
910
+ id: "ai-profile-claude",
911
+ kind: "ai",
912
+ label: "Claude",
913
+ username: "claude",
914
+ icon: "Bot",
915
+ color: "#f59e0b",
741
916
  isNamed: true,
742
917
  },
743
918
  },
744
919
  ],
745
920
  });
746
- expect(container.querySelector('.lucide-bot')).toBeTruthy();
747
- const bubble = screen.getByText('Resolved actor presentation.').closest('[class*="commentText"]');
921
+ expect(container.querySelector(".lucide-bot")).toBeTruthy();
922
+ const bubble = screen
923
+ .getByText("Resolved actor presentation.")
924
+ .closest('[class*="commentText"]');
748
925
  expect(bubble).toHaveStyle({
749
- borderColor: 'rgba(245, 158, 11, 0.35)',
926
+ borderColor: "rgba(245, 158, 11, 0.35)",
750
927
  });
751
928
  });
752
929
  // --- Context Upload ---
753
- it('3.25 Render context image', async () => {
930
+ it("3.25 Render context image", async () => {
754
931
  const user = userEvent.setup();
755
- renderForm({ contextFiles: [{ path: 'img.png', timestamp: '' }] });
756
- await user.click(screen.getByTitle('Show context documents'));
932
+ renderForm({ contextFiles: [{ path: "img.png", timestamp: "" }] });
933
+ await user.click(screen.getByTitle("Show context documents"));
757
934
  const img = screen.getByAltText(/Context file 0/i);
758
935
  expect(img).toBeInTheDocument();
759
- expect(img).toHaveAttribute('src', 'img.png');
936
+ expect(img).toHaveAttribute("src", "img.png");
760
937
  });
761
- it('3.26 Remove context file', async () => {
938
+ it("3.26 Remove context file", async () => {
762
939
  const user = userEvent.setup();
763
- renderForm({ contextFiles: [{ path: 'img.png', timestamp: '' }] });
764
- await user.click(screen.getByTitle('Show context documents'));
765
- const deleteBtn = screen.getByTitle('Delete');
940
+ renderForm({ contextFiles: [{ path: "img.png", timestamp: "" }] });
941
+ await user.click(screen.getByTitle("Show context documents"));
942
+ const deleteBtn = screen.getByTitle("Delete");
766
943
  await user.click(deleteBtn);
767
944
  expect(defaultProps.onRemoveContextFile).toHaveBeenCalledWith(0);
768
945
  });