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