@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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { buildCopySyncDetailsText, buildReferenceMismatchSummaries, buildSyncDiagnosticsSummary, buildSyncEventRows, getActiveReferenceMismatchCount, resolveSyncStageLabel, resolveSyncStatusMeta, } from './syncStatusPresentation';
|
|
3
|
+
describe('syncStatusPresentation', () => {
|
|
4
|
+
it('resolves sync status badges for visible header states', () => {
|
|
5
|
+
expect(resolveSyncStatusMeta('healthy')).toMatchObject({
|
|
6
|
+
label: 'Healthy',
|
|
7
|
+
icon: 'cloud',
|
|
8
|
+
});
|
|
9
|
+
expect(resolveSyncStatusMeta('attention')).toMatchObject({
|
|
10
|
+
label: 'Needs Attention',
|
|
11
|
+
icon: 'cloud',
|
|
12
|
+
});
|
|
13
|
+
expect(resolveSyncStatusMeta('off')).toMatchObject({
|
|
14
|
+
label: 'Off',
|
|
15
|
+
icon: 'off',
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
it('describes startup and repair stages for the sync manager', () => {
|
|
19
|
+
expect(resolveSyncStageLabel('attach-cloud', false, true)).toBe('Initial cloud pull');
|
|
20
|
+
expect(resolveSyncStageLabel('error', true, true)).toBe('Repairing');
|
|
21
|
+
expect(resolveSyncStageLabel('error', false, true)).toBe('Recovering');
|
|
22
|
+
});
|
|
23
|
+
it('builds the diagnostics summary from sync snapshot counts', () => {
|
|
24
|
+
const summary = buildSyncDiagnosticsSummary({
|
|
25
|
+
aiProfileSnapshotCount: 2,
|
|
26
|
+
aiProfileSnapshotRawCount: 4,
|
|
27
|
+
lastPushedAiProfileCount: 3,
|
|
28
|
+
lastPushedAiProfileWatermarkCount: 5,
|
|
29
|
+
documentSnapshotCount: 1,
|
|
30
|
+
assetSnapshotCount: 2,
|
|
31
|
+
forceFullAiProfilePushQueued: true,
|
|
32
|
+
aiProfileSnapshotLastFetchAt: null,
|
|
33
|
+
aiProfileSnapshotLastFetchError: '',
|
|
34
|
+
aiProfileSnapshotLastSkipReason: null,
|
|
35
|
+
});
|
|
36
|
+
expect(summary).toContainEqual({
|
|
37
|
+
label: 'AI profile snapshot',
|
|
38
|
+
value: '2 local profiles',
|
|
39
|
+
});
|
|
40
|
+
expect(summary).toContainEqual({
|
|
41
|
+
label: 'Document snapshot',
|
|
42
|
+
value: '1 local doc',
|
|
43
|
+
});
|
|
44
|
+
expect(summary).toContainEqual({
|
|
45
|
+
label: 'Queued full AI sync',
|
|
46
|
+
value: 'Yes',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
it('keeps only active reference mismatches until the next successful sync event', () => {
|
|
50
|
+
const events = [
|
|
51
|
+
{
|
|
52
|
+
occurredAt: '2026-04-18T10:00:00.000Z',
|
|
53
|
+
status: 'error',
|
|
54
|
+
errorMessage: 'Detected task reference number mismatch during normal sync.',
|
|
55
|
+
details: {
|
|
56
|
+
path: 'docs/spec.md',
|
|
57
|
+
existingReferenceNumber: 14,
|
|
58
|
+
incomingReferenceNumber: 8,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
occurredAt: '2026-04-18T09:59:00.000Z',
|
|
63
|
+
status: 'success',
|
|
64
|
+
errorMessage: '',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
occurredAt: '2026-04-18T09:58:00.000Z',
|
|
68
|
+
status: 'error',
|
|
69
|
+
errorMessage: 'Detected task reference number mismatch during normal sync.',
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
expect(getActiveReferenceMismatchCount(events)).toBe(1);
|
|
73
|
+
expect(buildReferenceMismatchSummaries(events)).toEqual([
|
|
74
|
+
expect.objectContaining({
|
|
75
|
+
pathLabel: 'docs/spec.md',
|
|
76
|
+
refsLabel: 'Existing 14 vs incoming 8',
|
|
77
|
+
}),
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
it('builds readable sync event rows for loading, errors, and structured events', () => {
|
|
81
|
+
expect(buildSyncEventRows({
|
|
82
|
+
syncRecentEvents: [],
|
|
83
|
+
syncRecentEventsError: null,
|
|
84
|
+
syncRecentEventsLoading: true,
|
|
85
|
+
})).toEqual([{
|
|
86
|
+
key: 'loading',
|
|
87
|
+
text: 'Loading recent sync events...',
|
|
88
|
+
tone: 'muted',
|
|
89
|
+
}]);
|
|
90
|
+
expect(buildSyncEventRows({
|
|
91
|
+
syncRecentEvents: [],
|
|
92
|
+
syncRecentEventsError: 'Unable to load events.',
|
|
93
|
+
syncRecentEventsLoading: false,
|
|
94
|
+
})).toEqual([{
|
|
95
|
+
key: 'error',
|
|
96
|
+
text: 'Unable to load events.',
|
|
97
|
+
tone: 'error',
|
|
98
|
+
}]);
|
|
99
|
+
expect(buildSyncEventRows({
|
|
100
|
+
syncRecentEvents: [{
|
|
101
|
+
occurredAt: '2026-04-18T10:00:00.000Z',
|
|
102
|
+
status: 'error',
|
|
103
|
+
details: {
|
|
104
|
+
source: 'retry.blocked',
|
|
105
|
+
reason: 'lease-held',
|
|
106
|
+
phase: 'active',
|
|
107
|
+
},
|
|
108
|
+
}],
|
|
109
|
+
syncRecentEventsError: null,
|
|
110
|
+
syncRecentEventsLoading: false,
|
|
111
|
+
})[0]).toMatchObject({
|
|
112
|
+
tone: 'error',
|
|
113
|
+
text: '2026-04-18T10:00:00.000Z manual retry blocked: another window is already syncing during active sync',
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
it('assembles the sync manager report text from the current view model', () => {
|
|
117
|
+
const report = buildCopySyncDetailsText({
|
|
118
|
+
currentWorkspaceId: 'workspace-123',
|
|
119
|
+
syncStatusLabel: 'Needs Attention',
|
|
120
|
+
workspaceSyncSummary: 'Sync is paused until you sign in again.',
|
|
121
|
+
workspaceSyncRecommendedAction: 'Sign in, then press Sync to retry.',
|
|
122
|
+
formattedLastSyncTime: '4/17/2026, 10:32:47 PM',
|
|
123
|
+
formattedLastPullTime: '4/17/2026, 10:32:47 PM',
|
|
124
|
+
formattedLastPushTime: '4/17/2026, 10:31:45 PM',
|
|
125
|
+
workspaceSyncDiagnostics: {
|
|
126
|
+
lastErrorAt: '2026-04-18T01:33:05.980Z',
|
|
127
|
+
pushBlockedReason: 'attach-cloud',
|
|
128
|
+
pullBlockedReason: null,
|
|
129
|
+
retryBlockedReason: 'auth-required',
|
|
130
|
+
repairActive: false,
|
|
131
|
+
pushInFlight: false,
|
|
132
|
+
pullInFlight: true,
|
|
133
|
+
retryPending: false,
|
|
134
|
+
aiProfileSnapshotCount: 14,
|
|
135
|
+
aiProfileSnapshotRawCount: 14,
|
|
136
|
+
lastPushedAiProfileCount: 14,
|
|
137
|
+
lastPushedAiProfileWatermarkCount: 14,
|
|
138
|
+
aiProfileSnapshotLastFetchAt: '2026-04-18T11:15:52.000Z',
|
|
139
|
+
aiProfileSnapshotLastFetchError: null,
|
|
140
|
+
aiProfileSnapshotLastSkipReason: null,
|
|
141
|
+
documentSnapshotCount: 127,
|
|
142
|
+
assetSnapshotCount: 56,
|
|
143
|
+
forceFullAiProfilePushQueued: false,
|
|
144
|
+
},
|
|
145
|
+
workspaceSyncPendingChanges: 31,
|
|
146
|
+
syncLastError: 'Detected task reference number mismatch during normal sync.',
|
|
147
|
+
syncStageLabel: 'Active',
|
|
148
|
+
referenceMismatchCount: 3,
|
|
149
|
+
syncRecentEvents: [{
|
|
150
|
+
occurredAt: '2026-04-18T01:33:06.044Z',
|
|
151
|
+
eventType: 'apply',
|
|
152
|
+
status: 'success',
|
|
153
|
+
changeCount: 200,
|
|
154
|
+
requestMs: 167,
|
|
155
|
+
statusCode: 0,
|
|
156
|
+
}],
|
|
157
|
+
});
|
|
158
|
+
expect(report).toContain('Workspace ID: workspace-123');
|
|
159
|
+
expect(report).toContain('Push blocked reason: attach-cloud');
|
|
160
|
+
expect(report).toContain('Retry blocked reason: auth-required');
|
|
161
|
+
expect(report).toContain('Reference mismatches detected: 3');
|
|
162
|
+
expect(report).toContain('2026-04-18T01:33:06.044Z apply succeeded (200 changes) in 167ms [0]');
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -5,5 +5,7 @@ export declare function summarizeTaskChange(field: string, change: {
|
|
|
5
5
|
to?: unknown;
|
|
6
6
|
} | undefined, formatter?: TaskEventValueFormatter): string;
|
|
7
7
|
export declare function summarizeTaskEvent(event: TaskEvent, formatter?: TaskEventValueFormatter): string;
|
|
8
|
+
export declare function getTaskEventPrimaryChangeField(event: TaskEvent): string | null;
|
|
9
|
+
export declare function getTaskActivityItems(task: Pick<TaskItem, 'activity' | 'comments'>): TaskActivityItem[];
|
|
8
10
|
export declare function getLatestTaskActivity(task: Pick<TaskItem, 'activity' | 'comments'>): TaskActivityItem | null;
|
|
9
11
|
export declare function summarizeTaskActivity(item: TaskActivityItem | null, formatter?: TaskEventValueFormatter): string;
|
|
@@ -1,16 +1,31 @@
|
|
|
1
1
|
function getActivityFieldLabel(field) {
|
|
2
2
|
if (field === 'title')
|
|
3
|
-
return '
|
|
3
|
+
return 'Title';
|
|
4
4
|
if (field === 'description')
|
|
5
|
-
return '
|
|
5
|
+
return 'Description';
|
|
6
6
|
if (field === 'assignee')
|
|
7
|
-
return '
|
|
7
|
+
return 'Assigned to';
|
|
8
|
+
if (field === 'status')
|
|
9
|
+
return 'Status';
|
|
10
|
+
if (field === 'scheduledDate')
|
|
11
|
+
return 'Scheduled date';
|
|
12
|
+
if (field === 'dueDate')
|
|
13
|
+
return 'Due date';
|
|
8
14
|
if (field === 'workstreamId')
|
|
9
|
-
return '
|
|
15
|
+
return 'Workstream';
|
|
10
16
|
if (field === 'initiativeId')
|
|
11
|
-
return '
|
|
17
|
+
return 'Initiative';
|
|
12
18
|
return field;
|
|
13
19
|
}
|
|
20
|
+
function formatStatusValue(value) {
|
|
21
|
+
if (value === 'task')
|
|
22
|
+
return 'Task';
|
|
23
|
+
return value
|
|
24
|
+
.replace(/[-_]+/g, ' ')
|
|
25
|
+
.replace(/\s+/g, ' ')
|
|
26
|
+
.trim()
|
|
27
|
+
.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
28
|
+
}
|
|
14
29
|
function formatValue(value, field, formatter) {
|
|
15
30
|
const formatted = formatter?.(field, value);
|
|
16
31
|
if (typeof formatted === 'string')
|
|
@@ -23,6 +38,8 @@ function formatValue(value, field, formatter) {
|
|
|
23
38
|
}
|
|
24
39
|
if (typeof value === 'object')
|
|
25
40
|
return 'updated';
|
|
41
|
+
if (field === 'status')
|
|
42
|
+
return formatStatusValue(String(value));
|
|
26
43
|
return String(value);
|
|
27
44
|
}
|
|
28
45
|
function normalizeChecklistItems(value) {
|
|
@@ -75,10 +92,131 @@ function summarizeChecklistChange(change) {
|
|
|
75
92
|
}
|
|
76
93
|
return 'Checklist updated';
|
|
77
94
|
}
|
|
95
|
+
function stripQueryAndHash(value) {
|
|
96
|
+
return value.split(/[?#]/, 1)[0] || value;
|
|
97
|
+
}
|
|
98
|
+
function basenameFromPath(value) {
|
|
99
|
+
const normalized = stripQueryAndHash(value).replace(/\\/g, '/');
|
|
100
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
101
|
+
return segments[segments.length - 1] || normalized;
|
|
102
|
+
}
|
|
103
|
+
function isImageAttachmentCandidate(value) {
|
|
104
|
+
return /\.(png|jpe?g|webp|gif)$/i.test(stripQueryAndHash(value));
|
|
105
|
+
}
|
|
106
|
+
function isDocumentAttachmentCandidate(value) {
|
|
107
|
+
if (value.includes('/api/taskforce/documents/'))
|
|
108
|
+
return true;
|
|
109
|
+
return /\.(pdf|txt|md|markdown|csv|json|docx?|html?|js|jsx|ts|tsx|css|py|java|go|rs|sh)$/i.test(stripQueryAndHash(value));
|
|
110
|
+
}
|
|
111
|
+
function normalizeAttachmentList(value) {
|
|
112
|
+
if (!Array.isArray(value))
|
|
113
|
+
return [];
|
|
114
|
+
return value.flatMap((entry, index) => {
|
|
115
|
+
const attachment = (entry && typeof entry === 'object' ? entry : null);
|
|
116
|
+
const path = typeof entry === 'string'
|
|
117
|
+
? entry
|
|
118
|
+
: String(attachment?.path || '').trim();
|
|
119
|
+
const fsPath = String(attachment?.fsPath || '').trim();
|
|
120
|
+
const assetId = String(attachment?.assetId || '').trim();
|
|
121
|
+
const referenceLabel = String(attachment?.referenceLabel || '').trim();
|
|
122
|
+
const label = [
|
|
123
|
+
attachment?.displayName,
|
|
124
|
+
attachment?.originalFilename,
|
|
125
|
+
attachment?.caption,
|
|
126
|
+
referenceLabel,
|
|
127
|
+
fsPath ? basenameFromPath(fsPath) : '',
|
|
128
|
+
path ? basenameFromPath(path) : '',
|
|
129
|
+
]
|
|
130
|
+
.map((candidate) => String(candidate || '').trim())
|
|
131
|
+
.find(Boolean) || `Attachment ${index + 1}`;
|
|
132
|
+
const key = assetId || fsPath || path || `${label}:${index}`;
|
|
133
|
+
const kindCandidates = [
|
|
134
|
+
path,
|
|
135
|
+
fsPath,
|
|
136
|
+
String(attachment?.originalFilename || '').trim(),
|
|
137
|
+
String(attachment?.displayName || '').trim(),
|
|
138
|
+
referenceLabel,
|
|
139
|
+
].filter(Boolean);
|
|
140
|
+
const kind = kindCandidates.some((candidate) => isImageAttachmentCandidate(candidate))
|
|
141
|
+
? 'image'
|
|
142
|
+
: (referenceLabel.startsWith('D-') || kindCandidates.some((candidate) => isDocumentAttachmentCandidate(candidate)))
|
|
143
|
+
? 'document'
|
|
144
|
+
: 'attachment';
|
|
145
|
+
return [{ key, label, kind }];
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
function pluralizeAttachmentKind(kind, count) {
|
|
149
|
+
if (count === 1)
|
|
150
|
+
return kind;
|
|
151
|
+
if (kind === 'image')
|
|
152
|
+
return 'images';
|
|
153
|
+
if (kind === 'document')
|
|
154
|
+
return 'documents';
|
|
155
|
+
return 'attachments';
|
|
156
|
+
}
|
|
157
|
+
function describeAttachmentCollection(entries) {
|
|
158
|
+
if (entries.length === 0)
|
|
159
|
+
return 'attachments';
|
|
160
|
+
const counts = entries.reduce((acc, entry) => {
|
|
161
|
+
acc[entry.kind] += 1;
|
|
162
|
+
return acc;
|
|
163
|
+
}, { image: 0, document: 0, attachment: 0 });
|
|
164
|
+
const nonZeroParts = Object.entries(counts)
|
|
165
|
+
.filter(([, count]) => count > 0)
|
|
166
|
+
.map(([kind, count]) => count === 1 ? kind : `${count} ${pluralizeAttachmentKind(kind, count)}`);
|
|
167
|
+
if (nonZeroParts.length === 1)
|
|
168
|
+
return nonZeroParts[0];
|
|
169
|
+
if (entries.length <= 3 && nonZeroParts.length <= 2)
|
|
170
|
+
return nonZeroParts.join(' and ');
|
|
171
|
+
return `${entries.length} attachments`;
|
|
172
|
+
}
|
|
173
|
+
function capitalizeLeading(value) {
|
|
174
|
+
if (!value)
|
|
175
|
+
return value;
|
|
176
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
177
|
+
}
|
|
178
|
+
function summarizeAttachmentChange(change) {
|
|
179
|
+
const before = normalizeAttachmentList(change?.from);
|
|
180
|
+
const after = normalizeAttachmentList(change?.to);
|
|
181
|
+
const beforeByKey = new Map(before.map((entry) => [entry.key, entry]));
|
|
182
|
+
const afterByKey = new Map(after.map((entry) => [entry.key, entry]));
|
|
183
|
+
const added = after.filter((entry) => !beforeByKey.has(entry.key));
|
|
184
|
+
const removed = before.filter((entry) => !afterByKey.has(entry.key));
|
|
185
|
+
if (added.length === 1 && removed.length === 0) {
|
|
186
|
+
return `${capitalizeLeading(added[0].kind)} attached: ${added[0].label}`;
|
|
187
|
+
}
|
|
188
|
+
if (removed.length === 1 && added.length === 0) {
|
|
189
|
+
return `${capitalizeLeading(removed[0].kind)} removed: ${removed[0].label}`;
|
|
190
|
+
}
|
|
191
|
+
if (added.length > 0 && removed.length === 0) {
|
|
192
|
+
return `${capitalizeLeading(describeAttachmentCollection(added))} attached`;
|
|
193
|
+
}
|
|
194
|
+
if (removed.length > 0 && added.length === 0) {
|
|
195
|
+
return `${capitalizeLeading(describeAttachmentCollection(removed))} removed`;
|
|
196
|
+
}
|
|
197
|
+
if (added.length === 1 && removed.length === 1) {
|
|
198
|
+
return `${capitalizeLeading(added[0].kind)} replaced: ${removed[0].label} -> ${added[0].label}`;
|
|
199
|
+
}
|
|
200
|
+
if (added.length > 0 || removed.length > 0) {
|
|
201
|
+
return `${capitalizeLeading(describeAttachmentCollection(added))} attached, ${describeAttachmentCollection(removed)} removed`;
|
|
202
|
+
}
|
|
203
|
+
const relabeled = after.filter((entry) => {
|
|
204
|
+
const previous = beforeByKey.get(entry.key);
|
|
205
|
+
return previous && previous.label !== entry.label;
|
|
206
|
+
});
|
|
207
|
+
if (relabeled.length === 1 && before.length === 1 && after.length === 1) {
|
|
208
|
+
const previous = beforeByKey.get(relabeled[0].key);
|
|
209
|
+
return `${capitalizeLeading(relabeled[0].kind)} renamed: ${previous?.label || 'Attachment'} -> ${relabeled[0].label}`;
|
|
210
|
+
}
|
|
211
|
+
return 'Attachment details updated';
|
|
212
|
+
}
|
|
78
213
|
export function summarizeTaskChange(field, change, formatter) {
|
|
79
214
|
if (field === 'checklistItems') {
|
|
80
215
|
return summarizeChecklistChange(change);
|
|
81
216
|
}
|
|
217
|
+
if (field === 'attachments') {
|
|
218
|
+
return summarizeAttachmentChange(change);
|
|
219
|
+
}
|
|
82
220
|
const label = getActivityFieldLabel(field);
|
|
83
221
|
if (field === 'title') {
|
|
84
222
|
return `${label}: updated`;
|
|
@@ -108,22 +246,28 @@ export function summarizeTaskEvent(event, formatter) {
|
|
|
108
246
|
if (event.action === 'task-unarchived')
|
|
109
247
|
return 'Task restored';
|
|
110
248
|
if (event.action === 'task-status-changed' && changes.status) {
|
|
111
|
-
return `Status: ${formatValue(changes.status.from, 'status', formatter)} -> ${formatValue(changes.status.to, 'status', formatter)}`;
|
|
249
|
+
return `Status changed: ${formatValue(changes.status.from, 'status', formatter)} -> ${formatValue(changes.status.to, 'status', formatter)}`;
|
|
112
250
|
}
|
|
113
251
|
if (event.action === 'task-schedule-changed') {
|
|
114
252
|
if (changes.scheduledDate) {
|
|
115
|
-
return `Scheduled: ${formatValue(changes.scheduledDate.from, 'scheduledDate', formatter)} -> ${formatValue(changes.scheduledDate.to, 'scheduledDate', formatter)}`;
|
|
253
|
+
return `Scheduled date changed: ${formatValue(changes.scheduledDate.from, 'scheduledDate', formatter)} -> ${formatValue(changes.scheduledDate.to, 'scheduledDate', formatter)}`;
|
|
116
254
|
}
|
|
117
255
|
if (changes.dueDate) {
|
|
118
|
-
return `Due: ${formatValue(changes.dueDate.from, 'dueDate', formatter)} -> ${formatValue(changes.dueDate.to, 'dueDate', formatter)}`;
|
|
256
|
+
return `Due date changed: ${formatValue(changes.dueDate.from, 'dueDate', formatter)} -> ${formatValue(changes.dueDate.to, 'dueDate', formatter)}`;
|
|
119
257
|
}
|
|
120
258
|
return 'Schedule updated';
|
|
121
259
|
}
|
|
122
260
|
if (event.action === 'task-relationship-changed' && changes.workstreamId) {
|
|
123
|
-
return `Workstream: ${formatValue(changes.workstreamId.from, 'workstreamId', formatter)} -> ${formatValue(changes.workstreamId.to, 'workstreamId', formatter)}`;
|
|
261
|
+
return `Workstream changed: ${formatValue(changes.workstreamId.from, 'workstreamId', formatter)} -> ${formatValue(changes.workstreamId.to, 'workstreamId', formatter)}`;
|
|
262
|
+
}
|
|
263
|
+
if (event.action === 'task-relationship-changed' && changes.initiativeId) {
|
|
264
|
+
return `Initiative changed: ${formatValue(changes.initiativeId.from, 'initiativeId', formatter)} -> ${formatValue(changes.initiativeId.to, 'initiativeId', formatter)}`;
|
|
265
|
+
}
|
|
266
|
+
if (event.action === 'task-attachments-changed') {
|
|
267
|
+
return changes.attachments
|
|
268
|
+
? summarizeTaskChange('attachments', changes.attachments, formatter)
|
|
269
|
+
: 'Attachments updated';
|
|
124
270
|
}
|
|
125
|
-
if (event.action === 'task-attachments-changed')
|
|
126
|
-
return 'Attachments updated';
|
|
127
271
|
if (fields.length === 1) {
|
|
128
272
|
const field = fields[0];
|
|
129
273
|
const change = changes[field];
|
|
@@ -133,25 +277,87 @@ export function summarizeTaskEvent(event, formatter) {
|
|
|
133
277
|
return `${fields.length} fields updated`;
|
|
134
278
|
return 'Task updated';
|
|
135
279
|
}
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
280
|
+
function activityTimestamp(item) {
|
|
281
|
+
const parsed = Date.parse(String(item.timestamp || ''));
|
|
282
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
283
|
+
}
|
|
284
|
+
export function getTaskEventPrimaryChangeField(event) {
|
|
285
|
+
const changes = event.details?.changes || {};
|
|
286
|
+
const fields = Object.keys(changes);
|
|
287
|
+
if (event.action === 'task-status-changed' && changes.status)
|
|
288
|
+
return 'status';
|
|
289
|
+
if (event.action === 'task-schedule-changed') {
|
|
290
|
+
if (changes.scheduledDate)
|
|
291
|
+
return 'scheduledDate';
|
|
292
|
+
if (changes.dueDate)
|
|
293
|
+
return 'dueDate';
|
|
294
|
+
}
|
|
295
|
+
if (event.action === 'task-relationship-changed') {
|
|
296
|
+
if (changes.workstreamId)
|
|
297
|
+
return 'workstreamId';
|
|
298
|
+
if (changes.initiativeId)
|
|
299
|
+
return 'initiativeId';
|
|
300
|
+
}
|
|
301
|
+
if (event.action === 'task-attachments-changed' && changes.attachments)
|
|
302
|
+
return 'attachments';
|
|
303
|
+
return fields.length === 1 ? fields[0] : null;
|
|
304
|
+
}
|
|
305
|
+
export function getTaskActivityItems(task) {
|
|
306
|
+
const deduped = new Map();
|
|
307
|
+
const commentIds = new Set();
|
|
308
|
+
const addItem = (item) => {
|
|
309
|
+
if (item.type === 'event' && item.event.action === 'task-comment-added')
|
|
310
|
+
return;
|
|
311
|
+
const key = item.type === 'comment'
|
|
312
|
+
? `comment:${String(item.comment.id || item.id).replace(/^comment:/, '')}`
|
|
313
|
+
: `event:${String(item.event.id || item.id).replace(/^event:/, '')}`;
|
|
314
|
+
deduped.set(key, { ...item, id: key });
|
|
315
|
+
if (item.type === 'comment') {
|
|
316
|
+
commentIds.add(String(item.comment.id || item.id).replace(/^comment:/, ''));
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
if (Array.isArray(task.activity)) {
|
|
320
|
+
task.activity.forEach(addItem);
|
|
321
|
+
}
|
|
322
|
+
if (Array.isArray(task.comments)) {
|
|
323
|
+
task.comments.forEach((comment) => {
|
|
324
|
+
const commentId = String(comment.id || '').trim();
|
|
325
|
+
if (!commentId || commentIds.has(commentId))
|
|
326
|
+
return;
|
|
327
|
+
addItem({
|
|
328
|
+
id: `comment:${commentId}`,
|
|
142
329
|
type: 'comment',
|
|
143
330
|
timestamp: comment.timestamp,
|
|
144
331
|
comment
|
|
145
|
-
})
|
|
146
|
-
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
return [...deduped.values()].sort((left, right) => {
|
|
336
|
+
const leftTs = activityTimestamp(left);
|
|
337
|
+
const rightTs = activityTimestamp(right);
|
|
338
|
+
if (leftTs !== null && rightTs !== null && leftTs !== rightTs)
|
|
339
|
+
return leftTs - rightTs;
|
|
340
|
+
if (leftTs !== null && rightTs === null)
|
|
341
|
+
return -1;
|
|
342
|
+
if (leftTs === null && rightTs !== null)
|
|
343
|
+
return 1;
|
|
344
|
+
return String(left.id || '').localeCompare(String(right.id || ''));
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
export function getLatestTaskActivity(task) {
|
|
348
|
+
const items = getTaskActivityItems(task);
|
|
147
349
|
if (items.length === 0)
|
|
148
350
|
return null;
|
|
149
351
|
return [...items].sort((left, right) => {
|
|
150
|
-
const leftTs =
|
|
151
|
-
const rightTs =
|
|
152
|
-
if (
|
|
352
|
+
const leftTs = activityTimestamp(left);
|
|
353
|
+
const rightTs = activityTimestamp(right);
|
|
354
|
+
if (leftTs !== null && rightTs !== null && leftTs !== rightTs) {
|
|
153
355
|
return rightTs - leftTs;
|
|
154
356
|
}
|
|
357
|
+
if (leftTs !== null && rightTs === null)
|
|
358
|
+
return -1;
|
|
359
|
+
if (leftTs === null && rightTs !== null)
|
|
360
|
+
return 1;
|
|
155
361
|
return String(right.id || '').localeCompare(String(left.id || ''));
|
|
156
362
|
})[0] || null;
|
|
157
363
|
}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { summarizeTaskChange, summarizeTaskEvent } from './taskActivity';
|
|
2
|
+
import { getTaskActivityItems, summarizeTaskChange, summarizeTaskEvent } from './taskActivity';
|
|
3
3
|
describe('summarizeTaskChange', () => {
|
|
4
4
|
it('summarizes title edits without dumping before and after text', () => {
|
|
5
5
|
expect(summarizeTaskChange('title', {
|
|
6
6
|
from: 'Old title',
|
|
7
7
|
to: 'New title',
|
|
8
|
-
})).toBe('
|
|
8
|
+
})).toBe('Title: updated');
|
|
9
9
|
});
|
|
10
|
-
it('summarizes assignee changes with
|
|
10
|
+
it('summarizes assignee changes with sentence-case labels and formatted values', () => {
|
|
11
11
|
expect(summarizeTaskChange('assignee', { from: 'unassigned', to: 'member-1' }, (_field, value) => {
|
|
12
12
|
if (value === 'unassigned')
|
|
13
13
|
return 'Unassigned';
|
|
14
14
|
if (value === 'member-1')
|
|
15
15
|
return 'Emma Peel';
|
|
16
16
|
return undefined;
|
|
17
|
-
})).toBe('
|
|
17
|
+
})).toBe('Assigned to: Unassigned -> Emma Peel');
|
|
18
18
|
});
|
|
19
19
|
it('summarizes description additions without rendering the body text', () => {
|
|
20
20
|
expect(summarizeTaskChange('description', {
|
|
21
21
|
from: '',
|
|
22
22
|
to: 'Line one\n\nLine two',
|
|
23
|
-
})).toBe('
|
|
23
|
+
})).toBe('Description: added');
|
|
24
24
|
});
|
|
25
25
|
it('summarizes description clears without rendering the old body text', () => {
|
|
26
26
|
expect(summarizeTaskChange('description', {
|
|
27
27
|
from: 'Old body',
|
|
28
28
|
to: '',
|
|
29
|
-
})).toBe('
|
|
29
|
+
})).toBe('Description: cleared');
|
|
30
30
|
});
|
|
31
31
|
it('summarizes checklist completion clearly', () => {
|
|
32
32
|
expect(summarizeTaskChange('checklistItems', {
|
|
@@ -59,7 +59,49 @@ describe('summarizeTaskChange', () => {
|
|
|
59
59
|
})).toBe('Checklist reordered');
|
|
60
60
|
});
|
|
61
61
|
it('uses readable labels for workstream relationship changes', () => {
|
|
62
|
-
expect(summarizeTaskChange('workstreamId', { from: null, to: 'workstream-1' }, (_field, value) => value === 'workstream-1' ? 'WS-12' : undefined)).toBe('
|
|
62
|
+
expect(summarizeTaskChange('workstreamId', { from: null, to: 'workstream-1' }, (_field, value) => value === 'workstream-1' ? 'WS-12' : undefined)).toBe('Workstream: none -> WS-12');
|
|
63
|
+
});
|
|
64
|
+
it('summarizes image attachment additions clearly', () => {
|
|
65
|
+
expect(summarizeTaskChange('attachments', {
|
|
66
|
+
from: [],
|
|
67
|
+
to: [{
|
|
68
|
+
path: '/api/taskforce/context/workspaces/default/assets/images/homepage-proof.png',
|
|
69
|
+
caption: 'Homepage proof',
|
|
70
|
+
}],
|
|
71
|
+
})).toBe('Image attached: Homepage proof');
|
|
72
|
+
});
|
|
73
|
+
it('summarizes document attachment removals clearly', () => {
|
|
74
|
+
expect(summarizeTaskChange('attachments', {
|
|
75
|
+
from: [{
|
|
76
|
+
path: '/api/taskforce/documents/asset-doc-1/content',
|
|
77
|
+
originalFilename: 'spec.md',
|
|
78
|
+
assetId: 'asset-doc-1',
|
|
79
|
+
}],
|
|
80
|
+
to: [],
|
|
81
|
+
})).toBe('Document removed: spec.md');
|
|
82
|
+
});
|
|
83
|
+
it('summarizes multiple document attachments concisely', () => {
|
|
84
|
+
expect(summarizeTaskChange('attachments', {
|
|
85
|
+
from: [],
|
|
86
|
+
to: [
|
|
87
|
+
{ path: '/api/taskforce/documents/asset-doc-1/content', originalFilename: 'spec.md', assetId: 'asset-doc-1' },
|
|
88
|
+
{ path: '/api/taskforce/documents/asset-doc-2/content', originalFilename: 'notes.pdf', assetId: 'asset-doc-2' },
|
|
89
|
+
],
|
|
90
|
+
})).toBe('2 documents attached');
|
|
91
|
+
});
|
|
92
|
+
it('summarizes attachment renames when the same asset remains attached', () => {
|
|
93
|
+
expect(summarizeTaskChange('attachments', {
|
|
94
|
+
from: [{
|
|
95
|
+
path: '/api/taskforce/documents/asset-doc-1/content',
|
|
96
|
+
assetId: 'asset-doc-1',
|
|
97
|
+
caption: 'Draft spec',
|
|
98
|
+
}],
|
|
99
|
+
to: [{
|
|
100
|
+
path: '/api/taskforce/documents/asset-doc-1/content',
|
|
101
|
+
assetId: 'asset-doc-1',
|
|
102
|
+
caption: 'Final spec',
|
|
103
|
+
}],
|
|
104
|
+
})).toBe('Document renamed: Draft spec -> Final spec');
|
|
63
105
|
});
|
|
64
106
|
});
|
|
65
107
|
describe('summarizeTaskEvent', () => {
|
|
@@ -80,6 +122,75 @@ describe('summarizeTaskEvent', () => {
|
|
|
80
122
|
},
|
|
81
123
|
},
|
|
82
124
|
},
|
|
83
|
-
}, (_field, value) => value === 'workstream-1' ? 'WS-12' : undefined)).toBe('Workstream: none -> WS-12');
|
|
125
|
+
}, (_field, value) => value === 'workstream-1' ? 'WS-12' : undefined)).toBe('Workstream changed: none -> WS-12');
|
|
126
|
+
});
|
|
127
|
+
it('uses attachment-specific summaries for attachment activity events', () => {
|
|
128
|
+
expect(summarizeTaskEvent({
|
|
129
|
+
id: 'event-2',
|
|
130
|
+
taskId: 'task-1',
|
|
131
|
+
workspaceId: 'default',
|
|
132
|
+
action: 'task-attachments-changed',
|
|
133
|
+
actor: 'user',
|
|
134
|
+
actorType: 'human',
|
|
135
|
+
createdAt: '2026-04-12T21:42:00.000Z',
|
|
136
|
+
details: {
|
|
137
|
+
changes: {
|
|
138
|
+
attachments: {
|
|
139
|
+
from: [{
|
|
140
|
+
path: '/api/taskforce/context/workspaces/default/assets/images/old-proof.png',
|
|
141
|
+
caption: 'Old proof',
|
|
142
|
+
}],
|
|
143
|
+
to: [{
|
|
144
|
+
path: '/api/taskforce/context/workspaces/default/assets/images/new-proof.png',
|
|
145
|
+
caption: 'New proof',
|
|
146
|
+
}],
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
})).toBe('Image replaced: Old proof -> New proof');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
describe('getTaskActivityItems', () => {
|
|
154
|
+
it('combines comments and audit events while hiding duplicate comment-added events', () => {
|
|
155
|
+
const items = getTaskActivityItems({
|
|
156
|
+
comments: [{
|
|
157
|
+
id: 'comment-1',
|
|
158
|
+
author: 'user',
|
|
159
|
+
text: 'Captured comment',
|
|
160
|
+
timestamp: '2026-03-22T10:00:00.000Z',
|
|
161
|
+
}],
|
|
162
|
+
activity: [
|
|
163
|
+
{
|
|
164
|
+
id: 'event:event-1',
|
|
165
|
+
type: 'event',
|
|
166
|
+
timestamp: '2026-03-22T09:00:00.000Z',
|
|
167
|
+
event: {
|
|
168
|
+
id: 'event-1',
|
|
169
|
+
taskId: 'task-1',
|
|
170
|
+
workspaceId: 'default',
|
|
171
|
+
action: 'task-status-changed',
|
|
172
|
+
actor: 'user',
|
|
173
|
+
actorType: 'human',
|
|
174
|
+
createdAt: '2026-03-22T09:00:00.000Z',
|
|
175
|
+
details: { changes: { status: { from: 'task', to: 'in-progress' } } },
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: 'event:comment-event-1',
|
|
180
|
+
type: 'event',
|
|
181
|
+
timestamp: '2026-03-22T10:00:00.000Z',
|
|
182
|
+
event: {
|
|
183
|
+
id: 'comment-event-1',
|
|
184
|
+
taskId: 'task-1',
|
|
185
|
+
workspaceId: 'default',
|
|
186
|
+
action: 'task-comment-added',
|
|
187
|
+
actor: 'user',
|
|
188
|
+
actorType: 'human',
|
|
189
|
+
createdAt: '2026-03-22T10:00:00.000Z',
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
expect(items.map((item) => item.id)).toEqual(['event:event-1', 'comment:comment-1']);
|
|
84
195
|
});
|
|
85
196
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type HeaderWorkspaceSyncStatus = 'off' | 'syncing' | 'attention' | 'healthy';
|
|
2
|
+
type WorkspaceSyncBlockedReason = 'cloud-auth-unconfigured' | 'runtime-not-local' | 'sync-disabled' | 'invalid-workspace' | 'repair-active' | 'attach-cloud' | 'provision-local' | 'auth-required' | 'push-in-flight' | 'pull-in-flight' | 'retry-pending' | 'lease-held';
|
|
2
3
|
interface ResolveHeaderWorkspaceSyncPresentationInput {
|
|
3
4
|
runtimeMode: 'local' | 'cloud';
|
|
4
5
|
isAuthenticated: boolean;
|
|
@@ -7,6 +8,9 @@ interface ResolveHeaderWorkspaceSyncPresentationInput {
|
|
|
7
8
|
workspaceSyncSummary: string;
|
|
8
9
|
workspaceSyncRecommendedAction: string;
|
|
9
10
|
workspaceSyncError: string;
|
|
11
|
+
pushBlockedReason?: WorkspaceSyncBlockedReason | null;
|
|
12
|
+
pullBlockedReason?: WorkspaceSyncBlockedReason | null;
|
|
13
|
+
retryBlockedReason?: WorkspaceSyncBlockedReason | null;
|
|
10
14
|
}
|
|
11
15
|
export interface HeaderWorkspaceSyncPresentation {
|
|
12
16
|
actionable: boolean;
|