@taskforcehq/taskforce 0.3.311 → 0.3.313

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 (454) hide show
  1. package/README.md +40 -2
  2. package/dist/TaskforceCore.js +1 -1
  3. package/dist/components/features/InitiativesModule.js +1 -1
  4. package/dist/components/features/TaskSettings.js +1 -1
  5. package/dist/components/views/StandaloneLayout.js +3 -6
  6. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +1 -2
  7. package/dist/components/views/standalone/modals/AccountHubModal.js +2 -3
  8. package/dist/components/views/standalone/modals/HelpModal.js +15 -2
  9. package/dist/hooks/useFilterSort.js +1 -1
  10. package/dist/hooks/useTaskforce.js +2 -2
  11. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +5 -2
  12. package/dist/ui/assets/{AgentsModule-D8u9P5dZ.js → AgentsModule-BULqIo8e.js} +1 -1
  13. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-CsUauFA2.js → AnnotatedAttachmentWorkspace-DHr4rHK9.js} +1 -1
  14. package/dist/ui/assets/{ContextAttachmentManager-DbQvDHFz.js → ContextAttachmentManager-DoMe2OpY.js} +1 -1
  15. package/dist/ui/assets/{DocumentWorkspace-BmTkN1A8.js → DocumentWorkspace-DumBWhvb.js} +1 -1
  16. package/dist/ui/assets/{EntityActivityTimeline-BXHTM-Nh.js → EntityActivityTimeline-sHk8Nzbq.js} +1 -1
  17. package/dist/ui/assets/{InitiativesModule-D33RyXx0.js → InitiativesModule-3X9UNzHh.js} +1 -1
  18. package/dist/ui/assets/{PlansPage-D0RXlT9_.js → PlansPage-C3A3EYaV.js} +1 -1
  19. package/dist/ui/assets/{TaskContextUpload-Vsg67lpR.js → TaskContextUpload-C8G7pWkX.js} +1 -1
  20. package/dist/ui/assets/{TaskSettings-DshLw7_d.js → TaskSettings-DM1o9Wla.js} +2 -2
  21. package/dist/ui/assets/{WorkflowsModule-CLM-1BnF.js → WorkflowsModule-CW0crzvD.js} +1 -1
  22. package/dist/ui/assets/documentReferences-BZB3nMRb.js +1 -0
  23. package/dist/ui/assets/index-BWFQ9mGn.js +5 -0
  24. package/dist/ui/assets/{index-DOR9PRqt.css → index-DcK_8tuE.css} +1 -1
  25. package/dist/ui/index.html +2 -2
  26. package/package.json +12 -4
  27. package/scripts/postinstall.js +4 -3
  28. package/dist/TaskforceCore.CategoryFallback.test.d.ts +0 -1
  29. package/dist/TaskforceCore.CategoryFallback.test.js +0 -101
  30. package/dist/TaskforceCore.filter-persistence.test.d.ts +0 -1
  31. package/dist/TaskforceCore.filter-persistence.test.js +0 -402
  32. package/dist/TaskforceCore.routing.test.d.ts +0 -1
  33. package/dist/TaskforceCore.routing.test.js +0 -311
  34. package/dist/TaskforceCore.test.d.ts +0 -1
  35. package/dist/TaskforceCore.test.js +0 -7910
  36. package/dist/TaskforceWidget.test.d.ts +0 -1
  37. package/dist/TaskforceWidget.test.js +0 -92
  38. package/dist/UnsavedChangesRelaxed.test.d.ts +0 -1
  39. package/dist/UnsavedChangesRelaxed.test.js +0 -16
  40. package/dist/__tests__/localizationCatalogs.test.d.ts +0 -1
  41. package/dist/__tests__/localizationCatalogs.test.js +0 -54
  42. package/dist/__tests__/workflowSimulation.test.d.ts +0 -1
  43. package/dist/__tests__/workflowSimulation.test.js +0 -113
  44. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.d.ts +0 -1
  45. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.js +0 -278
  46. package/dist/annotatedAttachments/payload.test.d.ts +0 -1
  47. package/dist/annotatedAttachments/payload.test.js +0 -67
  48. package/dist/compat/workspaceSyncCompat.test.d.ts +0 -1
  49. package/dist/compat/workspaceSyncCompat.test.js +0 -124
  50. package/dist/components/features/AgentsModule.test.d.ts +0 -1
  51. package/dist/components/features/AgentsModule.test.js +0 -601
  52. package/dist/components/features/AnnotatedAttachmentWorkspace.test.d.ts +0 -1
  53. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +0 -2883
  54. package/dist/components/features/DocumentWorkspace.test.d.ts +0 -1
  55. package/dist/components/features/DocumentWorkspace.test.js +0 -1439
  56. package/dist/components/features/TaskSettings.test.d.ts +0 -1
  57. package/dist/components/features/TaskSettings.test.js +0 -1674
  58. package/dist/components/features/settings/TaxonomyLibraryManager.test.d.ts +0 -1
  59. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +0 -141
  60. package/dist/components/task/TaskActionHeader.test.d.ts +0 -1
  61. package/dist/components/task/TaskActionHeader.test.js +0 -91
  62. package/dist/components/task/TaskCard.test.d.ts +0 -1
  63. package/dist/components/task/TaskCard.test.js +0 -513
  64. package/dist/components/task/TaskContextUpload.test.d.ts +0 -1
  65. package/dist/components/task/TaskContextUpload.test.js +0 -506
  66. package/dist/components/task/TaskForm.test.d.ts +0 -1
  67. package/dist/components/task/TaskForm.test.js +0 -946
  68. package/dist/components/task/TaskKanban.empty-column-drop.test.d.ts +0 -1
  69. package/dist/components/task/TaskKanban.empty-column-drop.test.js +0 -66
  70. package/dist/components/task/TaskKanban.planning-drop.test.d.ts +0 -1
  71. package/dist/components/task/TaskKanban.planning-drop.test.js +0 -93
  72. package/dist/components/task/TaskKanban.test.d.ts +0 -1
  73. package/dist/components/task/TaskKanban.test.js +0 -99
  74. package/dist/components/task/TaskList.test.d.ts +0 -1
  75. package/dist/components/task/TaskList.test.js +0 -473
  76. package/dist/components/task/TaskStatusActions.test.d.ts +0 -1
  77. package/dist/components/task/TaskStatusActions.test.js +0 -54
  78. package/dist/components/task/taskKanbanDropRules.test.d.ts +0 -1
  79. package/dist/components/task/taskKanbanDropRules.test.js +0 -114
  80. package/dist/components/ui/LevelSelector.test.d.ts +0 -1
  81. package/dist/components/ui/LevelSelector.test.js +0 -55
  82. package/dist/components/ui/Markdown.test.d.ts +0 -1
  83. package/dist/components/ui/Markdown.test.js +0 -38
  84. package/dist/components/ui/MultiSelectFilter.test.d.ts +0 -1
  85. package/dist/components/ui/MultiSelectFilter.test.js +0 -74
  86. package/dist/components/ui/TaxonomyDropdown.test.d.ts +0 -1
  87. package/dist/components/ui/TaxonomyDropdown.test.js +0 -130
  88. package/dist/components/ui/TopNoticeLayer.test.d.ts +0 -1
  89. package/dist/components/ui/TopNoticeLayer.test.js +0 -25
  90. package/dist/components/views/AppShellHeader.test.d.ts +0 -1
  91. package/dist/components/views/AppShellHeader.test.js +0 -42
  92. package/dist/components/views/PlanComparisonPage.test.d.ts +0 -1
  93. package/dist/components/views/PlanComparisonPage.test.js +0 -758
  94. package/dist/components/views/PlansPage.test.d.ts +0 -1
  95. package/dist/components/views/PlansPage.test.js +0 -710
  96. package/dist/components/views/chrome/WorkspaceChromeSections.test.d.ts +0 -1
  97. package/dist/components/views/chrome/WorkspaceChromeSections.test.js +0 -54
  98. package/dist/components/views/chrome/workspaceChrome.test.d.ts +0 -1
  99. package/dist/components/views/chrome/workspaceChrome.test.js +0 -52
  100. package/dist/components/views/panels/DocumentFilterToolbar.test.d.ts +0 -1
  101. package/dist/components/views/panels/DocumentFilterToolbar.test.js +0 -19
  102. package/dist/components/views/panels/FilterToolbar.test.d.ts +0 -1
  103. package/dist/components/views/panels/FilterToolbar.test.js +0 -47
  104. package/dist/components/views/panels/PlanningDrawer.test.d.ts +0 -1
  105. package/dist/components/views/panels/PlanningDrawer.test.js +0 -529
  106. package/dist/components/views/planningScope.test.d.ts +0 -1
  107. package/dist/components/views/planningScope.test.js +0 -60
  108. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +0 -1
  109. package/dist/components/views/standalone/modals/AccountHubModal.test.js +0 -72
  110. package/dist/components/views/standalone/modals/HelpModal.test.d.ts +0 -1
  111. package/dist/components/views/standalone/modals/HelpModal.test.js +0 -23
  112. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +0 -1
  113. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +0 -24
  114. package/dist/components/views/teamManagementAccess.test.d.ts +0 -1
  115. package/dist/components/views/teamManagementAccess.test.js +0 -44
  116. package/dist/config/deployment.test.d.ts +0 -1
  117. package/dist/config/deployment.test.js +0 -90
  118. package/dist/config/envSchema.test.d.ts +0 -1
  119. package/dist/config/envSchema.test.js +0 -125
  120. package/dist/core/AiProfileService.test.d.ts +0 -1
  121. package/dist/core/AiProfileService.test.js +0 -184
  122. package/dist/core/AiProfiles.test.d.ts +0 -1
  123. package/dist/core/AiProfiles.test.js +0 -1873
  124. package/dist/core/AuthTokenService.test.d.ts +0 -1
  125. package/dist/core/AuthTokenService.test.js +0 -78
  126. package/dist/core/BulkUpdateFieldsPreview.test.d.ts +0 -1
  127. package/dist/core/BulkUpdateFieldsPreview.test.js +0 -62
  128. package/dist/core/CompletedAt.test.d.ts +0 -1
  129. package/dist/core/CompletedAt.test.js +0 -44
  130. package/dist/core/CreatedBy.test.d.ts +0 -1
  131. package/dist/core/CreatedBy.test.js +0 -188
  132. package/dist/core/DeletedTaskTrash.test.d.ts +0 -1
  133. package/dist/core/DeletedTaskTrash.test.js +0 -104
  134. package/dist/core/EntitlementsPolicy.test.d.ts +0 -1
  135. package/dist/core/EntitlementsPolicy.test.js +0 -280
  136. package/dist/core/GlobalSettingsService.test.d.ts +0 -1
  137. package/dist/core/GlobalSettingsService.test.js +0 -45
  138. package/dist/core/JsonBackupRuntime.test.d.ts +0 -1
  139. package/dist/core/JsonBackupRuntime.test.js +0 -155
  140. package/dist/core/PlanFeatureCatalog.test.d.ts +0 -1
  141. package/dist/core/PlanFeatureCatalog.test.js +0 -139
  142. package/dist/core/PlanVersionPolicy.test.d.ts +0 -1
  143. package/dist/core/PlanVersionPolicy.test.js +0 -236
  144. package/dist/core/PlanningEntities.test.d.ts +0 -1
  145. package/dist/core/PlanningEntities.test.js +0 -509
  146. package/dist/core/ScheduleFields.test.d.ts +0 -1
  147. package/dist/core/ScheduleFields.test.js +0 -193
  148. package/dist/core/SetupState.test.d.ts +0 -1
  149. package/dist/core/SetupState.test.js +0 -113
  150. package/dist/core/SignupMonetizationSettings.test.d.ts +0 -1
  151. package/dist/core/SignupMonetizationSettings.test.js +0 -515
  152. package/dist/core/Stats.test.d.ts +0 -1
  153. package/dist/core/Stats.test.js +0 -36
  154. package/dist/core/SystemAdmin.test.d.ts +0 -1
  155. package/dist/core/SystemAdmin.test.js +0 -1106
  156. package/dist/core/TaskActivitySync.test.d.ts +0 -1
  157. package/dist/core/TaskActivitySync.test.js +0 -90
  158. package/dist/core/TaskAttachmentsCanonical.test.d.ts +0 -1
  159. package/dist/core/TaskAttachmentsCanonical.test.js +0 -356
  160. package/dist/core/TaskAuditTimeline.test.d.ts +0 -1
  161. package/dist/core/TaskAuditTimeline.test.js +0 -319
  162. package/dist/core/TaskTaxonomyValidation.test.d.ts +0 -1
  163. package/dist/core/TaskTaxonomyValidation.test.js +0 -440
  164. package/dist/core/Taskforce.ids.test.d.ts +0 -1
  165. package/dist/core/Taskforce.ids.test.js +0 -611
  166. package/dist/core/Taskforce.listTasksSlim.test.d.ts +0 -1
  167. package/dist/core/Taskforce.listTasksSlim.test.js +0 -91
  168. package/dist/core/Taskforce.mcpAuth.test.d.ts +0 -1
  169. package/dist/core/Taskforce.mcpAuth.test.js +0 -450
  170. package/dist/core/Taskforce.realtimeMutation.test.d.ts +0 -1
  171. package/dist/core/Taskforce.realtimeMutation.test.js +0 -131
  172. package/dist/core/UserProfileAvatarDrafts.test.d.ts +0 -1
  173. package/dist/core/UserProfileAvatarDrafts.test.js +0 -138
  174. package/dist/core/WorkspaceDelete.test.d.ts +0 -1
  175. package/dist/core/WorkspaceDelete.test.js +0 -159
  176. package/dist/core/WorkspacePermissions.test.d.ts +0 -1
  177. package/dist/core/WorkspacePermissions.test.js +0 -270
  178. package/dist/core/WorkspacePlanMode.test.d.ts +0 -1
  179. package/dist/core/WorkspacePlanMode.test.js +0 -383
  180. package/dist/core/WorkspaceSyncSettings.test.d.ts +0 -1
  181. package/dist/core/WorkspaceSyncSettings.test.js +0 -65
  182. package/dist/core/__tests__/TaskAttachmentsCanonical.test.d.ts +0 -1
  183. package/dist/core/__tests__/TaskAttachmentsCanonical.test.js +0 -212
  184. package/dist/documentReviews/DocumentReviewSessionStore.test.d.ts +0 -1
  185. package/dist/documentReviews/DocumentReviewSessionStore.test.js +0 -127
  186. package/dist/hooks/authGuardPolicy.test.d.ts +0 -1
  187. package/dist/hooks/authGuardPolicy.test.js +0 -76
  188. package/dist/hooks/bootstrapTimeouts.test.d.ts +0 -1
  189. package/dist/hooks/bootstrapTimeouts.test.js +0 -12
  190. package/dist/hooks/sync/controlPlane.test.d.ts +0 -1
  191. package/dist/hooks/sync/controlPlane.test.js +0 -121
  192. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +0 -1
  193. package/dist/hooks/sync/lifecyclePolicy.test.js +0 -124
  194. package/dist/hooks/sync/orchestratorShared.test.d.ts +0 -1
  195. package/dist/hooks/sync/orchestratorShared.test.js +0 -98
  196. package/dist/hooks/sync/pullLifecycle.test.d.ts +0 -1
  197. package/dist/hooks/sync/pullLifecycle.test.js +0 -80
  198. package/dist/hooks/sync/recovery.test.d.ts +0 -1
  199. package/dist/hooks/sync/recovery.test.js +0 -292
  200. package/dist/hooks/sync/transfers.test.d.ts +0 -1
  201. package/dist/hooks/sync/transfers.test.js +0 -928
  202. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +0 -1
  203. package/dist/hooks/sync/useRecentSyncEvents.test.js +0 -124
  204. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +0 -1
  205. package/dist/hooks/sync/useSyncStatusActions.test.js +0 -133
  206. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +0 -1
  207. package/dist/hooks/sync/useSyncStatusControls.test.js +0 -88
  208. package/dist/hooks/useFilterSort.test.d.ts +0 -1
  209. package/dist/hooks/useFilterSort.test.js +0 -419
  210. package/dist/hooks/useRealtimeSync.test.d.ts +0 -1
  211. package/dist/hooks/useRealtimeSync.test.js +0 -311
  212. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.d.ts +0 -1
  213. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +0 -54
  214. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +0 -1
  215. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +0 -228
  216. package/dist/hooks/useSyncOrchestrator.retry-closure.test.d.ts +0 -1
  217. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +0 -2027
  218. package/dist/hooks/useTaskData.test.d.ts +0 -1
  219. package/dist/hooks/useTaskData.test.js +0 -291
  220. package/dist/hooks/useTaskMutations.test.d.ts +0 -1
  221. package/dist/hooks/useTaskMutations.test.js +0 -256
  222. package/dist/hooks/useTaskforce.contract.test.d.ts +0 -1
  223. package/dist/hooks/useTaskforce.contract.test.js +0 -7
  224. package/dist/hooks/useTaskforce.priority-filters.test.d.ts +0 -1
  225. package/dist/hooks/useTaskforce.priority-filters.test.js +0 -28
  226. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +0 -1
  227. package/dist/hooks/useTaskforce.runtime-routing.test.js +0 -246
  228. package/dist/hooks/useTaskforce.sync-behavior.test.d.ts +0 -1
  229. package/dist/hooks/useTaskforce.sync-behavior.test.js +0 -3644
  230. package/dist/hooks/useWorkspaceSyncController.test.d.ts +0 -1
  231. package/dist/hooks/useWorkspaceSyncController.test.js +0 -192
  232. package/dist/hooks/workspace/useTaskforceApiRouting.test.d.ts +0 -1
  233. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +0 -77
  234. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +0 -1
  235. package/dist/hooks/workspace/workspaceLocalState.test.js +0 -59
  236. package/dist/localization/localizationCatalogs.test.d.ts +0 -1
  237. package/dist/localization/localizationCatalogs.test.js +0 -37
  238. package/dist/mcp/aiProfileBootstrap.test.d.ts +0 -1
  239. package/dist/mcp/aiProfileBootstrap.test.js +0 -68
  240. package/dist/mcp/canonicalAssetHelpers.test.d.ts +0 -1
  241. package/dist/mcp/canonicalAssetHelpers.test.js +0 -67
  242. package/dist/mcp/clientIntegrations.test.d.ts +0 -1
  243. package/dist/mcp/clientIntegrations.test.js +0 -219
  244. package/dist/mcp/optionResolution.test.d.ts +0 -1
  245. package/dist/mcp/optionResolution.test.js +0 -43
  246. package/dist/mcp/runtime.test.d.ts +0 -1
  247. package/dist/mcp/runtime.test.js +0 -5211
  248. package/dist/mcp/structuredCommentValidation.test.d.ts +0 -1
  249. package/dist/mcp/structuredCommentValidation.test.js +0 -31
  250. package/dist/mcp/structuredDocValidation.test.d.ts +0 -1
  251. package/dist/mcp/structuredDocValidation.test.js +0 -35
  252. package/dist/mcp/toolRegistry.test.d.ts +0 -1
  253. package/dist/mcp/toolRegistry.test.js +0 -34
  254. package/dist/migrations/billingSchemaParity.test.d.ts +0 -1
  255. package/dist/migrations/billingSchemaParity.test.js +0 -160
  256. package/dist/migrations/taskSchemaMigrations.test.d.ts +0 -1
  257. package/dist/migrations/taskSchemaMigrations.test.js +0 -225
  258. package/dist/qaOpen.test.d.ts +0 -1
  259. package/dist/qaOpen.test.js +0 -56
  260. package/dist/releaseVersion.test.d.ts +0 -1
  261. package/dist/releaseVersion.test.js +0 -32
  262. package/dist/runtime/appGates.test.d.ts +0 -1
  263. package/dist/runtime/appGates.test.js +0 -42
  264. package/dist/server/aiProfileAvatarAssets.test.d.ts +0 -1
  265. package/dist/server/aiProfileAvatarAssets.test.js +0 -213
  266. package/dist/server/annotatedAttachmentsRoutes.test.d.ts +0 -1
  267. package/dist/server/annotatedAttachmentsRoutes.test.js +0 -532
  268. package/dist/server/auth.test.d.ts +0 -1
  269. package/dist/server/auth.test.js +0 -131
  270. package/dist/server/buildInfo.test.d.ts +0 -1
  271. package/dist/server/buildInfo.test.js +0 -29
  272. package/dist/server/cors.test.d.ts +0 -1
  273. package/dist/server/cors.test.js +0 -77
  274. package/dist/server/documentReviewRoutes.test.d.ts +0 -1
  275. package/dist/server/documentReviewRoutes.test.js +0 -190
  276. package/dist/server/email.test.d.ts +0 -1
  277. package/dist/server/email.test.js +0 -113
  278. package/dist/server/index.cookieProxy.test.d.ts +0 -1
  279. package/dist/server/index.cookieProxy.test.js +0 -67
  280. package/dist/server/index.rateLimit.test.d.ts +0 -1
  281. package/dist/server/index.rateLimit.test.js +0 -22
  282. package/dist/server/index.test.d.ts +0 -1
  283. package/dist/server/index.test.js +0 -283
  284. package/dist/server/rateLimit.test.d.ts +0 -1
  285. package/dist/server/rateLimit.test.js +0 -107
  286. package/dist/server/realtimeSync.auth.test.d.ts +0 -1
  287. package/dist/server/realtimeSync.auth.test.js +0 -62
  288. package/dist/server/realtimeSync.replay.test.d.ts +0 -1
  289. package/dist/server/realtimeSync.replay.test.js +0 -192
  290. package/dist/server/realtimeSync.workspaceIsolation.test.d.ts +0 -1
  291. package/dist/server/realtimeSync.workspaceIsolation.test.js +0 -101
  292. package/dist/server/routes/billing.test.d.ts +0 -1
  293. package/dist/server/routes/billing.test.js +0 -3515
  294. package/dist/server/routes/shared.test.d.ts +0 -1
  295. package/dist/server/routes/shared.test.js +0 -32
  296. package/dist/server/routes/sync.integration.test.d.ts +0 -1
  297. package/dist/server/routes/sync.integration.test.js +0 -2932
  298. package/dist/server/routes/syncAuxRoutes.test.d.ts +0 -1
  299. package/dist/server/routes/syncAuxRoutes.test.js +0 -158
  300. package/dist/server/routes/syncRouteGuards.test.d.ts +0 -1
  301. package/dist/server/routes/syncRouteGuards.test.js +0 -94
  302. package/dist/server/routes.test.d.ts +0 -1
  303. package/dist/server/routes.test.js +0 -15612
  304. package/dist/server/runtimeMode.test.d.ts +0 -1
  305. package/dist/server/runtimeMode.test.js +0 -26
  306. package/dist/server/securityHeaders.test.d.ts +0 -1
  307. package/dist/server/securityHeaders.test.js +0 -70
  308. package/dist/services/templateEngine.test.d.ts +0 -1
  309. package/dist/services/templateEngine.test.js +0 -150
  310. package/dist/services/templates.test.d.ts +0 -1
  311. package/dist/services/templates.test.js +0 -45
  312. package/dist/services/workflowDocumentFixtures.test.d.ts +0 -1
  313. package/dist/services/workflowDocumentFixtures.test.js +0 -78
  314. package/dist/services/workflowExportMatrix.test.d.ts +0 -1
  315. package/dist/services/workflowExportMatrix.test.js +0 -113
  316. package/dist/services/workflowExportSnapshots.test.d.ts +0 -1
  317. package/dist/services/workflowExportSnapshots.test.js +0 -30
  318. package/dist/services/workflowGeneration.test.d.ts +0 -1
  319. package/dist/services/workflowGeneration.test.js +0 -12
  320. package/dist/services/workflowSimulation.test.d.ts +0 -1
  321. package/dist/services/workflowSimulation.test.js +0 -113
  322. package/dist/services/workflowV2Policy.test.d.ts +0 -1
  323. package/dist/services/workflowV2Policy.test.js +0 -80
  324. package/dist/shared/customTaxonomyLibrary.test.d.ts +0 -1
  325. package/dist/shared/customTaxonomyLibrary.test.js +0 -20
  326. package/dist/shared/passwordPolicy.test.d.ts +0 -1
  327. package/dist/shared/passwordPolicy.test.js +0 -34
  328. package/dist/shared/runtimeContract.test.d.ts +0 -1
  329. package/dist/shared/runtimeContract.test.js +0 -23
  330. package/dist/shared/taxonomyLibrary.test.d.ts +0 -1
  331. package/dist/shared/taxonomyLibrary.test.js +0 -26
  332. package/dist/shared/taxonomyState.test.d.ts +0 -1
  333. package/dist/shared/taxonomyState.test.js +0 -71
  334. package/dist/soak/preflight.cli.d.ts +0 -1
  335. package/dist/soak/preflight.cli.js +0 -28
  336. package/dist/soak/preflight.d.ts +0 -26
  337. package/dist/soak/preflight.js +0 -113
  338. package/dist/soak/preflight.test.d.ts +0 -1
  339. package/dist/soak/preflight.test.js +0 -39
  340. package/dist/storage/objectStorageClient.test.d.ts +0 -1
  341. package/dist/storage/objectStorageClient.test.js +0 -82
  342. package/dist/storage/objectStorageConfig.test.d.ts +0 -1
  343. package/dist/storage/objectStorageConfig.test.js +0 -50
  344. package/dist/storage/postgresAdapter.test.d.ts +0 -1
  345. package/dist/storage/postgresAdapter.test.js +0 -29
  346. package/dist/storage/workspaceAssetStore.test.d.ts +0 -1
  347. package/dist/storage/workspaceAssetStore.test.js +0 -588
  348. package/dist/sync/cloudSyncApi.test.d.ts +0 -1
  349. package/dist/sync/cloudSyncApi.test.js +0 -111
  350. package/dist/sync/collaborationSyncPayload.test.d.ts +0 -1
  351. package/dist/sync/collaborationSyncPayload.test.js +0 -125
  352. package/dist/sync/contentSyncPayload.test.d.ts +0 -1
  353. package/dist/sync/contentSyncPayload.test.js +0 -143
  354. package/dist/sync/encryptionService.test.d.ts +0 -1
  355. package/dist/sync/encryptionService.test.js +0 -52
  356. package/dist/sync/planningSyncPayload.test.d.ts +0 -1
  357. package/dist/sync/planningSyncPayload.test.js +0 -92
  358. package/dist/sync/realtimeFeature.test.d.ts +0 -1
  359. package/dist/sync/realtimeFeature.test.js +0 -30
  360. package/dist/sync/realtimeReconcile.test.d.ts +0 -1
  361. package/dist/sync/realtimeReconcile.test.js +0 -43
  362. package/dist/sync/syncApplyHandlers.test.d.ts +0 -1
  363. package/dist/sync/syncApplyHandlers.test.js +0 -626
  364. package/dist/sync/syncService.test.d.ts +0 -1
  365. package/dist/sync/syncService.test.js +0 -903
  366. package/dist/sync/taskSyncPayload.test.d.ts +0 -1
  367. package/dist/sync/taskSyncPayload.test.js +0 -54
  368. package/dist/sync/workspacePullFeed.test.d.ts +0 -1
  369. package/dist/sync/workspacePullFeed.test.js +0 -438
  370. package/dist/sync/workspaceSyncModel.test.d.ts +0 -1
  371. package/dist/sync/workspaceSyncModel.test.js +0 -1442
  372. package/dist/test/setup.d.ts +0 -1
  373. package/dist/test/setup.js +0 -15
  374. package/dist/ui/assets/documentReferences-BPWp6ce0.js +0 -1
  375. package/dist/ui/assets/index-zvbo2Jse.js +0 -5
  376. package/dist/utils/accountProfileSummaryCache.test.d.ts +0 -1
  377. package/dist/utils/accountProfileSummaryCache.test.js +0 -63
  378. package/dist/utils/annotatedAttachments.test.d.ts +0 -1
  379. package/dist/utils/annotatedAttachments.test.js +0 -108
  380. package/dist/utils/assignees.test.d.ts +0 -1
  381. package/dist/utils/assignees.test.js +0 -25
  382. package/dist/utils/avatarUpload.test.d.ts +0 -1
  383. package/dist/utils/avatarUpload.test.js +0 -40
  384. package/dist/utils/canonicalAssetKind.test.d.ts +0 -1
  385. package/dist/utils/canonicalAssetKind.test.js +0 -24
  386. package/dist/utils/coalescedRefresh.test.d.ts +0 -1
  387. package/dist/utils/coalescedRefresh.test.js +0 -30
  388. package/dist/utils/constants.icons.test.d.ts +0 -1
  389. package/dist/utils/constants.icons.test.js +0 -41
  390. package/dist/utils/planParser.test.d.ts +0 -1
  391. package/dist/utils/planParser.test.js +0 -183
  392. package/dist/utils/planningReferences.test.d.ts +0 -1
  393. package/dist/utils/planningReferences.test.js +0 -17
  394. package/dist/utils/supportContact.test.d.ts +0 -1
  395. package/dist/utils/supportContact.test.js +0 -19
  396. package/dist/utils/syncEventDetails.test.d.ts +0 -1
  397. package/dist/utils/syncEventDetails.test.js +0 -23
  398. package/dist/utils/syncEventPresentation.test.d.ts +0 -1
  399. package/dist/utils/syncEventPresentation.test.js +0 -64
  400. package/dist/utils/syncStatusPresentation.test.d.ts +0 -1
  401. package/dist/utils/syncStatusPresentation.test.js +0 -164
  402. package/dist/utils/taskActivity.test.d.ts +0 -1
  403. package/dist/utils/taskActivity.test.js +0 -218
  404. package/dist/utils/taskDraftDefaults.test.d.ts +0 -1
  405. package/dist/utils/taskDraftDefaults.test.js +0 -34
  406. package/dist/utils/taskDragZones.test.d.ts +0 -1
  407. package/dist/utils/taskDragZones.test.js +0 -14
  408. package/dist/utils/taskNormalization.test.d.ts +0 -1
  409. package/dist/utils/taskNormalization.test.js +0 -158
  410. package/dist/utils/taskSearch.test.d.ts +0 -1
  411. package/dist/utils/taskSearch.test.js +0 -69
  412. package/dist/utils/taskUtils.test.d.ts +0 -1
  413. package/dist/utils/taskUtils.test.js +0 -19
  414. package/dist/utils/taskforceApiClient.test.d.ts +0 -1
  415. package/dist/utils/taskforceApiClient.test.js +0 -44
  416. package/dist/utils/taxonomyFormDefaults.test.d.ts +0 -1
  417. package/dist/utils/taxonomyFormDefaults.test.js +0 -63
  418. package/dist/utils/uiNotice.test.d.ts +0 -1
  419. package/dist/utils/uiNotice.test.js +0 -18
  420. package/dist/utils/workspaceIdentity.test.d.ts +0 -1
  421. package/dist/utils/workspaceIdentity.test.js +0 -61
  422. package/dist/utils/workspaceLifecycle.test.d.ts +0 -1
  423. package/dist/utils/workspaceLifecycle.test.js +0 -64
  424. package/dist/utils/workspaceSyncPresentation.test.d.ts +0 -1
  425. package/dist/utils/workspaceSyncPresentation.test.js +0 -71
  426. package/scripts/bootstrap-playwright-auth.mjs +0 -155
  427. package/scripts/check-cloud-mcp.mjs +0 -83
  428. package/scripts/check-compat-boundaries.mjs +0 -41
  429. package/scripts/check-env.ts +0 -31
  430. package/scripts/check-localization-strings.mjs +0 -49
  431. package/scripts/check-runtime-boundaries.mjs +0 -44
  432. package/scripts/debug-global.mjs +0 -34
  433. package/scripts/debug-login.mjs +0 -32
  434. package/scripts/debug-server.mjs +0 -36
  435. package/scripts/deploy-prod.sh +0 -41
  436. package/scripts/deploy-staging.sh +0 -23
  437. package/scripts/export-sqlite-to-postgres.mjs +0 -123
  438. package/scripts/generate-env-example.ts +0 -8
  439. package/scripts/mcp-stdio-adapter.mjs +0 -160
  440. package/scripts/migrate-task-data-assets.mjs +0 -408
  441. package/scripts/playwright-auth.sh +0 -19
  442. package/scripts/qa-open-lib.mjs +0 -175
  443. package/scripts/qa-open-lib.mjs.d.ts +0 -49
  444. package/scripts/qa-open.mjs +0 -352
  445. package/scripts/release-version-lib.mjs +0 -88
  446. package/scripts/release-version.mjs +0 -72
  447. package/scripts/run-billing-performance-smoke.sh +0 -24
  448. package/scripts/run-dev-proxy.sh +0 -51
  449. package/scripts/run-e2e-realtime.sh +0 -30
  450. package/scripts/run-smoke.sh +0 -35
  451. package/scripts/run-soak.sh +0 -41
  452. package/scripts/test-global.js +0 -143
  453. package/scripts/update-homebrew-formula.sh +0 -108
  454. package/scripts/with-esbuild.sh +0 -11
@@ -1,1674 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3
- import userEvent from '@testing-library/user-event';
4
- import { describe, it, expect, vi } from 'vitest';
5
- import { TaskSettings } from './TaskSettings';
6
- import { TASKFORCE_THEME_OPTIONS } from '../../utils/theme';
7
- describe('TaskSettings Component', () => {
8
- // Launch decision: Cloud MCP settings must stay identity-neutral for shared connections.
9
- // Do not reintroduce per-connection AI identity controls here unless explicitly requested.
10
- // Default mock props
11
- const defaultSettingsModel = {
12
- currentTheme: 'dark',
13
- globalTheme: 'dark',
14
- themeUseGlobalDefault: false,
15
- keyShortcut: 'Alt+T',
16
- jsonBackupEnabled: false,
17
- globalJsonBackupEnabled: false,
18
- jsonBackupUseGlobalDefault: false,
19
- showTaskCardStatusLabel: true,
20
- exportWorkflowsPath: '.agent/workflows',
21
- exportingResource: null,
22
- exportResult: null,
23
- pathSaved: false,
24
- initialSection: null,
25
- exportEnvironment: 'antigravity',
26
- fetchCloudAuthApi: vi.fn(async () => ({
27
- ok: true,
28
- json: async () => ({ items: [] })
29
- })),
30
- // Mock Callbacks
31
- onThemeChange: vi.fn(),
32
- onSaveTheme: vi.fn(),
33
- onSaveGlobalTheme: vi.fn(),
34
- onResetProjectToGlobal: vi.fn(),
35
- onKeyShortcutChange: vi.fn(),
36
- onJsonBackupEnabledChange: vi.fn(),
37
- onSaveGlobalJsonBackupEnabled: vi.fn(),
38
- onSaveGlobalWeekStartsOn: vi.fn(),
39
- onChecklistDropdownEnabledChange: vi.fn(),
40
- onShowTaskCardStatusLabelChange: vi.fn(async () => true),
41
- onSaveSettings: vi.fn(),
42
- onExportWorkflowsPathChange: vi.fn(),
43
- onExportEnvironmentChange: vi.fn(),
44
- onShowFolderBrowserChange: vi.fn(),
45
- onBrowserTargetChange: vi.fn(),
46
- onFetchFolders: vi.fn(),
47
- // Category Manager Props
48
- categories: [
49
- { value: 'general', label: 'General', disabled: false },
50
- { value: 'backend', label: 'Backend', color: 'red-500', icon: 'Server', path: 'src/backend' }
51
- ],
52
- pathValidation: {
53
- 'src/backend': { exists: true, isDirectory: true, isFile: false }
54
- },
55
- onUpdateCategory: vi.fn(),
56
- onRemoveCategory: vi.fn(),
57
- onSaveCategory: vi.fn(),
58
- onAddPath: vi.fn(),
59
- onRemovePath: vi.fn(),
60
- onUpdateCategoryIcon: vi.fn(),
61
- onUpdateCategoryColor: vi.fn(),
62
- // Type Manager Props
63
- types: [
64
- { value: 'task', label: 'Task' },
65
- { value: 'issue', label: 'Issue' }
66
- ],
67
- onSaveType: vi.fn(),
68
- onRemoveType: vi.fn(),
69
- onUpdateType: vi.fn(),
70
- // Taxonomy Props
71
- taxonomies: [],
72
- onUpdateTaxonomies: vi.fn(),
73
- priorities: [
74
- { value: 1, label: 'Low', color: 'teal-500', icon: 'ArrowDown' },
75
- { value: 2, label: 'Medium', color: 'blue-500', icon: 'Minus' },
76
- { value: 3, label: 'High', color: 'amber-500', icon: 'ArrowUp' },
77
- ],
78
- onUpdatePriorities: vi.fn(),
79
- onAnalyzeSystemTaxonomyPack: vi.fn(() => ({
80
- unmatchedCategoryValues: [],
81
- unmatchedTypeValues: [],
82
- incompatiblePriorityValues: []
83
- })),
84
- onApplySystemTaxonomyPack: vi.fn(async () => ({ success: true })),
85
- approaches: [
86
- { value: 'evaluate', label: 'Evaluate', color: 'amber-500', icon: 'Search' },
87
- { value: 'collaborate', label: 'Collaborate', color: 'blue-500', icon: 'Users' },
88
- { value: 'plan', label: 'Plan', color: 'teal-500', icon: 'FileText' },
89
- ],
90
- onUpdateApproaches: vi.fn(),
91
- };
92
- const defaultProps = {
93
- settingsModel: defaultSettingsModel,
94
- onSectionChange: vi.fn()
95
- };
96
- const renderComponent = (overrides = {}) => {
97
- const { settingsModel: settingsModelOverrides = {}, ...rest } = overrides;
98
- const mergedSettingsModel = {
99
- ...defaultSettingsModel,
100
- ...settingsModelOverrides
101
- };
102
- if (Object.prototype.hasOwnProperty.call(overrides, 'initialSection')) {
103
- mergedSettingsModel.initialSection = overrides.initialSection;
104
- }
105
- const props = {
106
- ...defaultProps,
107
- ...rest,
108
- settingsModel: mergedSettingsModel
109
- };
110
- return render(_jsx(TaskSettings, { ...props }));
111
- };
112
- const renderMcpComponent = (overrides = {}) => {
113
- const { settingsModel: settingsModelOverrides = {}, ...rest } = overrides;
114
- return renderComponent({
115
- ...rest,
116
- renderMode: 'mcp-only',
117
- settingsModel: {
118
- ...settingsModelOverrides,
119
- initialSection: 'mcp'
120
- }
121
- });
122
- };
123
- const openMcpSettings = async (user) => {
124
- const mcpButton = screen.queryByRole('button', { name: /^MCP$/i });
125
- if (mcpButton) {
126
- await user.click(mcpButton);
127
- }
128
- };
129
- const openCloudMcpSettings = async (user) => {
130
- await openMcpSettings(user);
131
- const cloudToggle = screen.queryByRole('button', { name: 'Cloud MCP' });
132
- if (cloudToggle) {
133
- await user.click(cloudToggle);
134
- }
135
- await screen.findByText('Cloud MCP Configuration');
136
- };
137
- const originalFetch = global.fetch;
138
- it('4.1 Open settings - Renders correctly', () => {
139
- renderComponent();
140
- expect(screen.getByText('Sections')).toBeInTheDocument();
141
- expect(screen.getByRole('button', { name: /general/i })).toBeInTheDocument();
142
- });
143
- it('4.1a Sidebar renders all primary settings sections', () => {
144
- renderComponent();
145
- expect(screen.getByRole('button', { name: /general/i })).toBeInTheDocument();
146
- expect(screen.getByRole('button', { name: /appearance/i })).toBeInTheDocument();
147
- expect(screen.getByRole('button', { name: /application/i })).toBeInTheDocument();
148
- expect(screen.getByRole('button', { name: /task cards/i })).toBeInTheDocument();
149
- expect(screen.getByRole('button', { name: /task forms/i })).toBeInTheDocument();
150
- expect(screen.queryByRole('button', { name: /documents/i })).not.toBeInTheDocument();
151
- expect(screen.queryByRole('button', { name: /commands/i })).not.toBeInTheDocument();
152
- expect(screen.queryByRole('button', { name: /initiatives/i })).not.toBeInTheDocument();
153
- expect(screen.queryByRole('button', { name: /workflows/i })).not.toBeInTheDocument();
154
- expect(screen.queryByRole('button', { name: /agents/i })).not.toBeInTheDocument();
155
- expect(screen.getByRole('button', { name: /taxonomy/i })).toBeInTheDocument();
156
- expect(screen.queryByRole('button', { name: /^mcp$/i })).not.toBeInTheDocument();
157
- expect(screen.getByRole('button', { name: /integrations/i })).toBeInTheDocument();
158
- });
159
- it('4.1b General tab removes old profile/account chrome', () => {
160
- renderComponent();
161
- expect(screen.queryByText('Workspace Profile')).not.toBeInTheDocument();
162
- expect(screen.queryByText('Account Plan')).not.toBeInTheDocument();
163
- expect(screen.queryByText('Project Settings')).not.toBeInTheDocument();
164
- expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument();
165
- expect(screen.getByRole('button', { name: 'Reset to Global' })).toBeInTheDocument();
166
- });
167
- it('4.1c Danger zone is hidden in local mode', () => {
168
- renderComponent({
169
- settingsModel: {
170
- runtimeMode: 'local',
171
- workspaceSwitchingEnabled: true,
172
- isAuthenticated: true,
173
- currentWorkspaceRole: 'owner'
174
- }
175
- });
176
- expect(screen.queryByText('Danger Zone')).not.toBeInTheDocument();
177
- });
178
- it('4.1d Global tab no longer shows operating mode', async () => {
179
- const user = userEvent.setup();
180
- renderComponent();
181
- await user.click(screen.getByRole('button', { name: 'Global' }));
182
- expect(screen.queryByText('Operating Mode')).not.toBeInTheDocument();
183
- });
184
- it('4.3 Theme toggle', async () => {
185
- const user = userEvent.setup();
186
- renderComponent();
187
- await user.click(screen.getByRole('button', { name: /application/i }));
188
- const lightBtn = screen.getByRole('button', { name: /^light$/i });
189
- await user.click(lightBtn);
190
- expect(defaultSettingsModel.onThemeChange).toHaveBeenCalledWith('light');
191
- expect(defaultSettingsModel.onSaveTheme).toHaveBeenCalledWith('light');
192
- });
193
- it('4.3a Renders all registered themes in the picker', async () => {
194
- const user = userEvent.setup();
195
- renderComponent();
196
- await user.click(screen.getByRole('button', { name: /application/i }));
197
- for (const themeOption of TASKFORCE_THEME_OPTIONS) {
198
- expect(screen.getByRole('button', { name: new RegExp(`^${themeOption.label}$`, 'i') })).toBeInTheDocument();
199
- }
200
- });
201
- it('4.4 Theme persists', async () => {
202
- const user = userEvent.setup();
203
- // This tests that the callback is invoked, persistence is handled by parent
204
- renderComponent();
205
- await user.click(screen.getByRole('button', { name: /application/i }));
206
- const midnightBtn = screen.getByRole('button', { name: /^midnight$/i });
207
- await user.click(midnightBtn);
208
- expect(defaultSettingsModel.onThemeChange).toHaveBeenCalledWith('midnight');
209
- expect(defaultSettingsModel.onSaveTheme).toHaveBeenCalledWith('midnight');
210
- });
211
- it('4.4a Dawn theme can be selected', async () => {
212
- const user = userEvent.setup();
213
- renderComponent();
214
- await user.click(screen.getByRole('button', { name: /application/i }));
215
- const dawnBtn = screen.getByRole('button', { name: /^dawn$/i });
216
- await user.click(dawnBtn);
217
- expect(defaultSettingsModel.onThemeChange).toHaveBeenCalledWith('dawn');
218
- expect(defaultSettingsModel.onSaveTheme).toHaveBeenCalledWith('dawn');
219
- });
220
- it('4.4b JSON backup toggle updates setting', async () => {
221
- const user = userEvent.setup();
222
- renderComponent();
223
- const backupToggle = screen.getByRole('checkbox', { name: /enable automatic json backup/i });
224
- await user.click(backupToggle);
225
- expect(defaultSettingsModel.onJsonBackupEnabledChange).toHaveBeenCalledWith(true);
226
- });
227
- it('4.4c JSON backup controls are hidden in cloud mode', async () => {
228
- const user = userEvent.setup();
229
- renderComponent({
230
- settingsModel: {
231
- runtimeMode: 'cloud'
232
- }
233
- });
234
- expect(screen.queryByRole('checkbox', { name: /enable automatic json backup/i })).not.toBeInTheDocument();
235
- await user.click(screen.getByRole('button', { name: 'Global' }));
236
- expect(screen.queryByRole('checkbox', { name: /enable global json backup default/i })).not.toBeInTheDocument();
237
- });
238
- it('4.4d Task forms section owns checklist dropdown setting', async () => {
239
- const user = userEvent.setup();
240
- renderComponent();
241
- await user.click(screen.getByRole('button', { name: /task forms/i }));
242
- const checklistToggle = screen.getByRole('checkbox', { name: /enable checklist dropdown in task form/i });
243
- await user.click(checklistToggle);
244
- expect(defaultSettingsModel.onChecklistDropdownEnabledChange).toHaveBeenCalledWith(false);
245
- });
246
- it('4.4e Task cards section owns the status label setting', async () => {
247
- const user = userEvent.setup();
248
- renderComponent();
249
- await user.click(screen.getByRole('button', { name: /task cards/i }));
250
- const statusLabelToggle = screen.getByRole('checkbox', { name: /show status label/i });
251
- await user.click(statusLabelToggle);
252
- expect(defaultSettingsModel.onShowTaskCardStatusLabelChange).toHaveBeenCalledWith(false);
253
- });
254
- it('4.4f Initial appearance section opens directly', () => {
255
- renderComponent({ initialSection: 'appearance-task-forms' });
256
- expect(screen.getByRole('checkbox', { name: /enable checklist dropdown in task form/i })).toBeInTheDocument();
257
- });
258
- // 4.5 Open category manager - It's always visible in the new design
259
- it('4.5 Categories section is visible', () => {
260
- renderComponent({ initialSection: 'categories' });
261
- expect(screen.getAllByText('Categories')[0]).toBeInTheDocument();
262
- });
263
- it('4.6 Add category', async () => {
264
- const user = userEvent.setup();
265
- renderComponent({ initialSection: 'categories' });
266
- // Click create button
267
- const createBtn = screen.getByTitle('Create new category');
268
- await user.click(createBtn);
269
- // Type name
270
- const input = screen.getByPlaceholderText('e.g. Backend, Frontend, API...');
271
- await user.type(input, 'Frontend');
272
- // Click Create
273
- const submitBtn = screen.getByRole('button', { name: 'Create' });
274
- await user.click(submitBtn);
275
- expect(defaultSettingsModel.onSaveCategory).toHaveBeenCalledWith('Frontend');
276
- });
277
- it('4.7 Rename category', async () => {
278
- const user = userEvent.setup();
279
- renderComponent({ initialSection: 'categories' });
280
- // Select a category (using custom dropdown)
281
- const dropdown = screen.getByText('Select a category to configure...').closest('button');
282
- await user.click(dropdown);
283
- const option = screen.getByText('Backend');
284
- await user.click(option);
285
- // Find label input - getByDisplayValue finds both option and input, so we filter
286
- const elements = screen.getAllByDisplayValue('Backend');
287
- const labelInput = elements.find(el => el.tagName === 'INPUT');
288
- if (!labelInput)
289
- throw new Error('Could not find input with value "Backend"');
290
- await user.clear(labelInput);
291
- await user.type(labelInput, 'Backend API');
292
- await user.tab(); // Trigger blur
293
- expect(defaultSettingsModel.onUpdateCategory).toHaveBeenCalledWith(expect.objectContaining({ label: 'Backend API' }), 'Backend');
294
- });
295
- it('4.8 Retire category', async () => {
296
- // Mock window.confirm
297
- vi.spyOn(window, 'confirm').mockImplementation(() => true);
298
- const user = userEvent.setup();
299
- renderComponent({ initialSection: 'categories' });
300
- // Select category
301
- const dropdown = screen.getByText('Select a category to configure...').closest('button');
302
- await user.click(dropdown);
303
- const option = screen.getByText('Backend');
304
- await user.click(option);
305
- // Click retire
306
- const retireBtn = screen.getByTitle('Retire Category');
307
- await user.click(retireBtn);
308
- expect(defaultSettingsModel.onRemoveCategory).toHaveBeenCalledWith('backend');
309
- });
310
- it('4.8a Protected fallback category cannot be retired', async () => {
311
- const user = userEvent.setup();
312
- renderComponent({
313
- initialSection: 'categories',
314
- settingsModel: {
315
- categories: [
316
- { value: 'default', label: 'General', disabled: false },
317
- { value: 'backend', label: 'Backend', disabled: false }
318
- ]
319
- }
320
- });
321
- const dropdown = screen.getByText('Select a category to configure...').closest('button');
322
- await user.click(dropdown);
323
- await user.click(screen.getAllByText('General').at(-1));
324
- expect(screen.queryByTitle('Retire Category')).not.toBeInTheDocument();
325
- expect(screen.getByText('General is the protected fallback category and cannot be retired.')).toBeInTheDocument();
326
- });
327
- it('4.9 Category icon', async () => {
328
- const user = userEvent.setup();
329
- renderComponent({ initialSection: 'categories' });
330
- // Select category
331
- const dropdown = screen.getByText('Select a category to configure...').closest('button');
332
- await user.click(dropdown);
333
- const option = screen.getByText('Backend');
334
- await user.click(option);
335
- // Click an icon (assuming some icons are rendered)
336
- // We'll just look for one of the icon buttons
337
- const iconBtn = screen.getByTitle('Bug'); // Assuming Bug is in AVAILABLE_ICONS
338
- await user.click(iconBtn);
339
- expect(defaultSettingsModel.onUpdateCategoryIcon).toHaveBeenCalledWith('backend', 'Bug');
340
- });
341
- it('4.10 Category color', async () => {
342
- const user = userEvent.setup();
343
- renderComponent({ initialSection: 'categories' });
344
- // Select category
345
- const dropdown = screen.getByText('Select a category to configure...').closest('button');
346
- await user.click(dropdown);
347
- const option = screen.getByText('Backend');
348
- await user.click(option);
349
- // Click a color swatch
350
- const colorBtn = screen.getByTitle('green-500'); // Use color token
351
- await user.click(colorBtn);
352
- expect(defaultSettingsModel.onUpdateCategoryColor).toHaveBeenCalledWith('backend', 'green-500');
353
- });
354
- it('4.11 Context paths', async () => {
355
- const user = userEvent.setup();
356
- renderComponent({ initialSection: 'categories' });
357
- // Select category
358
- const dropdown = screen.getByText('Select a category to configure...').closest('button');
359
- await user.click(dropdown);
360
- const option = screen.getByText('Backend');
361
- await user.click(option);
362
- // Add path
363
- const input = screen.getByPlaceholderText('Add path (e.g. frontend/src/...)');
364
- await user.type(input, 'src/api');
365
- const addBtn = screen.getByTitle('Add path');
366
- await user.click(addBtn);
367
- expect(defaultSettingsModel.onAddPath).toHaveBeenCalledWith('backend', 'src/api');
368
- });
369
- // 4.12 Open type manager - Always visible
370
- it('4.12 Task Types section is visible', () => {
371
- renderComponent({ initialSection: 'types' });
372
- expect(screen.getAllByText('Task Types')[0]).toBeInTheDocument();
373
- });
374
- it('4.13 Add type', async () => {
375
- const user = userEvent.setup();
376
- renderComponent({ initialSection: 'types' });
377
- // Find type section create button (it's the second "Create new..." button effectively, but inside TypeManager)
378
- // Best to scope by container or find by unique attributes
379
- const typeDropdown = screen.getByText('Select a type to configure...').closest('button');
380
- await user.click(typeDropdown);
381
- // Click create button next to types select
382
- // We can find it by looking for the button with title "Create new type"
383
- const createTypeBtn = screen.getByTitle('Create new type');
384
- await user.click(createTypeBtn);
385
- const input = screen.getByPlaceholderText('e.g. Bug, Feature, Chore...');
386
- await user.type(input, 'Refactor');
387
- const createBtn = screen.getByRole('button', { name: 'Create' });
388
- await user.click(createBtn);
389
- expect(defaultSettingsModel.onSaveType).toHaveBeenCalledWith('Refactor');
390
- });
391
- it('4.14 Retire type', async () => {
392
- vi.spyOn(window, 'confirm').mockImplementation(() => true);
393
- const user = userEvent.setup();
394
- renderComponent({ initialSection: 'types' });
395
- const dropdown = screen.getByText('Select a type to configure...').closest('button');
396
- await user.click(dropdown);
397
- const option = screen.getAllByText('Issue').at(-1);
398
- await user.click(option);
399
- const retireBtn = screen.getByTitle('Retire Type');
400
- await user.click(retireBtn);
401
- expect(defaultSettingsModel.onRemoveType).toHaveBeenCalledWith('issue');
402
- });
403
- it('4.14ab Retired types can be restored', async () => {
404
- const user = userEvent.setup();
405
- renderComponent({
406
- initialSection: 'types',
407
- settingsModel: {
408
- types: [
409
- { value: 'task', label: 'Task', color: 'blue-500', icon: 'CheckSquare', status: 'active' },
410
- { value: 'issue', label: 'Issue', color: 'red-500', icon: 'AlertCircle', status: 'retired' }
411
- ]
412
- }
413
- });
414
- const dropdown = screen.getByText('Select a type to configure...').closest('button');
415
- await user.click(dropdown);
416
- await user.click(screen.getByText('Issue (Retired)'));
417
- const restoreBtn = screen.getByTitle('Restore Type');
418
- await user.click(restoreBtn);
419
- expect(defaultSettingsModel.onUpdateType).toHaveBeenCalledWith('issue', { status: 'active' });
420
- });
421
- it('4.14aa Protected fallback type cannot be retired', async () => {
422
- const user = userEvent.setup();
423
- renderComponent({
424
- initialSection: 'types',
425
- settingsModel: {
426
- types: [
427
- { value: 'task', label: 'Task', color: 'blue-500', icon: 'CheckSquare' },
428
- { value: 'review', label: 'Review', color: 'violet-500', icon: 'Search' }
429
- ]
430
- }
431
- });
432
- const dropdown = screen.getByText('Select a type to configure...').closest('button');
433
- await user.click(dropdown);
434
- await user.click(screen.getByText('Task'));
435
- expect(screen.queryByTitle('Retire Type')).not.toBeInTheDocument();
436
- expect(screen.getByText('Task is the protected fallback type and cannot be retired.')).toBeInTheDocument();
437
- });
438
- it('4.14a Rename type', async () => {
439
- const user = userEvent.setup();
440
- renderComponent({ initialSection: 'types' });
441
- const dropdown = screen.getByText('Select a type to configure...').closest('button');
442
- await user.click(dropdown);
443
- const option = screen.getByText('Task');
444
- await user.click(option);
445
- const labelInput = screen.getByDisplayValue('Task');
446
- await user.clear(labelInput);
447
- await user.type(labelInput, 'Action Item');
448
- await user.tab();
449
- expect(defaultSettingsModel.onUpdateType).toHaveBeenCalledWith('task', { label: 'Action Item' });
450
- });
451
- it('4.14b Update type color', async () => {
452
- const user = userEvent.setup();
453
- renderComponent({ initialSection: 'types' });
454
- const dropdown = screen.getByText('Select a type to configure...').closest('button');
455
- await user.click(dropdown);
456
- await user.click(screen.getByText('Task'));
457
- await user.click(screen.getByTitle('green-500'));
458
- expect(defaultSettingsModel.onUpdateType).toHaveBeenCalledWith('task', { color: 'green-500' });
459
- });
460
- it('4.15 Export workflows', async () => {
461
- renderComponent({ initialSection: 'commands' });
462
- expect(screen.getByText('Command Workflows')).toBeInTheDocument();
463
- });
464
- it('4.14c Priority editor hides generic taxonomy controls', async () => {
465
- const user = userEvent.setup();
466
- renderComponent();
467
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
468
- const priorityRow = screen.getByText('Priority Levels');
469
- await user.click(priorityRow.closest('button'));
470
- const dropdown = screen.getByText('Select a priority level to configure...').closest('button');
471
- await user.click(dropdown);
472
- await user.click(screen.getByText('Medium'));
473
- expect(screen.queryByText('Mark as Required')).not.toBeInTheDocument();
474
- expect(screen.queryByText('Allow Multi-select')).not.toBeInTheDocument();
475
- expect(screen.queryByText('Widget Type')).not.toBeInTheDocument();
476
- expect(screen.getByText(/fixed ordered scale/i)).toBeInTheDocument();
477
- });
478
- it('4.14c1 Taxonomy libraries are visible from the taxonomy settings list', async () => {
479
- const user = userEvent.setup();
480
- renderComponent();
481
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
482
- expect(screen.getByRole('button', { name: /system taxonomy library/i })).toBeInTheDocument();
483
- expect(screen.getByText(/browse curated packs for categories, task types, and priority scales/i)).toBeInTheDocument();
484
- expect(screen.getByRole('button', { name: /custom taxonomy library/i })).toBeInTheDocument();
485
- });
486
- it('4.14c2 System Taxonomy Library opens preview and apply controls', async () => {
487
- const user = userEvent.setup();
488
- renderComponent();
489
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
490
- await user.click(screen.getByRole('button', { name: /system taxonomy library/i }));
491
- expect(screen.getByText('Browse starter packs before you replace anything.')).toBeInTheDocument();
492
- expect(screen.getByText('Software Development')).toBeInTheDocument();
493
- expect(screen.getByText('Apply Selected Sections')).toBeInTheDocument();
494
- expect(screen.getByRole('checkbox', { name: 'Categories' })).toBeInTheDocument();
495
- expect(screen.getByRole('checkbox', { name: 'Types' })).toBeInTheDocument();
496
- expect(screen.getByRole('checkbox', { name: 'Priorities' })).toBeInTheDocument();
497
- expect(screen.getByRole('checkbox', { name: /remap unmatched existing values to default/i })).toBeInTheDocument();
498
- });
499
- it('4.14c2b System Taxonomy Library applies a selected pack', async () => {
500
- const user = userEvent.setup();
501
- const onApplySystemTaxonomyPack = vi.fn(async () => ({ success: true }));
502
- renderComponent({
503
- settingsModel: {
504
- onApplySystemTaxonomyPack
505
- }
506
- });
507
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
508
- await user.click(screen.getByRole('button', { name: /system taxonomy library/i }));
509
- await user.click(screen.getByRole('button', { name: 'Apply Pack' }));
510
- expect(onApplySystemTaxonomyPack).toHaveBeenCalledWith(expect.objectContaining({
511
- pack: expect.objectContaining({ id: 'general' }),
512
- sections: {
513
- categories: true,
514
- types: true,
515
- priorities: true
516
- },
517
- remapExistingValuesToDefault: false
518
- }));
519
- expect(await screen.findByText(/was applied to the selected taxonomy sections/i)).toBeInTheDocument();
520
- });
521
- it('4.14c2c System Taxonomy Library surfaces preflight warnings for unmatched values', async () => {
522
- const user = userEvent.setup();
523
- renderComponent({
524
- settingsModel: {
525
- onAnalyzeSystemTaxonomyPack: vi.fn(() => ({
526
- unmatchedCategoryValues: ['marketing'],
527
- unmatchedTypeValues: ['request', 'incident'],
528
- incompatiblePriorityValues: [4]
529
- }))
530
- }
531
- });
532
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
533
- await user.click(screen.getByRole('button', { name: /system taxonomy library/i }));
534
- expect(screen.getByText(/1 category value will be preserved as legacy/i)).toBeInTheDocument();
535
- expect(screen.getByText(/2 type values will be preserved as legacy/i)).toBeInTheDocument();
536
- expect(screen.getByText(/priority replacement will be blocked unless you remap/i)).toBeInTheDocument();
537
- });
538
- it('4.14c3 Custom Taxonomy Library opens the custom pack browser', async () => {
539
- const user = userEvent.setup();
540
- renderComponent();
541
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
542
- await user.click(screen.getByRole('button', { name: /custom taxonomy library/i }));
543
- expect(screen.getByText('Browse reusable custom taxonomy packs.')).toBeInTheDocument();
544
- expect(screen.getByText('Choose Taxonomies to Install')).toBeInTheDocument();
545
- expect(screen.getByRole('button', { name: /install selected taxonomies/i })).toBeInTheDocument();
546
- });
547
- it('4.14d Retires custom taxonomy options instead of deleting them', async () => {
548
- const user = userEvent.setup();
549
- renderComponent({
550
- settingsModel: {
551
- taxonomies: [{
552
- id: 'review-status',
553
- label: 'Review Status',
554
- widgetType: 'select',
555
- isSystem: false,
556
- multiSelect: false,
557
- options: [
558
- { value: 'pending', label: 'Pending', status: 'active' },
559
- { value: 'legal-review', label: 'Legal Review', status: 'active' }
560
- ]
561
- }]
562
- }
563
- });
564
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
565
- await user.click(screen.getByText('Review Status').closest('button'));
566
- const dropdown = screen.getByText('Select an option to configure...').closest('button');
567
- await user.click(dropdown);
568
- await user.click(screen.getByText('Legal Review'));
569
- await user.click(screen.getByTitle('Retire Option'));
570
- expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenCalledWith([
571
- expect.objectContaining({
572
- id: 'review-status',
573
- options: [
574
- expect.objectContaining({ value: 'pending', status: 'active' }),
575
- expect.objectContaining({ value: 'legal-review', status: 'retired' })
576
- ]
577
- })
578
- ]);
579
- });
580
- it('4.14e Toggles custom taxonomy filter visibility', async () => {
581
- const user = userEvent.setup();
582
- renderComponent({
583
- settingsModel: {
584
- taxonomies: [{
585
- id: 'review-status',
586
- label: 'Review Status',
587
- widgetType: 'select',
588
- isSystem: false,
589
- multiSelect: false,
590
- filterEnabled: true,
591
- options: [
592
- { value: 'pending', label: 'Pending', status: 'active' }
593
- ]
594
- }]
595
- }
596
- });
597
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
598
- await user.click(screen.getByText('Review Status').closest('button'));
599
- const filterToggle = screen.getByRole('checkbox', { name: 'Show in Filters' });
600
- await user.click(filterToggle);
601
- expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenCalledWith([
602
- expect.objectContaining({
603
- id: 'review-status',
604
- filterEnabled: false
605
- })
606
- ]);
607
- });
608
- it('4.14e1 Toggles custom taxonomy task-form visibility', async () => {
609
- const user = userEvent.setup();
610
- renderComponent({
611
- settingsModel: {
612
- taxonomies: [{
613
- id: 'review-status',
614
- label: 'Review Status',
615
- widgetType: 'select',
616
- isSystem: false,
617
- multiSelect: false,
618
- formEnabled: true,
619
- options: [
620
- { value: 'pending', label: 'Pending', status: 'active' }
621
- ]
622
- }]
623
- }
624
- });
625
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
626
- await user.click(screen.getByText('Review Status').closest('button'));
627
- const formToggle = screen.getByRole('checkbox', { name: 'Show in Task Form' });
628
- await user.click(formToggle);
629
- expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenCalledWith([
630
- expect.objectContaining({
631
- id: 'review-status',
632
- formEnabled: false
633
- })
634
- ]);
635
- });
636
- it('4.14f Updates custom taxonomy description', async () => {
637
- const user = userEvent.setup();
638
- renderComponent({
639
- settingsModel: {
640
- taxonomies: [{
641
- id: 'review-status',
642
- label: 'Review Status',
643
- widgetType: 'select',
644
- isSystem: false,
645
- multiSelect: false,
646
- options: [
647
- { value: 'pending', label: 'Pending', status: 'active' }
648
- ]
649
- }]
650
- }
651
- });
652
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
653
- await user.click(screen.getByText('Review Status').closest('button'));
654
- const descriptionInput = screen.getByPlaceholderText('Optional guidance for when and how to use this taxonomy...');
655
- fireEvent.change(descriptionInput, { target: { value: 'Use this to track review routing.' } });
656
- expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
657
- expect.objectContaining({
658
- id: 'review-status',
659
- description: 'Use this to track review routing.'
660
- })
661
- ]);
662
- });
663
- it('4.14g Updates custom taxonomy aliases', async () => {
664
- const user = userEvent.setup();
665
- renderComponent({
666
- settingsModel: {
667
- taxonomies: [{
668
- id: 'review-status',
669
- label: 'Review Status',
670
- widgetType: 'select',
671
- isSystem: false,
672
- multiSelect: false,
673
- options: [
674
- { value: 'pending', label: 'Pending', status: 'active' }
675
- ]
676
- }]
677
- }
678
- });
679
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
680
- await user.click(screen.getByText('Review Status').closest('button'));
681
- const aliasInputs = screen.getAllByPlaceholderText('Optional aliases, comma-separated');
682
- fireEvent.change(aliasInputs[0], { target: { value: 'review route, routing' } });
683
- fireEvent.blur(aliasInputs[0]);
684
- expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
685
- expect.objectContaining({
686
- id: 'review-status',
687
- aliases: ['review route', 'routing']
688
- })
689
- ]);
690
- });
691
- it('4.14h Updates custom taxonomy option aliases', async () => {
692
- const user = userEvent.setup();
693
- renderComponent({
694
- settingsModel: {
695
- taxonomies: [{
696
- id: 'review-status',
697
- label: 'Review Status',
698
- widgetType: 'select',
699
- isSystem: false,
700
- multiSelect: false,
701
- options: [
702
- { value: 'pending', label: 'Pending', status: 'active' }
703
- ]
704
- }]
705
- }
706
- });
707
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
708
- await user.click(screen.getByText('Review Status').closest('button'));
709
- const dropdown = screen.getByText('Select an option to configure...').closest('button');
710
- await user.click(dropdown);
711
- await user.click(screen.getByText('Pending'));
712
- const aliasInputs = screen.getAllByPlaceholderText('Optional aliases, comma-separated');
713
- fireEvent.change(aliasInputs[1], { target: { value: 'queued, awaiting review' } });
714
- fireEvent.blur(aliasInputs[1]);
715
- expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
716
- expect.objectContaining({
717
- id: 'review-status',
718
- options: [
719
- expect.objectContaining({
720
- value: 'pending',
721
- aliases: ['queued', 'awaiting review']
722
- })
723
- ]
724
- })
725
- ]);
726
- });
727
- it('4.14i Retires whole custom taxonomies instead of deleting them', async () => {
728
- vi.spyOn(window, 'confirm').mockImplementation(() => true);
729
- const user = userEvent.setup();
730
- renderComponent({
731
- settingsModel: {
732
- taxonomies: [{
733
- id: 'review-status',
734
- label: 'Review Status',
735
- widgetType: 'select',
736
- isSystem: false,
737
- multiSelect: false,
738
- options: [{ value: 'pending', label: 'Pending', status: 'active' }]
739
- }]
740
- }
741
- });
742
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
743
- await user.click(screen.getByText('Review Status').closest('button'));
744
- await user.click(screen.getByRole('button', { name: /retire taxonomy/i }));
745
- expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
746
- expect.objectContaining({
747
- id: 'review-status',
748
- status: 'retired'
749
- })
750
- ]);
751
- });
752
- it('4.14j Restores retired custom taxonomies', async () => {
753
- const user = userEvent.setup();
754
- renderComponent({
755
- settingsModel: {
756
- taxonomies: [{
757
- id: 'review-status',
758
- label: 'Review Status',
759
- status: 'retired',
760
- widgetType: 'select',
761
- isSystem: false,
762
- multiSelect: false,
763
- options: [{ value: 'pending', label: 'Pending', status: 'active' }]
764
- }]
765
- }
766
- });
767
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
768
- await user.click(screen.getByText(/Review Status \(Retired\)/).closest('button'));
769
- await user.click(screen.getByRole('button', { name: /restore taxonomy/i }));
770
- expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
771
- expect.objectContaining({
772
- id: 'review-status',
773
- status: 'active'
774
- })
775
- ]);
776
- });
777
- it('4.14k Reorders custom taxonomies from the list view', async () => {
778
- const user = userEvent.setup();
779
- renderComponent({
780
- settingsModel: {
781
- taxonomies: [
782
- {
783
- id: 'alpha',
784
- label: 'Alpha',
785
- widgetType: 'select',
786
- isSystem: false,
787
- multiSelect: false,
788
- options: []
789
- },
790
- {
791
- id: 'beta',
792
- label: 'Beta',
793
- widgetType: 'select',
794
- isSystem: false,
795
- multiSelect: false,
796
- options: []
797
- }
798
- ]
799
- }
800
- });
801
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
802
- await user.click(screen.getAllByTitle('Move taxonomy down').find((button) => !button.hasAttribute('disabled')));
803
- expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
804
- expect.objectContaining({ id: 'beta' }),
805
- expect.objectContaining({ id: 'alpha' })
806
- ]);
807
- });
808
- it('4.14l Reorders custom taxonomy options from the detail view', async () => {
809
- const user = userEvent.setup();
810
- renderComponent({
811
- settingsModel: {
812
- taxonomies: [{
813
- id: 'review-status',
814
- label: 'Review Status',
815
- widgetType: 'select',
816
- isSystem: false,
817
- multiSelect: false,
818
- options: [
819
- { value: 'pending', label: 'Pending', status: 'active' },
820
- { value: 'approved', label: 'Approved', status: 'active' }
821
- ]
822
- }]
823
- }
824
- });
825
- await user.click(screen.getByRole('button', { name: /taxonomy/i }));
826
- await user.click(screen.getByText('Review Status').closest('button'));
827
- const dropdown = screen.getByText('Select an option to configure...').closest('button');
828
- await user.click(dropdown);
829
- await user.click(screen.getByText('Approved'));
830
- await user.click(screen.getByTitle('Move option up'));
831
- expect(defaultSettingsModel.onUpdateTaxonomies).toHaveBeenLastCalledWith([
832
- expect.objectContaining({
833
- id: 'review-status',
834
- options: [
835
- expect.objectContaining({ value: 'approved' }),
836
- expect.objectContaining({ value: 'pending' })
837
- ]
838
- })
839
- ]);
840
- });
841
- it('4.16 Agents section shows registered AI profiles', async () => {
842
- renderComponent({ initialSection: 'agents' });
843
- expect(screen.getByText('Registered AI Profiles')).toBeInTheDocument();
844
- });
845
- it('4.17 initialSection navigates to detail view', () => {
846
- renderComponent({ initialSection: 'categories' });
847
- // In detail view, Categories is shown in detailTitle
848
- expect(screen.getByText('Categories')).toBeInTheDocument();
849
- expect(screen.queryByText('System Taxonomies')).not.toBeInTheDocument();
850
- });
851
- it('4.18 legacy documents initialSection falls back to general tab', () => {
852
- renderComponent({ initialSection: 'documents' });
853
- expect(screen.getByRole('button', { name: /General/i })).toBeInTheDocument();
854
- expect(screen.queryByText('Cloud Document Storage')).not.toBeInTheDocument();
855
- });
856
- it('4.19 document settings copy is not rendered in settings', () => {
857
- renderComponent();
858
- expect(screen.queryByText('Document Storage (Admin)')).not.toBeInTheDocument();
859
- expect(screen.queryByText('Local Documents')).not.toBeInTheDocument();
860
- expect(screen.queryByText('Cloud Document Storage')).not.toBeInTheDocument();
861
- expect(screen.queryByRole('button', { name: 'Open App Admin' })).not.toBeInTheDocument();
862
- });
863
- it('4.20 Cloud runtime shows cloud MCP only', async () => {
864
- const fetchCloudAuthApi = vi.fn(async () => ({
865
- ok: true,
866
- json: async () => ({ items: [] })
867
- }));
868
- const user = userEvent.setup();
869
- renderMcpComponent({
870
- settingsModel: {
871
- runtimeMode: 'cloud',
872
- isAuthenticated: true,
873
- cloudAuthBaseUrl: 'https://cloud.example.com',
874
- fetchCloudAuthApi
875
- }
876
- });
877
- await openMcpSettings(user);
878
- expect(await screen.findByText('Cloud MCP Configuration')).toBeInTheDocument();
879
- expect(screen.getByText(/Cloud MCP connects agents to Taskforce through the hosted MCP endpoint/i)).toBeInTheDocument();
880
- expect(screen.getByText(/Once the MCP is configured, prompt your agent:/i)).toBeInTheDocument();
881
- expect(screen.getByRole('button', { name: 'Resolve your profile with Taskforce.' })).toBeInTheDocument();
882
- const cloudInstructionLabel = screen.getByText('General MCP Configuration Instructions');
883
- const cloudProfilePrompt = screen.getByText(/Once the MCP is configured, prompt your agent:/i);
884
- expect(cloudInstructionLabel.compareDocumentPosition(cloudProfilePrompt) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
885
- expect(screen.getByRole('button', { name: 'Generate Token' })).toBeInTheDocument();
886
- expect(screen.queryByRole('button', { name: 'Local MCP' })).not.toBeInTheDocument();
887
- expect(screen.queryByText('MCP Path Mapping (Host Root)')).not.toBeInTheDocument();
888
- expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/tokens', expect.objectContaining({
889
- method: 'GET',
890
- credentials: 'include'
891
- }));
892
- }, 10000);
893
- it('4.20a MCP setup tells users how to register the agent profile', async () => {
894
- renderMcpComponent({
895
- settingsModel: {
896
- runtimeMode: 'local',
897
- isAuthenticated: false
898
- }
899
- });
900
- expect(screen.getByText(/Local MCP connects through your local Taskforce install/i)).toBeInTheDocument();
901
- expect(screen.getByText(/Once the MCP is configured, prompt your agent:/i)).toBeInTheDocument();
902
- expect(screen.getByRole('button', { name: 'Resolve your profile with Taskforce.' })).toBeInTheDocument();
903
- const localInstructionLabel = screen.getByText('General MCP Configuration Instructions');
904
- const localProfilePrompt = screen.getByText(/Once the MCP is configured, prompt your agent:/i);
905
- expect(localInstructionLabel.compareDocumentPosition(localProfilePrompt) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
906
- expect(screen.queryByText('Ask Your Agent to Register')).not.toBeInTheDocument();
907
- });
908
- it('4.21 Local authenticated runtime defaults MCP tab to local with local-first toggle', async () => {
909
- const fetchCloudAuthApi = vi.fn(async () => ({
910
- ok: true,
911
- json: async () => ({ items: [] })
912
- }));
913
- const user = userEvent.setup();
914
- renderMcpComponent({
915
- settingsModel: {
916
- runtimeMode: 'local',
917
- isAuthenticated: true,
918
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
919
- cloudAuthBaseUrl: 'https://cloud.example.com',
920
- fetchCloudAuthApi
921
- }
922
- });
923
- await openMcpSettings(user);
924
- const localButton = await screen.findByRole('button', { name: 'Local MCP' });
925
- const cloudButton = screen.getByRole('button', { name: 'Cloud MCP' });
926
- expect(localButton.compareDocumentPosition(cloudButton) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
927
- expect(screen.getByText('Local MCP Configuration')).toBeInTheDocument();
928
- expect(screen.queryByText('Cloud MCP Configuration')).not.toBeInTheDocument();
929
- expect(screen.queryByText('MCP Path Mapping (Host Root)')).not.toBeInTheDocument();
930
- await user.click(screen.getByRole('button', { name: 'Advanced' }));
931
- expect(screen.getByText('MCP Path Mapping (Host Root)')).toBeInTheDocument();
932
- await user.click(cloudButton);
933
- expect(screen.getByText('Cloud MCP Configuration')).toBeInTheDocument();
934
- expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
935
- expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/tokens', expect.objectContaining({
936
- method: 'GET',
937
- credentials: 'include'
938
- }));
939
- }, 10000);
940
- it('4.23b MCP token creation uses the provided cloud auth helper', async () => {
941
- const fetchCloudAuthApi = vi.fn(async (path, init) => {
942
- if (path === '/api/taskforce/settings/mcp/tokens' && init?.method === 'POST') {
943
- return {
944
- ok: true,
945
- json: async () => ({
946
- token: 'tfmcp_example_secret',
947
- record: {
948
- id: 'mcp-token-1',
949
- name: 'Workspace Alpha MCP',
950
- tokenPrefix: 'tfmcp_example',
951
- scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
952
- status: 'active',
953
- createdAt: new Date().toISOString()
954
- }
955
- })
956
- };
957
- }
958
- return {
959
- ok: true,
960
- json: async () => ({ items: [] })
961
- };
962
- });
963
- const user = userEvent.setup();
964
- renderMcpComponent({
965
- settingsModel: {
966
- runtimeMode: 'local',
967
- isAuthenticated: true,
968
- currentWorkspaceName: 'Workspace Alpha',
969
- fetchCloudAuthApi
970
- }
971
- });
972
- await openCloudMcpSettings(user);
973
- await user.click(screen.getByRole('button', { name: 'Generate Token' }));
974
- expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/tokens', expect.objectContaining({
975
- method: 'POST',
976
- credentials: 'include'
977
- }));
978
- expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/tokens', expect.objectContaining({
979
- body: JSON.stringify({
980
- name: 'Workspace Alpha MCP',
981
- scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
982
- expiresAt: null
983
- })
984
- }));
985
- expect(await screen.findByDisplayValue('tfmcp_example_secret')).toBeInTheDocument();
986
- }, 10000);
987
- it('4.23c MCP token name defaults to the workspace name', async () => {
988
- const user = userEvent.setup();
989
- renderMcpComponent({
990
- settingsModel: {
991
- runtimeMode: 'local',
992
- isAuthenticated: true,
993
- currentWorkspaceName: 'Workspace Alpha'
994
- }
995
- });
996
- await openCloudMcpSettings(user);
997
- expect(await screen.findByDisplayValue('Workspace Alpha MCP')).toBeInTheDocument();
998
- });
999
- it('4.23d MCP test connection uses the provided cloud auth helper', async () => {
1000
- const fetchCloudAuthApi = vi.fn(async (path, init) => {
1001
- if (path === '/api/taskforce/settings/mcp/test-connection') {
1002
- return {
1003
- ok: true,
1004
- json: async () => ({
1005
- result: {
1006
- workspaceName: 'Workspace Alpha',
1007
- workspaceId: 'workspace-alpha',
1008
- scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
1009
- endpoint: 'https://performance-mcp.taskforcehq.ai/mcp'
1010
- }
1011
- })
1012
- };
1013
- }
1014
- return {
1015
- ok: true,
1016
- json: async () => ({ items: [] })
1017
- };
1018
- });
1019
- const user = userEvent.setup();
1020
- renderMcpComponent({
1021
- settingsModel: {
1022
- runtimeMode: 'local',
1023
- isAuthenticated: true,
1024
- currentWorkspaceName: 'Workspace Alpha',
1025
- fetchCloudAuthApi
1026
- }
1027
- });
1028
- await openCloudMcpSettings(user);
1029
- expect(screen.queryByText('Test Connection')).not.toBeInTheDocument();
1030
- await user.type(await screen.findByPlaceholderText('Generate a token to populate the full secret'), 'tfmcp_example_secret');
1031
- await user.click(screen.getByRole('button', { name: 'Test Token' }));
1032
- expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/test-connection', expect.objectContaining({
1033
- method: 'POST',
1034
- credentials: 'include'
1035
- }));
1036
- expect(await screen.findByText('Connection looks good for this workspace.')).toBeInTheDocument();
1037
- expect(screen.getByText(/Granted scopes: tasks:read, tasks:write, workspace:read/i)).toBeInTheDocument();
1038
- expect(screen.getByText(/Endpoint: https:\/\/performance-mcp\.taskforcehq\.ai\/mcp/i)).toBeInTheDocument();
1039
- }, 10000);
1040
- it('4.23d2 MCP test connection shows a dedicated-host incident message when the endpoint returns a gateway failure', async () => {
1041
- const fetchCloudAuthApi = vi.fn(async (path) => {
1042
- if (path === '/api/taskforce/settings/mcp/test-connection') {
1043
- return {
1044
- ok: false,
1045
- json: async () => ({
1046
- code: 'MCP_ENDPOINT_GATEWAY_ERROR',
1047
- error: 'Cloud MCP endpoint https://performance-mcp.taskforcehq.ai/mcp returned 502.',
1048
- details: {
1049
- endpoint: 'https://performance-mcp.taskforcehq.ai/mcp',
1050
- upstreamStatus: 502
1051
- }
1052
- })
1053
- };
1054
- }
1055
- return {
1056
- ok: true,
1057
- json: async () => ({ items: [] })
1058
- };
1059
- });
1060
- const user = userEvent.setup();
1061
- renderMcpComponent({
1062
- settingsModel: {
1063
- runtimeMode: 'local',
1064
- isAuthenticated: true,
1065
- currentWorkspaceName: 'Workspace Alpha',
1066
- cloudMcpBaseUrl: 'https://performance-mcp.taskforcehq.ai',
1067
- fetchCloudAuthApi
1068
- }
1069
- });
1070
- await openCloudMcpSettings(user);
1071
- await user.type(await screen.findByPlaceholderText('Generate a token to populate the full secret'), 'tfmcp_example_secret');
1072
- await user.click(screen.getByRole('button', { name: 'Test Token' }));
1073
- expect(await screen.findByText('The dedicated MCP host is responding with a gateway error.')).toBeInTheDocument();
1074
- expect(screen.getByText(/Endpoint: https:\/\/performance-mcp\.taskforcehq\.ai\/mcp/i)).toBeInTheDocument();
1075
- expect(screen.getByText(/Error code: MCP_ENDPOINT_GATEWAY_ERROR/i)).toBeInTheDocument();
1076
- expect(screen.getByText(/This usually means the cloud MCP service is degraded/i)).toBeInTheDocument();
1077
- }, 10000);
1078
- it('4.23e Antigravity uses serverUrl instead of transport in the cloud snippet', async () => {
1079
- const fetchCloudAuthApi = vi.fn(async () => ({
1080
- ok: true,
1081
- json: async () => ({ items: [] })
1082
- }));
1083
- const user = userEvent.setup();
1084
- renderMcpComponent({
1085
- settingsModel: {
1086
- runtimeMode: 'local',
1087
- isAuthenticated: true,
1088
- currentWorkspaceName: 'Workspace Alpha',
1089
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1090
- fetchCloudAuthApi
1091
- }
1092
- });
1093
- await openCloudMcpSettings(user);
1094
- await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'antigravity');
1095
- const snippetMatches = await screen.findAllByText(/serverUrl/);
1096
- expect(snippetMatches.length).toBeGreaterThan(0);
1097
- expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
1098
- expect(screen.queryByText(/"transport"/)).not.toBeInTheDocument();
1099
- }, 10000);
1100
- it('4.23e1 falls back to the dedicated MCP hostname when only the cloud app host is configured', async () => {
1101
- const fetchCloudAuthApi = vi.fn(async () => ({
1102
- ok: true,
1103
- json: async () => ({ items: [] })
1104
- }));
1105
- const user = userEvent.setup();
1106
- renderMcpComponent({
1107
- settingsModel: {
1108
- runtimeMode: 'local',
1109
- isAuthenticated: true,
1110
- currentWorkspaceName: 'Workspace Alpha',
1111
- cloudAuthBaseUrl: 'https://performance-app.taskforcehq.ai',
1112
- cloudMcpBaseUrl: '',
1113
- fetchCloudAuthApi
1114
- }
1115
- });
1116
- await openCloudMcpSettings(user);
1117
- expect(await screen.findByText(/https:\/\/performance-mcp\.taskforcehq\.ai\/mcp/)).toBeInTheDocument();
1118
- expect(screen.queryByText(/https:\/\/performance-app\.taskforcehq\.ai\/mcp/)).not.toBeInTheDocument();
1119
- }, 10000);
1120
- it('4.23e1b prefers the dedicated MCP hostname even when cloudMcpBaseUrl is stale and points at the app host', async () => {
1121
- const fetchCloudAuthApi = vi.fn(async () => ({
1122
- ok: true,
1123
- json: async () => ({ items: [] })
1124
- }));
1125
- const user = userEvent.setup();
1126
- renderMcpComponent({
1127
- settingsModel: {
1128
- runtimeMode: 'local',
1129
- isAuthenticated: true,
1130
- currentWorkspaceName: 'Workspace Alpha',
1131
- cloudAuthBaseUrl: 'https://performance-app.taskforcehq.ai',
1132
- cloudMcpBaseUrl: 'https://performance-app.taskforcehq.ai',
1133
- fetchCloudAuthApi
1134
- }
1135
- });
1136
- await openCloudMcpSettings(user);
1137
- expect(await screen.findByText(/https:\/\/performance-mcp\.taskforcehq\.ai\/mcp/)).toBeInTheDocument();
1138
- expect(screen.queryByText(/https:\/\/performance-app\.taskforcehq\.ai\/mcp/)).not.toBeInTheDocument();
1139
- }, 10000);
1140
- it('4.23e2 Cursor uses top-level url instead of transport in the cloud config', async () => {
1141
- const fetchCloudAuthApi = vi.fn(async () => ({
1142
- ok: true,
1143
- json: async () => ({ items: [] })
1144
- }));
1145
- const user = userEvent.setup();
1146
- renderMcpComponent({
1147
- settingsModel: {
1148
- runtimeMode: 'local',
1149
- isAuthenticated: true,
1150
- currentWorkspaceName: 'Workspace Alpha',
1151
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1152
- fetchCloudAuthApi
1153
- }
1154
- });
1155
- await openCloudMcpSettings(user);
1156
- const urlMatches = await screen.findAllByText(/"url"/);
1157
- expect(urlMatches.length).toBeGreaterThan(0);
1158
- expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
1159
- expect(screen.queryByText(/"transport"/)).not.toBeInTheDocument();
1160
- expect(screen.queryByText(/serverUrl/)).not.toBeInTheDocument();
1161
- }, 10000);
1162
- it('4.23e2b Cline uses streamableHttp in the cloud config', async () => {
1163
- const fetchCloudAuthApi = vi.fn(async () => ({
1164
- ok: true,
1165
- json: async () => ({ items: [] })
1166
- }));
1167
- const user = userEvent.setup();
1168
- renderMcpComponent({
1169
- settingsModel: {
1170
- runtimeMode: 'local',
1171
- isAuthenticated: true,
1172
- currentWorkspaceName: 'Workspace Alpha',
1173
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1174
- fetchCloudAuthApi
1175
- }
1176
- });
1177
- await openCloudMcpSettings(user);
1178
- await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'cline');
1179
- expect(await screen.findByText(/Cline Configuration Instructions/i)).toBeInTheDocument();
1180
- expect(screen.getByText(/"type"/)).toBeInTheDocument();
1181
- expect(screen.getByText(/streamableHttp/)).toBeInTheDocument();
1182
- expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
1183
- }, 10000);
1184
- it('4.23e2c VS Code uses servers with http transport in the cloud config', async () => {
1185
- const fetchCloudAuthApi = vi.fn(async () => ({
1186
- ok: true,
1187
- json: async () => ({ items: [] })
1188
- }));
1189
- const user = userEvent.setup();
1190
- renderMcpComponent({
1191
- settingsModel: {
1192
- runtimeMode: 'local',
1193
- isAuthenticated: true,
1194
- currentWorkspaceName: 'Workspace Alpha',
1195
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1196
- fetchCloudAuthApi
1197
- }
1198
- });
1199
- await openCloudMcpSettings(user);
1200
- await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'vscode');
1201
- expect(await screen.findByText(/VS Code Configuration Instructions/i)).toBeInTheDocument();
1202
- expect(screen.getByText(/"servers"/)).toBeInTheDocument();
1203
- expect(screen.getByText(/"type"/)).toBeInTheDocument();
1204
- expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
1205
- }, 10000);
1206
- it('4.23e3 Codex shows config.toml MCP output', async () => {
1207
- const fetchCloudAuthApi = vi.fn(async () => ({
1208
- ok: true,
1209
- json: async () => ({ items: [] })
1210
- }));
1211
- const user = userEvent.setup();
1212
- renderMcpComponent({
1213
- settingsModel: {
1214
- runtimeMode: 'local',
1215
- isAuthenticated: true,
1216
- currentWorkspaceName: 'Workspace Alpha',
1217
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1218
- fetchCloudAuthApi
1219
- }
1220
- });
1221
- await openCloudMcpSettings(user);
1222
- await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'codex');
1223
- expect((await screen.findAllByText(/\.codex\/config\.toml/i)).length).toBeGreaterThan(0);
1224
- expect(screen.getByText(/\[mcp_servers\./)).toBeInTheDocument();
1225
- expect(screen.getByText(/http_headers = \{ Authorization = "Bearer/i)).toBeInTheDocument();
1226
- expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
1227
- }, 10000);
1228
- it('4.23e3b Claude Code shows a remote MCP add command', async () => {
1229
- const fetchCloudAuthApi = vi.fn(async () => ({
1230
- ok: true,
1231
- json: async () => ({ items: [] })
1232
- }));
1233
- const user = userEvent.setup();
1234
- renderMcpComponent({
1235
- settingsModel: {
1236
- runtimeMode: 'local',
1237
- isAuthenticated: true,
1238
- currentWorkspaceName: 'Workspace Alpha',
1239
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1240
- fetchCloudAuthApi
1241
- }
1242
- });
1243
- await openCloudMcpSettings(user);
1244
- await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'claude-code');
1245
- expect(await screen.findByText(/Claude Code Configuration Instructions/i)).toBeInTheDocument();
1246
- expect(screen.getByText(/claude mcp add --transport http/i)).toBeInTheDocument();
1247
- expect(screen.getByText(/claude mcp add --transport http taskforce-/i)).toBeInTheDocument();
1248
- expect(screen.getByText(/--header "Authorization: Bearer/i)).toBeInTheDocument();
1249
- }, 10000);
1250
- it('4.23e3c Gemini CLI shows a remote MCP add command', async () => {
1251
- const fetchCloudAuthApi = vi.fn(async () => ({
1252
- ok: true,
1253
- json: async () => ({ items: [] })
1254
- }));
1255
- const user = userEvent.setup();
1256
- renderMcpComponent({
1257
- settingsModel: {
1258
- runtimeMode: 'local',
1259
- isAuthenticated: true,
1260
- currentWorkspaceName: 'Workspace Alpha',
1261
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1262
- fetchCloudAuthApi
1263
- }
1264
- });
1265
- await openCloudMcpSettings(user);
1266
- await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'gemini-cli');
1267
- expect(await screen.findByText(/Gemini CLI Configuration Instructions/i)).toBeInTheDocument();
1268
- expect(screen.getByText(/gemini mcp add taskforce-/i)).toBeInTheDocument();
1269
- expect(screen.getByText(/--transport http/i)).toBeInTheDocument();
1270
- expect(screen.getByText(/--header "Authorization: Bearer/i)).toBeInTheDocument();
1271
- expect(screen.getByText(/Add --scope user if you want it available across all projects/i)).toBeInTheDocument();
1272
- }, 10000);
1273
- it('4.23e3d OpenClaw shows an openclaw.json MCP snippet', async () => {
1274
- const fetchCloudAuthApi = vi.fn(async () => ({
1275
- ok: true,
1276
- json: async () => ({ items: [] })
1277
- }));
1278
- const user = userEvent.setup();
1279
- renderMcpComponent({
1280
- settingsModel: {
1281
- runtimeMode: 'local',
1282
- isAuthenticated: true,
1283
- currentWorkspaceName: 'Workspace Alpha',
1284
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1285
- fetchCloudAuthApi
1286
- }
1287
- });
1288
- await openCloudMcpSettings(user);
1289
- await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'openclaw');
1290
- expect(await screen.findByText(/OpenClaw Configuration Instructions/i)).toBeInTheDocument();
1291
- expect((await screen.findAllByText(/~\/\.openclaw\/openclaw\.json/i)).length).toBeGreaterThan(0);
1292
- const configBlocks = Array.from(document.querySelectorAll('pre code'));
1293
- const openClawConfigBlock = configBlocks.find((block) => block.textContent?.includes('https://mcp.taskforcehq.ai/mcp'));
1294
- expect(openClawConfigBlock?.textContent).toContain('"mcp"');
1295
- expect(openClawConfigBlock?.textContent).toContain('"servers"');
1296
- expect(openClawConfigBlock?.textContent).toContain('"transport": "streamable-http"');
1297
- expect(openClawConfigBlock?.textContent).toContain('"Authorization": "Bearer');
1298
- expect(screen.getAllByText(/https:\/\/mcp\.taskforcehq\.ai\/mcp/).length).toBeGreaterThan(0);
1299
- }, 10000);
1300
- it('4.23e4 Cloud selector excludes desktop OAuth clients from the MCP client list', async () => {
1301
- const fetchCloudAuthApi = vi.fn(async () => ({
1302
- ok: true,
1303
- json: async () => ({ items: [] })
1304
- }));
1305
- const user = userEvent.setup();
1306
- renderMcpComponent({
1307
- settingsModel: {
1308
- runtimeMode: 'local',
1309
- isAuthenticated: true,
1310
- currentWorkspaceName: 'Workspace Alpha',
1311
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1312
- fetchCloudAuthApi
1313
- }
1314
- });
1315
- await openCloudMcpSettings(user);
1316
- expect(screen.queryByRole('option', { name: /Claude Desktop/i })).not.toBeInTheDocument();
1317
- expect(screen.queryByRole('option', { name: /ChatGPT Desktop/i })).not.toBeInTheDocument();
1318
- expect(await screen.findByText('General MCP Configuration Instructions')).toBeInTheDocument();
1319
- }, 10000);
1320
- it('4.23e4b Integrations tab shows ChatGPT and Claude connector management separately from PAT tokens', async () => {
1321
- const fetchCloudAuthApi = vi.fn(async (path) => {
1322
- if (path.startsWith('/api/taskforce/settings/mcp/connectors')) {
1323
- return {
1324
- ok: true,
1325
- json: async () => ({
1326
- items: [
1327
- {
1328
- id: 'grant-1',
1329
- provider: 'chatgpt',
1330
- clientId: 'client-1',
1331
- clientName: 'ChatGPT Connector',
1332
- workspaceId: 'workspace-alpha',
1333
- userId: 'user-1',
1334
- scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
1335
- status: 'active',
1336
- createdAt: '2026-03-14T12:00:00.000Z',
1337
- lastUsedAt: null,
1338
- },
1339
- {
1340
- id: 'grant-2',
1341
- provider: 'claude',
1342
- clientId: 'client-2',
1343
- clientName: 'Claude Desktop Connector',
1344
- workspaceId: 'workspace-alpha',
1345
- userId: 'user-1',
1346
- scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
1347
- status: 'active',
1348
- createdAt: '2026-03-14T13:00:00.000Z',
1349
- lastUsedAt: null,
1350
- }
1351
- ]
1352
- })
1353
- };
1354
- }
1355
- return {
1356
- ok: true,
1357
- json: async () => ({ items: [] })
1358
- };
1359
- });
1360
- const user = userEvent.setup();
1361
- renderComponent({
1362
- settingsModel: {
1363
- runtimeMode: 'local',
1364
- isAuthenticated: true,
1365
- currentWorkspaceName: 'Workspace Alpha',
1366
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1367
- fetchCloudAuthApi
1368
- }
1369
- });
1370
- await user.click(screen.getByRole('button', { name: /Integrations/i }));
1371
- expect(await screen.findByRole('heading', { name: 'Integrations' })).toBeInTheDocument();
1372
- expect(await screen.findByText('ChatGPT Connector')).toBeInTheDocument();
1373
- expect(await screen.findByText('Claude Desktop Connector')).toBeInTheDocument();
1374
- expect(screen.getAllByText(/Taskforce handles OAuth and binds each connector authorization/i)).toHaveLength(2);
1375
- expect(screen.getAllByRole('button', { name: /Disconnect/i })).toHaveLength(2);
1376
- }, 10000);
1377
- it('4.23e4c MCP tab no longer shows connector management cards', async () => {
1378
- const fetchCloudAuthApi = vi.fn(async () => ({
1379
- ok: true,
1380
- json: async () => ({ items: [] })
1381
- }));
1382
- const user = userEvent.setup();
1383
- renderMcpComponent({
1384
- settingsModel: {
1385
- runtimeMode: 'local',
1386
- isAuthenticated: true,
1387
- currentWorkspaceName: 'Workspace Alpha',
1388
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1389
- fetchCloudAuthApi
1390
- }
1391
- });
1392
- await openCloudMcpSettings(user);
1393
- expect(screen.queryByText('ChatGPT Connector')).not.toBeInTheDocument();
1394
- expect(screen.queryByText(/^Connectors$/)).not.toBeInTheDocument();
1395
- }, 10000);
1396
- it('4.23g Cloud MCP configuration offers snippet and full-config copy actions', async () => {
1397
- const user = userEvent.setup();
1398
- renderMcpComponent({
1399
- settingsModel: {
1400
- runtimeMode: 'local',
1401
- isAuthenticated: true,
1402
- currentWorkspaceName: 'Workspace Alpha',
1403
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1404
- }
1405
- });
1406
- await openCloudMcpSettings(user);
1407
- expect(screen.getAllByRole('button', { name: /copy/i }).length).toBeGreaterThan(0);
1408
- }, 10000);
1409
- it('4.23g1 Cloud MCP copy warns before a token is generated', async () => {
1410
- const user = userEvent.setup();
1411
- renderMcpComponent({
1412
- settingsModel: {
1413
- runtimeMode: 'local',
1414
- isAuthenticated: true,
1415
- currentWorkspaceName: 'Workspace Alpha',
1416
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1417
- }
1418
- });
1419
- await openCloudMcpSettings(user);
1420
- await user.click(screen.getByTitle('Copy full configuration file'));
1421
- expect(screen.getByText('Generate a token before copying the Cloud MCP configuration.')).toBeInTheDocument();
1422
- }, 10000);
1423
- it('4.23g2 Cloud MCP settings do not surface automation identity controls', async () => {
1424
- const user = userEvent.setup();
1425
- renderMcpComponent({
1426
- settingsModel: {
1427
- runtimeMode: 'local',
1428
- isAuthenticated: true,
1429
- currentWorkspaceName: 'Workspace Alpha',
1430
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1431
- }
1432
- });
1433
- await openCloudMcpSettings(user);
1434
- expect(screen.queryByText('Automation Identity (Optional)')).not.toBeInTheDocument();
1435
- expect(screen.queryByRole('combobox', { name: 'Use existing AI profile' })).not.toBeInTheDocument();
1436
- }, 10000);
1437
- it('4.23g3 Cloud MCP config does not include AI identity headers by default', async () => {
1438
- const user = userEvent.setup();
1439
- renderMcpComponent({
1440
- settingsModel: {
1441
- runtimeMode: 'local',
1442
- isAuthenticated: true,
1443
- currentWorkspaceName: 'Workspace Alpha',
1444
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1445
- }
1446
- });
1447
- await openCloudMcpSettings(user);
1448
- await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'codex');
1449
- expect(screen.queryByText(/X-Taskforce-AI-Name/i)).not.toBeInTheDocument();
1450
- expect(screen.queryByText(/X-Taskforce-AI-Profile-Token/i)).not.toBeInTheDocument();
1451
- }, 10000);
1452
- it('4.23h Active tokens sort first and inactive tokens only show delete actions', async () => {
1453
- const user = userEvent.setup();
1454
- const confirmSpy = vi.spyOn(window, 'confirm').mockImplementation(() => true);
1455
- const fetchCloudAuthApi = vi.fn(async (path, init) => {
1456
- if (path === '/api/taskforce/settings/mcp/tokens/token-expired' && init?.method === 'DELETE') {
1457
- return {
1458
- ok: true,
1459
- json: async () => ({ success: true, deleted: true })
1460
- };
1461
- }
1462
- return {
1463
- ok: true,
1464
- json: async () => ({
1465
- items: [
1466
- {
1467
- id: 'token-expired',
1468
- name: 'Expired Token',
1469
- scopes: ['tasks:read'],
1470
- tokenPrefix: 'tfmcp_expired',
1471
- status: 'expired',
1472
- createdAt: '2026-03-03T00:00:00.000Z',
1473
- lastUsedAt: null,
1474
- },
1475
- {
1476
- id: 'token-revoked',
1477
- name: 'Old Token',
1478
- scopes: ['tasks:read'],
1479
- tokenPrefix: 'tfmcp_old',
1480
- status: 'revoked',
1481
- createdAt: '2026-03-01T00:00:00.000Z',
1482
- lastUsedAt: null,
1483
- },
1484
- {
1485
- id: 'token-active',
1486
- name: 'Current Token',
1487
- scopes: ['tasks:read', 'tasks:write'],
1488
- tokenPrefix: 'tfmcp_new',
1489
- status: 'active',
1490
- createdAt: '2026-03-02T00:00:00.000Z',
1491
- lastUsedAt: null,
1492
- }
1493
- ]
1494
- })
1495
- };
1496
- });
1497
- renderMcpComponent({
1498
- settingsModel: {
1499
- runtimeMode: 'local',
1500
- isAuthenticated: true,
1501
- currentWorkspaceName: 'Workspace Alpha',
1502
- fetchCloudAuthApi
1503
- }
1504
- });
1505
- await openCloudMcpSettings(user);
1506
- expect(await screen.findByText('Current Token')).toBeInTheDocument();
1507
- expect(screen.getByText('Expired Token')).toBeInTheDocument();
1508
- expect(screen.getByText('Old Token')).toBeInTheDocument();
1509
- const tokenLabels = screen.getAllByText(/^(Current Token|Expired Token|Old Token)$/);
1510
- expect(tokenLabels[0]).toHaveTextContent('Current Token');
1511
- expect(tokenLabels[1]).toHaveTextContent('Expired Token');
1512
- expect(tokenLabels[2]).toHaveTextContent('Old Token');
1513
- expect(screen.getAllByRole('button', { name: 'Regenerate' })).toHaveLength(1);
1514
- expect(screen.getAllByRole('button', { name: 'Revoke' })).toHaveLength(1);
1515
- expect(screen.getAllByRole('button', { name: 'Delete' })).toHaveLength(2);
1516
- await user.click(screen.getAllByRole('button', { name: 'Delete' })[0]);
1517
- expect(confirmSpy).toHaveBeenCalledWith('Delete this token record? This does not affect active tokens.');
1518
- expect(fetchCloudAuthApi).toHaveBeenCalledWith('/api/taskforce/settings/mcp/tokens/token-expired', expect.objectContaining({
1519
- method: 'DELETE',
1520
- credentials: 'include'
1521
- }));
1522
- await waitFor(() => expect(screen.queryByText('Expired Token')).not.toBeInTheDocument());
1523
- confirmSpy.mockRestore();
1524
- }, 10000);
1525
- it('4.23f Cloud MCP snippet keeps the server id short enough for client tool-name limits', async () => {
1526
- const user = userEvent.setup();
1527
- renderMcpComponent({
1528
- settingsModel: {
1529
- runtimeMode: 'local',
1530
- isAuthenticated: true,
1531
- workspaceId: 'workspace-019c771a-b6d7-7bc8-b837-c1133f3ba7a2',
1532
- currentWorkspaceName: 'Workspace Alpha',
1533
- cloudMcpBaseUrl: 'https://mcp.taskforcehq.ai',
1534
- }
1535
- });
1536
- await openCloudMcpSettings(user);
1537
- const codeNodes = Array.from(document.querySelectorAll('code'));
1538
- const snippetNode = codeNodes.find((node) => node.textContent?.includes('"mcpServers"'));
1539
- expect(snippetNode).toBeTruthy();
1540
- const parsed = JSON.parse(snippetNode?.textContent || '{}');
1541
- const serverId = Object.keys(parsed?.mcpServers || {})[0] || '';
1542
- expect(serverId.startsWith('taskforce-')).toBe(true);
1543
- expect(serverId.length).toBeLessThanOrEqual(33);
1544
- expect(`mcp_${serverId}_set_schedule_preferences`.length).toBeLessThanOrEqual(64);
1545
- }, 10000);
1546
- it('4.24 Local signed-out runtime keeps Cloud MCP visible with an auth prompt', async () => {
1547
- const user = userEvent.setup();
1548
- const fetchCloudAuthApi = vi.fn(async () => ({
1549
- ok: true,
1550
- json: async () => ({ items: [] })
1551
- }));
1552
- renderMcpComponent({
1553
- settingsModel: {
1554
- runtimeMode: 'local',
1555
- isAuthenticated: false,
1556
- fetchCloudAuthApi
1557
- }
1558
- });
1559
- await openMcpSettings(user);
1560
- expect(screen.getByRole('button', { name: 'Local MCP' })).toBeInTheDocument();
1561
- expect(screen.getByRole('button', { name: 'Cloud MCP' })).toBeInTheDocument();
1562
- expect(screen.getByRole('combobox', { name: 'MCP client' })).toHaveValue('general');
1563
- expect(screen.getByText('General MCP Configuration Instructions')).toBeInTheDocument();
1564
- expect(screen.queryByText('MCP Path Mapping (Host Root)')).not.toBeInTheDocument();
1565
- await user.click(screen.getByRole('button', { name: 'Advanced' }));
1566
- expect(screen.getByText('MCP Path Mapping (Host Root)')).toBeInTheDocument();
1567
- await user.click(screen.getByRole('button', { name: 'Cloud MCP' }));
1568
- expect(screen.getByText('Sign in to use Cloud MCP')).toBeInTheDocument();
1569
- expect(screen.getByText(/Register or sign in to generate cloud MCP tokens/i)).toBeInTheDocument();
1570
- expect(screen.queryByText('Cloud MCP Configuration')).not.toBeInTheDocument();
1571
- expect(fetchCloudAuthApi).not.toHaveBeenCalled();
1572
- });
1573
- it('4.24a Local signed-out Cloud MCP prompt can open login and registration', async () => {
1574
- const user = userEvent.setup();
1575
- const onOpenCloudAuth = vi.fn();
1576
- renderMcpComponent({
1577
- settingsModel: {
1578
- runtimeMode: 'local',
1579
- isAuthenticated: false,
1580
- onOpenCloudAuth
1581
- }
1582
- });
1583
- await openMcpSettings(user);
1584
- await user.click(screen.getByRole('button', { name: 'Cloud MCP' }));
1585
- await user.click(screen.getByRole('button', { name: 'Sign In' }));
1586
- expect(onOpenCloudAuth).toHaveBeenCalledWith('login');
1587
- await user.click(screen.getByRole('button', { name: 'Register' }));
1588
- expect(onOpenCloudAuth).toHaveBeenCalledWith('register');
1589
- });
1590
- it('4.24b Local MCP selector offers General first and keeps the rest alphabetical', async () => {
1591
- const user = userEvent.setup();
1592
- renderMcpComponent({
1593
- settingsModel: {
1594
- runtimeMode: 'local',
1595
- isAuthenticated: false
1596
- }
1597
- });
1598
- await openMcpSettings(user);
1599
- const options = screen.getAllByRole('option').map((option) => option.textContent);
1600
- expect(options).toEqual([
1601
- 'General',
1602
- 'Antigravity',
1603
- 'Claude Code CLI',
1604
- 'Cline',
1605
- 'Codex',
1606
- 'Cursor',
1607
- 'Gemini CLI',
1608
- 'OpenClaw',
1609
- 'VS Code / Copilot',
1610
- 'Windsurf'
1611
- ]);
1612
- });
1613
- it('4.24c Local Codex shows config.toml MCP output without token headers', async () => {
1614
- const user = userEvent.setup();
1615
- renderMcpComponent({
1616
- settingsModel: {
1617
- runtimeMode: 'local',
1618
- isAuthenticated: false,
1619
- projectRoot: '/Users/rossburke/projects/taskforce',
1620
- projectName: 'taskforce',
1621
- tenantId: 'tenant-alpha',
1622
- workspaceId: 'workspace-alpha',
1623
- mcpScriptPath: '/usr/local/bin/taskforce'
1624
- }
1625
- });
1626
- await openMcpSettings(user);
1627
- await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'codex');
1628
- expect(await screen.findByText(/Codex Configuration Instructions/i)).toBeInTheDocument();
1629
- expect((await screen.findAllByText(/\.codex\/config\.toml/i)).length).toBeGreaterThan(0);
1630
- expect(screen.getByText(/\[mcp_servers\./)).toBeInTheDocument();
1631
- expect(screen.getByText(/command = "taskforce"/i)).toBeInTheDocument();
1632
- expect(screen.queryByText(/Authorization/i)).not.toBeInTheDocument();
1633
- });
1634
- it('4.24d Local VS Code uses stdio config under servers', async () => {
1635
- const user = userEvent.setup();
1636
- renderMcpComponent({
1637
- settingsModel: {
1638
- runtimeMode: 'local',
1639
- isAuthenticated: false,
1640
- projectRoot: '/Users/rossburke/projects/taskforce',
1641
- projectName: 'taskforce',
1642
- tenantId: 'tenant-alpha',
1643
- workspaceId: 'workspace-alpha',
1644
- mcpScriptPath: '/usr/local/bin/taskforce'
1645
- }
1646
- });
1647
- await openMcpSettings(user);
1648
- await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'vscode');
1649
- expect(await screen.findByText(/VS Code Configuration Instructions/i)).toBeInTheDocument();
1650
- expect(screen.getByText(/"servers"/)).toBeInTheDocument();
1651
- expect(screen.getByText(/"type"/)).toBeInTheDocument();
1652
- expect(screen.getByText(/"stdio"/)).toBeInTheDocument();
1653
- });
1654
- it('4.24e Local Claude Code shows a stdio add command', async () => {
1655
- const user = userEvent.setup();
1656
- renderMcpComponent({
1657
- settingsModel: {
1658
- runtimeMode: 'local',
1659
- isAuthenticated: false,
1660
- projectRoot: '/Users/rossburke/projects/taskforce',
1661
- projectName: 'taskforce',
1662
- tenantId: 'tenant-alpha',
1663
- workspaceId: 'workspace-alpha',
1664
- mcpScriptPath: '/usr/local/bin/taskforce'
1665
- }
1666
- });
1667
- await openMcpSettings(user);
1668
- await user.selectOptions(screen.getByRole('combobox', { name: 'MCP client' }), 'claude-code');
1669
- expect(await screen.findByText(/Claude Code Configuration Instructions/i)).toBeInTheDocument();
1670
- expect(screen.getByText(/claude mcp add taskforce-/i)).toBeInTheDocument();
1671
- expect(screen.getByText(/--env TASKFORCE_TENANT_ID=tenant-alpha/i)).toBeInTheDocument();
1672
- expect(screen.getByText(/-- taskforce mcp/i)).toBeInTheDocument();
1673
- });
1674
- });