@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
@@ -30,7 +30,7 @@ const baseInitiative = (overrides = {}) => ({
30
30
  createdAt: '2026-02-13T00:00:00.000Z',
31
31
  ...overrides,
32
32
  });
33
- describe('TaskCard parent/child progress', () => {
33
+ describe('TaskCard planning hierarchy references', () => {
34
34
  it('renders initiative/workstream/task hierarchy references when planning context is provided', () => {
35
35
  const task = baseTask({
36
36
  id: 'task-hierarchy',
@@ -105,7 +105,7 @@ describe('TaskCard parent/child progress', () => {
105
105
  });
106
106
  render(_jsx(TaskCard, { task: task, onClick: () => { }, onCopyId: () => { } }));
107
107
  expect(screen.getByText(/Latest Activity:/)).toBeInTheDocument();
108
- expect(screen.getByText(/Status: task -> review/)).toBeInTheDocument();
108
+ expect(screen.getByText(/Status changed: Task -> Review/)).toBeInTheDocument();
109
109
  expect(screen.getByTitle('2 activity items')).toBeInTheDocument();
110
110
  });
111
111
  it('uses the current workstream reference in latest activity summaries', () => {
@@ -136,7 +136,7 @@ describe('TaskCard parent/child progress', () => {
136
136
  ]
137
137
  });
138
138
  render(_jsx(TaskCard, { task: task, taskWorkstream: baseWorkstream({ id: 'workstream-1', referenceNumber: 12 }), onClick: () => { }, onCopyId: () => { } }));
139
- expect(screen.getByText(/Workstream: none -> WS-12/)).toBeInTheDocument();
139
+ expect(screen.getByText(/Workstream changed: none -> WS-12/)).toBeInTheDocument();
140
140
  expect(screen.queryByText(/workstream-1/i)).not.toBeInTheDocument();
141
141
  });
142
142
  it('renders named AI assignee labels from assignee options', () => {
@@ -151,6 +151,41 @@ describe('TaskCard parent/child progress', () => {
151
151
  ], onClick: () => { }, onCopyId: () => { } }));
152
152
  expect(screen.getByLabelText('Assigned to Codex')).toBeInTheDocument();
153
153
  });
154
+ it('renders AI profile photos in the assignee indicator when available', () => {
155
+ const task = baseTask({
156
+ id: 'task-ai-avatar',
157
+ title: 'AI-owned task',
158
+ assignee: 'ai-profile-123'
159
+ });
160
+ render(_jsx(TaskCard, { task: task, assigneeOptions: [
161
+ { value: 'ai-profile-123', label: 'Codex', icon: 'Bot', color: '#0ea5e9', kind: 'agent', avatarUrl: '/api/taskforce/context/ai-avatar.webp' }
162
+ ], onClick: () => { }, onCopyId: () => { } }));
163
+ const badge = screen.getByLabelText('Assigned to Codex');
164
+ expect(badge.querySelector('img')).toHaveAttribute('src', '/api/taskforce/context/ai-avatar.webp');
165
+ });
166
+ it('renders member profile photos in the assignee indicator when available', () => {
167
+ const task = baseTask({
168
+ id: 'task-member-avatar',
169
+ title: 'Member-owned task',
170
+ assignee: 'user-123'
171
+ });
172
+ render(_jsx(TaskCard, { task: task, assigneeOptions: [
173
+ { value: 'user-123', label: 'Ross Burke', icon: 'User', color: '#22c55e', kind: 'member', avatarUrl: '/api/taskforce/context/member-avatar.webp' }
174
+ ], onClick: () => { }, onCopyId: () => { } }));
175
+ const badge = screen.getByLabelText('Assigned to Ross Burke');
176
+ expect(badge.querySelector('img')).toHaveAttribute('src', '/api/taskforce/context/member-avatar.webp');
177
+ });
178
+ it('renders an empty avatar circle for unassigned tasks', () => {
179
+ const task = baseTask({
180
+ id: 'task-unassigned-avatar',
181
+ title: 'Unassigned task',
182
+ assignee: 'unassigned'
183
+ });
184
+ render(_jsx(TaskCard, { task: task, onClick: () => { }, onCopyId: () => { } }));
185
+ const indicator = screen.getByLabelText('Unassigned');
186
+ expect(indicator.querySelector('img')).toBeNull();
187
+ expect(indicator.querySelector('svg')).toBeNull();
188
+ });
154
189
  it('prefers provided type metadata over hardcoded defaults', () => {
155
190
  const task = baseTask({
156
191
  id: 'task-type-metadata',
@@ -1,528 +1,6 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useRef, useState } from 'react';
3
- import { AlertTriangle, Copy, Download, File, FileUp, Loader2, PencilLine, Trash2, ChevronDown, ChevronRight } from 'lucide-react';
4
- import styles from '../../Taskforce.module.css';
5
- import { dispatchOpenMarkdownDocument } from '../../utils/contextFiles';
6
- import { canOpenAnnotatedAttachment, dispatchOpenAnnotatedAttachment } from '../../utils/annotatedAttachments';
7
- import { dispatchTaskforceContextAssetsMutated } from '../../utils/contextAssetEvents';
8
- import { getDocumentReferenceLabel } from '../../utils/documentReferences';
9
- const ACCEPTED_CONTEXT_FILE_TYPES = [
10
- '.png', '.jpg', '.jpeg', '.webp', '.gif',
11
- '.pdf', '.txt', '.md', '.csv', '.json', '.doc', '.docx',
12
- '.html', '.js', '.ts', '.tsx', '.css', '.py', '.java', '.go', '.rs', '.sh'
13
- ].join(',');
14
- const SUPPORTED_CLIPBOARD_IMAGE_TYPES = new Set(['image/png', 'image/jpeg', 'image/webp']);
15
- const ACCEPTED_CONTEXT_EXTENSIONS = new Set(ACCEPTED_CONTEXT_FILE_TYPES.split(',').map((ext) => ext.trim().toLowerCase()));
16
- const MAX_CONTEXT_FILE_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB per file
17
- const CONTEXT_NOTICE_TIMEOUT_MS = 3500;
18
- function getFileExtension(fileName) {
19
- const trimmed = fileName.trim().toLowerCase();
20
- const dotIdx = trimmed.lastIndexOf('.');
21
- if (dotIdx === -1)
22
- return '';
23
- return trimmed.slice(dotIdx);
24
- }
25
- function formatFileList(fileNames) {
26
- if (fileNames.length === 0)
27
- return '';
28
- const shown = fileNames.slice(0, 3).join(', ');
29
- const extra = fileNames.length - 3;
30
- return extra > 0 ? `${shown}, +${extra} more` : shown;
31
- }
32
- function getLocalFileFingerprint(file) {
33
- return `${file.name.trim().toLowerCase()}::${file.size}::${file.lastModified}`;
34
- }
35
- function readFileAsDataUrl(file) {
36
- return new Promise((resolve, reject) => {
37
- const reader = new FileReader();
38
- reader.onload = () => resolve(String(reader.result || ''));
39
- reader.onerror = () => reject(reader.error || new Error('Failed to read file'));
40
- reader.readAsDataURL(file);
41
- });
42
- }
43
- function isImagePath(path) {
44
- return /\.(png|jpe?g|webp|gif)(\?|#|$)/i.test(path);
45
- }
46
- function getClipboardImageType(item) {
47
- const rawTypes = item.types;
48
- if (!rawTypes)
49
- return null;
50
- for (const type of Array.from(rawTypes)) {
51
- const normalizedType = String(type || '').trim().toLowerCase();
52
- if (SUPPORTED_CLIPBOARD_IMAGE_TYPES.has(normalizedType)) {
53
- return normalizedType;
54
- }
55
- }
56
- return null;
57
- }
58
- function buildDownloadUrl(pathValue, displayName) {
59
- const separator = pathValue.includes('?') ? '&' : '?';
60
- return `${pathValue}${separator}download=1&filename=${encodeURIComponent(displayName)}`;
61
- }
62
- function getAttachmentDisplayName(item) {
63
- if (typeof item === 'string')
64
- return item.split('/').pop() || 'Context file';
65
- return item.displayName || item.originalFilename || item.caption || item.fsPath || item.path.split('/').pop() || 'Context file';
66
- }
67
- function getAttachmentTypeLabel(item) {
68
- const candidates = typeof item === 'string'
69
- ? [item]
70
- : [item.originalFilename, item.displayName, item.caption, item.fsPath, item.path];
71
- for (const candidate of candidates) {
72
- if (!candidate)
73
- continue;
74
- const ext = getFileExtension(candidate);
75
- if (ext)
76
- return ext.replace('.', '').slice(0, 5).toUpperCase();
77
- }
78
- const path = typeof item === 'string' ? item : item.path;
79
- return /^https?:\/\//i.test(path) ? 'LINK' : 'FILE';
80
- }
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ContextAttachmentManager } from '../context/ContextAttachmentManager';
81
3
  export function TaskContextUpload(props) {
82
- const { taskId, taskReferenceLabel, workspaceId, contextFiles, onAddContextFile, onRemoveContextFile, onUpdateContextCaption } = props;
83
- const inputRef = useRef(null);
84
- const [uploading, setUploading] = useState(false);
85
- const [uploadError, setUploadError] = useState(null);
86
- const [linkValue, setLinkValue] = useState('');
87
- const [linkError, setLinkError] = useState(null);
88
- const [contextNotice, setContextNotice] = useState(null);
89
- const [showContextUpload, setShowContextUpload] = useState(false);
90
- const [isDropActive, setIsDropActive] = useState(false);
91
- const [dragDepth, setDragDepth] = useState(0);
92
- const contextKeyRef = useRef(new Set());
93
- const localUploadFingerprintRef = useRef(new Set());
94
- const normalizeKey = (value) => (value || '').trim();
95
- const getContextKeys = (path, fsPath) => {
96
- const keys = [];
97
- const normalizedPath = normalizeKey(path);
98
- const normalizedFsPath = normalizeKey(fsPath);
99
- if (normalizedPath)
100
- keys.push(`path:${normalizedPath}`);
101
- if (normalizedFsPath)
102
- keys.push(`fs:${normalizedFsPath}`);
103
- return keys;
104
- };
105
- const keySetFromFiles = useMemo(() => {
106
- const next = new Set();
107
- contextFiles.forEach((item) => {
108
- if (typeof item === 'string') {
109
- getContextKeys(item).forEach((key) => next.add(key));
110
- return;
111
- }
112
- getContextKeys(item.path, item.fsPath).forEach((key) => next.add(key));
113
- });
114
- return next;
115
- }, [contextFiles]);
116
- useEffect(() => {
117
- contextKeyRef.current = new Set(keySetFromFiles);
118
- }, [keySetFromFiles]);
119
- const hasAnyKey = (set, keys) => keys.some((key) => set.has(key));
120
- const addKeys = (set, keys) => keys.forEach((key) => set.add(key));
121
- const trimmedWorkspaceId = String(workspaceId || '').trim();
122
- const buildApiHeaders = (contentType = 'application/json') => {
123
- const headers = { 'Content-Type': contentType };
124
- if (trimmedWorkspaceId)
125
- headers['x-taskforce-workspace-id'] = trimmedWorkspaceId;
126
- return headers;
127
- };
128
- const addLink = (rawValue, clearInput = true, contextKeySet = contextKeyRef.current) => {
129
- const trimmed = rawValue.trim();
130
- if (!trimmed)
131
- return 'invalid';
132
- const isHttp = /^https?:\/\//i.test(trimmed);
133
- if (!isHttp)
134
- return 'invalid';
135
- const normalizedPath = trimmed;
136
- const keys = getContextKeys(normalizedPath);
137
- if (hasAnyKey(contextKeySet, keys))
138
- return 'duplicate';
139
- const nameFromPath = trimmed.split(/[\\/]/).pop() || trimmed;
140
- onAddContextFile({
141
- path: normalizedPath,
142
- caption: nameFromPath,
143
- timestamp: new Date().toISOString()
144
- });
145
- addKeys(contextKeySet, keys);
146
- if (clearInput)
147
- setLinkValue('');
148
- setLinkError(null);
149
- return 'added';
150
- };
151
- useEffect(() => {
152
- if (!contextNotice)
153
- return;
154
- const timeoutId = window.setTimeout(() => {
155
- setContextNotice(null);
156
- }, CONTEXT_NOTICE_TIMEOUT_MS);
157
- return () => window.clearTimeout(timeoutId);
158
- }, [contextNotice]);
159
- const handleSelectedFiles = async (selectedFilesInput) => {
160
- if (selectedFilesInput.length === 0)
161
- return;
162
- setUploadError(null);
163
- setContextNotice(null);
164
- const selectedFiles = selectedFilesInput;
165
- const unsupportedFiles = [];
166
- const oversizedFiles = [];
167
- const localDuplicateFiles = [];
168
- const uploadCandidates = [];
169
- const localFingerprintSet = new Set(localUploadFingerprintRef.current);
170
- selectedFiles.forEach((file) => {
171
- const ext = getFileExtension(file.name);
172
- if (!ACCEPTED_CONTEXT_EXTENSIONS.has(ext)) {
173
- unsupportedFiles.push(file.name);
174
- return;
175
- }
176
- if (file.size > MAX_CONTEXT_FILE_SIZE_BYTES) {
177
- oversizedFiles.push(file.name);
178
- return;
179
- }
180
- const fingerprint = getLocalFileFingerprint(file);
181
- if (localFingerprintSet.has(fingerprint)) {
182
- localDuplicateFiles.push(file.name);
183
- return;
184
- }
185
- localFingerprintSet.add(fingerprint);
186
- uploadCandidates.push({ file, fingerprint });
187
- });
188
- const validationNotices = [];
189
- if (unsupportedFiles.length > 0) {
190
- validationNotices.push(`${unsupportedFiles.length} unsupported file${unsupportedFiles.length === 1 ? '' : 's'} skipped: ${formatFileList(unsupportedFiles)}.`);
191
- }
192
- if (oversizedFiles.length > 0) {
193
- validationNotices.push(`${oversizedFiles.length} file${oversizedFiles.length === 1 ? '' : 's'} over 10 MB skipped: ${formatFileList(oversizedFiles)}.`);
194
- }
195
- if (localDuplicateFiles.length > 0) {
196
- validationNotices.push(`${localDuplicateFiles.length} local duplicate file${localDuplicateFiles.length === 1 ? '' : 's'} skipped before upload: ${formatFileList(localDuplicateFiles)}.`);
197
- }
198
- if (uploadCandidates.length === 0) {
199
- if (validationNotices.length > 0)
200
- setContextNotice(validationNotices.join(' '));
201
- if (inputRef.current)
202
- inputRef.current.value = '';
203
- return;
204
- }
205
- setUploading(true);
206
- const contextKeySet = new Set(contextKeyRef.current);
207
- let duplicateCount = 0;
208
- try {
209
- for (const { file, fingerprint } of uploadCandidates) {
210
- let data = null;
211
- // Keep uploads same-origin so local runtime never routes attachments through the cloud API base.
212
- const initRes = await fetch('/api/taskforce/context-upload/init', {
213
- method: 'POST',
214
- headers: buildApiHeaders(),
215
- body: JSON.stringify({
216
- originalName: file.name,
217
- mimeType: file.type || 'application/octet-stream',
218
- size: file.size,
219
- taskId: taskId || null,
220
- workspaceId: trimmedWorkspaceId || null
221
- })
222
- });
223
- if (initRes.ok) {
224
- const initData = await initRes.json();
225
- if (initData?.success && typeof initData?.uploadUrl === 'string' && typeof initData?.path === 'string') {
226
- const uploadRes = await fetch(initData.uploadUrl, {
227
- method: String(initData.method || 'PUT'),
228
- headers: initData.headers || { 'Content-Type': file.type || 'application/octet-stream' },
229
- body: file
230
- });
231
- if (!uploadRes.ok) {
232
- throw new Error(`Upload failed for ${file.name}`);
233
- }
234
- const finalizeRes = await fetch('/api/taskforce/context-upload/finalize', {
235
- method: 'POST',
236
- headers: buildApiHeaders(),
237
- body: JSON.stringify({
238
- relativePath: initData.relativePath,
239
- mimeType: file.type || 'application/octet-stream',
240
- originalName: file.name,
241
- size: file.size,
242
- taskId: taskId || null,
243
- workspaceId: trimmedWorkspaceId || null
244
- })
245
- });
246
- if (!finalizeRes.ok) {
247
- const finalizeError = await finalizeRes.json().catch(() => ({}));
248
- throw new Error(finalizeError?.error || `Upload failed for ${file.name}`);
249
- }
250
- data = await finalizeRes.json().catch(() => null);
251
- }
252
- else if (initData?.success && typeof initData?.path === 'string') {
253
- data = initData;
254
- }
255
- }
256
- if (!data) {
257
- const encoded = await readFileAsDataUrl(file);
258
- const res = await fetch('/api/taskforce/context-upload', {
259
- method: 'POST',
260
- headers: buildApiHeaders(),
261
- body: JSON.stringify({
262
- file: encoded,
263
- originalName: file.name,
264
- taskId: taskId || null,
265
- workspaceId: trimmedWorkspaceId || null
266
- })
267
- });
268
- if (!res.ok) {
269
- throw new Error(`Upload failed for ${file.name}`);
270
- }
271
- data = await res.json();
272
- }
273
- if (!data?.success || !data?.path) {
274
- throw new Error(`Upload failed for ${file.name}`);
275
- }
276
- const keys = getContextKeys(data.path, data.fsPath);
277
- if (hasAnyKey(contextKeySet, keys)) {
278
- duplicateCount += 1;
279
- continue;
280
- }
281
- onAddContextFile({
282
- path: data.path,
283
- fsPath: data.fsPath,
284
- caption: typeof data.caption === 'string' && data.caption.trim().length > 0 ? data.caption : file.name,
285
- displayName: typeof data.displayName === 'string' ? data.displayName : undefined,
286
- originalFilename: typeof data.originalFilename === 'string' ? data.originalFilename : file.name,
287
- assetId: typeof data.assetId === 'string' ? data.assetId : undefined,
288
- referenceNumber: typeof data.referenceNumber === 'number' ? data.referenceNumber : null,
289
- referenceLabel: typeof data.referenceLabel === 'string' ? data.referenceLabel : undefined,
290
- taskId: typeof data.taskId === 'string' ? data.taskId : null,
291
- timestamp: new Date().toISOString()
292
- });
293
- dispatchTaskforceContextAssetsMutated({
294
- workspaceId: trimmedWorkspaceId || 'default',
295
- taskId: taskId || null,
296
- reason: 'upload'
297
- });
298
- addKeys(contextKeySet, keys);
299
- localUploadFingerprintRef.current.add(fingerprint);
300
- }
301
- const notices = [...validationNotices];
302
- if (duplicateCount > 0) {
303
- notices.push(`${duplicateCount} duplicate file${duplicateCount === 1 ? '' : 's'} skipped.`);
304
- }
305
- setContextNotice(notices.length > 0 ? notices.join(' ') : null);
306
- contextKeyRef.current = contextKeySet;
307
- }
308
- catch (error) {
309
- setUploadError(error instanceof Error ? error.message : 'Failed to upload context file');
310
- }
311
- finally {
312
- setUploading(false);
313
- if (inputRef.current)
314
- inputRef.current.value = '';
315
- }
316
- };
317
- const handleFilesSelected = async (files) => {
318
- if (!files || files.length === 0)
319
- return;
320
- await handleSelectedFiles(Array.from(files));
321
- };
322
- const handlePasteImage = async () => {
323
- setUploadError(null);
324
- setContextNotice(null);
325
- if (!navigator.clipboard || typeof navigator.clipboard.read !== 'function') {
326
- setUploadError('Clipboard image paste is not available in this browser.');
327
- return;
328
- }
329
- try {
330
- const clipboardItems = await navigator.clipboard.read();
331
- const imageFiles = [];
332
- for (const item of clipboardItems) {
333
- const imageType = getClipboardImageType(item);
334
- if (!imageType)
335
- continue;
336
- const blob = await item.getType(imageType);
337
- const extension = imageType.toLowerCase() === 'image/png'
338
- ? 'png'
339
- : imageType.toLowerCase() === 'image/webp'
340
- ? 'webp'
341
- : 'jpg';
342
- imageFiles.push(new globalThis.File([blob], `pasted-image-${Date.now()}.${extension}`, { type: imageType }));
343
- }
344
- if (imageFiles.length === 0) {
345
- setUploadError('Clipboard does not currently contain a supported image.');
346
- return;
347
- }
348
- await handleSelectedFiles(imageFiles);
349
- }
350
- catch (error) {
351
- setUploadError(error?.message || 'Failed to read an image from the clipboard.');
352
- }
353
- };
354
- const parseDroppedLinks = (event) => {
355
- const droppedLinks = new Set();
356
- const rawUriList = event.dataTransfer.getData('text/uri-list') || '';
357
- const rawText = event.dataTransfer.getData('text/plain') || '';
358
- const candidates = `${rawUriList}\n${rawText}`
359
- .split('\n')
360
- .map((line) => line.trim())
361
- .filter((line) => !!line && !line.startsWith('#'));
362
- for (const candidate of candidates) {
363
- if (/^file:\/\//i.test(candidate)) {
364
- try {
365
- const fileUrl = new URL(candidate);
366
- const fsPath = decodeURIComponent(fileUrl.pathname || '').trim();
367
- if (fsPath)
368
- droppedLinks.add(fsPath);
369
- }
370
- catch {
371
- // Ignore malformed file URL drops
372
- }
373
- continue;
374
- }
375
- droppedLinks.add(candidate);
376
- }
377
- return Array.from(droppedLinks);
378
- };
379
- const handleDrop = (event) => {
380
- event.preventDefault();
381
- event.stopPropagation();
382
- setIsDropActive(false);
383
- setDragDepth(0);
384
- setUploadError(null);
385
- setLinkError(null);
386
- setContextNotice(null);
387
- if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
388
- void handleFilesSelected(event.dataTransfer.files);
389
- return;
390
- }
391
- const droppedLinks = parseDroppedLinks(event);
392
- if (droppedLinks.length === 0) {
393
- setLinkError('Drop a file or URL.');
394
- return;
395
- }
396
- const contextKeySet = new Set(contextKeyRef.current);
397
- let duplicateCount = 0;
398
- let addedCount = 0;
399
- let invalidCount = 0;
400
- droppedLinks.forEach((link) => {
401
- const result = addLink(link, false, contextKeySet);
402
- if (result === 'duplicate')
403
- duplicateCount += 1;
404
- if (result === 'added')
405
- addedCount += 1;
406
- if (result === 'invalid')
407
- invalidCount += 1;
408
- });
409
- const notices = [];
410
- if (duplicateCount > 0) {
411
- notices.push(`${duplicateCount} duplicate link${duplicateCount === 1 ? '' : 's'} skipped.`);
412
- }
413
- if (invalidCount > 0) {
414
- notices.push(`${invalidCount} local path${invalidCount === 1 ? '' : 's'} skipped. Upload files to attach them, or drop an http(s) URL to link.`);
415
- }
416
- setContextNotice(notices.length > 0 ? notices.join(' ') : (addedCount > 0 ? null : contextNotice));
417
- contextKeyRef.current = contextKeySet;
418
- };
419
- const handleDragEnter = (event) => {
420
- event.preventDefault();
421
- event.stopPropagation();
422
- setDragDepth((depth) => depth + 1);
423
- setIsDropActive(true);
424
- };
425
- const handleDragLeave = (event) => {
426
- event.preventDefault();
427
- event.stopPropagation();
428
- setDragDepth((depth) => {
429
- const nextDepth = Math.max(0, depth - 1);
430
- if (nextDepth === 0)
431
- setIsDropActive(false);
432
- return nextDepth;
433
- });
434
- };
435
- const handleDragOver = (event) => {
436
- event.preventDefault();
437
- event.stopPropagation();
438
- event.dataTransfer.dropEffect = 'copy';
439
- };
440
- const imageFiles = contextFiles.filter((item) => {
441
- const path = typeof item === 'string' ? item : item.path;
442
- return isImagePath(path);
443
- });
444
- const documentFiles = contextFiles.filter((item) => {
445
- const path = typeof item === 'string' ? item : item.path;
446
- return !isImagePath(path);
447
- });
448
- return (_jsxs("div", { className: styles.specialistSection, children: [_jsxs("div", { className: styles.toggleHeading, onClick: () => setShowContextUpload(!showContextUpload), title: showContextUpload ? 'Hide context documents' : 'Show context documents', children: [_jsxs("label", { className: styles.label, children: ["Context Documents ", contextFiles.length > 0 && `(${contextFiles.length})`] }), showContextUpload ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14 })] }), showContextUpload && (_jsxs(_Fragment, { children: [_jsx("p", { className: styles.settingsHint, children: "Upload files into task context storage, or add an external http(s) URL." }), _jsxs("div", { className: styles.contextUploadPanel, children: [_jsxs("div", { className: styles.contextUploadActions, children: [_jsxs("button", { type: "button", className: styles.secondaryHeaderBtn, onClick: () => inputRef.current?.click(), disabled: uploading, children: [uploading ? _jsx(Loader2, { size: 12, className: styles.spinner }) : _jsx(FileUp, { size: 12 }), uploading ? 'Uploading...' : 'Upload File'] }), _jsxs("button", { type: "button", className: styles.secondaryHeaderBtn, onClick: () => {
449
- void handlePasteImage();
450
- }, disabled: uploading, children: [_jsx(Copy, { size: 12 }), "Paste Image from Clipboard"] })] }), _jsx("input", { ref: inputRef, type: "file", accept: ACCEPTED_CONTEXT_FILE_TYPES, multiple: true, className: styles.hiddenInput, onChange: (e) => {
451
- void handleFilesSelected(e.target.files);
452
- } }), uploadError && _jsx("div", { className: styles.error, children: uploadError }), _jsxs("div", { className: styles.contextLinkRow, children: [_jsx("input", { type: "text", className: styles.input, placeholder: "Add external URL (https://...)", value: linkValue, onChange: (e) => {
453
- setLinkValue(e.target.value);
454
- if (linkError)
455
- setLinkError(null);
456
- if (contextNotice)
457
- setContextNotice(null);
458
- } }), _jsx("button", { type: "button", className: styles.secondaryHeaderBtn, onClick: () => {
459
- if (!linkValue.trim()) {
460
- setLinkError('Enter an http(s) URL to link.');
461
- return;
462
- }
463
- const result = addLink(linkValue, true);
464
- if (result === 'invalid') {
465
- setLinkError('Only external http(s) URLs can be linked here. Upload files to attach them.');
466
- return;
467
- }
468
- if (result === 'duplicate') {
469
- setContextNotice('Link already exists in task context.');
470
- }
471
- else if (result === 'added') {
472
- setContextNotice(null);
473
- }
474
- }, children: "Add URL" })] }), _jsxs("div", { className: `${styles.contextDropzone} ${isDropActive ? styles.contextDropzoneActive : ''}`, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, children: [_jsx(FileUp, { size: 14 }), _jsx("span", { children: "Drop files or URLs here" })] })] }), contextNotice && _jsx("div", { className: styles.contextNotice, children: contextNotice }), linkError && _jsx("div", { className: styles.error, children: linkError }), imageFiles.length > 0 && (_jsx("div", { className: styles.attachmentGrid, children: contextFiles.map((item, idx) => {
475
- const path = typeof item === 'string' ? item : item.path;
476
- if (!isImagePath(path))
477
- return null;
478
- const fsPath = typeof item === 'string' ? undefined : item.fsPath;
479
- const caption = typeof item === 'string' ? '' : item.caption || '';
480
- const displayName = getAttachmentDisplayName(item);
481
- const isImage = isImagePath(path);
482
- const isExternalUrl = /^https?:\/\//i.test(path);
483
- const canAnnotate = canOpenAnnotatedAttachment(item, { taskId });
484
- return (_jsxs("div", { className: styles.attachmentCard, children: [_jsxs("div", { className: styles.attachmentPreviewContainer, children: [isImage ? (_jsx("img", { src: path, alt: caption || `Context file ${idx}`, onClick: () => {
485
- if (dispatchOpenAnnotatedAttachment(item, { taskId, taskReferenceLabel }))
486
- return;
487
- window.open(path, '_blank');
488
- }, onError: (e) => {
489
- e.target.style.display = 'none';
490
- e.target.parentElement.classList.add(styles.brokenImage);
491
- } })) : (_jsxs("button", { type: "button", className: styles.contextFileLink, onClick: () => {
492
- if (dispatchOpenMarkdownDocument(item, fsPath))
493
- return;
494
- window.open(path, '_blank');
495
- }, title: "Open file", children: [_jsx(FileUp, { size: 16 }), _jsx("span", { children: caption || fsPath || path.split('/').pop() || 'Context file' })] })), _jsxs("div", { className: styles.brokenImagePlaceholder, children: [_jsx(AlertTriangle, { size: 16 }), _jsx("span", { children: "Preview unavailable" })] }), _jsxs("div", { className: styles.attachmentActions, children: [canAnnotate && (_jsx("button", { type: "button", className: styles.attachmentActionBtn, onClick: (e) => {
496
- e.stopPropagation();
497
- dispatchOpenAnnotatedAttachment(item, { taskId, taskReferenceLabel });
498
- }, title: "Annotate", children: _jsx(PencilLine, { size: 12 }) })), fsPath && (_jsx("button", { type: "button", className: styles.attachmentActionBtn, onClick: (e) => {
499
- e.stopPropagation();
500
- navigator.clipboard.writeText(fsPath);
501
- }, title: `Copy path: ${fsPath}`, children: _jsx(Copy, { size: 12 }) })), !isExternalUrl && (_jsx("button", { type: "button", className: styles.attachmentActionBtn, onClick: (e) => {
502
- e.stopPropagation();
503
- window.open(buildDownloadUrl(path, caption || displayName), '_blank');
504
- }, title: "Download", children: _jsx(Download, { size: 12 }) })), _jsx("button", { type: "button", className: styles.attachmentActionBtn, onClick: (e) => {
505
- e.stopPropagation();
506
- onRemoveContextFile(idx);
507
- }, title: "Delete", children: _jsx(Trash2, { size: 12 }) })] })] }), _jsx("input", { type: "text", className: styles.attachmentCaptionInput, placeholder: "Add a label...", value: caption, onChange: (e) => onUpdateContextCaption(idx, e.target.value) })] }, idx));
508
- }) })), documentFiles.length > 0 && (_jsx("div", { className: styles.documentAttachmentList, children: contextFiles.map((item, idx) => {
509
- const path = typeof item === 'string' ? item : item.path;
510
- if (isImagePath(path))
511
- return null;
512
- const fsPath = typeof item === 'string' ? undefined : item.fsPath;
513
- const caption = typeof item === 'string' ? '' : item.caption || '';
514
- const displayName = getAttachmentDisplayName(item);
515
- const fileType = getAttachmentTypeLabel(item);
516
- const documentReferenceLabel = typeof item === 'string' ? '' : getDocumentReferenceLabel(item);
517
- const isExternalUrl = /^https?:\/\//i.test(path);
518
- return (_jsxs("div", { className: styles.documentAttachmentItem, children: [_jsxs("div", { className: styles.documentAttachmentRow, children: [_jsxs("button", { type: "button", className: styles.documentAttachmentLink, onClick: () => {
519
- if (dispatchOpenMarkdownDocument(item, fsPath))
520
- return;
521
- window.open(path, '_blank');
522
- }, title: displayName, children: [_jsxs("span", { className: styles.documentAttachmentMain, children: [_jsx("span", { className: styles.documentAttachmentIcon, "aria-hidden": "true", children: _jsx(File, { size: 13 }) }), _jsxs("span", { className: styles.documentAttachmentText, children: [_jsx("span", { className: styles.documentAttachmentName, children: displayName }), documentReferenceLabel && (_jsx("span", { className: styles.taskIdBadge, children: documentReferenceLabel }))] })] }), _jsx("span", { className: styles.documentAttachmentType, children: fileType })] }), _jsxs("div", { className: styles.documentAttachmentActions, children: [fsPath && (_jsx("button", { type: "button", className: styles.documentAttachmentActionBtn, onClick: () => {
523
- navigator.clipboard.writeText(fsPath);
524
- }, title: `Copy path: ${fsPath}`, children: _jsx(Copy, { size: 12 }) })), !isExternalUrl && (_jsx("button", { type: "button", className: styles.documentAttachmentActionBtn, onClick: () => {
525
- window.open(buildDownloadUrl(path, caption || displayName), '_blank');
526
- }, title: "Download", children: _jsx(Download, { size: 12 }) })), _jsx("button", { type: "button", className: styles.documentAttachmentActionBtn, onClick: () => onRemoveContextFile(idx), title: "Delete", children: _jsx(Trash2, { size: 12 }) })] })] }), _jsx("input", { type: "text", className: styles.documentAttachmentCaptionInput, placeholder: "Add a label...", value: caption, onChange: (e) => onUpdateContextCaption(idx, e.target.value) })] }, idx));
527
- }) }))] }))] }));
4
+ const { taskId, taskReferenceLabel, workspaceId, apiBaseUrl, contextFiles, onAddContextFile, onRemoveContextFile, onUpdateContextCaption, } = props;
5
+ return (_jsx(ContextAttachmentManager, { ownerType: "task", ownerId: taskId, ownerReferenceLabel: taskReferenceLabel, workspaceId: workspaceId, apiBaseUrl: apiBaseUrl, attachments: contextFiles, onAddAttachment: onAddContextFile, onRemoveAttachment: onRemoveContextFile, onUpdateAttachmentCaption: onUpdateContextCaption }));
528
6
  }