@taskforcehq/taskforce 0.3.309 → 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 (470) hide show
  1. package/README.md +89 -127
  2. package/dist/TaskforceCore.js +1 -1
  3. package/dist/components/context/ContextAttachmentManager.js +4 -3
  4. package/dist/components/features/InitiativesModule.js +1 -1
  5. package/dist/components/features/TaskSettings.js +1 -1
  6. package/dist/components/ui/DisclosureSectionHeader.d.ts +12 -0
  7. package/dist/components/ui/DisclosureSectionHeader.js +8 -0
  8. package/dist/components/views/PlanComparisonPage.js +1 -1
  9. package/dist/components/views/StandaloneLayout.js +18 -4
  10. package/dist/components/views/panels/PlanningDrawer.js +2 -1
  11. package/dist/components/views/standalone/modals/AccountHubModal.js +1 -1
  12. package/dist/components/views/standalone/modals/HelpModal.d.ts +3 -1
  13. package/dist/components/views/standalone/modals/HelpModal.js +17 -2
  14. package/dist/core/PlanEntitlementService.d.ts +2 -0
  15. package/dist/core/PlanEntitlementService.js +57 -4
  16. package/dist/hooks/useFilterSort.js +1 -1
  17. package/dist/hooks/useTaskforce.js +2 -2
  18. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +5 -2
  19. package/dist/localization/locales/en-US.js +1 -1
  20. package/dist/localization/locales/es-419.js +1 -1
  21. package/dist/localization/locales/pt-BR.js +1 -1
  22. package/dist/mcp/runtime.js +49 -14
  23. package/dist/server/routes/billing.js +0 -1
  24. package/dist/ui/assets/AgentsModule-BULqIo8e.js +1 -0
  25. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-CCckzWYZ.js → AnnotatedAttachmentWorkspace-DHr4rHK9.js} +1 -1
  26. package/dist/ui/assets/ContextAttachmentManager-DoMe2OpY.js +3 -0
  27. package/dist/ui/assets/{DocumentWorkspace-BVaWPA25.js → DocumentWorkspace-DumBWhvb.js} +1 -1
  28. package/dist/ui/assets/{EntityActivityTimeline-DSj0ThaK.js → EntityActivityTimeline-sHk8Nzbq.js} +1 -1
  29. package/dist/ui/assets/{InitiativesModule-AxMkWtxH.js → InitiativesModule-3X9UNzHh.js} +1 -1
  30. package/dist/ui/assets/PlansPage-C3A3EYaV.js +1 -0
  31. package/dist/ui/assets/PlansPage-DeAzWYTb.css +1 -0
  32. package/dist/ui/assets/{TaskContextUpload-Dw4rTF4i.js → TaskContextUpload-C8G7pWkX.js} +1 -1
  33. package/dist/ui/assets/{TaskSettings-f2ga42_V.js → TaskSettings-DM1o9Wla.js} +2 -2
  34. package/dist/ui/assets/{WorkflowsModule-E4UL3mov.js → WorkflowsModule-CW0crzvD.js} +1 -1
  35. package/dist/ui/assets/documentReferences-BZB3nMRb.js +1 -0
  36. package/dist/ui/assets/index-BWFQ9mGn.js +5 -0
  37. package/dist/ui/assets/{index-BvAbqx5Q.css → index-DcK_8tuE.css} +1 -1
  38. package/dist/ui/assets/{vendor-icons-B32hGuF2.js → vendor-icons-CLnehDTw.js} +1 -1
  39. package/dist/ui/index.html +3 -3
  40. package/dist/utils/supportContact.d.ts +9 -0
  41. package/dist/utils/supportContact.js +32 -0
  42. package/package.json +12 -4
  43. package/scripts/postinstall.js +4 -3
  44. package/dist/TaskforceCore.CategoryFallback.test.d.ts +0 -1
  45. package/dist/TaskforceCore.CategoryFallback.test.js +0 -101
  46. package/dist/TaskforceCore.filter-persistence.test.d.ts +0 -1
  47. package/dist/TaskforceCore.filter-persistence.test.js +0 -402
  48. package/dist/TaskforceCore.routing.test.d.ts +0 -1
  49. package/dist/TaskforceCore.routing.test.js +0 -311
  50. package/dist/TaskforceCore.test.d.ts +0 -1
  51. package/dist/TaskforceCore.test.js +0 -7910
  52. package/dist/TaskforceWidget.test.d.ts +0 -1
  53. package/dist/TaskforceWidget.test.js +0 -92
  54. package/dist/UnsavedChangesRelaxed.test.d.ts +0 -1
  55. package/dist/UnsavedChangesRelaxed.test.js +0 -16
  56. package/dist/__tests__/localizationCatalogs.test.d.ts +0 -1
  57. package/dist/__tests__/localizationCatalogs.test.js +0 -54
  58. package/dist/__tests__/workflowSimulation.test.d.ts +0 -1
  59. package/dist/__tests__/workflowSimulation.test.js +0 -113
  60. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.d.ts +0 -1
  61. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.js +0 -278
  62. package/dist/annotatedAttachments/payload.test.d.ts +0 -1
  63. package/dist/annotatedAttachments/payload.test.js +0 -67
  64. package/dist/compat/workspaceSyncCompat.test.d.ts +0 -1
  65. package/dist/compat/workspaceSyncCompat.test.js +0 -124
  66. package/dist/components/features/AgentsModule.test.d.ts +0 -1
  67. package/dist/components/features/AgentsModule.test.js +0 -601
  68. package/dist/components/features/AnnotatedAttachmentWorkspace.test.d.ts +0 -1
  69. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +0 -2883
  70. package/dist/components/features/DocumentWorkspace.test.d.ts +0 -1
  71. package/dist/components/features/DocumentWorkspace.test.js +0 -1439
  72. package/dist/components/features/TaskSettings.test.d.ts +0 -1
  73. package/dist/components/features/TaskSettings.test.js +0 -1674
  74. package/dist/components/features/settings/TaxonomyLibraryManager.test.d.ts +0 -1
  75. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +0 -141
  76. package/dist/components/task/TaskActionHeader.test.d.ts +0 -1
  77. package/dist/components/task/TaskActionHeader.test.js +0 -91
  78. package/dist/components/task/TaskCard.test.d.ts +0 -1
  79. package/dist/components/task/TaskCard.test.js +0 -513
  80. package/dist/components/task/TaskContextUpload.test.d.ts +0 -1
  81. package/dist/components/task/TaskContextUpload.test.js +0 -506
  82. package/dist/components/task/TaskForm.test.d.ts +0 -1
  83. package/dist/components/task/TaskForm.test.js +0 -946
  84. package/dist/components/task/TaskKanban.empty-column-drop.test.d.ts +0 -1
  85. package/dist/components/task/TaskKanban.empty-column-drop.test.js +0 -66
  86. package/dist/components/task/TaskKanban.planning-drop.test.d.ts +0 -1
  87. package/dist/components/task/TaskKanban.planning-drop.test.js +0 -93
  88. package/dist/components/task/TaskKanban.test.d.ts +0 -1
  89. package/dist/components/task/TaskKanban.test.js +0 -99
  90. package/dist/components/task/TaskList.test.d.ts +0 -1
  91. package/dist/components/task/TaskList.test.js +0 -473
  92. package/dist/components/task/TaskStatusActions.test.d.ts +0 -1
  93. package/dist/components/task/TaskStatusActions.test.js +0 -54
  94. package/dist/components/task/taskKanbanDropRules.test.d.ts +0 -1
  95. package/dist/components/task/taskKanbanDropRules.test.js +0 -114
  96. package/dist/components/ui/LevelSelector.test.d.ts +0 -1
  97. package/dist/components/ui/LevelSelector.test.js +0 -55
  98. package/dist/components/ui/Markdown.test.d.ts +0 -1
  99. package/dist/components/ui/Markdown.test.js +0 -38
  100. package/dist/components/ui/MultiSelectFilter.test.d.ts +0 -1
  101. package/dist/components/ui/MultiSelectFilter.test.js +0 -74
  102. package/dist/components/ui/TaxonomyDropdown.test.d.ts +0 -1
  103. package/dist/components/ui/TaxonomyDropdown.test.js +0 -130
  104. package/dist/components/ui/TopNoticeLayer.test.d.ts +0 -1
  105. package/dist/components/ui/TopNoticeLayer.test.js +0 -25
  106. package/dist/components/views/AppShellHeader.test.d.ts +0 -1
  107. package/dist/components/views/AppShellHeader.test.js +0 -42
  108. package/dist/components/views/PlanComparisonPage.test.d.ts +0 -1
  109. package/dist/components/views/PlanComparisonPage.test.js +0 -757
  110. package/dist/components/views/PlansPage.test.d.ts +0 -1
  111. package/dist/components/views/PlansPage.test.js +0 -710
  112. package/dist/components/views/chrome/WorkspaceChromeSections.test.d.ts +0 -1
  113. package/dist/components/views/chrome/WorkspaceChromeSections.test.js +0 -54
  114. package/dist/components/views/chrome/workspaceChrome.test.d.ts +0 -1
  115. package/dist/components/views/chrome/workspaceChrome.test.js +0 -52
  116. package/dist/components/views/panels/DocumentFilterToolbar.test.d.ts +0 -1
  117. package/dist/components/views/panels/DocumentFilterToolbar.test.js +0 -19
  118. package/dist/components/views/panels/FilterToolbar.test.d.ts +0 -1
  119. package/dist/components/views/panels/FilterToolbar.test.js +0 -47
  120. package/dist/components/views/panels/PlanningDrawer.test.d.ts +0 -1
  121. package/dist/components/views/panels/PlanningDrawer.test.js +0 -529
  122. package/dist/components/views/planningScope.test.d.ts +0 -1
  123. package/dist/components/views/planningScope.test.js +0 -60
  124. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +0 -1
  125. package/dist/components/views/standalone/modals/AccountHubModal.test.js +0 -66
  126. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +0 -1
  127. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +0 -24
  128. package/dist/components/views/teamManagementAccess.test.d.ts +0 -1
  129. package/dist/components/views/teamManagementAccess.test.js +0 -44
  130. package/dist/config/deployment.test.d.ts +0 -1
  131. package/dist/config/deployment.test.js +0 -90
  132. package/dist/config/envSchema.test.d.ts +0 -1
  133. package/dist/config/envSchema.test.js +0 -125
  134. package/dist/core/AiProfileService.test.d.ts +0 -1
  135. package/dist/core/AiProfileService.test.js +0 -184
  136. package/dist/core/AiProfiles.test.d.ts +0 -1
  137. package/dist/core/AiProfiles.test.js +0 -1873
  138. package/dist/core/AuthTokenService.test.d.ts +0 -1
  139. package/dist/core/AuthTokenService.test.js +0 -78
  140. package/dist/core/BulkUpdateFieldsPreview.test.d.ts +0 -1
  141. package/dist/core/BulkUpdateFieldsPreview.test.js +0 -62
  142. package/dist/core/CompletedAt.test.d.ts +0 -1
  143. package/dist/core/CompletedAt.test.js +0 -44
  144. package/dist/core/CreatedBy.test.d.ts +0 -1
  145. package/dist/core/CreatedBy.test.js +0 -188
  146. package/dist/core/DeletedTaskTrash.test.d.ts +0 -1
  147. package/dist/core/DeletedTaskTrash.test.js +0 -104
  148. package/dist/core/EntitlementsPolicy.test.d.ts +0 -1
  149. package/dist/core/EntitlementsPolicy.test.js +0 -280
  150. package/dist/core/GlobalSettingsService.test.d.ts +0 -1
  151. package/dist/core/GlobalSettingsService.test.js +0 -45
  152. package/dist/core/JsonBackupRuntime.test.d.ts +0 -1
  153. package/dist/core/JsonBackupRuntime.test.js +0 -155
  154. package/dist/core/PlanFeatureCatalog.test.d.ts +0 -1
  155. package/dist/core/PlanFeatureCatalog.test.js +0 -139
  156. package/dist/core/PlanVersionPolicy.test.d.ts +0 -1
  157. package/dist/core/PlanVersionPolicy.test.js +0 -204
  158. package/dist/core/PlanningEntities.test.d.ts +0 -1
  159. package/dist/core/PlanningEntities.test.js +0 -509
  160. package/dist/core/ScheduleFields.test.d.ts +0 -1
  161. package/dist/core/ScheduleFields.test.js +0 -193
  162. package/dist/core/SetupState.test.d.ts +0 -1
  163. package/dist/core/SetupState.test.js +0 -113
  164. package/dist/core/SignupMonetizationSettings.test.d.ts +0 -1
  165. package/dist/core/SignupMonetizationSettings.test.js +0 -515
  166. package/dist/core/Stats.test.d.ts +0 -1
  167. package/dist/core/Stats.test.js +0 -36
  168. package/dist/core/SystemAdmin.test.d.ts +0 -1
  169. package/dist/core/SystemAdmin.test.js +0 -1106
  170. package/dist/core/TaskActivitySync.test.d.ts +0 -1
  171. package/dist/core/TaskActivitySync.test.js +0 -90
  172. package/dist/core/TaskAttachmentsCanonical.test.d.ts +0 -1
  173. package/dist/core/TaskAttachmentsCanonical.test.js +0 -356
  174. package/dist/core/TaskAuditTimeline.test.d.ts +0 -1
  175. package/dist/core/TaskAuditTimeline.test.js +0 -319
  176. package/dist/core/TaskTaxonomyValidation.test.d.ts +0 -1
  177. package/dist/core/TaskTaxonomyValidation.test.js +0 -440
  178. package/dist/core/Taskforce.ids.test.d.ts +0 -1
  179. package/dist/core/Taskforce.ids.test.js +0 -611
  180. package/dist/core/Taskforce.listTasksSlim.test.d.ts +0 -1
  181. package/dist/core/Taskforce.listTasksSlim.test.js +0 -91
  182. package/dist/core/Taskforce.mcpAuth.test.d.ts +0 -1
  183. package/dist/core/Taskforce.mcpAuth.test.js +0 -450
  184. package/dist/core/Taskforce.realtimeMutation.test.d.ts +0 -1
  185. package/dist/core/Taskforce.realtimeMutation.test.js +0 -131
  186. package/dist/core/UserProfileAvatarDrafts.test.d.ts +0 -1
  187. package/dist/core/UserProfileAvatarDrafts.test.js +0 -138
  188. package/dist/core/WorkspaceDelete.test.d.ts +0 -1
  189. package/dist/core/WorkspaceDelete.test.js +0 -159
  190. package/dist/core/WorkspacePermissions.test.d.ts +0 -1
  191. package/dist/core/WorkspacePermissions.test.js +0 -270
  192. package/dist/core/WorkspacePlanMode.test.d.ts +0 -1
  193. package/dist/core/WorkspacePlanMode.test.js +0 -383
  194. package/dist/core/WorkspaceSyncSettings.test.d.ts +0 -1
  195. package/dist/core/WorkspaceSyncSettings.test.js +0 -65
  196. package/dist/core/__tests__/TaskAttachmentsCanonical.test.d.ts +0 -1
  197. package/dist/core/__tests__/TaskAttachmentsCanonical.test.js +0 -212
  198. package/dist/documentReviews/DocumentReviewSessionStore.test.d.ts +0 -1
  199. package/dist/documentReviews/DocumentReviewSessionStore.test.js +0 -127
  200. package/dist/hooks/authGuardPolicy.test.d.ts +0 -1
  201. package/dist/hooks/authGuardPolicy.test.js +0 -76
  202. package/dist/hooks/bootstrapTimeouts.test.d.ts +0 -1
  203. package/dist/hooks/bootstrapTimeouts.test.js +0 -12
  204. package/dist/hooks/sync/controlPlane.test.d.ts +0 -1
  205. package/dist/hooks/sync/controlPlane.test.js +0 -121
  206. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +0 -1
  207. package/dist/hooks/sync/lifecyclePolicy.test.js +0 -124
  208. package/dist/hooks/sync/orchestratorShared.test.d.ts +0 -1
  209. package/dist/hooks/sync/orchestratorShared.test.js +0 -98
  210. package/dist/hooks/sync/pullLifecycle.test.d.ts +0 -1
  211. package/dist/hooks/sync/pullLifecycle.test.js +0 -80
  212. package/dist/hooks/sync/recovery.test.d.ts +0 -1
  213. package/dist/hooks/sync/recovery.test.js +0 -292
  214. package/dist/hooks/sync/transfers.test.d.ts +0 -1
  215. package/dist/hooks/sync/transfers.test.js +0 -928
  216. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +0 -1
  217. package/dist/hooks/sync/useRecentSyncEvents.test.js +0 -124
  218. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +0 -1
  219. package/dist/hooks/sync/useSyncStatusActions.test.js +0 -133
  220. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +0 -1
  221. package/dist/hooks/sync/useSyncStatusControls.test.js +0 -88
  222. package/dist/hooks/useFilterSort.test.d.ts +0 -1
  223. package/dist/hooks/useFilterSort.test.js +0 -419
  224. package/dist/hooks/useRealtimeSync.test.d.ts +0 -1
  225. package/dist/hooks/useRealtimeSync.test.js +0 -311
  226. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.d.ts +0 -1
  227. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +0 -54
  228. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +0 -1
  229. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +0 -228
  230. package/dist/hooks/useSyncOrchestrator.retry-closure.test.d.ts +0 -1
  231. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +0 -2027
  232. package/dist/hooks/useTaskData.test.d.ts +0 -1
  233. package/dist/hooks/useTaskData.test.js +0 -291
  234. package/dist/hooks/useTaskMutations.test.d.ts +0 -1
  235. package/dist/hooks/useTaskMutations.test.js +0 -256
  236. package/dist/hooks/useTaskforce.contract.test.d.ts +0 -1
  237. package/dist/hooks/useTaskforce.contract.test.js +0 -7
  238. package/dist/hooks/useTaskforce.priority-filters.test.d.ts +0 -1
  239. package/dist/hooks/useTaskforce.priority-filters.test.js +0 -28
  240. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +0 -1
  241. package/dist/hooks/useTaskforce.runtime-routing.test.js +0 -246
  242. package/dist/hooks/useTaskforce.sync-behavior.test.d.ts +0 -1
  243. package/dist/hooks/useTaskforce.sync-behavior.test.js +0 -3644
  244. package/dist/hooks/useWorkspaceSyncController.test.d.ts +0 -1
  245. package/dist/hooks/useWorkspaceSyncController.test.js +0 -192
  246. package/dist/hooks/workspace/useTaskforceApiRouting.test.d.ts +0 -1
  247. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +0 -77
  248. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +0 -1
  249. package/dist/hooks/workspace/workspaceLocalState.test.js +0 -59
  250. package/dist/localization/localizationCatalogs.test.d.ts +0 -1
  251. package/dist/localization/localizationCatalogs.test.js +0 -37
  252. package/dist/mcp/aiProfileBootstrap.test.d.ts +0 -1
  253. package/dist/mcp/aiProfileBootstrap.test.js +0 -68
  254. package/dist/mcp/canonicalAssetHelpers.test.d.ts +0 -1
  255. package/dist/mcp/canonicalAssetHelpers.test.js +0 -67
  256. package/dist/mcp/clientIntegrations.test.d.ts +0 -1
  257. package/dist/mcp/clientIntegrations.test.js +0 -219
  258. package/dist/mcp/optionResolution.test.d.ts +0 -1
  259. package/dist/mcp/optionResolution.test.js +0 -43
  260. package/dist/mcp/runtime.test.d.ts +0 -1
  261. package/dist/mcp/runtime.test.js +0 -5120
  262. package/dist/mcp/structuredCommentValidation.test.d.ts +0 -1
  263. package/dist/mcp/structuredCommentValidation.test.js +0 -31
  264. package/dist/mcp/structuredDocValidation.test.d.ts +0 -1
  265. package/dist/mcp/structuredDocValidation.test.js +0 -35
  266. package/dist/mcp/toolRegistry.test.d.ts +0 -1
  267. package/dist/mcp/toolRegistry.test.js +0 -34
  268. package/dist/migrations/billingSchemaParity.test.d.ts +0 -1
  269. package/dist/migrations/billingSchemaParity.test.js +0 -160
  270. package/dist/migrations/taskSchemaMigrations.test.d.ts +0 -1
  271. package/dist/migrations/taskSchemaMigrations.test.js +0 -225
  272. package/dist/qaOpen.test.d.ts +0 -1
  273. package/dist/qaOpen.test.js +0 -56
  274. package/dist/releaseVersion.test.d.ts +0 -1
  275. package/dist/releaseVersion.test.js +0 -32
  276. package/dist/runtime/appGates.test.d.ts +0 -1
  277. package/dist/runtime/appGates.test.js +0 -42
  278. package/dist/server/aiProfileAvatarAssets.test.d.ts +0 -1
  279. package/dist/server/aiProfileAvatarAssets.test.js +0 -213
  280. package/dist/server/annotatedAttachmentsRoutes.test.d.ts +0 -1
  281. package/dist/server/annotatedAttachmentsRoutes.test.js +0 -532
  282. package/dist/server/auth.test.d.ts +0 -1
  283. package/dist/server/auth.test.js +0 -131
  284. package/dist/server/buildInfo.test.d.ts +0 -1
  285. package/dist/server/buildInfo.test.js +0 -29
  286. package/dist/server/cors.test.d.ts +0 -1
  287. package/dist/server/cors.test.js +0 -77
  288. package/dist/server/documentReviewRoutes.test.d.ts +0 -1
  289. package/dist/server/documentReviewRoutes.test.js +0 -190
  290. package/dist/server/email.test.d.ts +0 -1
  291. package/dist/server/email.test.js +0 -113
  292. package/dist/server/index.cookieProxy.test.d.ts +0 -1
  293. package/dist/server/index.cookieProxy.test.js +0 -67
  294. package/dist/server/index.rateLimit.test.d.ts +0 -1
  295. package/dist/server/index.rateLimit.test.js +0 -22
  296. package/dist/server/index.test.d.ts +0 -1
  297. package/dist/server/index.test.js +0 -283
  298. package/dist/server/rateLimit.test.d.ts +0 -1
  299. package/dist/server/rateLimit.test.js +0 -107
  300. package/dist/server/realtimeSync.auth.test.d.ts +0 -1
  301. package/dist/server/realtimeSync.auth.test.js +0 -62
  302. package/dist/server/realtimeSync.replay.test.d.ts +0 -1
  303. package/dist/server/realtimeSync.replay.test.js +0 -192
  304. package/dist/server/realtimeSync.workspaceIsolation.test.d.ts +0 -1
  305. package/dist/server/realtimeSync.workspaceIsolation.test.js +0 -101
  306. package/dist/server/routes/billing.test.d.ts +0 -1
  307. package/dist/server/routes/billing.test.js +0 -3455
  308. package/dist/server/routes/shared.test.d.ts +0 -1
  309. package/dist/server/routes/shared.test.js +0 -32
  310. package/dist/server/routes/sync.integration.test.d.ts +0 -1
  311. package/dist/server/routes/sync.integration.test.js +0 -2932
  312. package/dist/server/routes/syncAuxRoutes.test.d.ts +0 -1
  313. package/dist/server/routes/syncAuxRoutes.test.js +0 -158
  314. package/dist/server/routes/syncRouteGuards.test.d.ts +0 -1
  315. package/dist/server/routes/syncRouteGuards.test.js +0 -94
  316. package/dist/server/routes.test.d.ts +0 -1
  317. package/dist/server/routes.test.js +0 -15612
  318. package/dist/server/runtimeMode.test.d.ts +0 -1
  319. package/dist/server/runtimeMode.test.js +0 -26
  320. package/dist/server/securityHeaders.test.d.ts +0 -1
  321. package/dist/server/securityHeaders.test.js +0 -70
  322. package/dist/services/templateEngine.test.d.ts +0 -1
  323. package/dist/services/templateEngine.test.js +0 -150
  324. package/dist/services/templates.test.d.ts +0 -1
  325. package/dist/services/templates.test.js +0 -45
  326. package/dist/services/workflowDocumentFixtures.test.d.ts +0 -1
  327. package/dist/services/workflowDocumentFixtures.test.js +0 -78
  328. package/dist/services/workflowExportMatrix.test.d.ts +0 -1
  329. package/dist/services/workflowExportMatrix.test.js +0 -113
  330. package/dist/services/workflowExportSnapshots.test.d.ts +0 -1
  331. package/dist/services/workflowExportSnapshots.test.js +0 -30
  332. package/dist/services/workflowGeneration.test.d.ts +0 -1
  333. package/dist/services/workflowGeneration.test.js +0 -12
  334. package/dist/services/workflowSimulation.test.d.ts +0 -1
  335. package/dist/services/workflowSimulation.test.js +0 -113
  336. package/dist/services/workflowV2Policy.test.d.ts +0 -1
  337. package/dist/services/workflowV2Policy.test.js +0 -80
  338. package/dist/shared/customTaxonomyLibrary.test.d.ts +0 -1
  339. package/dist/shared/customTaxonomyLibrary.test.js +0 -20
  340. package/dist/shared/passwordPolicy.test.d.ts +0 -1
  341. package/dist/shared/passwordPolicy.test.js +0 -34
  342. package/dist/shared/runtimeContract.test.d.ts +0 -1
  343. package/dist/shared/runtimeContract.test.js +0 -23
  344. package/dist/shared/taxonomyLibrary.test.d.ts +0 -1
  345. package/dist/shared/taxonomyLibrary.test.js +0 -26
  346. package/dist/shared/taxonomyState.test.d.ts +0 -1
  347. package/dist/shared/taxonomyState.test.js +0 -71
  348. package/dist/soak/preflight.cli.d.ts +0 -1
  349. package/dist/soak/preflight.cli.js +0 -28
  350. package/dist/soak/preflight.d.ts +0 -26
  351. package/dist/soak/preflight.js +0 -113
  352. package/dist/soak/preflight.test.d.ts +0 -1
  353. package/dist/soak/preflight.test.js +0 -39
  354. package/dist/storage/objectStorageClient.test.d.ts +0 -1
  355. package/dist/storage/objectStorageClient.test.js +0 -82
  356. package/dist/storage/objectStorageConfig.test.d.ts +0 -1
  357. package/dist/storage/objectStorageConfig.test.js +0 -50
  358. package/dist/storage/postgresAdapter.test.d.ts +0 -1
  359. package/dist/storage/postgresAdapter.test.js +0 -29
  360. package/dist/storage/workspaceAssetStore.test.d.ts +0 -1
  361. package/dist/storage/workspaceAssetStore.test.js +0 -588
  362. package/dist/sync/cloudSyncApi.test.d.ts +0 -1
  363. package/dist/sync/cloudSyncApi.test.js +0 -111
  364. package/dist/sync/collaborationSyncPayload.test.d.ts +0 -1
  365. package/dist/sync/collaborationSyncPayload.test.js +0 -125
  366. package/dist/sync/contentSyncPayload.test.d.ts +0 -1
  367. package/dist/sync/contentSyncPayload.test.js +0 -143
  368. package/dist/sync/encryptionService.test.d.ts +0 -1
  369. package/dist/sync/encryptionService.test.js +0 -52
  370. package/dist/sync/planningSyncPayload.test.d.ts +0 -1
  371. package/dist/sync/planningSyncPayload.test.js +0 -92
  372. package/dist/sync/realtimeFeature.test.d.ts +0 -1
  373. package/dist/sync/realtimeFeature.test.js +0 -30
  374. package/dist/sync/realtimeReconcile.test.d.ts +0 -1
  375. package/dist/sync/realtimeReconcile.test.js +0 -43
  376. package/dist/sync/syncApplyHandlers.test.d.ts +0 -1
  377. package/dist/sync/syncApplyHandlers.test.js +0 -626
  378. package/dist/sync/syncService.test.d.ts +0 -1
  379. package/dist/sync/syncService.test.js +0 -903
  380. package/dist/sync/taskSyncPayload.test.d.ts +0 -1
  381. package/dist/sync/taskSyncPayload.test.js +0 -54
  382. package/dist/sync/workspacePullFeed.test.d.ts +0 -1
  383. package/dist/sync/workspacePullFeed.test.js +0 -438
  384. package/dist/sync/workspaceSyncModel.test.d.ts +0 -1
  385. package/dist/sync/workspaceSyncModel.test.js +0 -1442
  386. package/dist/test/setup.d.ts +0 -1
  387. package/dist/test/setup.js +0 -15
  388. package/dist/ui/assets/AgentsModule-BS4Pi_nC.js +0 -1
  389. package/dist/ui/assets/ContextAttachmentManager-CfG_ER7C.js +0 -3
  390. package/dist/ui/assets/PlansPage-B_z-D6Nh.css +0 -1
  391. package/dist/ui/assets/PlansPage-Ndt7mYfo.js +0 -1
  392. package/dist/ui/assets/documentReferences-BOUcJm6-.js +0 -1
  393. package/dist/ui/assets/index-MAHKvFqp.js +0 -4
  394. package/dist/utils/accountProfileSummaryCache.test.d.ts +0 -1
  395. package/dist/utils/accountProfileSummaryCache.test.js +0 -63
  396. package/dist/utils/annotatedAttachments.test.d.ts +0 -1
  397. package/dist/utils/annotatedAttachments.test.js +0 -108
  398. package/dist/utils/assignees.test.d.ts +0 -1
  399. package/dist/utils/assignees.test.js +0 -25
  400. package/dist/utils/avatarUpload.test.d.ts +0 -1
  401. package/dist/utils/avatarUpload.test.js +0 -40
  402. package/dist/utils/canonicalAssetKind.test.d.ts +0 -1
  403. package/dist/utils/canonicalAssetKind.test.js +0 -24
  404. package/dist/utils/coalescedRefresh.test.d.ts +0 -1
  405. package/dist/utils/coalescedRefresh.test.js +0 -30
  406. package/dist/utils/constants.icons.test.d.ts +0 -1
  407. package/dist/utils/constants.icons.test.js +0 -41
  408. package/dist/utils/planParser.test.d.ts +0 -1
  409. package/dist/utils/planParser.test.js +0 -183
  410. package/dist/utils/planningReferences.test.d.ts +0 -1
  411. package/dist/utils/planningReferences.test.js +0 -17
  412. package/dist/utils/syncEventDetails.test.d.ts +0 -1
  413. package/dist/utils/syncEventDetails.test.js +0 -23
  414. package/dist/utils/syncEventPresentation.test.d.ts +0 -1
  415. package/dist/utils/syncEventPresentation.test.js +0 -64
  416. package/dist/utils/syncStatusPresentation.test.d.ts +0 -1
  417. package/dist/utils/syncStatusPresentation.test.js +0 -164
  418. package/dist/utils/taskActivity.test.d.ts +0 -1
  419. package/dist/utils/taskActivity.test.js +0 -218
  420. package/dist/utils/taskDraftDefaults.test.d.ts +0 -1
  421. package/dist/utils/taskDraftDefaults.test.js +0 -34
  422. package/dist/utils/taskDragZones.test.d.ts +0 -1
  423. package/dist/utils/taskDragZones.test.js +0 -14
  424. package/dist/utils/taskNormalization.test.d.ts +0 -1
  425. package/dist/utils/taskNormalization.test.js +0 -158
  426. package/dist/utils/taskSearch.test.d.ts +0 -1
  427. package/dist/utils/taskSearch.test.js +0 -69
  428. package/dist/utils/taskUtils.test.d.ts +0 -1
  429. package/dist/utils/taskUtils.test.js +0 -19
  430. package/dist/utils/taskforceApiClient.test.d.ts +0 -1
  431. package/dist/utils/taskforceApiClient.test.js +0 -44
  432. package/dist/utils/taxonomyFormDefaults.test.d.ts +0 -1
  433. package/dist/utils/taxonomyFormDefaults.test.js +0 -63
  434. package/dist/utils/uiNotice.test.d.ts +0 -1
  435. package/dist/utils/uiNotice.test.js +0 -18
  436. package/dist/utils/workspaceIdentity.test.d.ts +0 -1
  437. package/dist/utils/workspaceIdentity.test.js +0 -61
  438. package/dist/utils/workspaceLifecycle.test.d.ts +0 -1
  439. package/dist/utils/workspaceLifecycle.test.js +0 -64
  440. package/dist/utils/workspaceSyncPresentation.test.d.ts +0 -1
  441. package/dist/utils/workspaceSyncPresentation.test.js +0 -71
  442. package/scripts/bootstrap-playwright-auth.mjs +0 -155
  443. package/scripts/check-cloud-mcp.mjs +0 -83
  444. package/scripts/check-compat-boundaries.mjs +0 -41
  445. package/scripts/check-env.ts +0 -31
  446. package/scripts/check-localization-strings.mjs +0 -49
  447. package/scripts/check-runtime-boundaries.mjs +0 -44
  448. package/scripts/debug-global.mjs +0 -34
  449. package/scripts/debug-login.mjs +0 -32
  450. package/scripts/debug-server.mjs +0 -36
  451. package/scripts/deploy-prod.sh +0 -41
  452. package/scripts/deploy-staging.sh +0 -23
  453. package/scripts/export-sqlite-to-postgres.mjs +0 -123
  454. package/scripts/generate-env-example.ts +0 -8
  455. package/scripts/mcp-stdio-adapter.mjs +0 -160
  456. package/scripts/migrate-task-data-assets.mjs +0 -408
  457. package/scripts/playwright-auth.sh +0 -19
  458. package/scripts/qa-open-lib.mjs +0 -175
  459. package/scripts/qa-open-lib.mjs.d.ts +0 -49
  460. package/scripts/qa-open.mjs +0 -352
  461. package/scripts/release-version-lib.mjs +0 -88
  462. package/scripts/release-version.mjs +0 -72
  463. package/scripts/run-billing-performance-smoke.sh +0 -24
  464. package/scripts/run-dev-proxy.sh +0 -51
  465. package/scripts/run-e2e-realtime.sh +0 -30
  466. package/scripts/run-smoke.sh +0 -35
  467. package/scripts/run-soak.sh +0 -41
  468. package/scripts/test-global.js +0 -143
  469. package/scripts/update-homebrew-formula.sh +0 -108
  470. 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
- });