@taskforcehq/taskforce 0.3.305 → 0.3.306
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Taskforce.module.css +295 -89
- package/dist/TaskforceCore.js +215 -43
- package/dist/TaskforceCore.test.js +433 -28
- 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 +46 -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/DocumentViewer.d.ts +5 -1
- package/dist/components/features/DocumentViewer.js +60 -3
- package/dist/components/features/DocumentWorkspace.js +1 -1
- package/dist/components/features/DocumentWorkspace.test.js +33 -0
- package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
- 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.js +85 -90
- package/dist/components/task/TaskForm.test.js +110 -8
- package/dist/components/task/TaskKanban.d.ts +2 -1
- package/dist/components/task/TaskKanban.js +94 -13
- 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 -14
- package/dist/components/views/PlanComparisonPage.test.js +238 -0
- package/dist/components/views/PlansPage.js +127 -36
- package/dist/components/views/PlansPage.test.js +294 -12
- package/dist/components/views/StandaloneLayout.js +283 -533
- 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 +26 -1
- package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
- 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/core/AiProfileService.d.ts +39 -2
- package/dist/core/AiProfileService.js +639 -73
- package/dist/core/AiProfileService.test.js +19 -1
- package/dist/core/AiProfiles.test.js +566 -7
- 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.js +3 -3
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +115 -6
- package/dist/core/PlanEntitlementService.d.ts +23 -2
- package/dist/core/PlanEntitlementService.js +343 -57
- package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
- package/dist/core/PlanFeatureCatalog.test.js +84 -0
- package/dist/core/PlanVersionPolicy.test.js +35 -2
- package/dist/core/PlanningEntities.test.js +52 -0
- package/dist/core/SignupMonetizationSettings.test.js +199 -27
- package/dist/core/SyncReconciliationService.js +2 -65
- package/dist/core/SystemAdmin.test.js +42 -42
- package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
- package/dist/core/Taskforce.d.ts +225 -14
- package/dist/core/Taskforce.js +765 -192
- 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 +117 -16
- package/dist/core/shared.d.ts +1 -1
- package/dist/core/shared.js +5 -1
- package/dist/core/types.d.ts +68 -2
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -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 +15 -3
- package/dist/hooks/sync/transfers.js +179 -62
- 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 +9 -2
- package/dist/hooks/useSyncOrchestrator.js +359 -178
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
- package/dist/hooks/useTaskData.js +8 -3
- package/dist/hooks/useTaskData.test.js +45 -0
- package/dist/hooks/useTaskforce.d.ts +60 -2
- package/dist/hooks/useTaskforce.js +549 -240
- 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 +2380 -212
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +6 -1
- 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/documentAssetRegistrar.js +3 -3
- package/dist/mcp/runtime.d.ts +2 -0
- package/dist/mcp/runtime.js +131 -64
- package/dist/mcp/runtime.test.js +291 -12
- 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 +3 -0
- package/dist/server/routes/admin.js +272 -19
- package/dist/server/routes/auth.d.ts +15 -2
- package/dist/server/routes/auth.js +994 -57
- package/dist/server/routes/authSupport.d.ts +1 -1
- package/dist/server/routes/authSupport.js +1 -1
- package/dist/server/routes/billing.js +361 -35
- package/dist/server/routes/billing.test.js +592 -12
- package/dist/server/routes/documents.d.ts +1 -0
- package/dist/server/routes/documents.js +172 -70
- package/dist/server/routes/shared.d.ts +28 -2
- package/dist/server/routes/shared.js +69 -4
- package/dist/server/routes/sync.integration.test.js +437 -39
- package/dist/server/routes/sync.js +61 -605
- 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.js +43 -1
- package/dist/server/routes/workspaces.js +36 -5
- package/dist/server/routes.js +60 -3
- package/dist/server/routes.test.js +1824 -171
- 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 +17 -70
- package/dist/sync/syncApplyHandlers.d.ts +7 -0
- package/dist/sync/syncApplyHandlers.js +28 -13
- package/dist/sync/syncApplyHandlers.test.js +70 -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 +5 -0
- package/dist/sync/syncService.js +52 -0
- package/dist/sync/syncService.test.js +57 -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 +45 -0
- package/dist/sync/workspaceSyncModel.js +183 -28
- package/dist/sync/workspaceSyncModel.test.js +291 -12
- package/dist/sync/workspaceSyncState.d.ts +17 -0
- package/dist/sync/workspaceSyncState.js +2 -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-BG6P_GVW.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-Dhm7M8e7.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-ln0aF7xc.js → TaskSettings-DZX4jk7e.js} +1 -1
- package/dist/ui/assets/{WorkflowsModule-BcS4afRn.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-BSUaRwF8.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.js +3 -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 +3 -2
- package/scripts/export-sqlite-to-postgres.mjs +7 -9
- 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-N2MnQBZk.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
- package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
- package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
- package/dist/ui/assets/index-C2-OXZV7.css +0 -1
- package/dist/ui/assets/index-DcSI5F86.js +0 -6
- package/scripts/migrate-planning-model.ts +0 -233
|
@@ -15,7 +15,7 @@ import { IconMap, resolveColor } from "../../utils/constants";
|
|
|
15
15
|
import { formatDate } from "../../utils/formatting";
|
|
16
16
|
import { t } from "../../localization";
|
|
17
17
|
import { TaskContextUpload } from "./TaskContextUpload";
|
|
18
|
-
import { summarizeTaskChange, summarizeTaskEvent, } from "../../utils/taskActivity";
|
|
18
|
+
import { getTaskActivityItems, getTaskEventPrimaryChangeField, summarizeTaskChange, summarizeTaskEvent, } from "../../utils/taskActivity";
|
|
19
19
|
import { ensureAssigneeOptionPresent, getAssigneeKind, getAssigneeLabel, } from "../../utils/assignees";
|
|
20
20
|
import { getTaskReferenceLabel } from "../../utils/taskReferences";
|
|
21
21
|
import { getVisibleTaskFormTaxonomies } from "../../utils/taxonomyFormDefaults.js";
|
|
@@ -235,18 +235,7 @@ export function TaskForm(props) {
|
|
|
235
235
|
initiative.id,
|
|
236
236
|
getInitiativeReferenceLabel(initiative) || initiative.id,
|
|
237
237
|
])), [initiatives]);
|
|
238
|
-
const taskActivityItems = React.useMemo(() => {
|
|
239
|
-
if (Array.isArray(currentTask?.activity) &&
|
|
240
|
-
currentTask.activity.length > 0) {
|
|
241
|
-
return currentTask.activity;
|
|
242
|
-
}
|
|
243
|
-
return comments.map((comment) => ({
|
|
244
|
-
id: `comment:${comment.id}`,
|
|
245
|
-
type: "comment",
|
|
246
|
-
timestamp: comment.timestamp,
|
|
247
|
-
comment,
|
|
248
|
-
}));
|
|
249
|
-
}, [comments, currentTask?.activity]);
|
|
238
|
+
const taskActivityItems = React.useMemo(() => getTaskActivityItems({ activity: currentTask?.activity, comments }), [comments, currentTask?.activity]);
|
|
250
239
|
const formatActivityFieldValue = (field, value) => {
|
|
251
240
|
const normalizedValue = String(value || "").trim();
|
|
252
241
|
if (!normalizedValue)
|
|
@@ -284,8 +273,53 @@ export function TaskForm(props) {
|
|
|
284
273
|
formatAiAuthorLabel,
|
|
285
274
|
resolvedAssigneeOptions,
|
|
286
275
|
]);
|
|
276
|
+
const resolveActivityActorPresentation = React.useCallback(({ actorId, actorType, actorProfile, fallbackKind, }) => {
|
|
277
|
+
const actorRef = String(actorId || "").trim();
|
|
278
|
+
const normalizedActor = actorRef.toLowerCase();
|
|
279
|
+
const actorFallback = actorFallbackById.get(actorRef);
|
|
280
|
+
const actorOption = actorOptionByValue.get(actorRef) ||
|
|
281
|
+
actorOptionByValue.get(normalizedActor);
|
|
282
|
+
const resolvedActor = actorProfile || actorFallback || actorOption || null;
|
|
283
|
+
const resolvedKind = actorType === "system"
|
|
284
|
+
? "system"
|
|
285
|
+
: actorType === "ai" || resolvedActor?.kind === "ai"
|
|
286
|
+
? "agent"
|
|
287
|
+
: actorType === "human" || resolvedActor?.kind === "human"
|
|
288
|
+
? "member"
|
|
289
|
+
: actorOption?.kind === "agent"
|
|
290
|
+
? "agent"
|
|
291
|
+
: actorOption?.kind === "member"
|
|
292
|
+
? "member"
|
|
293
|
+
: fallbackKind ||
|
|
294
|
+
(normalizedActor === "" ||
|
|
295
|
+
normalizedActor === "user" ||
|
|
296
|
+
normalizedActor === "human"
|
|
297
|
+
? "member"
|
|
298
|
+
: "agent");
|
|
299
|
+
const label = resolvedKind === "system"
|
|
300
|
+
? "System"
|
|
301
|
+
: resolveActorLabel(actorRef, resolvedKind === "agent" ? "ai" : "human", resolvedActor?.label || null);
|
|
302
|
+
const color = resolvedActor?.color;
|
|
303
|
+
const iconName = String(resolvedActor?.icon ||
|
|
304
|
+
(resolvedKind === "agent"
|
|
305
|
+
? "Bot"
|
|
306
|
+
: resolvedKind === "member"
|
|
307
|
+
? "User"
|
|
308
|
+
: "ClipboardList"));
|
|
309
|
+
const ActorIcon = IconMap[iconName] ||
|
|
310
|
+
(resolvedKind === "agent"
|
|
311
|
+
? Bot
|
|
312
|
+
: resolvedKind === "member"
|
|
313
|
+
? User
|
|
314
|
+
: ClipboardList);
|
|
315
|
+
return { kind: resolvedKind, label, color, ActorIcon };
|
|
316
|
+
}, [
|
|
317
|
+
actorFallbackById,
|
|
318
|
+
actorOptionByValue,
|
|
319
|
+
resolveActorLabel,
|
|
320
|
+
]);
|
|
287
321
|
const latestActivityActorLabel = React.useMemo(() => {
|
|
288
|
-
const latestItem = taskActivityItems[
|
|
322
|
+
const latestItem = taskActivityItems[taskActivityItems.length - 1];
|
|
289
323
|
if (!latestItem)
|
|
290
324
|
return null;
|
|
291
325
|
if (latestItem.type === "comment") {
|
|
@@ -350,95 +384,56 @@ export function TaskForm(props) {
|
|
|
350
384
|
const activitySidebar = editingTaskId ? (_jsxs("aside", { className: formStyles.activitySidebar, "aria-label": t("taskForm.commentsSectionTitle"), children: [_jsxs("div", { className: formStyles.activitySidebarHeader, children: [_jsx("h2", { className: formStyles.activitySidebarTitle, children: t("taskForm.commentsSectionTitle") }), _jsx("span", { className: formStyles.activitySidebarCount, children: activityCount })] }), _jsxs("div", { className: `${formStyles.commentPanel} ${formStyles.activitySidebarPanel}`.trim(), children: [_jsxs("div", { className: formStyles.commentThread, children: [activityCount === 0 && (_jsx("div", { className: formStyles.emptyComments, children: t("taskForm.noComments") })), taskActivityItems.map((item) => {
|
|
351
385
|
if (item.type === "comment") {
|
|
352
386
|
const c = item.comment;
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
: "agent");
|
|
373
|
-
const authorLabel = c.actor?.label ||
|
|
374
|
-
actorFallback?.label ||
|
|
375
|
-
actorOption?.label ||
|
|
376
|
-
(authorRef
|
|
377
|
-
? getAssigneeLabel(authorRef, resolvedAssigneeOptions)
|
|
378
|
-
: "") ||
|
|
379
|
-
(commentKind === "agent"
|
|
380
|
-
? formatAiAuthorLabel(authorRef || "ai")
|
|
381
|
-
: t("taskForm.you"));
|
|
382
|
-
const authorColor = c.actor?.color || actorFallback?.color || actorOption?.color;
|
|
383
|
-
const iconName = String(resolvedActor?.icon ||
|
|
384
|
-
(commentKind === "agent" ? "Bot" : "User"));
|
|
385
|
-
const CommentActorIcon = IconMap[iconName] || (commentKind === "agent" ? Bot : User);
|
|
387
|
+
const actorPresentation = resolveActivityActorPresentation({
|
|
388
|
+
actorId: c.author,
|
|
389
|
+
actorType: c.actor?.kind === "ai"
|
|
390
|
+
? "ai"
|
|
391
|
+
: c.actor?.kind === "human"
|
|
392
|
+
? "human"
|
|
393
|
+
: null,
|
|
394
|
+
actorProfile: c.actor,
|
|
395
|
+
});
|
|
396
|
+
const commentKind = actorPresentation.kind === "agent" ? "agent" : "member";
|
|
397
|
+
const { ActorIcon: CommentActorIcon } = actorPresentation;
|
|
398
|
+
const authorColor = actorPresentation.color;
|
|
399
|
+
const authorLabel = actorPresentation.label;
|
|
400
|
+
const commentAccentStyle = authorColor && hexToRgba(authorColor, 0.35)
|
|
401
|
+
? {
|
|
402
|
+
background: `linear-gradient(135deg, ${hexToRgba(authorColor, 0.18)} 0%, ${hexToRgba(authorColor, 0.1)} 100%)`,
|
|
403
|
+
borderColor: hexToRgba(authorColor, 0.35),
|
|
404
|
+
}
|
|
405
|
+
: undefined;
|
|
386
406
|
return (_jsxs("div", { className: `${formStyles.comment} ${commentKind === "agent" ? formStyles.commentAi : formStyles.commentUser}`, children: [_jsxs("div", { className: formStyles.commentHeader, children: [_jsx("span", { className: formStyles.commentAuthor, style: authorColor ? { color: authorColor } : undefined, children: _jsxs(_Fragment, { children: [_jsx(CommentActorIcon, { size: 12, strokeWidth: 1.5 }), " ", authorLabel] }) }), _jsx("span", { className: formStyles.commentTime, children: formatDate(c.timestamp, {
|
|
387
407
|
month: "short",
|
|
388
408
|
day: "numeric",
|
|
389
409
|
hour: "2-digit",
|
|
390
410
|
minute: "2-digit",
|
|
391
|
-
}) })] }), _jsx("div", { className: formStyles.commentText, style:
|
|
392
|
-
? {
|
|
393
|
-
background: `linear-gradient(135deg, ${hexToRgba(authorColor, 0.18)} 0%, ${hexToRgba(authorColor, 0.1)} 100%)`,
|
|
394
|
-
borderColor: hexToRgba(authorColor, 0.35),
|
|
395
|
-
}
|
|
396
|
-
: undefined, children: _jsx(Markdown, { onTaskIdClick: onOpenTaskById, children: c.text }) })] }, c.id));
|
|
411
|
+
}) })] }), _jsx("div", { className: formStyles.commentText, style: commentAccentStyle, children: _jsx(Markdown, { onTaskIdClick: onOpenTaskById, children: c.text }) })] }, c.id));
|
|
397
412
|
}
|
|
398
413
|
const event = item.event;
|
|
399
|
-
const
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
: "system";
|
|
410
|
-
const authorLabel = event.actorProfile?.label ||
|
|
411
|
-
actorFallback?.label ||
|
|
412
|
-
actorOption?.label ||
|
|
413
|
-
(eventKind === "system"
|
|
414
|
-
? "System"
|
|
415
|
-
: actorRef
|
|
416
|
-
? getAssigneeLabel(actorRef, resolvedAssigneeOptions)
|
|
417
|
-
: eventKind === "agent"
|
|
418
|
-
? formatAiAuthorLabel("ai")
|
|
419
|
-
: t("taskForm.you"));
|
|
420
|
-
const authorColor = event.actorProfile?.color ||
|
|
421
|
-
actorFallback?.color ||
|
|
422
|
-
actorOption?.color;
|
|
423
|
-
const iconName = String(resolvedActor?.icon ||
|
|
424
|
-
(eventKind === "agent"
|
|
425
|
-
? "Bot"
|
|
426
|
-
: eventKind === "member"
|
|
427
|
-
? "User"
|
|
428
|
-
: "ClipboardList"));
|
|
429
|
-
const EventActorIcon = IconMap[iconName] ||
|
|
430
|
-
(eventKind === "agent"
|
|
431
|
-
? Bot
|
|
432
|
-
: eventKind === "member"
|
|
433
|
-
? User
|
|
434
|
-
: ClipboardList);
|
|
414
|
+
const actorPresentation = resolveActivityActorPresentation({
|
|
415
|
+
actorId: event.actor,
|
|
416
|
+
actorType: event.actorType,
|
|
417
|
+
actorProfile: event.actorProfile,
|
|
418
|
+
fallbackKind: "system",
|
|
419
|
+
});
|
|
420
|
+
const { ActorIcon: EventActorIcon } = actorPresentation;
|
|
421
|
+
const authorColor = actorPresentation.color;
|
|
422
|
+
const authorLabel = actorPresentation.label;
|
|
423
|
+
const eventKind = actorPresentation.kind;
|
|
435
424
|
const changeEntries = Object.entries(event.details?.changes || {});
|
|
425
|
+
const primaryChangeField = getTaskEventPrimaryChangeField(event);
|
|
426
|
+
const displayedChangeEntries = changeEntries.length > 1 && primaryChangeField
|
|
427
|
+
? changeEntries.filter(([field]) => field !== primaryChangeField)
|
|
428
|
+
: changeEntries.length > 1
|
|
429
|
+
? changeEntries
|
|
430
|
+
: [];
|
|
436
431
|
return (_jsxs("div", { className: `${formStyles.comment} ${formStyles.activityEvent} ${eventKind === "system" ? formStyles.activityEventSystem : ""}`, children: [_jsxs("div", { className: `${formStyles.commentHeader} ${formStyles.activityEventHeader}`, children: [_jsx("span", { className: formStyles.commentAuthor, style: authorColor ? { color: authorColor } : undefined, children: _jsxs(_Fragment, { children: [_jsx(EventActorIcon, { size: 12, strokeWidth: 1.5 }), " ", authorLabel] }) }), _jsx("span", { className: formStyles.commentTime, children: formatDate(event.createdAt, {
|
|
437
432
|
month: "short",
|
|
438
433
|
day: "numeric",
|
|
439
434
|
hour: "2-digit",
|
|
440
435
|
minute: "2-digit",
|
|
441
|
-
}) })] }), _jsxs("div", { className: formStyles.activityEventText, children: [_jsx("div", { children: summarizeTaskEvent(event, formatActivityFieldValue) }),
|
|
436
|
+
}) })] }), _jsxs("div", { className: formStyles.activityEventText, children: [_jsx("div", { children: summarizeTaskEvent(event, formatActivityFieldValue) }), displayedChangeEntries.length > 0 && (_jsx("div", { className: formStyles.activityEventChanges, children: displayedChangeEntries.map(([field, change]) => (_jsx("div", { className: formStyles.activityEventChange, children: summarizeTaskChange(field, change, formatActivityFieldValue) }, field))) }))] })] }, item.id));
|
|
442
437
|
}), _jsx("div", { ref: commentsEndRef })] }), _jsxs("div", { className: formStyles.commentInputArea, children: [_jsx("textarea", { className: formStyles.commentInput, placeholder: t("taskForm.commentPlaceholder"), value: newCommentText, onChange: (e) => onNewCommentTextChange(e.target.value), onKeyDown: (e) => {
|
|
443
438
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
444
439
|
e.preventDefault();
|
|
@@ -455,7 +455,7 @@ describe("TaskForm Component", () => {
|
|
|
455
455
|
],
|
|
456
456
|
},
|
|
457
457
|
});
|
|
458
|
-
expect(screen.getByText("
|
|
458
|
+
expect(screen.getByText("Assigned to: Unassigned -> Planner Bot")).toBeInTheDocument();
|
|
459
459
|
expect(screen.queryByText(/ai-profile-1/i)).not.toBeInTheDocument();
|
|
460
460
|
});
|
|
461
461
|
it("renders planning relationship audit activity with reference labels instead of raw ids", () => {
|
|
@@ -491,7 +491,7 @@ describe("TaskForm Component", () => {
|
|
|
491
491
|
],
|
|
492
492
|
},
|
|
493
493
|
});
|
|
494
|
-
expect(screen.getByText("Workstream: none -> WS-12")).toBeInTheDocument();
|
|
494
|
+
expect(screen.getByText("Workstream changed: none -> WS-12")).toBeInTheDocument();
|
|
495
495
|
expect(screen.queryByText(/workstream-1/i)).not.toBeInTheDocument();
|
|
496
496
|
});
|
|
497
497
|
it("3.13 Update title", async () => {
|
|
@@ -504,8 +504,8 @@ describe("TaskForm Component", () => {
|
|
|
504
504
|
});
|
|
505
505
|
it("keeps scheduled date editable in the task form", () => {
|
|
506
506
|
renderForm({
|
|
507
|
-
editingTaskId: "
|
|
508
|
-
currentTask: { id: "
|
|
507
|
+
editingTaskId: "task-1",
|
|
508
|
+
currentTask: { id: "task-1", title: "Schedulable task" },
|
|
509
509
|
});
|
|
510
510
|
const scheduledLabel = screen.getByText("Scheduled:");
|
|
511
511
|
const scheduledInput = scheduledLabel.nextElementSibling;
|
|
@@ -513,7 +513,6 @@ describe("TaskForm Component", () => {
|
|
|
513
513
|
if (!scheduledInput)
|
|
514
514
|
throw new Error("Scheduled date input missing");
|
|
515
515
|
expect(scheduledInput.disabled).toBe(false);
|
|
516
|
-
expect(screen.queryByText("Parent tasks cannot be scheduled. Schedule leaf tasks instead.")).not.toBeInTheDocument();
|
|
517
516
|
});
|
|
518
517
|
it("3.14 Context Documents section renders", async () => {
|
|
519
518
|
const user = userEvent.setup();
|
|
@@ -682,9 +681,112 @@ describe("TaskForm Component", () => {
|
|
|
682
681
|
],
|
|
683
682
|
},
|
|
684
683
|
});
|
|
685
|
-
expect(screen.getByText("Status:
|
|
684
|
+
expect(screen.getByText("Status changed: Task -> In Progress")).toBeInTheDocument();
|
|
686
685
|
expect(screen.queryByText("System")).not.toBeInTheDocument();
|
|
687
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",
|
|
762
|
+
activity: [
|
|
763
|
+
{
|
|
764
|
+
id: "event:multi-change-1",
|
|
765
|
+
type: "event",
|
|
766
|
+
timestamp: "2026-03-22T10:00:00.000Z",
|
|
767
|
+
event: {
|
|
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",
|
|
775
|
+
details: {
|
|
776
|
+
changes: {
|
|
777
|
+
status: { from: "task", to: "in-progress" },
|
|
778
|
+
assignee: { from: "unassigned", to: "member-2" },
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
},
|
|
783
|
+
],
|
|
784
|
+
},
|
|
785
|
+
});
|
|
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();
|
|
789
|
+
});
|
|
688
790
|
it("summarizes description audit events without rendering the full description body", () => {
|
|
689
791
|
renderForm({
|
|
690
792
|
editingTaskId: "123",
|
|
@@ -720,7 +822,7 @@ describe("TaskForm Component", () => {
|
|
|
720
822
|
],
|
|
721
823
|
},
|
|
722
824
|
});
|
|
723
|
-
expect(screen.getByText("
|
|
825
|
+
expect(screen.getByText("Description: added")).toBeInTheDocument();
|
|
724
826
|
expect(screen.queryByText(/Line one/)).not.toBeInTheDocument();
|
|
725
827
|
expect(screen.queryByText(/Line two/)).not.toBeInTheDocument();
|
|
726
828
|
expect(screen.queryByText(/Line three/)).not.toBeInTheDocument();
|
|
@@ -760,7 +862,7 @@ describe("TaskForm Component", () => {
|
|
|
760
862
|
],
|
|
761
863
|
},
|
|
762
864
|
});
|
|
763
|
-
expect(screen.getByText("
|
|
865
|
+
expect(screen.getByText("Title: updated")).toBeInTheDocument();
|
|
764
866
|
expect(screen.queryByText("Old title")).not.toBeInTheDocument();
|
|
765
867
|
expect(screen.queryByText("New title")).not.toBeInTheDocument();
|
|
766
868
|
});
|
|
@@ -53,6 +53,7 @@ interface TaskKanbanProps {
|
|
|
53
53
|
filterPriorities?: number[];
|
|
54
54
|
filterStatus?: string[];
|
|
55
55
|
filterAssignees?: TaskAssignee[];
|
|
56
|
+
filtersReady?: boolean;
|
|
56
57
|
scheduleFilteredTaskIds?: string[];
|
|
57
58
|
categories?: CategoryConfig[];
|
|
58
59
|
compressed?: boolean;
|
|
@@ -71,5 +72,5 @@ interface TaskKanbanProps {
|
|
|
71
72
|
workstreams?: WorkstreamItem[];
|
|
72
73
|
initiatives?: InitiativeItem[];
|
|
73
74
|
}
|
|
74
|
-
export declare function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery, copiedId, taxonomies, types, priorities, approaches, assigneeOptions, onUpdateTask, onTaskClick, onOpenTaskById, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onUnarchive, onDelete, allTasks, onAddTaskToColumn, emptyColumnMode, filterCategories, filterTypes, filterPriorities, filterStatus, filterAssignees, scheduleFilteredTaskIds, categories, compressed, readOnlyMode, recentlyChangedTaskIds, scheduleDates, onScheduleDaySelected, persistedScrollLeft, onScrollLeftChange, sortBy, sortOrder, planningDropTargets, onAssignTaskToWorkstream, onAssignWorkstreamToInitiative, showTaskCardStatusLabel, workstreams, initiatives, }: TaskKanbanProps): import("react/jsx-runtime").JSX.Element;
|
|
75
|
+
export declare function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery, copiedId, taxonomies, types, priorities, approaches, assigneeOptions, onUpdateTask, onTaskClick, onOpenTaskById, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onUnarchive, onDelete, allTasks, onAddTaskToColumn, emptyColumnMode, filterCategories, filterTypes, filterPriorities, filterStatus, filterAssignees, filtersReady, scheduleFilteredTaskIds, categories, compressed, readOnlyMode, recentlyChangedTaskIds, scheduleDates, onScheduleDaySelected, persistedScrollLeft, onScrollLeftChange, sortBy, sortOrder, planningDropTargets, onAssignTaskToWorkstream, onAssignWorkstreamToInitiative, showTaskCardStatusLabel, workstreams, initiatives, }: TaskKanbanProps): import("react/jsx-runtime").JSX.Element;
|
|
75
76
|
export {};
|
|
@@ -11,7 +11,7 @@ import { buildScheduleDropResult, buildStatusTransitionUpdates, getGroupedTaskSt
|
|
|
11
11
|
import * as Icons from 'lucide-react';
|
|
12
12
|
import { IconMap, resolveColor } from '../../utils/constants';
|
|
13
13
|
const EXPIRED_CARD_ID_PREFIX = 'expired::';
|
|
14
|
-
export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery, copiedId, taxonomies, types, priorities, approaches, assigneeOptions = [], onUpdateTask, onTaskClick, onOpenTaskById, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onUnarchive, onDelete, allTasks = [], onAddTaskToColumn, emptyColumnMode = 'show', filterCategories = [], filterTypes = [], filterPriorities = [], filterStatus = [], filterAssignees = [], scheduleFilteredTaskIds, categories = [], compressed = false, readOnlyMode = null, recentlyChangedTaskIds = [], scheduleDates, onScheduleDaySelected, persistedScrollLeft, onScrollLeftChange, sortBy = 'created', sortOrder = 'desc', planningDropTargets = null, onAssignTaskToWorkstream, onAssignWorkstreamToInitiative, showTaskCardStatusLabel = true, workstreams = [], initiatives = [], }) {
|
|
14
|
+
export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery, copiedId, taxonomies, types, priorities, approaches, assigneeOptions = [], onUpdateTask, onTaskClick, onOpenTaskById, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onUnarchive, onDelete, allTasks = [], onAddTaskToColumn, emptyColumnMode = 'show', filterCategories = [], filterTypes = [], filterPriorities = [], filterStatus = [], filterAssignees = [], filtersReady = true, scheduleFilteredTaskIds, categories = [], compressed = false, readOnlyMode = null, recentlyChangedTaskIds = [], scheduleDates, onScheduleDaySelected, persistedScrollLeft, onScrollLeftChange, sortBy = 'created', sortOrder = 'desc', planningDropTargets = null, onAssignTaskToWorkstream, onAssignWorkstreamToInitiative, showTaskCardStatusLabel = true, workstreams = [], initiatives = [], }) {
|
|
15
15
|
const [activeId, setActiveId] = useState(null);
|
|
16
16
|
const [isHeaderPanning, setIsHeaderPanning] = useState(false);
|
|
17
17
|
const [headerPanEnabled, setHeaderPanEnabled] = useState(false);
|
|
@@ -130,20 +130,99 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
130
130
|
]);
|
|
131
131
|
return dateToColumn.get(scheduledDate) || '__offweek';
|
|
132
132
|
};
|
|
133
|
+
const inferredKanbanColumns = useMemo(() => {
|
|
134
|
+
if (groupBy === 'schedule')
|
|
135
|
+
return [];
|
|
136
|
+
const existingValues = new Set(kanbanColumns.map((column) => String(column.value)));
|
|
137
|
+
const existingLabels = new Set(kanbanColumns.map((column) => column.label));
|
|
138
|
+
const inferred = [];
|
|
139
|
+
const buildColumn = (task) => {
|
|
140
|
+
const rawValue = groupBy === 'status'
|
|
141
|
+
? getTaskStatus(task)
|
|
142
|
+
: task[groupBy];
|
|
143
|
+
const value = String(rawValue ?? '');
|
|
144
|
+
if (!value || existingValues.has(value) || existingLabels.has(value))
|
|
145
|
+
return null;
|
|
146
|
+
if (groupBy === 'category') {
|
|
147
|
+
const category = categories.find((entry) => entry.value === value || entry.label === value);
|
|
148
|
+
return {
|
|
149
|
+
value,
|
|
150
|
+
label: category?.label || value,
|
|
151
|
+
icon: category?.icon || 'Folder',
|
|
152
|
+
color: category?.color,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (groupBy === 'type') {
|
|
156
|
+
const type = types?.find((entry) => entry.value === value || entry.label === value);
|
|
157
|
+
return {
|
|
158
|
+
value,
|
|
159
|
+
label: type?.label || value,
|
|
160
|
+
icon: type?.icon || 'CheckSquare',
|
|
161
|
+
color: type?.color,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (groupBy === 'priority') {
|
|
165
|
+
const priority = priorities?.find((entry) => Number(entry.value) === Number(value));
|
|
166
|
+
return {
|
|
167
|
+
value: Number.isFinite(Number(value)) ? Number(value) : value,
|
|
168
|
+
label: priority?.label || value,
|
|
169
|
+
icon: priority?.icon || 'Flag',
|
|
170
|
+
color: priority?.color,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (groupBy === 'complexity') {
|
|
174
|
+
return {
|
|
175
|
+
value: Number.isFinite(Number(value)) ? Number(value) : value,
|
|
176
|
+
label: value,
|
|
177
|
+
icon: 'Gauge',
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
if (groupBy === 'status') {
|
|
181
|
+
return {
|
|
182
|
+
value,
|
|
183
|
+
label: value,
|
|
184
|
+
icon: 'Circle',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (groupBy === 'assignee') {
|
|
188
|
+
const option = assigneeOptions.find((entry) => entry.value === value);
|
|
189
|
+
return {
|
|
190
|
+
value,
|
|
191
|
+
label: option?.label || value,
|
|
192
|
+
icon: option?.icon === 'HelpCircle' ? 'Circle' : option?.icon,
|
|
193
|
+
color: option?.color,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
value,
|
|
198
|
+
label: value,
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
for (const task of tasks) {
|
|
202
|
+
const column = buildColumn(task);
|
|
203
|
+
if (!column)
|
|
204
|
+
continue;
|
|
205
|
+
inferred.push(column);
|
|
206
|
+
existingValues.add(String(column.value));
|
|
207
|
+
existingLabels.add(column.label);
|
|
208
|
+
}
|
|
209
|
+
return inferred;
|
|
210
|
+
}, [assigneeOptions, categories, groupBy, kanbanColumns, priorities, tasks, types]);
|
|
211
|
+
const effectiveKanbanColumns = useMemo(() => [...kanbanColumns, ...inferredKanbanColumns], [inferredKanbanColumns, kanbanColumns]);
|
|
133
212
|
const columnLookup = useMemo(() => {
|
|
134
213
|
const map = new Map();
|
|
135
|
-
for (const column of
|
|
214
|
+
for (const column of effectiveKanbanColumns) {
|
|
136
215
|
map.set(String(column.value), column);
|
|
137
216
|
if (!map.has(column.label)) {
|
|
138
217
|
map.set(column.label, column);
|
|
139
218
|
}
|
|
140
219
|
}
|
|
141
220
|
return map;
|
|
142
|
-
}, [
|
|
221
|
+
}, [effectiveKanbanColumns]);
|
|
143
222
|
// Totals for "X / Y" display
|
|
144
223
|
const columnTotals = useMemo(() => {
|
|
145
224
|
const counts = {};
|
|
146
|
-
|
|
225
|
+
effectiveKanbanColumns.forEach(col => { counts[String(col.value)] = 0; });
|
|
147
226
|
const totalsSource = allTasks.length > 0 ? allTasks : tasks;
|
|
148
227
|
totalsSource.forEach(task => {
|
|
149
228
|
let stringValue = '';
|
|
@@ -170,12 +249,12 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
170
249
|
}
|
|
171
250
|
});
|
|
172
251
|
return counts;
|
|
173
|
-
}, [allTasks, tasks,
|
|
252
|
+
}, [allTasks, tasks, effectiveKanbanColumns, groupBy, columnLookup, todayDateOnly]);
|
|
174
253
|
// Group items by the groupBy field
|
|
175
254
|
const groupedTasks = useMemo(() => {
|
|
176
255
|
const groups = {};
|
|
177
256
|
// Initialize all columns
|
|
178
|
-
|
|
257
|
+
effectiveKanbanColumns.forEach(col => {
|
|
179
258
|
groups[String(col.value)] = [];
|
|
180
259
|
});
|
|
181
260
|
// Distribute tasks
|
|
@@ -232,7 +311,7 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
232
311
|
});
|
|
233
312
|
}
|
|
234
313
|
return groups;
|
|
235
|
-
}, [tasks, allTasks,
|
|
314
|
+
}, [tasks, allTasks, effectiveKanbanColumns, groupBy, columnLookup, scheduleFilteredTaskIdSet, todayDateOnly, sortBy, sortOrder]);
|
|
236
315
|
const activeTaskId = activeId ? getTaskIdFromCardId(activeId) : null;
|
|
237
316
|
const activeTaskOverlay = activeTaskId ? tasks.find(t => t.id === activeTaskId) : null;
|
|
238
317
|
// Scroll Syncing Refs and Logic
|
|
@@ -248,6 +327,8 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
248
327
|
const lastReportedScrollLeftRef = useRef(-1);
|
|
249
328
|
// Calculate total width for scroll syncing
|
|
250
329
|
const getIsColumnHidden = (columnValue, title) => {
|
|
330
|
+
if (!filtersReady)
|
|
331
|
+
return false;
|
|
251
332
|
if (groupBy === 'category')
|
|
252
333
|
return !filterCategories.includes(String(columnValue));
|
|
253
334
|
if (groupBy === 'type')
|
|
@@ -269,7 +350,7 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
269
350
|
const COL_WIDTH = 320;
|
|
270
351
|
const COL_COLLAPSED = 72;
|
|
271
352
|
let width = 0;
|
|
272
|
-
|
|
353
|
+
effectiveKanbanColumns.forEach((col, index) => {
|
|
273
354
|
const items = groupedTasks[String(col.value)] || [];
|
|
274
355
|
const isExplicitlyHidden = getIsColumnHidden(col.value, col.label);
|
|
275
356
|
const isAutoHidden = groupBy !== 'schedule' && emptyColumnMode === 'hide' && items.length === 0;
|
|
@@ -280,7 +361,7 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
280
361
|
width += GAP;
|
|
281
362
|
});
|
|
282
363
|
return width;
|
|
283
|
-
}, [
|
|
364
|
+
}, [effectiveKanbanColumns, groupedTasks, emptyColumnMode, groupBy, filterCategories, filterTypes, filterPriorities, filterStatus, filterAssignees, filtersReady]);
|
|
284
365
|
const emitScrollLeft = useCallback((scrollLeft) => {
|
|
285
366
|
if (!onScrollLeftChange)
|
|
286
367
|
return;
|
|
@@ -353,7 +434,7 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
353
434
|
return () => {
|
|
354
435
|
window.removeEventListener('resize', updatePanAvailability);
|
|
355
436
|
};
|
|
356
|
-
}, [totalWidth,
|
|
437
|
+
}, [totalWidth, effectiveKanbanColumns.length, groupedTasks]);
|
|
357
438
|
useEffect(() => {
|
|
358
439
|
const handlePointerMove = (event) => {
|
|
359
440
|
const pan = panStateRef.current;
|
|
@@ -474,7 +555,7 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
474
555
|
return;
|
|
475
556
|
// Find the column definition
|
|
476
557
|
const column = columnLookup.get(containerId)
|
|
477
|
-
||
|
|
558
|
+
|| effectiveKanbanColumns.find(c => String(c.value) === containerId || c.label === containerId);
|
|
478
559
|
if (column) {
|
|
479
560
|
if (groupBy === 'schedule') {
|
|
480
561
|
const targetColumn = String(column.value);
|
|
@@ -681,12 +762,12 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
681
762
|
return (_jsxs(DndContext, { sensors: sensors, collisionDetection: customCollisionDetection, onDragStart: handleDragStart, onDragOver: handleDragOver, onDragEnd: handleDragEnd, onDragCancel: (event) => {
|
|
682
763
|
lastPlanningOverRef.current = null;
|
|
683
764
|
setActiveId(null);
|
|
684
|
-
}, children: [planningDropTargets, _jsxs("div", { className: styles.kanbanWrapper, children: [_jsx("div", { ref: topScrollRef, className: `${styles.kanbanTopScroll} tf-scrollbar`, children: _jsx("div", { className: styles.kanbanTopScrollSpacer, style: { width: `${totalWidth}px` } }) }), _jsx("div", { className: styles.kanbanContainer, ref: containerRef, children:
|
|
765
|
+
}, children: [planningDropTargets, _jsxs("div", { className: styles.kanbanWrapper, children: [_jsx("div", { ref: topScrollRef, className: `${styles.kanbanTopScroll} tf-scrollbar`, children: _jsx("div", { className: styles.kanbanTopScrollSpacer, style: { width: `${totalWidth}px` } }) }), _jsx("div", { className: styles.kanbanContainer, ref: containerRef, children: effectiveKanbanColumns.map((column) => {
|
|
685
766
|
const columnTasks = groupedTasks[String(column.value)] || [];
|
|
686
767
|
const isExplicitlyHidden = getIsColumnHidden(column.value, column.label);
|
|
687
768
|
const isAutoHidden = groupBy !== 'schedule' && emptyColumnMode === 'hide' && columnTasks.length === 0;
|
|
688
769
|
const isScheduleMode = groupBy === 'schedule';
|
|
689
|
-
const hasBacklogColumn = isScheduleMode &&
|
|
770
|
+
const hasBacklogColumn = isScheduleMode && effectiveKanbanColumns.some((entry) => String(entry.value) === 'backlog');
|
|
690
771
|
const isBacklog = isScheduleMode && String(column.value) === 'backlog';
|
|
691
772
|
const isExpired = isScheduleMode && String(column.value) === 'expired';
|
|
692
773
|
const backlogTasks = groupedTasks.backlog || [];
|
|
@@ -61,3 +61,39 @@ describe('TaskKanban schedule expired behavior', () => {
|
|
|
61
61
|
expect(screen.getByText('1 / 2')).toBeInTheDocument();
|
|
62
62
|
});
|
|
63
63
|
});
|
|
64
|
+
describe('TaskKanban filter bootstrap behavior', () => {
|
|
65
|
+
it('keeps task columns visible while filters are still initializing', () => {
|
|
66
|
+
const task = makeTask({
|
|
67
|
+
id: 'task-filter-bootstrap',
|
|
68
|
+
title: 'Filter bootstrap visible task',
|
|
69
|
+
category: 'shared'
|
|
70
|
+
});
|
|
71
|
+
render(_jsx(TaskKanban, { tasks: [task], allTasks: [task], columns: [
|
|
72
|
+
{ value: 'shared', label: 'Shared', color: '#3b82f6', icon: 'Folder' }
|
|
73
|
+
], groupBy: "category", filterCategories: [], filtersReady: false, onUpdateTask: vi.fn(), onTaskClick: vi.fn() }));
|
|
74
|
+
expect(screen.getByText('Shared')).toBeInTheDocument();
|
|
75
|
+
expect(screen.getByText('Filter bootstrap visible task')).toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
it('renders a discoverable task when category columns are not loaded yet', () => {
|
|
78
|
+
const task = makeTask({
|
|
79
|
+
id: 'task-missing-category-columns',
|
|
80
|
+
title: 'Visible before categories load',
|
|
81
|
+
category: 'default'
|
|
82
|
+
});
|
|
83
|
+
render(_jsx(TaskKanban, { tasks: [task], allTasks: [task], columns: [], groupBy: "category", filterCategories: [], filtersReady: false, onUpdateTask: vi.fn(), onTaskClick: vi.fn() }));
|
|
84
|
+
expect(screen.getByText('default')).toBeInTheDocument();
|
|
85
|
+
expect(screen.getByText('Visible before categories load')).toBeInTheDocument();
|
|
86
|
+
});
|
|
87
|
+
it('hides filtered-out columns after filters initialize', () => {
|
|
88
|
+
const task = makeTask({
|
|
89
|
+
id: 'task-filter-ready',
|
|
90
|
+
title: 'Filter ready hidden task',
|
|
91
|
+
category: 'shared'
|
|
92
|
+
});
|
|
93
|
+
render(_jsx(TaskKanban, { tasks: [task], allTasks: [task], columns: [
|
|
94
|
+
{ value: 'shared', label: 'Shared', color: '#3b82f6', icon: 'Folder' }
|
|
95
|
+
], groupBy: "category", filterCategories: [], filtersReady: true, onUpdateTask: vi.fn(), onTaskClick: vi.fn() }));
|
|
96
|
+
expect(screen.queryByText('Shared')).not.toBeInTheDocument();
|
|
97
|
+
expect(screen.queryByText('Filter ready hidden task')).not.toBeInTheDocument();
|
|
98
|
+
});
|
|
99
|
+
});
|