@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
@@ -7,9 +7,15 @@ describe('AgentsModule', () => {
7
7
  vi.restoreAllMocks();
8
8
  });
9
9
  it('groups registered AI profiles by surface type in a stable section order', async () => {
10
- const fetchMock = vi.fn().mockResolvedValue({
10
+ const fetchMock = vi.fn()
11
+ .mockResolvedValueOnce({
11
12
  ok: true,
12
13
  json: async () => ({
14
+ aiProfileSeatUsage: {
15
+ used: 2,
16
+ limit: null,
17
+ remaining: null
18
+ },
13
19
  assignees: [
14
20
  { value: 'member-1', label: 'Member One', kind: 'member' },
15
21
  {
@@ -20,6 +26,7 @@ describe('AgentsModule', () => {
20
26
  color: '#10a37f',
21
27
  kind: 'agent',
22
28
  surfaceType: 'chat_app',
29
+ seatScope: 'cloud_metered',
23
30
  description: 'General-purpose chat surface for planning and writing.',
24
31
  role: 'Product copilot',
25
32
  provider: 'OpenAI',
@@ -36,6 +43,7 @@ describe('AgentsModule', () => {
36
43
  color: '#0ea5e9',
37
44
  kind: 'agent',
38
45
  surfaceType: 'coding_tool',
46
+ seatScope: 'local_unmetered',
39
47
  description: 'Terminal-first coding agent.',
40
48
  role: 'Implementation partner',
41
49
  provider: 'OpenAI',
@@ -52,6 +60,7 @@ describe('AgentsModule', () => {
52
60
  color: '#0ea5e9',
53
61
  kind: 'agent',
54
62
  surfaceType: 'coding_tool',
63
+ seatScope: 'local_unmetered',
55
64
  description: 'Duplicate Codex profile.',
56
65
  role: 'Implementation partner',
57
66
  provider: 'OpenAI',
@@ -68,6 +77,7 @@ describe('AgentsModule', () => {
68
77
  color: '#64748b',
69
78
  kind: 'agent',
70
79
  surfaceType: null,
80
+ seatScope: 'cloud_metered',
71
81
  createdAt: '2026-03-28T08:00:00.000Z',
72
82
  updatedAt: '2026-04-01T08:00:00.000Z',
73
83
  lastActiveAt: null
@@ -76,7 +86,7 @@ describe('AgentsModule', () => {
76
86
  })
77
87
  });
78
88
  vi.stubGlobal('fetch', fetchMock);
79
- const { container } = render(_jsx(AgentsModule, { workspaceId: "workspace-1" }));
89
+ const { container } = render(_jsx(AgentsModule, { workspaceId: "workspace-1", cloudAuthConfigured: true, authSessionResolved: true, isAuthenticated: true, cloudAiProfileSeatUsage: { used: 2, limit: 5, remaining: 3 } }));
80
90
  await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
81
91
  expect((await screen.findAllByText('Chat Apps')).length).toBeGreaterThan(0);
82
92
  expect(screen.getAllByText('Coding Tools').length).toBeGreaterThan(0);
@@ -84,19 +94,259 @@ describe('AgentsModule', () => {
84
94
  expect(screen.getAllByText('Codex')).toHaveLength(1);
85
95
  expect(screen.getByText(/2 duplicates/i)).toBeInTheDocument();
86
96
  expect(screen.getByText('Coding tool')).toBeInTheDocument();
97
+ expect(screen.getAllByText('Cloud MCP').length).toBeGreaterThan(0);
98
+ expect(screen.getAllByText('Local MCP').length).toBeGreaterThan(0);
99
+ expect(screen.getByText('Registered Cloud Agents: 2/5')).toBeInTheDocument();
87
100
  expect(screen.getAllByText('General-purpose chat surface for planning and writing.').length).toBeGreaterThan(0);
88
101
  expect(screen.getAllByText('Product copilot').length).toBeGreaterThan(0);
89
- expect(screen.getByText('OpenAI · gpt-5.4')).toBeInTheDocument();
90
- expect(screen.getByText(/Profile instances/i)).toBeInTheDocument();
102
+ expect(screen.getAllByText('OpenAI').length).toBeGreaterThan(0);
103
+ expect(screen.getAllByText('gpt-5.4').length).toBeGreaterThan(0);
104
+ expect(screen.queryByText(/Profile instances/i)).not.toBeInTheDocument();
105
+ expect(screen.queryByText(/Choose a keeper here if duplicates need to be merged/i)).not.toBeInTheDocument();
91
106
  const text = container.textContent || '';
92
107
  expect(text.indexOf('Chat Apps')).toBeLessThan(text.indexOf('Coding Tools'));
93
108
  expect(text.indexOf('Coding Tools')).toBeLessThan(text.indexOf('Unclassified'));
94
109
  fireEvent.click(screen.getByRole('button', { name: /codex/i }));
95
110
  expect(screen.getAllByText('Terminal-first coding agent.').length).toBeGreaterThan(0);
96
111
  expect(screen.getAllByText('Implementation partner')[0]).toBeInTheDocument();
97
- expect(screen.getByText('OpenAI · gpt-5-codex')).toBeInTheDocument();
112
+ expect(screen.getAllByText('OpenAI').length).toBeGreaterThan(0);
113
+ expect(screen.getAllByText('gpt-5-codex').length).toBeGreaterThan(0);
98
114
  expect(screen.getByText(/2 linked/i)).toBeInTheDocument();
115
+ expect(screen.getByText(/Profile instances/i)).toBeInTheDocument();
116
+ expect(screen.getByText(/Choose a keeper here if duplicates need to be merged/i)).toBeInTheDocument();
99
117
  expect(screen.getAllByTitle('Keep this profile, merge others into it')).toHaveLength(2);
100
118
  expect(screen.getByText('@codex-duplicate')).toBeInTheDocument();
119
+ expect(screen.getAllByTitle('Cloud MCP').length).toBeGreaterThan(0);
120
+ expect(screen.getAllByTitle('Local MCP').length).toBeGreaterThan(0);
121
+ });
122
+ it('removes a profile from the active roster and reloads the list', async () => {
123
+ const confirmMock = vi.fn().mockReturnValue(true);
124
+ const fetchMock = vi.fn()
125
+ .mockResolvedValueOnce({
126
+ ok: true,
127
+ json: async () => ({
128
+ aiProfileSeatUsage: {
129
+ used: 1,
130
+ limit: null,
131
+ remaining: null
132
+ },
133
+ assignees: [
134
+ {
135
+ value: 'ai-codex',
136
+ label: 'Codex',
137
+ username: 'codex',
138
+ icon: 'Bot',
139
+ color: '#0ea5e9',
140
+ kind: 'agent',
141
+ surfaceType: 'coding_tool',
142
+ description: 'Terminal-first coding agent.',
143
+ role: 'Implementation partner',
144
+ provider: 'OpenAI',
145
+ model: 'gpt-5-codex',
146
+ createdAt: '2026-04-02T09:00:00.000Z',
147
+ updatedAt: '2026-04-04T13:15:00.000Z',
148
+ lastActiveAt: '2026-04-04T13:45:00.000Z'
149
+ }
150
+ ]
151
+ })
152
+ })
153
+ .mockResolvedValueOnce({
154
+ ok: true,
155
+ json: async () => ({
156
+ profile: {
157
+ id: 'ai-codex',
158
+ archivedReason: 'manual_archive'
159
+ }
160
+ })
161
+ })
162
+ .mockResolvedValueOnce({
163
+ ok: true,
164
+ json: async () => ({
165
+ aiProfileSeatUsage: {
166
+ used: 0,
167
+ limit: null,
168
+ remaining: null
169
+ },
170
+ assignees: []
171
+ })
172
+ });
173
+ vi.stubGlobal('fetch', fetchMock);
174
+ vi.stubGlobal('confirm', confirmMock);
175
+ render(_jsx(AgentsModule, { workspaceId: "workspace-1", cloudAuthConfigured: true, authSessionResolved: true, isAuthenticated: true, cloudAiProfileSeatUsage: { used: 1, limit: 3, remaining: 2 } }));
176
+ expect(await screen.findByRole('button', { name: /codex/i })).toBeInTheDocument();
177
+ fireEvent.click(screen.getByRole('button', { name: /remove from roster/i }));
178
+ await waitFor(() => expect(confirmMock).toHaveBeenCalledTimes(1));
179
+ expect(fetchMock).toHaveBeenNthCalledWith(2, '/api/taskforce/workspace/ai-profiles/archive', expect.objectContaining({
180
+ method: 'POST',
181
+ credentials: 'include'
182
+ }));
183
+ expect(fetchMock).toHaveBeenNthCalledWith(3, '/api/taskforce/workspace/assignee-options?workspaceId=workspace-1', expect.objectContaining({
184
+ credentials: 'include'
185
+ }));
186
+ expect(await screen.findByText('AI profile removed from active roster.')).toBeInTheDocument();
187
+ expect(await screen.findByText('No AI profiles registered yet.')).toBeInTheDocument();
188
+ });
189
+ it('shows archive permission errors inline above the remove button', async () => {
190
+ const confirmMock = vi.fn().mockReturnValue(true);
191
+ const fetchMock = vi.fn()
192
+ .mockResolvedValueOnce({
193
+ ok: true,
194
+ json: async () => ({
195
+ aiProfileSeatUsage: {
196
+ used: 1,
197
+ limit: null,
198
+ remaining: null
199
+ },
200
+ assignees: [
201
+ {
202
+ value: 'ai-codex',
203
+ label: 'Codex',
204
+ username: 'codex',
205
+ icon: 'Bot',
206
+ color: '#0ea5e9',
207
+ kind: 'agent',
208
+ surfaceType: 'coding_tool',
209
+ description: 'Terminal-first coding agent.',
210
+ role: 'Implementation partner',
211
+ provider: 'OpenAI',
212
+ model: 'gpt-5-codex',
213
+ createdAt: '2026-04-02T09:00:00.000Z',
214
+ updatedAt: '2026-04-04T13:15:00.000Z',
215
+ lastActiveAt: '2026-04-04T13:45:00.000Z'
216
+ }
217
+ ]
218
+ })
219
+ })
220
+ .mockResolvedValueOnce({
221
+ ok: false,
222
+ json: async () => ({
223
+ error: 'Forbidden: owner or admin role required.'
224
+ })
225
+ });
226
+ vi.stubGlobal('fetch', fetchMock);
227
+ vi.stubGlobal('confirm', confirmMock);
228
+ render(_jsx(AgentsModule, { workspaceId: "workspace-1", cloudAuthConfigured: true, authSessionResolved: true, isAuthenticated: true, cloudAiProfileSeatUsage: { used: 1, limit: 3, remaining: 2 } }));
229
+ expect(await screen.findByRole('button', { name: /codex/i })).toBeInTheDocument();
230
+ fireEvent.click(screen.getByRole('button', { name: /remove from roster/i }));
231
+ const inlineError = await screen.findByRole('alert');
232
+ expect(inlineError).toHaveTextContent('Forbidden: owner or admin role required.');
233
+ const removeButton = screen.getByRole('button', { name: /remove from roster/i });
234
+ expect(removeButton.compareDocumentPosition(inlineError) & Node.DOCUMENT_POSITION_PRECEDING).toBeTruthy();
235
+ });
236
+ it('falls back to local seat usage only when cloud auth is not configured', async () => {
237
+ const fetchMock = vi.fn()
238
+ .mockResolvedValueOnce({
239
+ ok: true,
240
+ json: async () => ({
241
+ aiProfileSeatUsage: {
242
+ used: 1,
243
+ limit: 3,
244
+ remaining: 2
245
+ },
246
+ assignees: [
247
+ {
248
+ value: 'ai-codex',
249
+ label: 'Codex',
250
+ username: 'codex',
251
+ icon: 'Bot',
252
+ color: '#0ea5e9',
253
+ kind: 'agent',
254
+ surfaceType: 'coding_tool',
255
+ seatScope: 'local_unmetered',
256
+ createdAt: '2026-04-02T09:00:00.000Z',
257
+ updatedAt: '2026-04-04T13:15:00.000Z',
258
+ lastActiveAt: '2026-04-04T13:45:00.000Z'
259
+ }
260
+ ]
261
+ })
262
+ });
263
+ vi.stubGlobal('fetch', fetchMock);
264
+ render(_jsx(AgentsModule, { workspaceId: "workspace-1" }));
265
+ expect(await screen.findByText('Registered Cloud Agents: 1/3')).toBeInTheDocument();
266
+ });
267
+ it('shows no cloud seat label when cloud auth is configured but cloud seat usage is unavailable', async () => {
268
+ const fetchMock = vi.fn()
269
+ .mockResolvedValueOnce({
270
+ ok: true,
271
+ json: async () => ({
272
+ aiProfileSeatUsage: {
273
+ used: 0,
274
+ limit: null,
275
+ remaining: null
276
+ },
277
+ assignees: [
278
+ {
279
+ value: 'ai-codex',
280
+ label: 'Codex',
281
+ username: 'codex',
282
+ icon: 'Bot',
283
+ color: '#0ea5e9',
284
+ kind: 'agent',
285
+ surfaceType: 'coding_tool',
286
+ seatScope: 'cloud_metered',
287
+ createdAt: '2026-04-02T09:00:00.000Z',
288
+ updatedAt: '2026-04-04T13:15:00.000Z',
289
+ lastActiveAt: '2026-04-04T13:45:00.000Z'
290
+ }
291
+ ]
292
+ })
293
+ });
294
+ vi.stubGlobal('fetch', fetchMock);
295
+ render(_jsx(AgentsModule, { workspaceId: "workspace-1", cloudAuthConfigured: true, authSessionResolved: true, isAuthenticated: true }));
296
+ await screen.findByRole('button', { name: /codex/i });
297
+ expect(screen.queryByText(/Registered Cloud Agents:/i)).not.toBeInTheDocument();
298
+ expect(screen.queryByText('Registered Cloud Agents: 0/Unlimited')).not.toBeInTheDocument();
299
+ });
300
+ it('shows resolved cloud seat usage when the account summary updates after mount', async () => {
301
+ const fetchMock = vi.fn()
302
+ .mockResolvedValueOnce({
303
+ ok: true,
304
+ json: async () => ({
305
+ aiProfileSeatUsage: {
306
+ used: 0,
307
+ limit: null,
308
+ remaining: null
309
+ },
310
+ assignees: [
311
+ {
312
+ value: 'ai-codex',
313
+ label: 'Codex',
314
+ username: 'codex',
315
+ icon: 'Bot',
316
+ color: '#0ea5e9',
317
+ kind: 'agent',
318
+ surfaceType: 'coding_tool',
319
+ seatScope: 'cloud_metered',
320
+ createdAt: '2026-04-02T09:00:00.000Z',
321
+ updatedAt: '2026-04-04T13:15:00.000Z',
322
+ lastActiveAt: '2026-04-04T13:45:00.000Z'
323
+ }
324
+ ]
325
+ })
326
+ });
327
+ vi.stubGlobal('fetch', fetchMock);
328
+ const { rerender } = render(_jsx(AgentsModule, { workspaceId: "workspace-1", cloudAuthConfigured: true, authSessionResolved: true, isAuthenticated: true, cloudAiProfileSeatUsage: null }));
329
+ await screen.findByRole('button', { name: /codex/i });
330
+ expect(screen.queryByText(/Registered Cloud Agents:/i)).not.toBeInTheDocument();
331
+ rerender(_jsx(AgentsModule, { workspaceId: "workspace-1", cloudAuthConfigured: true, authSessionResolved: true, isAuthenticated: true, cloudAiProfileSeatUsage: { used: 0, limit: 20, remaining: 20 } }));
332
+ expect(await screen.findByText('Registered Cloud Agents: 0/20')).toBeInTheDocument();
333
+ });
334
+ it('shows a cloud login prompt instead of unlimited when cloud auth is configured but the user is signed out', async () => {
335
+ const fetchMock = vi.fn()
336
+ .mockResolvedValueOnce({
337
+ ok: true,
338
+ json: async () => ({
339
+ aiProfileSeatUsage: {
340
+ used: 0,
341
+ limit: null,
342
+ remaining: null
343
+ },
344
+ assignees: []
345
+ })
346
+ });
347
+ vi.stubGlobal('fetch', fetchMock);
348
+ render(_jsx(AgentsModule, { workspaceId: "workspace-1", cloudAuthConfigured: true, authSessionResolved: true, isAuthenticated: false }));
349
+ expect(await screen.findByText('Log into account for Cloud agents')).toBeInTheDocument();
350
+ expect(screen.queryByText('Registered Cloud Agents: 0/Unlimited')).not.toBeInTheDocument();
101
351
  });
102
352
  });
@@ -4,10 +4,14 @@ export declare function schedulePrintCleanup(cleanup: () => void): number;
4
4
  interface Props {
5
5
  doc: DocMeta;
6
6
  taskTitle: string | null;
7
+ tasks?: Array<{
8
+ id: string;
9
+ title: string;
10
+ }>;
7
11
  apiBaseUrl?: string;
8
12
  onSaved?: (updatedAt: string) => void;
9
13
  onDeleted?: () => void;
10
14
  enableTaskGeneration?: boolean;
11
15
  }
12
- export declare function DocumentViewer({ doc, taskTitle, apiBaseUrl, onSaved, onDeleted, enableTaskGeneration }: Props): import("react/jsx-runtime").JSX.Element;
16
+ export declare function DocumentViewer({ doc, taskTitle, tasks, apiBaseUrl, onSaved, onDeleted, enableTaskGeneration }: Props): import("react/jsx-runtime").JSX.Element;
13
17
  export {};
@@ -8,6 +8,7 @@ import { Printer, Link, Download, Clock, HardDrive, Tag, Pencil, X, Save, Eye, S
8
8
  import styles from '../../Taskforce.module.css';
9
9
  import viewerStyles from './DocumentViewer.module.css';
10
10
  import taskFormStyles from '../task/TaskForm.module.css';
11
+ import { Modal } from '../ui/Modal';
11
12
  import { getEffectiveAttachmentState } from './documentWorkspaceModel';
12
13
  import { DocumentGenerateModal } from './DocumentGenerateModal';
13
14
  import { fetchTaskforceApi, resolveTaskforceApiUrl } from '../../utils/taskforceApiClient';
@@ -500,7 +501,7 @@ function toggleNthTaskCheckbox(markdown, targetIndex, nextChecked) {
500
501
  });
501
502
  return updated.join('\n');
502
503
  }
503
- export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDeleted, enableTaskGeneration = false }) {
504
+ export function DocumentViewer({ doc, taskTitle, tasks = [], apiBaseUrl = '', onSaved, onDeleted, enableTaskGeneration = false }) {
504
505
  const fallbackReadableName = getReadableDocumentName(doc);
505
506
  const [currentDocumentName, setCurrentDocumentName] = useState(fallbackReadableName);
506
507
  const readableName = currentDocumentName;
@@ -532,6 +533,10 @@ export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDel
532
533
  const [monochromeMode, setMonochromeMode] = useState(false);
533
534
  const [deleting, setDeleting] = useState(false);
534
535
  const [renaming, setRenaming] = useState(false);
536
+ const [attachModalOpen, setAttachModalOpen] = useState(false);
537
+ const [attachSearch, setAttachSearch] = useState('');
538
+ const [attachingTaskId, setAttachingTaskId] = useState(null);
539
+ const [attachError, setAttachError] = useState(null);
535
540
  const [renameValue, setRenameValue] = useState(readableName);
536
541
  const isEditable = doc.editable !== false;
537
542
  const attachmentState = getEffectiveAttachmentState(doc);
@@ -545,6 +550,12 @@ export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDel
545
550
  const reviewThreadEndRef = useRef(null);
546
551
  const markdownContainerRef = useRef(null);
547
552
  const activeReviewSession = reviewSessions.find((session) => session.id === selectedReviewSessionId) || reviewSessions[0] || null;
553
+ const filteredTasks = tasks.filter((task) => {
554
+ const query = attachSearch.trim().toLowerCase();
555
+ if (!query)
556
+ return true;
557
+ return task.title.toLowerCase().includes(query);
558
+ });
548
559
  const fetchContent = useCallback(async () => {
549
560
  setLoadingContent(true);
550
561
  setLoadError(null);
@@ -941,6 +952,44 @@ export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDel
941
952
  setDeleting(false);
942
953
  }
943
954
  }, [apiBaseUrl, attachmentState, deleting, doc.assetId, doc.attachmentCount, onDeleted, readableName]);
955
+ const handleOpenAttachModal = useCallback(() => {
956
+ setAttachSearch('');
957
+ setAttachError(null);
958
+ setAttachingTaskId(null);
959
+ setAttachModalOpen(true);
960
+ }, []);
961
+ const handleAttachToTask = useCallback(async (taskId) => {
962
+ if (!doc.assetId)
963
+ return;
964
+ setAttachError(null);
965
+ setAttachingTaskId(taskId);
966
+ try {
967
+ const res = await fetchTaskforceApi(`/api/taskforce/documents/${encodeURIComponent(doc.assetId)}/attach`, {
968
+ method: 'POST',
969
+ credentials: 'include',
970
+ headers: { 'Content-Type': 'application/json' },
971
+ body: JSON.stringify({
972
+ targetType: 'task',
973
+ targetId: taskId,
974
+ }),
975
+ }, apiBaseUrl);
976
+ const data = await res.json().catch(() => ({}));
977
+ if (!res.ok)
978
+ throw new Error(data.error || `Attach failed (${res.status})`);
979
+ if (data.alreadyAttached) {
980
+ setAttachError('This document is already attached to that task.');
981
+ return;
982
+ }
983
+ setAttachModalOpen(false);
984
+ onSaved?.(new Date().toISOString());
985
+ }
986
+ catch (error) {
987
+ setAttachError(error?.message || 'Failed to attach document.');
988
+ }
989
+ finally {
990
+ setAttachingTaskId(null);
991
+ }
992
+ }, [apiBaseUrl, doc.assetId, onSaved]);
944
993
  const handleToggleReviewStatus = useCallback(async () => {
945
994
  if (!activeReviewSession || reviewBusy)
946
995
  return;
@@ -1227,7 +1276,7 @@ export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDel
1227
1276
  return;
1228
1277
  setRenameValue(readableName);
1229
1278
  setRenaming(true);
1230
- }, children: readableName })), taskTitle && (_jsx("span", { className: viewerStyles.metaTaskName, title: `From task: ${taskTitle}`, children: taskTitle })), attachmentState === 'unattached' && (_jsx("span", { className: viewerStyles.statusBadge, children: "Unattached" })), doc.updatedAt && (_jsxs("span", { className: viewerStyles.metaDate, title: formatDate(doc.updatedAt), children: [_jsx(Clock, { size: 11 }), formatDate(doc.updatedAt)] })), _jsxs("span", { className: viewerStyles.metaSize, children: [_jsx(HardDrive, { size: 11 }), formatBytes(doc.sizeBytes)] }), documentReferenceLabel && (_jsx(ReferenceBadgeButton, { label: documentReferenceLabel, copied: copiedReference, onClick: () => { void handleCopyReference(); }, title: "Copy document reference" })), saveStatus.type === 'saved' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusSaved}`, children: [_jsx(CheckCircle, { size: 11 }), "Saved"] })), saveStatus.type === 'error' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusError}`, title: saveStatus.message, children: [_jsx(AlertCircle, { size: 11 }), saveStatus.message || 'Save failed'] })), isDirty && saveStatus.type !== 'saving' && (_jsx("span", { className: viewerStyles.unsaved, children: "Unsaved changes" }))] }), _jsx("div", { className: viewerStyles.actions, children: editMode ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: viewerStyles.layoutToggle, children: [_jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'editor' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('editor'), title: "Editor only", children: _jsx(Pencil, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'split' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('split'), title: "Split view", children: _jsx(SplitSquareHorizontal, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'preview' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('preview'), title: "Preview only", children: _jsx(Eye, { size: 12 }) })] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCancelEdit, title: "Discard changes", children: [_jsx(X, { size: 13 }), "Cancel"] }), _jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.saveBtn}`, onClick: handleSave, disabled: saveStatus.type === 'saving' || !isDirty, title: "Save (Ctrl+S)", children: [_jsx(Save, { size: 13 }), saveStatus.type === 'saving' ? 'Saving…' : 'Save'] })] })) : (_jsxs(_Fragment, { children: [doc.assetId && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setRenaming((prev) => !prev), title: "Rename document", children: [_jsx(Pencil, { size: 13 }), renaming ? 'Close Rename' : 'Rename'] })), isEditable && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.editBtn}`, onClick: handleEnterEdit, title: "Edit document", children: [_jsx(Pencil, { size: 13 }), "Edit"] })), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleToggleReviewSidebar, title: showReviewSidebar ? 'Hide review and comments' : 'Show review and comments', children: [_jsx(MessageSquareText, { size: 13 }), showReviewSidebar ? 'Hide Review' : 'Show Review'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCopyLink, title: "Copy link", children: [_jsx(Link, { size: 13 }), copied ? 'Copied!' : 'Copy link'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleDownload, title: "Download .md", children: [_jsx(Download, { size: 13 }), "Download"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handlePrint, title: "Print", children: [_jsx(Printer, { size: 13 }), "Print"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setMonochromeMode((prev) => !prev), title: "Toggle black and white style", children: [_jsx(Eye, { size: 13 }), monochromeMode ? 'Color' : 'B/W'] }), enableTaskGeneration && doc.docType === 'implementation-plan' && !editMode && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.generateBtn}`, onClick: () => setShowGenModal(true), title: "Generate tasks from this plan", children: [_jsx(Zap, { size: 13 }), "Generate Tasks"] })), canDelete && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.dangerBtn}`, onClick: () => { void handleDelete(); }, disabled: deleting, title: attachmentState === 'attached'
1279
+ }, children: readableName })), taskTitle && (_jsx("span", { className: viewerStyles.metaTaskName, title: `From task: ${taskTitle}`, children: taskTitle })), attachmentState === 'unattached' && (_jsx("span", { className: viewerStyles.statusBadge, children: "Unattached" })), doc.updatedAt && (_jsxs("span", { className: viewerStyles.metaDate, title: formatDate(doc.updatedAt), children: [_jsx(Clock, { size: 11 }), formatDate(doc.updatedAt)] })), _jsxs("span", { className: viewerStyles.metaSize, children: [_jsx(HardDrive, { size: 11 }), formatBytes(doc.sizeBytes)] }), documentReferenceLabel && (_jsx(ReferenceBadgeButton, { label: documentReferenceLabel, copied: copiedReference, onClick: () => { void handleCopyReference(); }, title: "Copy document reference" })), saveStatus.type === 'saved' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusSaved}`, children: [_jsx(CheckCircle, { size: 11 }), "Saved"] })), saveStatus.type === 'error' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusError}`, title: saveStatus.message, children: [_jsx(AlertCircle, { size: 11 }), saveStatus.message || 'Save failed'] })), isDirty && saveStatus.type !== 'saving' && (_jsx("span", { className: viewerStyles.unsaved, children: "Unsaved changes" }))] }), _jsx("div", { className: viewerStyles.actions, children: editMode ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: viewerStyles.layoutToggle, children: [_jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'editor' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('editor'), title: "Editor only", children: _jsx(Pencil, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'split' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('split'), title: "Split view", children: _jsx(SplitSquareHorizontal, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'preview' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('preview'), title: "Preview only", children: _jsx(Eye, { size: 12 }) })] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCancelEdit, title: "Discard changes", children: [_jsx(X, { size: 13 }), "Cancel"] }), _jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.saveBtn}`, onClick: handleSave, disabled: saveStatus.type === 'saving' || !isDirty, title: "Save (Ctrl+S)", children: [_jsx(Save, { size: 13 }), saveStatus.type === 'saving' ? 'Saving…' : 'Save'] })] })) : (_jsxs(_Fragment, { children: [doc.assetId && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setRenaming((prev) => !prev), title: "Rename document", children: [_jsx(Pencil, { size: 13 }), renaming ? 'Close Rename' : 'Rename'] })), isEditable && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.editBtn}`, onClick: handleEnterEdit, title: "Edit document", children: [_jsx(Pencil, { size: 13 }), "Edit"] })), doc.assetId && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: handleOpenAttachModal, title: "Attach document to a task", children: [_jsx(Link, { size: 13 }), "Attach"] })), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleToggleReviewSidebar, title: showReviewSidebar ? 'Hide review and comments' : 'Show review and comments', children: [_jsx(MessageSquareText, { size: 13 }), showReviewSidebar ? 'Hide Review' : 'Show Review'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCopyLink, title: "Copy link", children: [_jsx(Link, { size: 13 }), copied ? 'Copied!' : 'Copy link'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleDownload, title: "Download .md", children: [_jsx(Download, { size: 13 }), "Download"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handlePrint, title: "Print", children: [_jsx(Printer, { size: 13 }), "Print"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setMonochromeMode((prev) => !prev), title: "Toggle black and white style", children: [_jsx(Eye, { size: 13 }), monochromeMode ? 'Color' : 'B/W'] }), enableTaskGeneration && doc.docType === 'implementation-plan' && !editMode && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.generateBtn}`, onClick: () => setShowGenModal(true), title: "Generate tasks from this plan", children: [_jsx(Zap, { size: 13 }), "Generate Tasks"] })), canDelete && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.dangerBtn}`, onClick: () => { void handleDelete(); }, disabled: deleting, title: attachmentState === 'attached'
1231
1280
  ? 'Remove this document from linked tasks and delete it'
1232
1281
  : 'Permanently delete this unattached document', children: [_jsx(Trash2, { size: 13 }), deleting
1233
1282
  ? 'Deleting…'
@@ -1254,5 +1303,13 @@ export function DocumentViewer({ doc, taskTitle, apiBaseUrl = '', onSaved, onDel
1254
1303
  event.preventDefault();
1255
1304
  void handleAddReviewComment();
1256
1305
  }
1257
- } })] }), _jsx("button", { type: "button", className: taskFormStyles.sendCommentBtn, onClick: () => { void handleAddReviewComment(); }, disabled: reviewBusy || !reviewDraft.trim() || activeReviewSession?.status === 'resolved', "aria-label": "Send review comment", title: "Send review comment", children: _jsx(Send, { size: 15 }) })] })] })] }) }))] }))), enableTaskGeneration && showGenModal && content !== null && (_jsx(DocumentGenerateModal, { content: content, docTitle: doc.title, apiBaseUrl: apiBaseUrl, onClose: () => setShowGenModal(false), onGenerated: (_count) => setShowGenModal(false) })), !editMode && (_jsx("div", { className: viewerStyles.printHint, children: "For the cleanest output, disable browser Headers and footers in the print dialog." }))] }));
1306
+ } })] }), _jsx("button", { type: "button", className: taskFormStyles.sendCommentBtn, onClick: () => { void handleAddReviewComment(); }, disabled: reviewBusy || !reviewDraft.trim() || activeReviewSession?.status === 'resolved', "aria-label": "Send review comment", title: "Send review comment", children: _jsx(Send, { size: 15 }) })] })] })] }) }))] }))), enableTaskGeneration && showGenModal && content !== null && (_jsx(DocumentGenerateModal, { content: content, docTitle: doc.title, apiBaseUrl: apiBaseUrl, onClose: () => setShowGenModal(false), onGenerated: (_count) => setShowGenModal(false) })), _jsx(Modal, { isOpen: attachModalOpen, onClose: () => {
1307
+ if (attachingTaskId)
1308
+ return;
1309
+ setAttachModalOpen(false);
1310
+ }, title: "Attach document", size: "md", footer: (_jsx("button", { className: viewerStyles.actionBtn, onClick: () => setAttachModalOpen(false), disabled: Boolean(attachingTaskId), children: "Close" })), children: _jsxs("div", { className: viewerStyles.attachModalBody, children: [_jsx("p", { className: viewerStyles.attachModalCopy, children: "Attach this document to a task from the document manager. Initiative and workstream targets can be enabled later on the same attachment model." }), _jsx("input", { type: "text", className: styles.input, placeholder: "Search tasks\u2026", value: attachSearch, onChange: (event) => setAttachSearch(event.target.value) }), attachError && (_jsxs("div", { className: viewerStyles.attachModalError, children: [_jsx(AlertCircle, { size: 14 }), _jsx("span", { children: attachError })] })), _jsx("div", { className: `${viewerStyles.attachTaskList} tf-scrollbar`, children: filteredTasks.length === 0 ? (_jsx("div", { className: viewerStyles.attachEmptyState, children: tasks.length === 0 ? 'No tasks are available in this workspace.' : 'No tasks match your search.' })) : (filteredTasks.map((task) => {
1311
+ const isCurrentPrimaryAttachment = doc.taskId === task.id;
1312
+ const isBusy = attachingTaskId === task.id;
1313
+ return (_jsxs("button", { type: "button", className: viewerStyles.attachTaskRow, disabled: isBusy, onClick: () => { void handleAttachToTask(task.id); }, children: [_jsx("span", { className: viewerStyles.attachTaskTitle, children: task.title }), _jsx("span", { className: viewerStyles.attachTaskMeta, children: isBusy ? 'Attaching…' : isCurrentPrimaryAttachment ? 'Attached' : 'Attach' })] }, task.id));
1314
+ })) })] }) }), !editMode && (_jsx("div", { className: viewerStyles.printHint, children: "For the cleanest output, disable browser Headers and footers in the print dialog." }))] }));
1258
1315
  }
@@ -6,7 +6,7 @@ import { useDocumentWorkspaceController } from './useDocumentWorkspaceController
6
6
  export { DOCUMENT_TYPE_FILTER_OPTIONS, DOCUMENT_ATTACHMENT_FILTER_OPTIONS } from './documentWorkspaceFilters';
7
7
  export function DocumentWorkspace({ tasks, apiBaseUrl = '', documents, loading, error, selectedDoc, searchQuery, hasActiveFilters, onSearchChange, onSelectDoc, onRefresh, onDeleteSelected, enableTaskGeneration = false }) {
8
8
  const taskById = new Map(tasks.map(t => [t.id, t]));
9
- return (_jsxs("div", { className: styles.docWorkspace, children: [_jsx("div", { className: styles.docIndexPanel, children: _jsx(DocumentIndex, { documents: documents, selectedDocId: selectedDoc?.id ?? null, loading: loading, error: error, searchQuery: searchQuery, onSearchChange: onSearchChange, onSelect: (doc) => onSelectDoc(doc), onRefresh: onRefresh, taskById: taskById, hasActiveFilters: hasActiveFilters }) }), _jsx("div", { className: styles.docViewerPanel, children: selectedDoc ? (_jsx(DocumentViewer, { doc: selectedDoc, taskTitle: selectedDoc.taskId ? (taskById.get(selectedDoc.taskId)?.title ?? null) : null, apiBaseUrl: apiBaseUrl, onSaved: (_updatedAt) => onRefresh(), onDeleted: () => void onDeleteSelected(), enableTaskGeneration: enableTaskGeneration })) : !loading && (_jsx("div", { className: styles.docViewerEmpty, children: _jsx("p", { className: "tf-empty-copy", children: documents.length === 0 ? 'No documents match your current filters.' : 'Select a document to view it.' }) })) })] }));
9
+ return (_jsxs("div", { className: styles.docWorkspace, children: [_jsx("div", { className: styles.docIndexPanel, children: _jsx(DocumentIndex, { documents: documents, selectedDocId: selectedDoc?.id ?? null, loading: loading, error: error, searchQuery: searchQuery, onSearchChange: onSearchChange, onSelect: (doc) => onSelectDoc(doc), onRefresh: onRefresh, taskById: taskById, hasActiveFilters: hasActiveFilters }) }), _jsx("div", { className: styles.docViewerPanel, children: selectedDoc ? (_jsx(DocumentViewer, { doc: selectedDoc, taskTitle: selectedDoc.taskId ? (taskById.get(selectedDoc.taskId)?.title ?? null) : null, tasks: tasks, apiBaseUrl: apiBaseUrl, onSaved: (_updatedAt) => onRefresh(), onDeleted: () => void onDeleteSelected(), enableTaskGeneration: enableTaskGeneration })) : !loading && (_jsx("div", { className: styles.docViewerEmpty, children: _jsx("p", { className: "tf-empty-copy", children: documents.length === 0 ? 'No documents match your current filters.' : 'Select a document to view it.' }) })) })] }));
10
10
  }
11
11
  export function DocumentWorkspaceShell({ tasks, runtimeMode = 'local', apiBaseUrl = '', cloudAuthBaseUrl = '', typeFilters: controlledTypeFilters, attachmentFilters: controlledAttachmentFilters, onTypeFiltersChange, onAttachmentFiltersChange, requestedDocPath = null, requestedDocAssetId = null, onRequestedDocHandled, enableTaskGeneration = false }) {
12
12
  const { documentApiBaseUrl, filteredDocs, loading, error, selectedDoc, searchQuery, hasActiveFilters, setSearchQuery, fetchDocuments, selectDoc, handleDeleteSelected, } = useDocumentWorkspaceController({
@@ -145,6 +145,39 @@ describe('Document workspace management', () => {
145
145
  confirmSpy.mockRestore();
146
146
  alertSpy.mockRestore();
147
147
  });
148
+ it('opens an attach modal from the document viewer and attaches the document to a task', async () => {
149
+ const user = userEvent.setup();
150
+ fetchTaskforceApi
151
+ .mockResolvedValueOnce({
152
+ ok: true,
153
+ text: async () => '# Example\n',
154
+ })
155
+ .mockResolvedValueOnce(createEmptyReviewSessionsResponse())
156
+ .mockResolvedValueOnce({
157
+ ok: true,
158
+ json: async () => ({ success: true, attached: true, alreadyAttached: false }),
159
+ });
160
+ const onSaved = vi.fn();
161
+ render(_jsx(DocumentViewer, { doc: createDoc({
162
+ id: 'doc-attach',
163
+ assetId: 'asset-doc-attach',
164
+ attachmentCount: 0,
165
+ attachmentState: 'unattached',
166
+ }), taskTitle: null, tasks: [
167
+ { id: 'task-1', title: 'First attach target' },
168
+ { id: 'task-2', title: 'Second attach target' },
169
+ ], onSaved: onSaved }));
170
+ await waitFor(() => expect(fetchTaskforceApi).toHaveBeenCalledWith('/api/taskforce/documents/asset-doc-1/content', { credentials: 'same-origin' }, ''));
171
+ await user.click(screen.getByRole('button', { name: /^Attach$/i }));
172
+ expect(screen.getByText(/Attach this document to a task from the document manager/i)).toBeInTheDocument();
173
+ await user.type(screen.getByPlaceholderText('Search tasks…'), 'Second');
174
+ await user.click(screen.getByRole('button', { name: /Second attach target/i }));
175
+ await waitFor(() => expect(fetchTaskforceApi).toHaveBeenLastCalledWith('/api/taskforce/documents/asset-doc-attach/attach', expect.objectContaining({
176
+ method: 'POST',
177
+ credentials: 'include',
178
+ }), ''));
179
+ expect(onSaved).toHaveBeenCalled();
180
+ });
148
181
  it('shows delete and unlink for attached docs and sends forceDetach on delete', async () => {
149
182
  const user = userEvent.setup();
150
183
  fetchTaskforceApi
@@ -15,7 +15,7 @@ export interface DocMeta {
15
15
  updatedAt: string | null;
16
16
  editable?: boolean;
17
17
  attachmentCount?: number;
18
- attachmentState?: 'attached' | 'unattached' | 'standalone';
18
+ attachmentState?: 'attached' | 'unattached';
19
19
  }
20
20
  export declare function normalizeDocPath(value: string): string;
21
21
  export declare function docPathMatches(candidate: string, requested: string): boolean;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import * as Icons from 'lucide-react';
4
- const { MessageSquare, User, Bot, Gauge, X, ClipboardList, HelpCircle, File } = Icons;
4
+ const { MessageSquare, User, Bot, Gauge, ClipboardList, HelpCircle, File } = Icons;
5
5
  import styles from '../../Taskforce.module.css';
6
6
  import cardStyles from './TaskCard.module.css';
7
7
  import { Markdown } from '../ui/Markdown';
@@ -15,7 +15,7 @@ import { getDocumentDownloadName } from '../../utils/documentNames';
15
15
  import { TaskStatusActions } from './TaskStatusActions';
16
16
  import { getTaskReferenceDisplayState } from '../../utils/taskReferences';
17
17
  import { getAssigneeKind, getAssigneeLabel } from '../../utils/assignees';
18
- import { getLatestTaskActivity, summarizeTaskActivity } from '../../utils/taskActivity';
18
+ import { getLatestTaskActivity, getTaskActivityItems, summarizeTaskActivity } from '../../utils/taskActivity';
19
19
  import { DEFAULT_TYPE_VALUE } from '../../shared/taxonomyDefaults.js';
20
20
  import { getVisibleTaxonomiesForTasks } from '../../utils/taxonomyLifecycle.js';
21
21
  import { getInitiativeReferenceLabel, getWorkstreamReferenceLabel } from '../../utils/planningReferences';
@@ -51,6 +51,18 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
51
51
  const hasLegacyClass = !!styles[`priorityItem_${displayPriority}`];
52
52
  const assigneeKind = getAssigneeKind(task.assignee);
53
53
  const assigneeLabel = getAssigneeLabel(task.assignee, assigneeOptions);
54
+ const assigneeOption = React.useMemo(() => {
55
+ const value = String(task.assignee || '').trim();
56
+ return assigneeOptions.find((option) => String(option.value || '').trim() === value) || null;
57
+ }, [assigneeOptions, task.assignee]);
58
+ const assigneeAvatarUrl = assigneeKind !== 'unassigned' && typeof assigneeOption?.avatarUrl === 'string'
59
+ ? assigneeOption.avatarUrl.trim()
60
+ : '';
61
+ const assigneeAvatarAccent = assigneeKind === 'agent'
62
+ ? 'var(--color-violet-500, #8b5cf6)'
63
+ : assigneeKind === 'member'
64
+ ? 'var(--color-green-500, #22c55e)'
65
+ : 'var(--text-secondary, #888)';
54
66
  const latestActivity = React.useMemo(() => getLatestTaskActivity(task), [task]);
55
67
  const resolvedReadOnlyMode = readOnlyMode || (task.isDeleted ? 'deleted' : (isArchived ? 'archived' : null));
56
68
  const isReadOnly = resolvedReadOnlyMode !== null;
@@ -70,9 +82,7 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
70
82
  return undefined;
71
83
  }, [assigneeOptions, initiativeReferenceLabel, taskInitiative, taskWorkstream, workstreamReferenceLabel]);
72
84
  const latestActivityText = React.useMemo(() => summarizeTaskActivity(latestActivity, formatActivityFieldValue), [formatActivityFieldValue, latestActivity]);
73
- const activityCount = Array.isArray(task.activity) && task.activity.length > 0
74
- ? task.activity.length
75
- : (task.comments?.length || 0);
85
+ const activityCount = React.useMemo(() => getTaskActivityItems(task).length, [task]);
76
86
  const cardClasses = [
77
87
  cardStyles.taskItem,
78
88
  task.status === 'on-hold' ? cardStyles.onHold : '',
@@ -286,29 +296,10 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
286
296
  borderColor: `color-mix(in srgb, ${complexityColor}, transparent 72%)`,
287
297
  gap: '4px',
288
298
  padding: '0 8px'
289
- }, children: [_jsx(Gauge, { size: 14 }), !compressed && _jsx("span", { children: complexityLabel })] }), _jsx("span", { className: styles.metaBadge, title: `Assigned to: ${assigneeLabel}`, "aria-label": assigneeKind === 'unassigned' ? 'Unassigned' : `Assigned to ${assigneeLabel}`, style: {
290
- backgroundColor: assigneeKind === 'agent'
291
- ? 'color-mix(in srgb, var(--color-violet-500, #8b5cf6), transparent 88%)'
292
- : assigneeKind === 'member'
293
- ? 'color-mix(in srgb, var(--color-green-500, #22c55e), transparent 88%)'
294
- : 'color-mix(in srgb, var(--text-secondary, #888), transparent 88%)',
295
- padding: '0 8px',
296
- color: assigneeKind === 'agent'
297
- ? 'var(--color-violet-500, #8b5cf6)'
298
- : assigneeKind === 'member'
299
- ? 'var(--color-green-500, #22c55e)'
300
- : 'var(--text-secondary, #888)',
301
- borderColor: assigneeKind === 'agent'
302
- ? 'color-mix(in srgb, var(--color-violet-500, #8b5cf6), transparent 72%)'
303
- : assigneeKind === 'member'
304
- ? 'color-mix(in srgb, var(--color-green-500, #22c55e), transparent 72%)'
305
- : 'color-mix(in srgb, var(--text-secondary, #888), transparent 70%)',
306
- display: 'flex',
307
- alignItems: 'center'
308
- }, children: assigneeKind === 'agent'
309
- ? _jsx(Bot, { size: compressed ? 14 : 16 })
299
+ }, children: [_jsx(Gauge, { size: 14 }), !compressed && _jsx("span", { children: complexityLabel })] }), _jsx("span", { className: `${cardStyles.assigneeAvatarIndicator} ${assigneeKind === 'unassigned' ? cardStyles.assigneeAvatarEmpty : ''}`, title: `Assigned to: ${assigneeLabel}`, "aria-label": assigneeKind === 'unassigned' ? 'Unassigned' : `Assigned to ${assigneeLabel}`, style: { '--assignee-avatar-color': assigneeAvatarAccent }, children: assigneeAvatarUrl ? (_jsx("img", { src: assigneeAvatarUrl, alt: "", className: cardStyles.assigneeAvatarImage })) : assigneeKind === 'agent'
300
+ ? _jsx(Bot, { size: compressed ? 18 : 20 })
310
301
  : assigneeKind === 'member'
311
- ? _jsx(User, { size: compressed ? 14 : 16 })
312
- : _jsx(HelpCircle, { size: compressed ? 14 : 16 }) })] })] }) })) })] })] }));
302
+ ? _jsx(User, { size: compressed ? 18 : 20 })
303
+ : null })] })] }) })) })] })] }));
313
304
  };
314
305
  export const TaskCard = React.memo(TaskCardInner);