@taskforcehq/taskforce 0.3.328 → 0.3.329
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 +233 -34
- package/dist/TaskforceCore.js +237 -230
- package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +1 -1
- package/dist/cli.js +34 -0
- package/dist/cliCloudTargetGuard.d.ts +5 -0
- package/dist/cliCloudTargetGuard.js +11 -0
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +24 -8
- package/dist/components/features/TaskSettings.d.ts +12 -1
- package/dist/components/features/TaskSettings.js +147 -40
- package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +1 -1
- package/dist/components/features/planning/PlanningModule.js +2 -5
- package/dist/components/features/planning/PlanningTaskOrderList.d.ts +1 -1
- package/dist/components/features/planning/PlanningTaskOrderList.js +17 -6
- package/dist/components/features/planning/planningTaskArrangement.d.ts +1 -0
- package/dist/components/features/planning/planningTaskArrangement.js +5 -5
- package/dist/components/features/taskforceAgentEditorModel.js +3 -1
- package/dist/components/task/TaskCard.d.ts +5 -0
- package/dist/components/task/TaskCard.js +65 -10
- package/dist/components/task/TaskImageReviews.js +14 -5
- package/dist/components/task/TaskKanban.d.ts +6 -1
- package/dist/components/task/TaskKanban.js +152 -6
- package/dist/components/task/TaskList.d.ts +5 -0
- package/dist/components/task/TaskList.js +3 -3
- package/dist/components/ui/PasswordInput.d.ts +6 -0
- package/dist/components/ui/PasswordInput.js +11 -0
- package/dist/components/ui/TopNoticeLayer.js +1 -1
- package/dist/components/views/AppShellHeader.js +32 -11
- package/dist/components/views/PlanComparisonPage.js +12 -0
- package/dist/components/views/StandaloneLayout.js +7 -31
- package/dist/components/views/WidgetView.js +3 -3
- package/dist/components/views/panels/PlanningDrawer.js +5 -12
- package/dist/components/views/standalone/modals/AccountSettingsModal.js +26 -17
- package/dist/config/envSchema.js +12 -0
- package/dist/config/localServiceRuntimeContract.d.ts +13 -0
- package/dist/config/localServiceRuntimeContract.js +135 -0
- package/dist/core/AiProfileService.js +46 -8
- package/dist/core/DeletedTaskLifecycleService.d.ts +1 -0
- package/dist/core/DeletedTaskLifecycleService.js +37 -6
- package/dist/core/TaskAttachmentLinksDurableMutationService.d.ts +3 -2
- package/dist/core/TaskAttachmentLinksDurableMutationService.js +33 -9
- package/dist/core/Taskforce.d.ts +62 -2
- package/dist/core/Taskforce.js +635 -32
- package/dist/core/types.d.ts +6 -0
- package/dist/hooks/sync/useDurableLocalOutboxDrain.d.ts +1 -1
- package/dist/hooks/sync/useDurableTaskCreateMutation.js +1 -1
- package/dist/hooks/sync/useDurableTaskMetadataMutation.d.ts +1 -0
- package/dist/hooks/sync/useDurableTaskMetadataMutation.js +1 -0
- package/dist/hooks/sync/useDurableTaskStatusMutation.d.ts +1 -0
- package/dist/hooks/tasks/taskHttpMutation.js +1 -0
- package/dist/hooks/tasks/useCloudTaskSearch.d.ts +12 -0
- package/dist/hooks/tasks/useCloudTaskSearch.js +71 -0
- package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.d.ts +16 -0
- package/dist/hooks/tasks/useTaskDescriptionImageDraftRecovery.js +197 -0
- package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +16 -3
- package/dist/hooks/tasks/useTaskEditorNavigation.js +72 -13
- package/dist/hooks/tasks/useTaskFormActions.d.ts +6 -0
- package/dist/hooks/tasks/useTaskFormActions.js +65 -12
- package/dist/hooks/ui/useSettingsModel.d.ts +24 -2
- package/dist/hooks/ui/useSettingsModel.js +13 -2
- package/dist/hooks/ui/useSettingsPersistence.d.ts +12 -1
- package/dist/hooks/ui/useSettingsPersistence.js +74 -47
- package/dist/hooks/useFilterSort.d.ts +7 -3
- package/dist/hooks/useFilterSort.js +15 -5
- package/dist/hooks/useTaskData.d.ts +10 -1
- package/dist/hooks/useTaskData.js +216 -7
- package/dist/hooks/useTaskMutations.d.ts +10 -2
- package/dist/hooks/useTaskMutations.js +223 -43
- package/dist/hooks/useTaskforce.d.ts +37 -9
- package/dist/hooks/useTaskforce.js +349 -39
- package/dist/hooks/useUiNotice.d.ts +4 -0
- package/dist/hooks/useUiNotice.js +4 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +3 -3
- package/dist/mcp/documentAssetRegistrar.js +44 -35
- package/dist/mcp/documentHelpers.d.ts +1 -0
- package/dist/mcp/documentHelpers.js +7 -3
- package/dist/mcp/runtime.js +172 -42
- package/dist/mcp/taskPlanningRegistrar.js +41 -38
- package/dist/mcp/toolCallCompatibility.js +38 -1
- package/dist/mcp/toolCatalog.d.ts +1 -0
- package/dist/migrations/taskSchemaMigrations.js +12 -0
- package/dist/runtime/appGateDefinitions.d.ts +30 -6
- package/dist/runtime/appGateDefinitions.js +6 -6
- package/dist/runtime/appGates.d.ts +12 -13
- package/dist/runtime/appGates.js +29 -13
- package/dist/runtime/nodeAppGates.d.ts +8 -0
- package/dist/runtime/nodeAppGates.js +27 -0
- package/dist/server/cloudCollectionRead.d.ts +1 -1
- package/dist/server/cloudCollectionRead.js +2 -2
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +15 -3
- package/dist/server/localServiceLease.d.ts +3 -0
- package/dist/server/localServiceLease.js +9 -1
- package/dist/server/localWorkspaceSyncServerRuntime.js +10 -3
- package/dist/server/rateLimit.d.ts +1 -0
- package/dist/server/rateLimit.js +4 -0
- package/dist/server/realtimeSyncWs.js +36 -12
- package/dist/server/routes/admin.js +1 -0
- package/dist/server/routes/agentRoles.js +3 -2
- package/dist/server/routes/agentSkills.js +3 -2
- package/dist/server/routes/agents.js +118 -16
- package/dist/server/routes/annotatedAttachments.js +3 -2
- package/dist/server/routes/auth.js +3 -1
- package/dist/server/routes/durableTaskHttpRouters.d.ts +9 -2
- package/dist/server/routes/localService.js +2 -0
- package/dist/server/routes/primitives.d.ts +2 -0
- package/dist/server/routes/syncAuxRoutes.js +5 -2
- package/dist/server/routes/syncPullApplyRoutes.js +23 -4
- package/dist/server/routes/syncV3FeedRoutes.js +18 -16
- package/dist/server/routes/syncV3LocalCaptureRoutes.js +28 -2
- package/dist/server/routes/syncV3LocalFeedApplyRoutes.d.ts +1 -0
- package/dist/server/routes/syncV3LocalFeedApplyRoutes.js +26 -0
- package/dist/server/routes/syncV3OperationRoutes.js +10 -0
- package/dist/server/routes/tasks.js +103 -11
- package/dist/server/routes/workspaces.js +43 -1
- package/dist/server/workspaceDeletionAdmission.d.ts +23 -0
- package/dist/server/workspaceDeletionAdmission.js +50 -0
- package/dist/service/localServiceCli.js +16 -0
- package/dist/service/localServiceClientLifecycle.js +36 -10
- package/dist/services/codexAppServerModelGateway.d.ts +112 -0
- package/dist/services/codexAppServerModelGateway.js +438 -0
- package/dist/services/taskforceAgentMcpBridge.d.ts +7 -1
- package/dist/services/taskforceAgentMcpBridge.js +23 -3
- package/dist/shared/taskforceAgentDefinition.js +7 -1
- package/dist/shared/taskforceAgentModels.d.ts +1 -0
- package/dist/shared/taskforceAgentModels.js +30 -1
- package/dist/storage/postgresAdapter.d.ts +20 -3
- package/dist/storage/postgresAdapter.js +106 -14
- package/dist/storage/postgresWorker.js +13 -4
- package/dist/storage/postgresWorkerProtocol.d.ts +29 -0
- package/dist/storage/postgresWorkerProtocol.js +30 -0
- package/dist/sync/cloudSyncApi.d.ts +4 -1
- package/dist/sync/cloudSyncApi.js +7 -2
- package/dist/sync/contentSyncState.d.ts +1 -1
- package/dist/sync/coordinator/localSyncExecutionPermitStore.d.ts +1 -0
- package/dist/sync/coordinator/localSyncExecutionPermitStore.js +7 -1
- package/dist/sync/coordinator/localWorkspaceSyncRunner.js +6 -2
- package/dist/sync/engine/workspaceSyncEngineBrowserGatewayRemoteServices.js +20 -2
- package/dist/sync/engine/workspaceSyncEngineLocalServices.d.ts +4 -0
- package/dist/sync/engine/workspaceSyncEngineLocalServices.js +11 -0
- package/dist/sync/engine/workspaceSyncEngineRemoteServices.d.ts +11 -0
- package/dist/sync/engine/workspaceSyncEngineRemoteServices.js +2 -1
- package/dist/sync/engine/workspaceSyncEngineServerRemoteServices.js +31 -4
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.d.ts +3 -1
- package/dist/sync/engine/workspaceSyncEngineServerRunnerOperations.js +25 -8
- package/dist/sync/engine/workspaceSyncEngineTransfers.d.ts +1 -1
- package/dist/sync/engine/workspaceSyncEngineTransfers.js +8 -3
- package/dist/sync/syncService.d.ts +2 -0
- package/dist/sync/syncService.js +4 -1
- package/dist/sync/v3/combinedFeedCapability.js +4 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.d.ts +2 -0
- package/dist/sync/v3/durableTaskAttachmentLinksMutationRouter.js +11 -1
- package/dist/sync/v3/durableTaskChecklistMutationRouter.d.ts +2 -0
- package/dist/sync/v3/durableTaskChecklistMutationRouter.js +4 -0
- package/dist/sync/v3/durableTaskHttpBulkMutationRouter.d.ts +1 -0
- package/dist/sync/v3/durableTaskHttpBulkMutationRouter.js +30 -8
- package/dist/sync/v3/durableTaskHttpRootMutationRouter.d.ts +5 -3
- package/dist/sync/v3/durableTaskHttpRootMutationRouter.js +27 -2
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.d.ts +2 -0
- package/dist/sync/v3/localTaskAttachmentLinksOutboxService.js +47 -7
- package/dist/sync/v3/localTaskCreateOutboxService.js +15 -6
- package/dist/sync/v3/localTaskMetadataClient.d.ts +1 -0
- package/dist/sync/v3/localTaskMetadataClient.js +1 -0
- package/dist/sync/v3/localTaskMetadataOutboxHandler.js +5 -1
- package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -0
- package/dist/sync/v3/localTaskMetadataOutboxService.js +7 -0
- package/dist/sync/v3/localTaskRestoreOutboxService.js +14 -6
- package/dist/sync/v3/localTaskStatusClient.d.ts +1 -0
- package/dist/sync/v3/localTaskStatusClient.js +6 -1
- package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -0
- package/dist/sync/v3/localTaskStatusOutboxService.js +54 -13
- package/dist/sync/v3/syncDurabilityStore.d.ts +7 -0
- package/dist/sync/v3/syncDurabilityStore.js +31 -11
- package/dist/sync/v3/taskAttachmentLinksMutationService.d.ts +6 -4
- package/dist/sync/v3/taskAttachmentLinksMutationService.js +19 -10
- package/dist/sync/v3/taskChecklistMutationService.d.ts +4 -2
- package/dist/sync/v3/taskChecklistMutationService.js +4 -0
- package/dist/sync/v3/taskCreateMutationService.d.ts +2 -2
- package/dist/sync/v3/taskCreateMutationService.js +7 -4
- package/dist/sync/v3/taskMetadataMutationService.d.ts +3 -0
- package/dist/sync/v3/taskMetadataMutationService.js +4 -0
- package/dist/sync/v3/taskRestoreMutationService.d.ts +2 -2
- package/dist/sync/v3/taskRestoreMutationService.js +7 -4
- package/dist/sync/v3/taskStatusMutationService.d.ts +2 -0
- package/dist/sync/v3/taskStatusMutationService.js +4 -0
- package/dist/sync/v3/workflowAssignmentSync.d.ts +1 -0
- package/dist/sync/v3/workflowAssignmentSync.js +3 -0
- package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.d.ts +1 -1
- package/dist/sync/v3/workspaceSyncV2ProjectionExclusions.js +13 -3
- package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.d.ts +11 -0
- package/dist/sync/v3/workspaceSyncV3AttachmentApplyObligationStore.js +27 -10
- package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.d.ts +2 -1
- package/dist/sync/v3/workspaceSyncV3AttachmentAssetHydration.js +52 -10
- package/dist/sync/v3/workspaceSyncV3CloudOperationHandler.js +9 -1
- package/dist/sync/v3/workspaceSyncV3CombinedFeedOrchestrator.js +8 -3
- package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +51 -10
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.d.ts +7 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairBaseline.js +30 -18
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.d.ts +9 -4
- package/dist/sync/v3/workspaceSyncV3CombinedRepairMaterialization.js +98 -47
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.d.ts +10 -0
- package/dist/sync/v3/workspaceSyncV3CombinedRepairSnapshot.js +58 -21
- package/dist/sync/v3/workspaceSyncV3CombinedWireContract.d.ts +3 -1
- package/dist/sync/v3/workspaceSyncV3CombinedWireContract.js +18 -0
- package/dist/sync/v3/workspaceSyncV3CoverageRegistry.js +6 -0
- package/dist/sync/v3/workspaceSyncV3FeedProfile.d.ts +1 -1
- package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3LegacyBoundaryAttestation.js +23 -10
- package/dist/sync/v3/workspaceSyncV3NestedTaskApply.d.ts +3 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskApply.js +64 -13
- package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.d.ts +9 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskCodecs.js +26 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +15 -5
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +50 -14
- package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.d.ts +2 -1
- package/dist/sync/v3/workspaceSyncV3NestedTaskRepairSnapshot.js +11 -3
- package/dist/sync/v3/workspaceSyncV3OperationProtocol.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3OperatorDiagnostics.js +19 -7
- package/dist/sync/v3/workspaceSyncV3TaskCommentAudit.js +9 -2
- package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.d.ts +7 -0
- package/dist/sync/v3/workspaceSyncV3TaskCoverCoverage.js +37 -0
- package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.d.ts +6 -0
- package/dist/sync/v3/workspaceSyncV3TaskCoverWireContract.js +9 -0
- package/dist/sync/workspacePullFeed.js +2 -1
- package/dist/sync/workspaceRepair.d.ts +3 -0
- package/dist/sync/workspaceRepair.js +67 -6
- package/dist/types.d.ts +47 -1
- package/dist/types.js +6 -0
- package/dist/ui/assets/{AiIdentityRosterCard-MZs7lVED.js → AiIdentityRosterCard-BZM4aQnM.js} +1 -1
- package/dist/ui/assets/AiProfilesModule-BbAEWtHH.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-B68Wg-RV.js +3 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-TzYu7pkG.css +1 -0
- package/dist/ui/assets/{AssetTraySortControl-Kf1pelSy.js → AssetTraySortControl-C8Rztyal.js} +1 -1
- package/dist/ui/assets/{ContextAttachmentManager-B8boOs6Q.js → ContextAttachmentManager-Cj3c7X9y.js} +2 -2
- package/dist/ui/assets/{DocumentWorkspace-DXH4XUsB.js → DocumentWorkspace-B5uvzems.js} +1 -1
- package/dist/ui/assets/{EntityActivityTimeline-BtSTq0ON.js → EntityActivityTimeline-BVFvCB5R.js} +1 -1
- package/dist/ui/assets/PlanningModule-B5DWQOGz.js +1 -0
- package/dist/ui/assets/PlansPage-AY4cGWco.js +1 -0
- package/dist/ui/assets/TaskContextUpload-DZxTVesW.js +1 -0
- package/dist/ui/assets/TaskSettings-BeWAuyZj.js +12 -0
- package/dist/ui/assets/{TaskforceAgentsModule-BazdUwxZ.js → TaskforceAgentsModule-BifCYtjR.js} +6 -6
- package/dist/ui/assets/{WorkflowManagerModule-DWFspC-o.js → WorkflowManagerModule-DEr5di_r.js} +1 -1
- package/dist/ui/assets/index-I5zhdtgt.js +7 -0
- package/dist/ui/assets/index-VRFsx3wz.css +1 -0
- package/dist/ui/assets/{vendor-icons-BkFLXavV.js → vendor-icons-D9Lpw-j4.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/taskActivity.d.ts +3 -2
- package/dist/utils/taskActivity.js +19 -2
- package/dist/utils/taskEditorReconciliation.d.ts +40 -0
- package/dist/utils/taskEditorReconciliation.js +217 -0
- package/dist/utils/taskNormalization.d.ts +1 -0
- package/dist/utils/taskNormalization.js +39 -0
- package/dist/utils/taskSearch.js +2 -2
- package/dist/utils/uiNotice.d.ts +4 -0
- package/package.json +16 -4
- package/dist/ui/assets/AiProfilesModule-C3i0LOxe.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-CBRZ8rca.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-DVZk5Tln.js +0 -3
- package/dist/ui/assets/PlanningModule-BPVyEa-f.js +0 -1
- package/dist/ui/assets/PlansPage-ChqgxALc.js +0 -1
- package/dist/ui/assets/TaskContextUpload-CB3ol0sN.js +0 -1
- package/dist/ui/assets/TaskSettings-g4ECKNOz.js +0 -12
- package/dist/ui/assets/index-BUplSsv_.js +0 -7
- package/dist/ui/assets/index-gz2EXuoK.css +0 -1
|
@@ -4,10 +4,14 @@ import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, us
|
|
|
4
4
|
import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, } from '@dnd-kit/sortable';
|
|
5
5
|
import { CSS } from '@dnd-kit/utilities';
|
|
6
6
|
import { GripVertical } from 'lucide-react';
|
|
7
|
+
import { isPlanningTaskTerminal } from './planningTaskArrangement';
|
|
7
8
|
import styles from './PlanningTaskOrderList.module.css';
|
|
8
9
|
function SortableTask(props) {
|
|
9
10
|
const sortable = useSortable({ id: props.task.id, disabled: props.disabled });
|
|
10
|
-
|
|
11
|
+
const reorderLabel = props.disabledReason
|
|
12
|
+
? `Reorder ${props.task.title} — ${props.disabledReason}`
|
|
13
|
+
: `Reorder ${props.task.title}`;
|
|
14
|
+
return (_jsxs("div", { ref: sortable.setNodeRef, className: styles.item, style: { transform: CSS.Transform.toString(sortable.transform), transition: sortable.transition }, children: [_jsx("button", { ref: sortable.setActivatorNodeRef, type: "button", className: styles.grip, disabled: props.disabled, "aria-label": reorderLabel, title: props.disabledReason || 'Drag to change execution order', ...sortable.attributes, ...sortable.listeners, children: _jsx(GripVertical, { size: 15, "aria-hidden": "true" }) }), _jsx("div", { className: styles.card, children: props.children })] }));
|
|
11
15
|
}
|
|
12
16
|
export function PlanningTaskOrderList(props) {
|
|
13
17
|
const [orderedIds, setOrderedIds] = React.useState(() => props.tasks.map((task) => task.id));
|
|
@@ -19,7 +23,8 @@ export function PlanningTaskOrderList(props) {
|
|
|
19
23
|
const byId = new Map(props.tasks.map((task) => [task.id, task]));
|
|
20
24
|
return orderedIds.map((id) => byId.get(id)).filter((task) => Boolean(task));
|
|
21
25
|
}, [orderedIds, props.tasks]);
|
|
22
|
-
const
|
|
26
|
+
const hasReorderAction = Boolean(props.onReorder);
|
|
27
|
+
const draggable = props.mode === 'execution' && hasReorderAction;
|
|
23
28
|
const handleDragEnd = React.useCallback(async (event) => {
|
|
24
29
|
if (!draggable || saving || !event.over || event.active.id === event.over.id)
|
|
25
30
|
return;
|
|
@@ -29,8 +34,11 @@ export function PlanningTaskOrderList(props) {
|
|
|
29
34
|
if (from < 0 || to < 0)
|
|
30
35
|
return;
|
|
31
36
|
const byId = new Map(orderedTasks.map((task) => [task.id, task]));
|
|
32
|
-
const
|
|
33
|
-
|
|
37
|
+
const sourceTask = byId.get(previous[from]);
|
|
38
|
+
const targetTask = byId.get(previous[to]);
|
|
39
|
+
if (!sourceTask || !targetTask)
|
|
40
|
+
return;
|
|
41
|
+
if (isPlanningTaskTerminal(sourceTask) !== isPlanningTaskTerminal(targetTask))
|
|
34
42
|
return;
|
|
35
43
|
const next = arrayMove(previous, from, to);
|
|
36
44
|
setOrderedIds(next);
|
|
@@ -45,8 +53,11 @@ export function PlanningTaskOrderList(props) {
|
|
|
45
53
|
setSaving(false);
|
|
46
54
|
}
|
|
47
55
|
}, [draggable, orderedIds, orderedTasks, props, saving]);
|
|
48
|
-
if (!
|
|
56
|
+
if (!hasReorderAction) {
|
|
49
57
|
return _jsx("div", { className: `${styles.list} ${props.className || ''}`.trim(), children: props.tasks.map((task) => (_jsx(React.Fragment, { children: props.children(task) }, task.id))) });
|
|
50
58
|
}
|
|
51
|
-
|
|
59
|
+
const disabledReason = props.mode === 'execution'
|
|
60
|
+
? undefined
|
|
61
|
+
: 'Switch to Execution order to reorder tasks';
|
|
62
|
+
return (_jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragEnd: (event) => void handleDragEnd(event), children: _jsx(SortableContext, { items: orderedIds, strategy: verticalListSortingStrategy, children: _jsx("div", { className: `${styles.list} ${props.className || ''}`.trim(), "aria-busy": saving || undefined, children: orderedTasks.map((task) => (_jsx(SortableTask, { task: task, disabled: !draggable || saving, disabledReason: disabledReason || (saving ? 'Saving execution order' : undefined), children: props.children(task) }, task.id))) }) }) }));
|
|
52
63
|
}
|
|
@@ -15,4 +15,5 @@ export declare const PLANNING_TASK_ARRANGE_OPTIONS: readonly ({
|
|
|
15
15
|
})[];
|
|
16
16
|
export declare function normalizePlanningTaskArrangeMode(value: unknown): PlanningTaskArrangeMode;
|
|
17
17
|
export declare function mergeVisiblePlanningTaskOrder(allTasks: readonly PlanningTaskPreview[], reorderedVisibleTaskIds: readonly string[]): string[];
|
|
18
|
+
export declare function isPlanningTaskTerminal(task: PlanningTaskPreview): boolean;
|
|
18
19
|
export declare function arrangePlanningTasks(tasks: readonly PlanningTaskPreview[], mode: PlanningTaskArrangeMode): PlanningTaskPreview[];
|
|
@@ -19,7 +19,7 @@ export function mergeVisiblePlanningTaskOrder(allTasks, reorderedVisibleTaskIds)
|
|
|
19
19
|
let visibleIndex = 0;
|
|
20
20
|
return allTasks.map((task) => (visible.has(task.id) ? reorderedVisibleTaskIds[visibleIndex++] : task.id));
|
|
21
21
|
}
|
|
22
|
-
function
|
|
22
|
+
export function isPlanningTaskTerminal(task) {
|
|
23
23
|
const status = getStatusDefinition(task.status).value;
|
|
24
24
|
return status === 'done' || status === 'cancelled';
|
|
25
25
|
}
|
|
@@ -50,7 +50,7 @@ function arrangeBlockedTasks(tasks, allTasksById) {
|
|
|
50
50
|
for (const task of tasks) {
|
|
51
51
|
for (const prerequisiteId of task.prerequisiteTaskIds || []) {
|
|
52
52
|
const prerequisite = allTasksById.get(prerequisiteId);
|
|
53
|
-
if (!prerequisite ||
|
|
53
|
+
if (!prerequisite || isPlanningTaskTerminal(prerequisite) || !taskIds.has(prerequisiteId))
|
|
54
54
|
continue;
|
|
55
55
|
dependentsByPrerequisite.set(prerequisiteId, [
|
|
56
56
|
...(dependentsByPrerequisite.get(prerequisiteId) || []),
|
|
@@ -85,15 +85,15 @@ function arrangeBlockedTasks(tasks, allTasksById) {
|
|
|
85
85
|
export function arrangePlanningTasks(tasks, mode) {
|
|
86
86
|
const taskById = new Map(tasks.map((task) => [task.id, task]));
|
|
87
87
|
if (mode === 'ready') {
|
|
88
|
-
const active = tasks.filter((task) => !
|
|
88
|
+
const active = tasks.filter((task) => !isPlanningTaskTerminal(task));
|
|
89
89
|
const ready = sortByExecutionOrder(active.filter((task) => isReady(task, taskById)));
|
|
90
90
|
const blocked = arrangeBlockedTasks(active.filter((task) => !isReady(task, taskById)), taskById);
|
|
91
|
-
return [...ready, ...blocked, ...sortByExecutionOrder(tasks.filter(
|
|
91
|
+
return [...ready, ...blocked, ...sortByExecutionOrder(tasks.filter(isPlanningTaskTerminal))];
|
|
92
92
|
}
|
|
93
93
|
return tasks.map((task, index) => ({ task, index })).sort((leftEntry, rightEntry) => {
|
|
94
94
|
const left = leftEntry.task;
|
|
95
95
|
const right = rightEntry.task;
|
|
96
|
-
const terminalComparison = Number(
|
|
96
|
+
const terminalComparison = Number(isPlanningTaskTerminal(left)) - Number(isPlanningTaskTerminal(right));
|
|
97
97
|
if (terminalComparison)
|
|
98
98
|
return terminalComparison;
|
|
99
99
|
if (mode === 'priority') {
|
|
@@ -96,6 +96,7 @@ export function buildTaskforceAgentDefinitionFromDraft(input) {
|
|
|
96
96
|
const artStyle = selectedAvatarArtStyle(draft.avatarArtStyleSelection, draft.avatarArtStyleCustom);
|
|
97
97
|
const requestedCapabilities = draft.requestedCapabilities
|
|
98
98
|
.filter((capability) => DEFAULT_AGENT_TOOL_CAPABILITY_SET.has(capability));
|
|
99
|
+
const selectedModel = getTaskforceAgentModel(input.modelKey);
|
|
99
100
|
return {
|
|
100
101
|
...(baseDefinition || {}),
|
|
101
102
|
schemaVersion: AGENT_DEFINITION_SCHEMA_VERSION,
|
|
@@ -104,8 +105,9 @@ export function buildTaskforceAgentDefinitionFromDraft(input) {
|
|
|
104
105
|
model: {
|
|
105
106
|
providerKey: input.providerKey,
|
|
106
107
|
modelKey: input.modelKey,
|
|
107
|
-
modelSource:
|
|
108
|
+
modelSource: selectedModel?.source || 'bedrock',
|
|
108
109
|
tier: input.tier,
|
|
110
|
+
...(selectedModel?.source === 'subscription_dev' ? { connectionScope: 'local_dev' } : {}),
|
|
109
111
|
},
|
|
110
112
|
behavior: {
|
|
111
113
|
...(personality ? { personality } : {}),
|
|
@@ -48,6 +48,11 @@ interface TaskCardProps {
|
|
|
48
48
|
onWorkstreamChange?: (task: TaskItem, workstreamId: string | null) => boolean | void | Promise<boolean | void>;
|
|
49
49
|
compressed?: boolean;
|
|
50
50
|
showStatusLabel?: boolean;
|
|
51
|
+
showDescription?: boolean;
|
|
52
|
+
showAttachments?: boolean;
|
|
53
|
+
showChecklistProgress?: boolean;
|
|
54
|
+
showLatestActivity?: boolean;
|
|
55
|
+
tintActivityBackground?: boolean;
|
|
51
56
|
}
|
|
52
57
|
export declare const TaskCard: React.NamedExoticComponent<TaskCardProps>;
|
|
53
58
|
export {};
|
|
@@ -23,7 +23,55 @@ import { TaxonomyDropdown } from '../ui/TaxonomyDropdown';
|
|
|
23
23
|
import { AssigneeAvatar, AssigneeIdentity } from '../ui/AssigneeIdentity';
|
|
24
24
|
import { TaskWorkstreamPicker } from './TaskWorkstreamPicker';
|
|
25
25
|
import { DocumentTypeIcon } from '../ui/DocumentTypeIcon';
|
|
26
|
-
|
|
26
|
+
function resolveLatestActivityActor(item, assigneeOptions) {
|
|
27
|
+
if (!item)
|
|
28
|
+
return null;
|
|
29
|
+
const actorRef = String(item.type === 'comment' ? item.comment.author : item.event.actor).trim();
|
|
30
|
+
const actorProfile = item.type === 'comment' ? item.comment.actor : item.event.actorProfile;
|
|
31
|
+
if (!actorRef && !actorProfile)
|
|
32
|
+
return null;
|
|
33
|
+
const normalizedActorRef = actorRef.toLowerCase();
|
|
34
|
+
const option = assigneeOptions.find((candidate) => (String(candidate.value || '').trim().toLowerCase() === normalizedActorRef));
|
|
35
|
+
const actorType = item.type === 'comment'
|
|
36
|
+
? actorProfile?.kind === 'ai'
|
|
37
|
+
? 'ai'
|
|
38
|
+
: actorProfile?.kind === 'human'
|
|
39
|
+
? 'human'
|
|
40
|
+
: option?.kind === 'agent'
|
|
41
|
+
? 'ai'
|
|
42
|
+
: option?.kind === 'member'
|
|
43
|
+
? 'human'
|
|
44
|
+
: null
|
|
45
|
+
: item.event.actorType;
|
|
46
|
+
const kind = actorType === 'system' || normalizedActorRef === 'system'
|
|
47
|
+
? 'system'
|
|
48
|
+
: actorType === 'ai' || actorProfile?.kind === 'ai' || option?.kind === 'agent'
|
|
49
|
+
? 'agent'
|
|
50
|
+
: 'member';
|
|
51
|
+
if (kind === 'system')
|
|
52
|
+
return { label: 'System' };
|
|
53
|
+
const profileLabel = String(actorProfile?.label || '').trim();
|
|
54
|
+
const optionLabel = String(option?.label || '').trim();
|
|
55
|
+
const isGenericActor = !normalizedActorRef
|
|
56
|
+
|| normalizedActorRef === 'user'
|
|
57
|
+
|| normalizedActorRef === 'human'
|
|
58
|
+
|| normalizedActorRef === 'ai';
|
|
59
|
+
const isOpaqueActor = /^(?:ai-profile|user|member)-[0-9a-f-]{8,}$/i.test(actorRef)
|
|
60
|
+
|| /^[0-9a-f]{16,}$/i.test(actorRef);
|
|
61
|
+
const readableActorRef = actorRef
|
|
62
|
+
.replace(/[-_]+/g, ' ')
|
|
63
|
+
.replace(/\s+/g, ' ')
|
|
64
|
+
.trim()
|
|
65
|
+
.replace(/\b\w/g, (character) => character.toUpperCase());
|
|
66
|
+
const label = profileLabel
|
|
67
|
+
|| optionLabel
|
|
68
|
+
|| (kind === 'agent' && (isGenericActor || isOpaqueActor) ? 'AI Agent' : '')
|
|
69
|
+
|| (isGenericActor || isOpaqueActor ? 'Workspace member' : readableActorRef)
|
|
70
|
+
|| (kind === 'agent' ? 'AI Agent' : 'Workspace member');
|
|
71
|
+
const color = resolveColor(actorProfile?.color || option?.color);
|
|
72
|
+
return { label, color };
|
|
73
|
+
}
|
|
74
|
+
const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [], types = [], priorities = [], categories = [], assigneeOptions = [], workstreams = [], taskWorkstream = null, taskInitiative = null, workspaceId = null, isOverlay = false, isArchived = false, readOnlyMode = null, isRecentlyChanged = false, selectable = false, selected = false, onSelectionChange, onClick, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onSetStatus, onArchiveTask, onUnarchive, onDelete, deleteActionTitle, taskReferences, onAssigneeChange, onWorkstreamChange, compressed = false, showStatusLabel = false, showDescription = true, showAttachments = true, showChecklistProgress = true, showLatestActivity = true, tintActivityBackground = false }) => {
|
|
27
75
|
const taskReference = getTaskReferenceDisplayState(task);
|
|
28
76
|
const taskReferenceLabel = taskReference.label || (taskReference.isProvisional ? 'Pending' : '');
|
|
29
77
|
const isProvisionalReference = taskReference.isProvisional;
|
|
@@ -71,7 +119,8 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
|
|
|
71
119
|
kind,
|
|
72
120
|
};
|
|
73
121
|
}, [assigneeLabel, assigneeOptions, task.assignee]);
|
|
74
|
-
const latestActivity = React.useMemo(() => getLatestTaskActivity(task), [task]);
|
|
122
|
+
const latestActivity = React.useMemo(() => task.latestActivity ?? getLatestTaskActivity(task), [task]);
|
|
123
|
+
const latestActivityActor = React.useMemo(() => resolveLatestActivityActor(latestActivity, assigneeOptions), [assigneeOptions, latestActivity]);
|
|
75
124
|
const resolvedReadOnlyMode = readOnlyMode || (task.isDeleted ? 'deleted' : (isArchived ? 'archived' : null));
|
|
76
125
|
const isReadOnly = resolvedReadOnlyMode !== null;
|
|
77
126
|
const deletedExpiryLabel = React.useMemo(() => {
|
|
@@ -99,7 +148,7 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
|
|
|
99
148
|
return undefined;
|
|
100
149
|
}, [assigneeOptions, initiativeReferenceLabel, taskInitiative, taskWorkstream, workstreamReferenceLabel]);
|
|
101
150
|
const latestActivityText = React.useMemo(() => summarizeTaskActivity(latestActivity, formatActivityFieldValue), [formatActivityFieldValue, latestActivity]);
|
|
102
|
-
const commentCount = React.useMemo(() => getTaskActivityItems(task).filter((item) => item.type === 'comment').length, [task]);
|
|
151
|
+
const commentCount = React.useMemo(() => task.commentCount ?? getTaskActivityItems(task).filter((item) => item.type === 'comment').length, [task]);
|
|
103
152
|
const cardClasses = [
|
|
104
153
|
cardStyles.taskItem,
|
|
105
154
|
task.status === 'on-hold' ? cardStyles.onHold : '',
|
|
@@ -196,10 +245,9 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
|
|
|
196
245
|
const toTitleCase = (str) => str.replace(/\b\w/g, t => t.toUpperCase());
|
|
197
246
|
const visibleTaxonomies = getVisibleTaxonomiesForTasks(taxonomies, [task]);
|
|
198
247
|
const checklistItems = task.checklistItems || [];
|
|
199
|
-
const checklistTotal = checklistItems.length;
|
|
200
|
-
const checklistCompleted =
|
|
201
|
-
? checklistItems.filter(item => item.isCompleted).length
|
|
202
|
-
: 0;
|
|
248
|
+
const checklistTotal = task.checklistCount ?? checklistItems.length;
|
|
249
|
+
const checklistCompleted = task.completedChecklistCount
|
|
250
|
+
?? (checklistTotal > 0 ? checklistItems.filter(item => item.isCompleted).length : 0);
|
|
203
251
|
const handleReferenceCopy = (event, label) => {
|
|
204
252
|
event.stopPropagation();
|
|
205
253
|
onCopyId?.(event, label);
|
|
@@ -232,6 +280,11 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
|
|
|
232
280
|
const cardCoverAttachment = (() => {
|
|
233
281
|
if (compressed || isOverlay || !task.cardCoverAssetId)
|
|
234
282
|
return null;
|
|
283
|
+
if (task.cardCoverAttachment
|
|
284
|
+
&& typeof task.cardCoverAttachment !== 'string'
|
|
285
|
+
&& task.cardCoverAttachment.assetId === task.cardCoverAssetId) {
|
|
286
|
+
return task.cardCoverAttachment;
|
|
287
|
+
}
|
|
235
288
|
return (task.attachments || []).find((attachment) => (typeof attachment !== 'string'
|
|
236
289
|
&& String(attachment.assetId || '').trim() === task.cardCoverAssetId
|
|
237
290
|
&& attachment.linkRole === 'image'
|
|
@@ -288,7 +341,7 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
|
|
|
288
341
|
}, children: _jsx("img", { src: cardCoverAttachment.path, alt: cardCoverAttachment.caption
|
|
289
342
|
|| cardCoverAttachment.displayName
|
|
290
343
|
|| cardCoverAttachment.originalFilename
|
|
291
|
-
|| task.title, loading: "lazy", decoding: "async", onError: () => setCardCoverLoadFailed(true) }) })), _jsxs("div", { className: cardStyles.taskContent, children: [/* Meta Badges moved to bottom */ isReadOnly && (_jsx("div", { className: `${cardStyles.taskMeta} ${cardStyles.taskMetaCompact}` })), _jsxs("div", { children: [_jsxs("div", { className: cardStyles.taskTitle, style: compressed ? { fontSize: '13px', lineHeight: '1.4' } : {}, children: [resolvedReadOnlyMode === 'archived' && '✓ ', resolvedReadOnlyMode === 'deleted' && 'Deleted: ', renderHighlighted(task.title, searchQuery)] }), deletedExpiryLabel && (_jsx("div", { className: cardStyles.deletedExpiry, title: "Trash items are permanently deleted 90 days after deletion.", children: deletedExpiryLabel })), task.description && !isReadOnly && !compressed && (_jsx("div", { className: cardStyles.taskDescription, children: _jsx(Markdown, { variant: "compact", taskReferences: taskReferences, children: task.description }) }))] }), cardAttachments.length > 0 && !isReadOnly && !compressed && (_jsxs("div", { className: cardStyles.attachmentThumbnails, children: [_jsx("div", { className: cardStyles.attachmentImageRow, children: cardAttachments.map((attachment, idx) => {
|
|
344
|
+
|| task.title, loading: "lazy", decoding: "async", onError: () => setCardCoverLoadFailed(true) }) })), _jsxs("div", { className: cardStyles.taskContent, children: [/* Meta Badges moved to bottom */ isReadOnly && (_jsx("div", { className: `${cardStyles.taskMeta} ${cardStyles.taskMetaCompact}` })), _jsxs("div", { children: [_jsxs("div", { className: cardStyles.taskTitle, style: compressed ? { fontSize: '13px', lineHeight: '1.4' } : {}, children: [resolvedReadOnlyMode === 'archived' && '✓ ', resolvedReadOnlyMode === 'deleted' && 'Deleted: ', renderHighlighted(task.title, searchQuery)] }), deletedExpiryLabel && (_jsx("div", { className: cardStyles.deletedExpiry, title: "Trash items are permanently deleted 90 days after deletion.", children: deletedExpiryLabel })), showDescription && task.description && !isReadOnly && !compressed && (_jsx("div", { className: cardStyles.taskDescription, children: _jsx(Markdown, { variant: "compact", taskReferences: taskReferences, children: task.description }) }))] }), showAttachments && cardAttachments.length > 0 && !isReadOnly && !compressed && (_jsxs("div", { className: cardStyles.attachmentThumbnails, children: [_jsx("div", { className: cardStyles.attachmentImageRow, children: cardAttachments.map((attachment, idx) => {
|
|
292
345
|
const path = typeof attachment === 'string' ? attachment : attachment.path;
|
|
293
346
|
if (!isImageAttachment(attachment))
|
|
294
347
|
return null;
|
|
@@ -319,7 +372,9 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
|
|
|
319
372
|
return;
|
|
320
373
|
window.open(path, '_blank');
|
|
321
374
|
}, title: name, "aria-label": `${name} ${fileType}`, style: { '--context-doc-accent': fileAccentColor }, children: [_jsx(DocumentTypeIcon, { label: fileType, accentColor: fileAccentColor, compact: true }), _jsx("span", { className: cardStyles.contextDocMain, children: _jsx("span", { className: cardStyles.contextDocName, children: name }) })] }, idx));
|
|
322
|
-
}) })] })), task.status === 'cancelled' && task.canceledReason && !compressed && (_jsxs("div", { className: cardStyles.taskCancellationReason, children: [_jsx("strong", { children: "Cancellation Reason:" }), " ", renderHighlighted(task.canceledReason, searchQuery)] })), (latestActivityText || commentCount > 0) && !isReadOnly && !compressed && (_jsxs("div", { className:
|
|
375
|
+
}) })] })), task.status === 'cancelled' && task.canceledReason && !compressed && (_jsxs("div", { className: cardStyles.taskCancellationReason, children: [_jsx("strong", { children: "Cancellation Reason:" }), " ", renderHighlighted(task.canceledReason, searchQuery)] })), showLatestActivity && (latestActivityText || commentCount > 0) && !isReadOnly && !compressed && (_jsxs("div", { className: `${cardStyles.taskLatestComment} ${tintActivityBackground && latestActivityActor?.color ? cardStyles.taskLatestCommentTinted : ''}`, "data-task-activity-summary": "true", style: latestActivityActor?.color
|
|
376
|
+
? { '--task-activity-accent': latestActivityActor.color }
|
|
377
|
+
: undefined, children: [_jsxs("div", { className: cardStyles.taskLatestCommentHeader, children: [_jsxs("div", { className: cardStyles.taskLatestActivityHeading, children: [_jsx("strong", { children: latestActivityText ? 'Latest Activity:' : 'Activity:' }), latestActivityText && latestActivityActor ? (_jsx("span", { className: cardStyles.taskLatestActivityActor, title: latestActivityActor.label, children: latestActivityActor.label })) : null] }), commentCount > 0 && (_jsxs("span", { className: `${styles.metaBadge} ${cardStyles.taskCommentCount}`, title: `${commentCount} ${commentCount === 1 ? 'comment' : 'comments'}`, "aria-label": `${commentCount} ${commentCount === 1 ? 'comment' : 'comments'}`, children: [_jsx(MessageSquare, { size: 12, "aria-hidden": "true" }), _jsx("span", { children: commentCount })] }))] }), latestActivityText && (_jsx("div", { className: cardStyles.taskLatestCommentText, title: latestActivityText, children: renderHighlighted(latestActivityText, searchQuery) }))] })), !isReadOnly && visibleTaxonomies.length > 0 && !compressed && (_jsx("div", { className: `${cardStyles.taskSpecialists} ${cardStyles.taxonomiesList}`, children: visibleTaxonomies.map(tax => {
|
|
323
378
|
const val = task.taxonomies?.[tax.id];
|
|
324
379
|
if (!val)
|
|
325
380
|
return null;
|
|
@@ -364,7 +419,7 @@ const TaskCardInner = ({ task, searchQuery = '', copiedId = null, taxonomies = [
|
|
|
364
419
|
const iconName = priorityConfig.icon || 'AlertCircle';
|
|
365
420
|
const IconComp = Icons[iconName] || Icons.AlertCircle;
|
|
366
421
|
return _jsx(IconComp, { size: 14 });
|
|
367
|
-
})() })), checklistTotal > 0 && (_jsxs("span", { className: styles.metaBadge, title: `${checklistCompleted}/${checklistTotal} checklist items complete`, style: {
|
|
422
|
+
})() })), showChecklistProgress && checklistTotal > 0 && (_jsxs("span", { className: styles.metaBadge, title: `${checklistCompleted}/${checklistTotal} checklist items complete`, style: {
|
|
368
423
|
color: 'var(--text-muted)',
|
|
369
424
|
backgroundColor: 'var(--bg-tertiary)',
|
|
370
425
|
borderColor: 'var(--border-primary)',
|
|
@@ -3,33 +3,42 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { Image, Loader2, MessageSquareText } from 'lucide-react';
|
|
4
4
|
import { fetchTaskforceApi } from '../../utils/taskforceApiClient';
|
|
5
5
|
import { dispatchOpenAnnotatedAttachment } from '../../utils/annotatedAttachments';
|
|
6
|
+
import { formatDate } from '../../utils/formatting';
|
|
6
7
|
import styles from './TaskImageReviews.module.css';
|
|
7
8
|
function reviewTitle(review) {
|
|
8
9
|
return String(review.title || '').trim() || review.image?.displayName || 'Image review';
|
|
9
10
|
}
|
|
10
11
|
export function TaskImageReviews({ taskId, taskReferenceLabel, workspaceId, apiBaseUrl }) {
|
|
11
12
|
const [reviews, setReviews] = useState(null);
|
|
13
|
+
const [loadError, setLoadError] = useState(false);
|
|
12
14
|
useEffect(() => {
|
|
13
15
|
let active = true;
|
|
14
16
|
setReviews(null);
|
|
17
|
+
setLoadError(false);
|
|
15
18
|
const params = new URLSearchParams({ workspaceId, taskId });
|
|
16
19
|
void fetchTaskforceApi(`/api/taskforce/annotated-attachments/sessions?${params.toString()}`, {
|
|
17
20
|
credentials: 'include',
|
|
18
21
|
}, apiBaseUrl)
|
|
19
|
-
.then(async (response) =>
|
|
22
|
+
.then(async (response) => {
|
|
23
|
+
if (!response.ok)
|
|
24
|
+
throw new Error('Failed to load image reviews.');
|
|
25
|
+
return response.json();
|
|
26
|
+
})
|
|
20
27
|
.then((data) => {
|
|
21
28
|
if (active)
|
|
22
29
|
setReviews(Array.isArray(data?.sessions) ? data.sessions : []);
|
|
23
30
|
})
|
|
24
31
|
.catch(() => {
|
|
25
|
-
if (active)
|
|
32
|
+
if (active) {
|
|
26
33
|
setReviews([]);
|
|
34
|
+
setLoadError(true);
|
|
35
|
+
}
|
|
27
36
|
});
|
|
28
37
|
return () => { active = false; };
|
|
29
38
|
}, [apiBaseUrl, taskId, workspaceId]);
|
|
30
|
-
if (reviews !== null && reviews.length === 0)
|
|
39
|
+
if (reviews !== null && reviews.length === 0 && !loadError)
|
|
31
40
|
return null;
|
|
32
|
-
return (_jsx("section", { className: styles.group, "aria-label": "Image reviews", children: reviews === null ? (_jsxs("div", { className: styles.loading, "aria-live": "polite", children: [_jsx(Loader2, { size: 14 }), " Loading image reviews"] })) : (_jsx("div", { className: styles.list, children: reviews.map((review) => {
|
|
41
|
+
return (_jsx("section", { className: styles.group, "aria-label": "Image reviews", children: reviews === null ? (_jsxs("div", { className: styles.loading, "aria-live": "polite", children: [_jsx(Loader2, { size: 14 }), " Loading image reviews"] })) : loadError ? (_jsx("div", { className: styles.loading, role: "status", children: "Unable to load image reviews." })) : (_jsx("div", { className: styles.list, children: reviews.map((review) => {
|
|
33
42
|
const image = review.image;
|
|
34
43
|
const markerCount = review.annotations.length;
|
|
35
44
|
const canOpen = Boolean(image?.assetId && image.apiUrl);
|
|
@@ -42,6 +51,6 @@ export function TaskImageReviews({ taskId, taskReferenceLabel, workspaceId, apiB
|
|
|
42
51
|
displayName: image.displayName,
|
|
43
52
|
referenceLabel: image.referenceLabel || undefined,
|
|
44
53
|
}, { taskId, taskReferenceLabel, sessionId: review.id });
|
|
45
|
-
}, children: [_jsx("span", { className: styles.icon, children: _jsx(Image, { size: 14 }) }), _jsxs("span", { className: styles.copy, children: [_jsx("span", { className: styles.kindLabel, children: "Image review session" }), _jsx("span", { className: `${styles.title} tf-heading-card`, children: reviewTitle(review) }), _jsxs("span", { className: "tf-text-meta", children: [markerCount, " ", markerCount === 1 ? 'marker' : 'markers'] })] }), _jsx(MessageSquareText, { className: styles.openIcon, size: 14, "aria-hidden": "true" })] }, review.id));
|
|
54
|
+
}, children: [_jsx("span", { className: styles.icon, children: _jsx(Image, { size: 14 }) }), _jsxs("span", { className: styles.copy, children: [_jsx("span", { className: styles.kindLabel, children: "Image review session" }), _jsx("span", { className: `${styles.title} tf-heading-card`, children: reviewTitle(review) }), _jsxs("span", { className: "tf-text-meta", children: [[image?.referenceLabel, image?.displayName].filter(Boolean).join(' · ') || 'Image', review.updatedAt ? ` · Updated ${formatDate(review.updatedAt, { month: 'short', day: 'numeric', year: 'numeric' })}` : ''] }), _jsxs("span", { className: "tf-text-meta", children: [markerCount, " ", markerCount === 1 ? 'marker' : 'markers'] })] }), _jsx(MessageSquareText, { className: styles.openIcon, size: 14, "aria-hidden": "true" })] }, review.id));
|
|
46
55
|
}) })) }));
|
|
47
56
|
}
|
|
@@ -64,6 +64,11 @@ interface TaskKanbanProps {
|
|
|
64
64
|
onAssignTaskToWorkstream?: (taskId: string, workstreamId: string | null) => boolean | void | Promise<boolean | void>;
|
|
65
65
|
onAssignWorkstreamToInitiative?: (workstreamId: string, initiativeId: string | null) => boolean | void | Promise<boolean | void>;
|
|
66
66
|
showTaskCardStatusLabel?: boolean;
|
|
67
|
+
showTaskCardDescription?: boolean;
|
|
68
|
+
showTaskCardAttachments?: boolean;
|
|
69
|
+
showTaskCardChecklistProgress?: boolean;
|
|
70
|
+
showTaskCardLatestActivity?: boolean;
|
|
71
|
+
tintTaskCardActivityBackground?: boolean;
|
|
67
72
|
workstreams?: WorkstreamItem[];
|
|
68
73
|
initiatives?: InitiativeItem[];
|
|
69
74
|
workspaceId?: string | null;
|
|
@@ -71,5 +76,5 @@ interface TaskKanbanProps {
|
|
|
71
76
|
selectedDeletedRecordIds?: Set<string>;
|
|
72
77
|
onDeletedSelectionChange?: (deletedRecordId: string, selected: boolean) => void;
|
|
73
78
|
}
|
|
74
|
-
export declare function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery, copiedId, taxonomies, types, priorities, assigneeOptions, onUpdateTask, onTaskClick, taskReferences, 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, workspaceId, deletedTaskRecordByTaskId, selectedDeletedRecordIds, onDeletedSelectionChange, }: TaskKanbanProps): import("react/jsx-runtime").JSX.Element;
|
|
79
|
+
export declare function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery, copiedId, taxonomies, types, priorities, assigneeOptions, onUpdateTask, onTaskClick, taskReferences, 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, showTaskCardDescription, showTaskCardAttachments, showTaskCardChecklistProgress, showTaskCardLatestActivity, tintTaskCardActivityBackground, workstreams, initiatives, workspaceId, deletedTaskRecordByTaskId, selectedDeletedRecordIds, onDeletedSelectionChange, }: TaskKanbanProps): import("react/jsx-runtime").JSX.Element;
|
|
75
80
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React, { useMemo, useState, useRef, useEffect, useCallback } from 'react';
|
|
2
|
+
import React, { startTransition, useMemo, useState, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
import { DndContext, DragOverlay, closestCorners, KeyboardSensor, PointerSensor, useSensor, useSensors, pointerWithin, defaultDropAnimationSideEffects, useDroppable } from '@dnd-kit/core';
|
|
4
4
|
import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
|
|
5
5
|
import { CSS } from '@dnd-kit/utilities';
|
|
@@ -14,7 +14,107 @@ import { AssigneeAvatar } from '../ui/AssigneeIdentity';
|
|
|
14
14
|
const EXPIRED_CARD_ID_PREFIX = 'expired::';
|
|
15
15
|
const EMPTY_DELETED_RECORD_MAP = new Map();
|
|
16
16
|
const EMPTY_SELECTED_DELETED_IDS = new Set();
|
|
17
|
-
|
|
17
|
+
const KANBAN_VIRTUALIZATION_TASK_THRESHOLD = 250;
|
|
18
|
+
const KANBAN_VIRTUALIZATION_COLUMN_THRESHOLD = 24;
|
|
19
|
+
const KANBAN_VIRTUAL_CARD_ESTIMATE_PX = 180;
|
|
20
|
+
const KANBAN_VIRTUAL_CARD_GAP_PX = 8;
|
|
21
|
+
const KANBAN_VIRTUAL_OVERSCAN_PX = 600;
|
|
22
|
+
const KANBAN_VIRTUAL_FALLBACK_VIEWPORT_PX = 720;
|
|
23
|
+
function useVirtualKanbanRows({ tasks, enabled, scrollRef, initialScrollTop, onScrollTopChange, }) {
|
|
24
|
+
const measuredHeightsRef = useRef(new Map());
|
|
25
|
+
const [measurementVersion, setMeasurementVersion] = useState(0);
|
|
26
|
+
const [viewport, setViewport] = useState(() => ({
|
|
27
|
+
scrollTop: initialScrollTop,
|
|
28
|
+
height: KANBAN_VIRTUAL_FALLBACK_VIEWPORT_PX,
|
|
29
|
+
}));
|
|
30
|
+
const measureRow = useCallback((taskId, height) => {
|
|
31
|
+
if (!Number.isFinite(height) || height <= 0)
|
|
32
|
+
return;
|
|
33
|
+
const normalizedHeight = Math.ceil(height);
|
|
34
|
+
const previousHeight = measuredHeightsRef.current.get(taskId) ?? KANBAN_VIRTUAL_CARD_ESTIMATE_PX;
|
|
35
|
+
if (previousHeight === normalizedHeight)
|
|
36
|
+
return;
|
|
37
|
+
const taskIndex = tasks.findIndex((task) => task.id === taskId);
|
|
38
|
+
let rowTop = 0;
|
|
39
|
+
for (let index = 0; index < taskIndex; index += 1) {
|
|
40
|
+
rowTop += measuredHeightsRef.current.get(tasks[index].id) ?? KANBAN_VIRTUAL_CARD_ESTIMATE_PX;
|
|
41
|
+
rowTop += KANBAN_VIRTUAL_CARD_GAP_PX;
|
|
42
|
+
}
|
|
43
|
+
measuredHeightsRef.current.set(taskId, normalizedHeight);
|
|
44
|
+
const element = scrollRef.current;
|
|
45
|
+
const heightDelta = normalizedHeight - previousHeight;
|
|
46
|
+
const previousRowBottom = rowTop + previousHeight;
|
|
47
|
+
if (element && taskIndex >= 0 && previousRowBottom <= element.scrollTop && heightDelta !== 0) {
|
|
48
|
+
const scrollTop = Math.max(0, element.scrollTop + heightDelta);
|
|
49
|
+
element.scrollTop = scrollTop;
|
|
50
|
+
setViewport((current) => ({ ...current, scrollTop }));
|
|
51
|
+
onScrollTopChange(scrollTop);
|
|
52
|
+
}
|
|
53
|
+
setMeasurementVersion((version) => version + 1);
|
|
54
|
+
}, [onScrollTopChange, scrollRef, tasks]);
|
|
55
|
+
const layout = useMemo(() => {
|
|
56
|
+
let top = 0;
|
|
57
|
+
const rows = tasks.map((task, index) => {
|
|
58
|
+
const row = { task, index, top };
|
|
59
|
+
const height = measuredHeightsRef.current.get(task.id) ?? KANBAN_VIRTUAL_CARD_ESTIMATE_PX;
|
|
60
|
+
top += height;
|
|
61
|
+
if (index < tasks.length - 1)
|
|
62
|
+
top += KANBAN_VIRTUAL_CARD_GAP_PX;
|
|
63
|
+
return row;
|
|
64
|
+
});
|
|
65
|
+
return { rows, totalHeight: top };
|
|
66
|
+
}, [measurementVersion, tasks]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const element = scrollRef.current;
|
|
69
|
+
if (!element)
|
|
70
|
+
return;
|
|
71
|
+
if (initialScrollTop > 0 && element.scrollTop !== initialScrollTop) {
|
|
72
|
+
element.scrollTop = initialScrollTop;
|
|
73
|
+
}
|
|
74
|
+
const updateViewport = (deferred = false) => {
|
|
75
|
+
const scrollTop = Math.max(0, element.scrollTop);
|
|
76
|
+
const height = element.clientHeight || KANBAN_VIRTUAL_FALLBACK_VIEWPORT_PX;
|
|
77
|
+
const commitViewport = () => setViewport((current) => (current.scrollTop === scrollTop && current.height === height
|
|
78
|
+
? current
|
|
79
|
+
: { scrollTop, height }));
|
|
80
|
+
if (deferred)
|
|
81
|
+
startTransition(commitViewport);
|
|
82
|
+
else
|
|
83
|
+
commitViewport();
|
|
84
|
+
onScrollTopChange(scrollTop);
|
|
85
|
+
};
|
|
86
|
+
const handleScroll = () => updateViewport(true);
|
|
87
|
+
const handleLayoutChange = () => updateViewport(false);
|
|
88
|
+
handleLayoutChange();
|
|
89
|
+
element.addEventListener('scroll', handleScroll, { passive: true });
|
|
90
|
+
const resizeObserver = typeof ResizeObserver === 'undefined'
|
|
91
|
+
? null
|
|
92
|
+
: new ResizeObserver(handleLayoutChange);
|
|
93
|
+
resizeObserver?.observe(element);
|
|
94
|
+
window.addEventListener('resize', handleLayoutChange);
|
|
95
|
+
return () => {
|
|
96
|
+
element.removeEventListener('scroll', handleScroll);
|
|
97
|
+
resizeObserver?.disconnect();
|
|
98
|
+
window.removeEventListener('resize', handleLayoutChange);
|
|
99
|
+
};
|
|
100
|
+
}, [initialScrollTop, onScrollTopChange, scrollRef]);
|
|
101
|
+
const visibleRows = useMemo(() => {
|
|
102
|
+
if (!enabled)
|
|
103
|
+
return layout.rows;
|
|
104
|
+
const minimumTop = Math.max(0, viewport.scrollTop - KANBAN_VIRTUAL_OVERSCAN_PX);
|
|
105
|
+
const maximumBottom = viewport.scrollTop + viewport.height + KANBAN_VIRTUAL_OVERSCAN_PX;
|
|
106
|
+
return layout.rows.filter((row) => {
|
|
107
|
+
const height = measuredHeightsRef.current.get(row.task.id) ?? KANBAN_VIRTUAL_CARD_ESTIMATE_PX;
|
|
108
|
+
return row.top + height >= minimumTop && row.top <= maximumBottom;
|
|
109
|
+
});
|
|
110
|
+
}, [enabled, layout.rows, measurementVersion, viewport.height, viewport.scrollTop]);
|
|
111
|
+
return {
|
|
112
|
+
measureRow,
|
|
113
|
+
rows: visibleRows,
|
|
114
|
+
totalHeight: layout.totalHeight,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery, copiedId, taxonomies, types, priorities, assigneeOptions = [], onUpdateTask, onTaskClick, taskReferences, 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 = false, showTaskCardDescription = true, showTaskCardAttachments = true, showTaskCardChecklistProgress = true, showTaskCardLatestActivity = true, tintTaskCardActivityBackground = false, workstreams = [], initiatives = [], workspaceId = null, deletedTaskRecordByTaskId = EMPTY_DELETED_RECORD_MAP, selectedDeletedRecordIds = EMPTY_SELECTED_DELETED_IDS, onDeletedSelectionChange, }) {
|
|
18
118
|
const [activeId, setActiveId] = useState(null);
|
|
19
119
|
const [isHeaderPanning, setIsHeaderPanning] = useState(false);
|
|
20
120
|
const [headerPanEnabled, setHeaderPanEnabled] = useState(false);
|
|
@@ -33,6 +133,10 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
33
133
|
const isTaskCardId = useCallback((id) => taskIdSet.has(getTaskIdFromCardId(id)), [taskIdSet, getTaskIdFromCardId]);
|
|
34
134
|
const scheduleFilteredTaskIdSet = useMemo(() => new Set(scheduleFilteredTaskIds || []), [scheduleFilteredTaskIds]);
|
|
35
135
|
const lastPlanningOverRef = useRef(null);
|
|
136
|
+
const columnScrollTopRef = useRef(new Map());
|
|
137
|
+
const rememberColumnScrollTop = useCallback((columnId, scrollTop) => {
|
|
138
|
+
columnScrollTopRef.current.set(columnId, scrollTop);
|
|
139
|
+
}, []);
|
|
36
140
|
const resolveActiveDragMeta = useCallback((activeLike) => {
|
|
37
141
|
const current = activeLike?.data?.current || {};
|
|
38
142
|
const activeId = String(activeLike?.id || '');
|
|
@@ -317,6 +421,9 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
317
421
|
}, [tasks, effectiveKanbanColumns, groupBy, columnLookup, scheduleFilteredTaskIdSet, todayDateOnly, sortBy, sortOrder, taxonomies]);
|
|
318
422
|
const activeTaskId = activeId ? getTaskIdFromCardId(activeId) : null;
|
|
319
423
|
const activeTaskOverlay = activeTaskId ? tasks.find(t => t.id === activeTaskId) : null;
|
|
424
|
+
const virtualizeLargeBoard = tasks.length > KANBAN_VIRTUALIZATION_TASK_THRESHOLD
|
|
425
|
+
&& groupBy !== 'schedule'
|
|
426
|
+
&& !readOnlyMode;
|
|
320
427
|
// Scroll Syncing Refs and Logic
|
|
321
428
|
const containerRef = useRef(null);
|
|
322
429
|
const topScrollRef = useRef(null);
|
|
@@ -762,6 +869,11 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
762
869
|
resolveTaskHierarchy,
|
|
763
870
|
groupBy,
|
|
764
871
|
showStatusLabel: showTaskCardStatusLabel,
|
|
872
|
+
showDescription: showTaskCardDescription,
|
|
873
|
+
showAttachments: showTaskCardAttachments,
|
|
874
|
+
showChecklistProgress: showTaskCardChecklistProgress,
|
|
875
|
+
showLatestActivity: showTaskCardLatestActivity,
|
|
876
|
+
tintActivityBackground: tintTaskCardActivityBackground,
|
|
765
877
|
deletedTaskRecordByTaskId,
|
|
766
878
|
selectedDeletedRecordIds,
|
|
767
879
|
onDeletedSelectionChange,
|
|
@@ -788,7 +900,12 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
788
900
|
resolveTaskHierarchy,
|
|
789
901
|
searchQuery,
|
|
790
902
|
selectedDeletedRecordIds,
|
|
903
|
+
showTaskCardAttachments,
|
|
904
|
+
showTaskCardChecklistProgress,
|
|
905
|
+
showTaskCardDescription,
|
|
791
906
|
showTaskCardStatusLabel,
|
|
907
|
+
showTaskCardLatestActivity,
|
|
908
|
+
tintTaskCardActivityBackground,
|
|
792
909
|
taskReferences,
|
|
793
910
|
taxonomies,
|
|
794
911
|
types,
|
|
@@ -815,13 +932,13 @@ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery
|
|
|
815
932
|
return null;
|
|
816
933
|
return (_jsx(KanbanColumn, { id: String(column.value), title: column.label, color: column.color, icon: column.icon, assigneeOption: groupBy === 'assignee'
|
|
817
934
|
? assigneeOptions.find((option) => String(option.value) === String(column.value))
|
|
818
|
-
: undefined, tasks: columnTasks, activeId: activeId, activeTaskId: activeTaskId, totalCount: columnTotals[String(column.value)], onAddTask: () => onAddTaskToColumn?.(groupBy, column.value), onTaskClick: onTaskClick, commonCardProps: commonCardProps, changedTaskIdSet: changedTaskIdSet, collapseEmpty: emptyColumnMode === 'collapse', compressed: compressed, onHeaderPointerDown: handleHeaderPointerDown, isHeaderPanning: isHeaderPanning, headerPanEnabled: headerPanEnabled, stickyLeft: stickyLeft, stickyZIndex: stickyZIndex, isPast: !!column.isPast, isSelected: !!column.isSelected, isWeekend: !!column.isWeekend, disableSorting: false, scheduleDate: groupBy === 'schedule' ? scheduleContext.dates[String(column.value)] : undefined, onScheduleDaySelected: onScheduleDaySelected }, String(column.value)));
|
|
935
|
+
: undefined, tasks: columnTasks, activeId: activeId, activeTaskId: activeTaskId, totalCount: columnTotals[String(column.value)], onAddTask: () => onAddTaskToColumn?.(groupBy, column.value), onTaskClick: onTaskClick, commonCardProps: commonCardProps, changedTaskIdSet: changedTaskIdSet, collapseEmpty: emptyColumnMode === 'collapse', compressed: compressed, onHeaderPointerDown: handleHeaderPointerDown, isHeaderPanning: isHeaderPanning, headerPanEnabled: headerPanEnabled, stickyLeft: stickyLeft, stickyZIndex: stickyZIndex, isPast: !!column.isPast, isSelected: !!column.isSelected, isWeekend: !!column.isWeekend, disableSorting: false, scheduleDate: groupBy === 'schedule' ? scheduleContext.dates[String(column.value)] : undefined, onScheduleDaySelected: onScheduleDaySelected, virtualize: virtualizeLargeBoard, initialScrollTop: columnScrollTopRef.current.get(String(column.value)) || 0, onScrollTopChange: rememberColumnScrollTop }, String(column.value)));
|
|
819
936
|
}) })] }), createPortal(_jsx(DragOverlay, { dropAnimation: dropAnimation, children: activeTaskOverlay ? ((() => {
|
|
820
937
|
const { taskWorkstream, taskInitiative } = resolveTaskHierarchy(activeTaskOverlay);
|
|
821
938
|
return (_jsx(TaskCard, { task: activeTaskOverlay, taskWorkstream: taskWorkstream, taskInitiative: taskInitiative, isOverlay: true, ...commonCardProps, isArchived: activeTaskOverlay.isArchived, categories: categories, compressed: compressed, isRecentlyChanged: changedTaskIdSet.has(activeTaskOverlay.id) }));
|
|
822
939
|
})()) : null }), document.body)] }));
|
|
823
940
|
}
|
|
824
|
-
const KanbanColumn = React.memo(function KanbanColumn({ id, title, color, icon, assigneeOption, tasks, activeId = null, activeTaskId = null, totalCount, onAddTask, onTaskClick, commonCardProps, changedTaskIdSet, collapseEmpty, compressed, onHeaderPointerDown, isHeaderPanning = false, headerPanEnabled = false, stickyLeft, stickyZIndex, isPast = false, isSelected = false, isWeekend = false, disableSorting = false, scheduleDate, onScheduleDaySelected }) {
|
|
941
|
+
const KanbanColumn = React.memo(function KanbanColumn({ id, title, color, icon, assigneeOption, tasks, activeId = null, activeTaskId = null, totalCount, onAddTask, onTaskClick, commonCardProps, changedTaskIdSet, collapseEmpty, compressed, onHeaderPointerDown, isHeaderPanning = false, headerPanEnabled = false, stickyLeft, stickyZIndex, isPast = false, isSelected = false, isWeekend = false, disableSorting = false, scheduleDate, onScheduleDaySelected, virtualize = false, initialScrollTop = 0, onScrollTopChange, }) {
|
|
825
942
|
const { setNodeRef } = useDroppable({
|
|
826
943
|
id: id,
|
|
827
944
|
data: {
|
|
@@ -832,6 +949,19 @@ const KanbanColumn = React.memo(function KanbanColumn({ id, title, color, icon,
|
|
|
832
949
|
const visibleTasks = activeTaskId ? tasks.filter((t) => t.id !== activeTaskId) : tasks;
|
|
833
950
|
const isSticky = typeof stickyLeft === 'number';
|
|
834
951
|
const scrollRef = useRef(null);
|
|
952
|
+
const virtualizationEnabled = virtualize
|
|
953
|
+
&& !disableSorting
|
|
954
|
+
&& visibleTasks.length > KANBAN_VIRTUALIZATION_COLUMN_THRESHOLD;
|
|
955
|
+
const handleScrollTopChange = useCallback((scrollTop) => {
|
|
956
|
+
onScrollTopChange?.(id, scrollTop);
|
|
957
|
+
}, [id, onScrollTopChange]);
|
|
958
|
+
const virtualRows = useVirtualKanbanRows({
|
|
959
|
+
tasks: visibleTasks,
|
|
960
|
+
enabled: virtualizationEnabled,
|
|
961
|
+
scrollRef,
|
|
962
|
+
initialScrollTop,
|
|
963
|
+
onScrollTopChange: handleScrollTopChange,
|
|
964
|
+
});
|
|
835
965
|
const toSortableId = (taskId) => (id === 'expired' ? `${EXPIRED_CARD_ID_PREFIX}${taskId}` : taskId);
|
|
836
966
|
const handleColumnClick = (event) => {
|
|
837
967
|
if (!scheduleDate || !onScheduleDaySelected)
|
|
@@ -847,13 +977,29 @@ const KanbanColumn = React.memo(function KanbanColumn({ id, title, color, icon,
|
|
|
847
977
|
const IconComp = icon && IconMap[icon] ? IconMap[icon] : Icons.Folder;
|
|
848
978
|
const resolvedColor = resolveColor(color) || 'var(--color-purple)';
|
|
849
979
|
return _jsx(IconComp, { size: 16, style: { color: resolvedColor } });
|
|
850
|
-
})(), !isCollapsed && _jsx("span", { style: { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: title })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 }, children: [(!isCollapsed || totalCount > 0) && (_jsx("span", { className: styles.kanbanCount, children: isCollapsed ? totalCount : `${tasks.length} / ${totalCount}` })), !commonCardProps.readOnlyMode && (_jsx("button", { className: "tf-control-icon tf-control-icon-compact", onClick: (e) => { e.stopPropagation(); onAddTask(); }, title: `Add task to ${title}`, "aria-label": `Add task to ${title}`, "data-no-header-pan": "true", children: _jsx(Icons.Plus, { size: 14 }) }))] })] }), _jsx("div", { className: styles.kanbanDroppable, children: _jsx("div", { className: `${styles.kanbanDroppableScroll} ${styles.appScrollbar} tf-scrollbar`, ref: scrollRef, children: disableSorting ? (visibleTasks.map((task) => (_jsx("div", { className: styles.kanbanCardWrapper, children: (() => {
|
|
980
|
+
})(), !isCollapsed && _jsx("span", { style: { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: title })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 }, children: [(!isCollapsed || totalCount > 0) && (_jsx("span", { className: styles.kanbanCount, children: isCollapsed ? totalCount : `${tasks.length} / ${totalCount}` })), !commonCardProps.readOnlyMode && (_jsx("button", { className: "tf-control-icon tf-control-icon-compact", onClick: (e) => { e.stopPropagation(); onAddTask(); }, title: `Add task to ${title}`, "aria-label": `Add task to ${title}`, "data-no-header-pan": "true", children: _jsx(Icons.Plus, { size: 14 }) }))] })] }), _jsx("div", { className: styles.kanbanDroppable, children: _jsx("div", { className: `${styles.kanbanDroppableScroll} ${virtualizationEnabled ? styles.kanbanDroppableScrollVirtualized : ''} ${styles.appScrollbar} tf-scrollbar`, ref: scrollRef, "data-kanban-column-scroll": id, children: disableSorting ? (visibleTasks.map((task) => (_jsx("div", { className: styles.kanbanCardWrapper, children: (() => {
|
|
851
981
|
const { taskWorkstream, taskInitiative } = commonCardProps.resolveTaskHierarchy?.(task) || {};
|
|
852
982
|
const deletedRecord = commonCardProps.deletedTaskRecordByTaskId?.get(task.id);
|
|
853
983
|
return (_jsx(TaskCard, { task: task, taskWorkstream: taskWorkstream, taskInitiative: taskInitiative, onClick: onTaskClick, ...commonCardProps, isArchived: task.isArchived, readOnlyMode: commonCardProps.readOnlyMode || null, compressed: compressed, isRecentlyChanged: false, selectable: Boolean(deletedRecord && commonCardProps.onDeletedSelectionChange), selected: Boolean(deletedRecord && commonCardProps.selectedDeletedRecordIds?.has(deletedRecord.id)), onSelectionChange: deletedRecord
|
|
854
984
|
? (selected) => commonCardProps.onDeletedSelectionChange?.(deletedRecord.id, selected)
|
|
855
985
|
: undefined }));
|
|
856
|
-
})() }, task.id)))) : (_jsx(SortableContext, { id: id, items: visibleTasks.map((t) => toSortableId(t.id)), strategy: verticalListSortingStrategy, children: visibleTasks.map((task) => (_jsx(SortableTaskCard, { sortableId: toSortableId(task.id), task: task, onClick: onTaskClick, commonCardProps: commonCardProps, compressed: compressed, isRecentlyChanged: changedTaskIdSet.has(task.id) }, toSortableId(task.id)))) })) }) })] }));
|
|
986
|
+
})() }, task.id)))) : (_jsx(SortableContext, { id: id, items: visibleTasks.map((t) => toSortableId(t.id)), strategy: verticalListSortingStrategy, children: virtualizationEnabled ? (_jsx("div", { className: styles.kanbanVirtualCanvas, style: { height: `${virtualRows.totalHeight}px` }, children: virtualRows.rows.map((row) => (_jsx(VirtualizedSortableTaskCard, { top: row.top, sortableId: toSortableId(row.task.id), task: row.task, onClick: onTaskClick, commonCardProps: commonCardProps, compressed: compressed, isRecentlyChanged: changedTaskIdSet.has(row.task.id), onMeasure: virtualRows.measureRow }, toSortableId(row.task.id)))) })) : visibleTasks.map((task) => (_jsx(SortableTaskCard, { sortableId: toSortableId(task.id), task: task, onClick: onTaskClick, commonCardProps: commonCardProps, compressed: compressed, isRecentlyChanged: changedTaskIdSet.has(task.id) }, toSortableId(task.id)))) })) }) })] }));
|
|
987
|
+
});
|
|
988
|
+
const VirtualizedSortableTaskCard = React.memo(function VirtualizedSortableTaskCard({ top, onMeasure, ...cardProps }) {
|
|
989
|
+
const rowRef = useRef(null);
|
|
990
|
+
useEffect(() => {
|
|
991
|
+
const element = rowRef.current;
|
|
992
|
+
if (!element)
|
|
993
|
+
return;
|
|
994
|
+
const reportHeight = () => onMeasure(cardProps.task.id, element.getBoundingClientRect().height);
|
|
995
|
+
reportHeight();
|
|
996
|
+
if (typeof ResizeObserver === 'undefined')
|
|
997
|
+
return;
|
|
998
|
+
const observer = new ResizeObserver(reportHeight);
|
|
999
|
+
observer.observe(element);
|
|
1000
|
+
return () => observer.disconnect();
|
|
1001
|
+
}, [cardProps.task.id, onMeasure]);
|
|
1002
|
+
return (_jsx("div", { ref: rowRef, className: styles.kanbanVirtualRow, style: { top: `${top}px` }, "data-kanban-virtual-index": cardProps.task.id, children: _jsx(SortableTaskCard, { ...cardProps }) }));
|
|
857
1003
|
});
|
|
858
1004
|
const SortableTaskCard = React.memo(function SortableTaskCard({ sortableId, task, onClick, commonCardProps, compressed, isRecentlyChanged = false }) {
|
|
859
1005
|
const dragDisabled = Boolean(commonCardProps?.readOnlyMode);
|
|
@@ -45,6 +45,11 @@ interface TaskListProps {
|
|
|
45
45
|
copiedId: string | null;
|
|
46
46
|
recentlyChangedTaskIds?: string[];
|
|
47
47
|
showTaskCardStatusLabel?: boolean;
|
|
48
|
+
showTaskCardDescription?: boolean;
|
|
49
|
+
showTaskCardAttachments?: boolean;
|
|
50
|
+
showTaskCardChecklistProgress?: boolean;
|
|
51
|
+
showTaskCardLatestActivity?: boolean;
|
|
52
|
+
tintTaskCardActivityBackground?: boolean;
|
|
48
53
|
deletedTaskRecordByTaskId?: Map<string, DeletedTaskRecord>;
|
|
49
54
|
selectedDeletedRecordIds?: Set<string>;
|
|
50
55
|
onDeletedSelectionChange?: (deletedRecordId: string, selected: boolean) => void;
|