@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
package/dist/core/types.d.ts
CHANGED
|
@@ -176,6 +176,12 @@ export interface TaskFile {
|
|
|
176
176
|
jsonBackupEnabled?: boolean;
|
|
177
177
|
manualComplexityEnabled?: boolean;
|
|
178
178
|
checklistDropdownEnabled?: boolean;
|
|
179
|
+
showTaskCardStatusLabel?: boolean;
|
|
180
|
+
showTaskCardDescription?: boolean;
|
|
181
|
+
showTaskCardAttachments?: boolean;
|
|
182
|
+
showTaskCardChecklistProgress?: boolean;
|
|
183
|
+
showTaskCardLatestActivity?: boolean;
|
|
184
|
+
tintTaskCardActivityBackground?: boolean;
|
|
179
185
|
taxonomies?: any[];
|
|
180
186
|
};
|
|
181
187
|
}
|
|
@@ -6,7 +6,7 @@ export declare function useDurableLocalOutboxDrain(input: {
|
|
|
6
6
|
workspaceId: string;
|
|
7
7
|
mutationFingerprint: string;
|
|
8
8
|
authorityScope: object;
|
|
9
|
-
remoteServices: Pick<WorkspaceSyncEngineRemoteServices, 'pullWorkspaceContent' | 'abandonTransferReceipts' | '
|
|
9
|
+
remoteServices: Pick<WorkspaceSyncEngineRemoteServices, 'pullWorkspaceContent' | 'abandonTransferReceipts' | 'buildIndependentContentApplyCompletionGroup'> | null;
|
|
10
10
|
localServices: Pick<WorkspaceSyncEngineLocalServices, 'applyV2Batch'>;
|
|
11
11
|
isAuthorityCurrent?: (authorityScope: object) => boolean;
|
|
12
12
|
onHydratedEventIds?: (eventIds: string[]) => void;
|
|
@@ -104,7 +104,7 @@ export function useDurableTaskCreateMutation(input) {
|
|
|
104
104
|
clientRef.current = input.client || createLocalTaskCreateClient();
|
|
105
105
|
const participant = await getParticipant(workspaceId);
|
|
106
106
|
const selected = selectTaskCreateActivationInput(candidate);
|
|
107
|
-
const participantId = selected
|
|
107
|
+
const participantId = input.enabled && selected
|
|
108
108
|
? participant.prepareForDraft(await fingerprintTaskDraft(selected.localTask))
|
|
109
109
|
: participant.participantId;
|
|
110
110
|
let result;
|
|
@@ -2,6 +2,7 @@ import type { TaskItem } from '../../types.js';
|
|
|
2
2
|
import { createLocalTaskMetadataClient, type LocalTaskMetadataMutationResult } from '../../sync/v3/localTaskMetadataClient.js';
|
|
3
3
|
export type DurableTaskMetadataMutationAdapter = (taskId: string, patch: Partial<TaskItem>, options?: {
|
|
4
4
|
saveSource?: 'manual' | 'autosave';
|
|
5
|
+
expectedDetailRevision?: string;
|
|
5
6
|
}) => Promise<LocalTaskMetadataMutationResult | null>;
|
|
6
7
|
type LocalTaskMetadataClient = ReturnType<typeof createLocalTaskMetadataClient>;
|
|
7
8
|
export declare function useDurableTaskMetadataMutation(input: {
|
|
@@ -17,6 +17,7 @@ export function useDurableTaskMetadataMutation(input) {
|
|
|
17
17
|
entityId,
|
|
18
18
|
patch: patch,
|
|
19
19
|
...(options?.saveSource ? { saveSource: options.saveSource } : {}),
|
|
20
|
+
...(options?.expectedDetailRevision ? { expectedDetailRevision: options.expectedDetailRevision } : {}),
|
|
20
21
|
isAuthenticated: input.isAuthenticated,
|
|
21
22
|
dispatchEnabled: input.dispatchEnabled,
|
|
22
23
|
});
|
|
@@ -6,6 +6,7 @@ export type DurableTaskStatusMutationAdapter = (taskId: string, payload: {
|
|
|
6
6
|
assigneeIntent: DurableTaskStatusAssigneeIntent;
|
|
7
7
|
canceledReason: string | null;
|
|
8
8
|
metadataPatch?: Partial<TaskItem>;
|
|
9
|
+
expectedDetailRevision?: string;
|
|
9
10
|
}) => Promise<LocalTaskStatusMutationResult | null>;
|
|
10
11
|
type LocalTaskStatusClient = ReturnType<typeof createLocalTaskStatusClient>;
|
|
11
12
|
export declare function useDurableTaskStatusMutation(input: {
|
|
@@ -44,6 +44,7 @@ export async function isConclusiveTaskHttpResponse(response) {
|
|
|
44
44
|
'TASK_ATTACHMENT_LINKS_INVALID',
|
|
45
45
|
'TASK_COMMENT_INVALID',
|
|
46
46
|
'TASK_COMMENT_IDENTITY_CONFLICT',
|
|
47
|
+
'TASK_DETAIL_REVISION_CONFLICT',
|
|
47
48
|
]).has(code);
|
|
48
49
|
}
|
|
49
50
|
function stableTaskCreateCandidate(candidate) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface UseCloudTaskSearchInput {
|
|
2
|
+
query: string;
|
|
3
|
+
runtimeMode: 'local' | 'cloud';
|
|
4
|
+
workspaceId: string;
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
scope: 'active' | 'archive';
|
|
7
|
+
debounceMs?: number;
|
|
8
|
+
pageSize?: number;
|
|
9
|
+
maxMatches?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function useCloudTaskSearch({ query, runtimeMode, workspaceId, enabled, scope, debounceMs, pageSize, maxMatches, }: UseCloudTaskSearchInput): ReadonlySet<string>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { normalizeTaskSearchQuery } from '../../utils/taskSearch';
|
|
3
|
+
export function useCloudTaskSearch({ query, runtimeMode, workspaceId, enabled, scope, debounceMs = 250, pageSize = 50, maxMatches = 500, }) {
|
|
4
|
+
const [matchIds, setMatchIds] = useState(() => new Set());
|
|
5
|
+
const requestSequenceRef = useRef(0);
|
|
6
|
+
const normalizedQuery = useMemo(() => normalizeTaskSearchQuery(query).raw, [query]);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
requestSequenceRef.current += 1;
|
|
9
|
+
const requestSequence = requestSequenceRef.current;
|
|
10
|
+
if (!enabled || runtimeMode !== 'cloud' || !normalizedQuery || !workspaceId.trim()) {
|
|
11
|
+
setMatchIds((current) => current.size === 0 ? current : new Set());
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
setMatchIds((current) => current.size === 0 ? current : new Set());
|
|
15
|
+
const abortController = new AbortController();
|
|
16
|
+
const timer = window.setTimeout(() => {
|
|
17
|
+
void (async () => {
|
|
18
|
+
const collected = new Set();
|
|
19
|
+
let offset = 0;
|
|
20
|
+
let scanned = 0;
|
|
21
|
+
const boundedPageSize = Math.max(1, Math.min(50, Math.trunc(pageSize)));
|
|
22
|
+
const boundedMaxMatches = Math.max(1, Math.trunc(maxMatches));
|
|
23
|
+
try {
|
|
24
|
+
while (scanned < boundedMaxMatches) {
|
|
25
|
+
const limit = Math.min(boundedPageSize, boundedMaxMatches - scanned);
|
|
26
|
+
const params = new URLSearchParams({
|
|
27
|
+
query: query.trim(),
|
|
28
|
+
scope,
|
|
29
|
+
limit: String(limit),
|
|
30
|
+
offset: String(offset),
|
|
31
|
+
});
|
|
32
|
+
const response = await fetch(`/api/taskforce/tasks/search?${params.toString()}`, {
|
|
33
|
+
signal: abortController.signal,
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok)
|
|
36
|
+
throw new Error(`Task search failed (${response.status}).`);
|
|
37
|
+
const body = await response.json();
|
|
38
|
+
const matches = body.matches || [];
|
|
39
|
+
scanned += matches.length;
|
|
40
|
+
for (const match of matches) {
|
|
41
|
+
const id = String(match?.id || '').trim();
|
|
42
|
+
if (id)
|
|
43
|
+
collected.add(id);
|
|
44
|
+
}
|
|
45
|
+
if (body.pagination?.hasMore !== true)
|
|
46
|
+
break;
|
|
47
|
+
const nextOffset = Number(body.pagination?.nextOffset);
|
|
48
|
+
if (!Number.isInteger(nextOffset) || nextOffset <= offset)
|
|
49
|
+
break;
|
|
50
|
+
offset = nextOffset;
|
|
51
|
+
}
|
|
52
|
+
if (!abortController.signal.aborted && requestSequenceRef.current === requestSequence) {
|
|
53
|
+
setMatchIds(collected);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (abortController.signal.aborted || requestSequenceRef.current !== requestSequence)
|
|
58
|
+
return;
|
|
59
|
+
// Server search augments the existing client matcher. On failure, retain
|
|
60
|
+
// the client-visible metadata results instead of blocking the task board.
|
|
61
|
+
setMatchIds(new Set());
|
|
62
|
+
}
|
|
63
|
+
})();
|
|
64
|
+
}, Math.max(0, debounceMs));
|
|
65
|
+
return () => {
|
|
66
|
+
window.clearTimeout(timer);
|
|
67
|
+
abortController.abort('superseded');
|
|
68
|
+
};
|
|
69
|
+
}, [debounceMs, enabled, maxMatches, normalizedQuery, pageSize, query, runtimeMode, scope, workspaceId]);
|
|
70
|
+
return matchIds;
|
|
71
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type TaskDescriptionImageDraftResolution = 'promote' | 'discard';
|
|
2
|
+
type DraftRecoveryFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
interface UseTaskDescriptionImageDraftRecoveryOptions {
|
|
4
|
+
fetchImpl?: DraftRecoveryFetch;
|
|
5
|
+
storage?: Storage | null;
|
|
6
|
+
now?: () => number;
|
|
7
|
+
}
|
|
8
|
+
export declare const TASK_DESCRIPTION_IMAGE_DRAFT_RECOVERY_STORAGE_KEY = "taskforce.task-description-image-draft-recovery.v1";
|
|
9
|
+
export declare function taskDescriptionImageDraftRetryDelay(attempts: number): number;
|
|
10
|
+
export declare function useTaskDescriptionImageDraftRecovery(workspaceId: string, createdByUserId: string, options?: UseTaskDescriptionImageDraftRecoveryOptions): {
|
|
11
|
+
resolveDraft: (input: {
|
|
12
|
+
draftUploadId: string;
|
|
13
|
+
resolution: TaskDescriptionImageDraftResolution;
|
|
14
|
+
}) => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
const defaultFetch = (input, init) => globalThis.fetch(input, init);
|
|
3
|
+
export const TASK_DESCRIPTION_IMAGE_DRAFT_RECOVERY_STORAGE_KEY = 'taskforce.task-description-image-draft-recovery.v1';
|
|
4
|
+
const RETRY_DELAYS_MS = [1_000, 5_000, 15_000, 60_000];
|
|
5
|
+
const MAX_AUTOMATIC_RETRY_ATTEMPTS = 8;
|
|
6
|
+
const DORMANT_RETRY_AT = Number.MAX_SAFE_INTEGER;
|
|
7
|
+
export function taskDescriptionImageDraftRetryDelay(attempts) {
|
|
8
|
+
const index = Math.max(0, Math.min(RETRY_DELAYS_MS.length - 1, Math.floor(attempts) - 1));
|
|
9
|
+
return RETRY_DELAYS_MS[index];
|
|
10
|
+
}
|
|
11
|
+
function recordKey(record) {
|
|
12
|
+
return `${record.workspaceId}\u0000${record.createdByUserId}\u0000${record.draftUploadId}`;
|
|
13
|
+
}
|
|
14
|
+
function isRecoveryRecord(value) {
|
|
15
|
+
if (!value || typeof value !== 'object')
|
|
16
|
+
return false;
|
|
17
|
+
const record = value;
|
|
18
|
+
return typeof record.workspaceId === 'string'
|
|
19
|
+
&& Boolean(record.workspaceId.trim())
|
|
20
|
+
&& typeof record.createdByUserId === 'string'
|
|
21
|
+
&& Boolean(record.createdByUserId.trim())
|
|
22
|
+
&& typeof record.draftUploadId === 'string'
|
|
23
|
+
&& Boolean(record.draftUploadId.trim())
|
|
24
|
+
&& (record.resolution === 'promote' || record.resolution === 'discard')
|
|
25
|
+
&& Number.isFinite(record.attempts)
|
|
26
|
+
&& Number.isFinite(record.nextAttemptAt)
|
|
27
|
+
&& Number.isFinite(record.createdAt);
|
|
28
|
+
}
|
|
29
|
+
function readRecoveryRecords(storage) {
|
|
30
|
+
if (!storage)
|
|
31
|
+
return [];
|
|
32
|
+
try {
|
|
33
|
+
const parsed = JSON.parse(storage.getItem(TASK_DESCRIPTION_IMAGE_DRAFT_RECOVERY_STORAGE_KEY) || '[]');
|
|
34
|
+
if (!Array.isArray(parsed))
|
|
35
|
+
return [];
|
|
36
|
+
const records = new Map();
|
|
37
|
+
parsed.filter(isRecoveryRecord).forEach((record) => {
|
|
38
|
+
records.set(recordKey(record), record);
|
|
39
|
+
});
|
|
40
|
+
return [...records.values()];
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function writeRecoveryRecords(storage, records) {
|
|
47
|
+
if (!storage)
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
if (records.length === 0) {
|
|
51
|
+
storage.removeItem(TASK_DESCRIPTION_IMAGE_DRAFT_RECOVERY_STORAGE_KEY);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
storage.setItem(TASK_DESCRIPTION_IMAGE_DRAFT_RECOVERY_STORAGE_KEY, JSON.stringify(records));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// The request still gets an immediate attempt when browser storage is unavailable.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function updateRecoveryRecords(storage, update) {
|
|
61
|
+
const next = update(readRecoveryRecords(storage));
|
|
62
|
+
writeRecoveryRecords(storage, next);
|
|
63
|
+
return next;
|
|
64
|
+
}
|
|
65
|
+
function defaultStorage() {
|
|
66
|
+
try {
|
|
67
|
+
return globalThis.localStorage || null;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function useTaskDescriptionImageDraftRecovery(workspaceId, createdByUserId, options = {}) {
|
|
74
|
+
const fetchImpl = options.fetchImpl || defaultFetch;
|
|
75
|
+
const storage = options.storage === undefined ? defaultStorage() : options.storage;
|
|
76
|
+
const now = options.now || Date.now;
|
|
77
|
+
const timerRef = useRef(null);
|
|
78
|
+
const inFlightRef = useRef(new Map());
|
|
79
|
+
const drainRef = useRef(async () => { });
|
|
80
|
+
const clearRetryTimer = useCallback(() => {
|
|
81
|
+
if (timerRef.current === null)
|
|
82
|
+
return;
|
|
83
|
+
clearTimeout(timerRef.current);
|
|
84
|
+
timerRef.current = null;
|
|
85
|
+
}, []);
|
|
86
|
+
const scheduleRetry = useCallback(() => {
|
|
87
|
+
clearRetryTimer();
|
|
88
|
+
const nextRecord = readRecoveryRecords(storage)
|
|
89
|
+
.filter((record) => (record.workspaceId === workspaceId
|
|
90
|
+
&& record.createdByUserId === createdByUserId
|
|
91
|
+
&& record.nextAttemptAt < DORMANT_RETRY_AT))
|
|
92
|
+
.sort((left, right) => left.nextAttemptAt - right.nextAttemptAt)[0];
|
|
93
|
+
if (!nextRecord)
|
|
94
|
+
return;
|
|
95
|
+
const delay = Math.max(0, nextRecord.nextAttemptAt - now());
|
|
96
|
+
timerRef.current = setTimeout(() => {
|
|
97
|
+
timerRef.current = null;
|
|
98
|
+
void drainRef.current(false);
|
|
99
|
+
}, delay);
|
|
100
|
+
}, [clearRetryTimer, createdByUserId, now, storage, workspaceId]);
|
|
101
|
+
const attemptRecovery = useCallback((record) => {
|
|
102
|
+
const key = recordKey(record);
|
|
103
|
+
const existing = inFlightRef.current.get(key);
|
|
104
|
+
if (existing)
|
|
105
|
+
return existing;
|
|
106
|
+
const request = (async () => {
|
|
107
|
+
await Promise.resolve();
|
|
108
|
+
try {
|
|
109
|
+
const response = await fetchImpl(`/api/taskforce/context-upload/draft/${record.resolution}`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: { 'Content-Type': 'application/json' },
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
workspaceId: record.workspaceId,
|
|
114
|
+
draftUploadId: record.draftUploadId,
|
|
115
|
+
uploadPurpose: 'task-description-draft',
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
throw new Error(`Draft upload ${record.resolution} was rejected (${response.status}).`);
|
|
120
|
+
}
|
|
121
|
+
updateRecoveryRecords(storage, (records) => (records.filter((candidate) => recordKey(candidate) !== key)));
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
updateRecoveryRecords(storage, (records) => records.map((candidate) => {
|
|
125
|
+
if (recordKey(candidate) !== key)
|
|
126
|
+
return candidate;
|
|
127
|
+
const attempts = Math.min(Number.MAX_SAFE_INTEGER, candidate.attempts + 1);
|
|
128
|
+
return {
|
|
129
|
+
...candidate,
|
|
130
|
+
attempts,
|
|
131
|
+
nextAttemptAt: attempts >= MAX_AUTOMATIC_RETRY_ATTEMPTS
|
|
132
|
+
? DORMANT_RETRY_AT
|
|
133
|
+
: now() + taskDescriptionImageDraftRetryDelay(attempts),
|
|
134
|
+
};
|
|
135
|
+
}));
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
inFlightRef.current.delete(key);
|
|
140
|
+
scheduleRetry();
|
|
141
|
+
}
|
|
142
|
+
})();
|
|
143
|
+
inFlightRef.current.set(key, request);
|
|
144
|
+
return request;
|
|
145
|
+
}, [fetchImpl, now, scheduleRetry, storage]);
|
|
146
|
+
const drain = useCallback(async (force = false) => {
|
|
147
|
+
const currentTime = now();
|
|
148
|
+
const dueRecords = readRecoveryRecords(storage).filter((record) => (record.workspaceId === workspaceId
|
|
149
|
+
&& record.createdByUserId === createdByUserId
|
|
150
|
+
&& (force || record.nextAttemptAt <= currentTime)));
|
|
151
|
+
await Promise.allSettled(dueRecords.map((record) => attemptRecovery(record)));
|
|
152
|
+
scheduleRetry();
|
|
153
|
+
}, [attemptRecovery, createdByUserId, now, scheduleRetry, storage, workspaceId]);
|
|
154
|
+
drainRef.current = drain;
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
void drain(true);
|
|
157
|
+
const retryWhenOnline = () => { void drain(true); };
|
|
158
|
+
const retryWhenVisible = () => {
|
|
159
|
+
if (document.visibilityState === 'visible')
|
|
160
|
+
void drain(true);
|
|
161
|
+
};
|
|
162
|
+
const retryAfterStorageChange = (event) => {
|
|
163
|
+
if (event.key === TASK_DESCRIPTION_IMAGE_DRAFT_RECOVERY_STORAGE_KEY)
|
|
164
|
+
void drain(false);
|
|
165
|
+
};
|
|
166
|
+
window.addEventListener('online', retryWhenOnline);
|
|
167
|
+
window.addEventListener('storage', retryAfterStorageChange);
|
|
168
|
+
document.addEventListener('visibilitychange', retryWhenVisible);
|
|
169
|
+
return () => {
|
|
170
|
+
clearRetryTimer();
|
|
171
|
+
window.removeEventListener('online', retryWhenOnline);
|
|
172
|
+
window.removeEventListener('storage', retryAfterStorageChange);
|
|
173
|
+
document.removeEventListener('visibilitychange', retryWhenVisible);
|
|
174
|
+
};
|
|
175
|
+
}, [clearRetryTimer, drain]);
|
|
176
|
+
const resolveDraft = useCallback(async (input) => {
|
|
177
|
+
const draftUploadId = input.draftUploadId.trim();
|
|
178
|
+
if (!workspaceId || !createdByUserId || !draftUploadId)
|
|
179
|
+
return;
|
|
180
|
+
const queuedAt = now();
|
|
181
|
+
const record = {
|
|
182
|
+
workspaceId,
|
|
183
|
+
createdByUserId,
|
|
184
|
+
draftUploadId,
|
|
185
|
+
resolution: input.resolution,
|
|
186
|
+
attempts: 0,
|
|
187
|
+
nextAttemptAt: queuedAt,
|
|
188
|
+
createdAt: queuedAt,
|
|
189
|
+
};
|
|
190
|
+
updateRecoveryRecords(storage, (records) => [
|
|
191
|
+
...records.filter((candidate) => recordKey(candidate) !== recordKey(record)),
|
|
192
|
+
record,
|
|
193
|
+
]);
|
|
194
|
+
await attemptRecovery(record);
|
|
195
|
+
}, [attemptRecovery, createdByUserId, now, storage, workspaceId]);
|
|
196
|
+
return { resolveDraft };
|
|
197
|
+
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { AttachmentItem, CategoryConfig, ChecklistItem, Comment, TaskAssignee, TaskItem, TaskStatus, TaxonomyDefinition, WorkstreamItem } from '../../types';
|
|
2
2
|
type TaskforceTab = 'add' | 'tasks' | 'settings';
|
|
3
|
+
type ApplyTaskOptions = {
|
|
4
|
+
preserveReturnTrail?: boolean;
|
|
5
|
+
adoptAsBaseline?: boolean;
|
|
6
|
+
};
|
|
7
|
+
type LoadTaskOptions = ApplyTaskOptions & {
|
|
8
|
+
shouldApply?: () => boolean;
|
|
9
|
+
};
|
|
3
10
|
interface UseTaskEditorNavigationInput {
|
|
4
11
|
activeCategories: CategoryConfig[];
|
|
5
12
|
activeTypes: Array<{
|
|
@@ -10,6 +17,7 @@ interface UseTaskEditorNavigationInput {
|
|
|
10
17
|
attachments: Array<string | AttachmentItem>;
|
|
11
18
|
checklistItems: ChecklistItem[];
|
|
12
19
|
comments: Comment[];
|
|
20
|
+
cancelTaskDetailLoad?: () => void;
|
|
13
21
|
description: string;
|
|
14
22
|
editingTaskId: string | null;
|
|
15
23
|
flushPendingAutoSave: () => Promise<boolean>;
|
|
@@ -17,7 +25,12 @@ interface UseTaskEditorNavigationInput {
|
|
|
17
25
|
getPreferredCategoryValue: (categories: CategoryConfig[]) => string;
|
|
18
26
|
getTitleDraft?: () => string;
|
|
19
27
|
lastUsedCategory: string;
|
|
28
|
+
loadTaskDetail?: (task: TaskItem, options?: {
|
|
29
|
+
shouldCommit?: () => boolean;
|
|
30
|
+
force?: boolean;
|
|
31
|
+
}) => Promise<TaskItem | null>;
|
|
20
32
|
newCommentText: string;
|
|
33
|
+
onTaskApplied?: (task: TaskItem) => void;
|
|
21
34
|
relationshipTasks: TaskItem[];
|
|
22
35
|
workstreams: WorkstreamItem[];
|
|
23
36
|
showArchive: boolean;
|
|
@@ -56,9 +69,9 @@ export declare function useTaskEditorNavigation(input: UseTaskEditorNavigationIn
|
|
|
56
69
|
resetForm: () => void;
|
|
57
70
|
resolveTaskIdInput: (value: string) => TaskItem | null;
|
|
58
71
|
resolveWorkstreamIdInput: (value: string) => WorkstreamItem | null;
|
|
59
|
-
handleEdit: (task: TaskItem, options?:
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
handleEdit: (task: TaskItem, options?: ApplyTaskOptions) => void;
|
|
73
|
+
refreshTaskDetail: (task: TaskItem, options?: LoadTaskOptions) => Promise<TaskItem | null>;
|
|
74
|
+
applyTaskToEditor: (task: TaskItem, options?: ApplyTaskOptions) => void;
|
|
62
75
|
handleOpenTaskById: (taskId: string) => void;
|
|
63
76
|
returnToPreviousTask: () => boolean;
|
|
64
77
|
clearReturnToParentTask: () => void;
|
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
import { useCallback } from 'react';
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
2
|
import { DEFAULT_TYPE_VALUE } from '../../shared/taxonomyDefaults.js';
|
|
3
3
|
import { getTaskReferenceLabel, parseTaskReference } from '../../utils/taskReferences';
|
|
4
4
|
import { formatWorkstreamReference, parseWorkstreamReference } from '../../utils/planningReferences';
|
|
5
5
|
import { buildDefaultFormTaxonomies } from '../../utils/taxonomyFormDefaults.js';
|
|
6
6
|
export function useTaskEditorNavigation(input) {
|
|
7
|
-
const { activeCategories, activeTypes, activeTab, attachments, checklistItems, comments, description, editingTaskId, flushPendingAutoSave, getDescriptionDraft, getPreferredCategoryValue, getTitleDraft, lastUsedCategory, newCommentText, relationshipTasks, workstreams, showArchive, taxonomies, setActiveTab, setAssignee, setAttachments, setAttachmentsDirty, setCategory, setChecklistItems, setComments, setComplexity, setDescription, setDueDate, setEditingTaskId, setError, setFormTaxonomies, setIsOpen, setNewCommentText, setPendingNavigation, setWorkstreamInput, setPriority, setScheduledDate, setShowArchive, setStatus, setTaskReturnTrail, setTitle, setType, setUnsavedModalOpen, taskReturnTrail, title, } = input;
|
|
7
|
+
const { activeCategories, activeTypes, activeTab, attachments, checklistItems, comments, cancelTaskDetailLoad, description, editingTaskId, flushPendingAutoSave, getDescriptionDraft, getPreferredCategoryValue, getTitleDraft, lastUsedCategory, loadTaskDetail, newCommentText, onTaskApplied, relationshipTasks, workstreams, showArchive, taxonomies, setActiveTab, setAssignee, setAttachments, setAttachmentsDirty, setCategory, setChecklistItems, setComments, setComplexity, setDescription, setDueDate, setEditingTaskId, setError, setFormTaxonomies, setIsOpen, setNewCommentText, setPendingNavigation, setWorkstreamInput, setPriority, setScheduledDate, setShowArchive, setStatus, setTaskReturnTrail, setTitle, setType, setUnsavedModalOpen, taskReturnTrail, title, } = input;
|
|
8
|
+
const editRequestSequenceRef = useRef(0);
|
|
9
|
+
const invalidatePendingEdit = useCallback(() => {
|
|
10
|
+
editRequestSequenceRef.current += 1;
|
|
11
|
+
cancelTaskDetailLoad?.();
|
|
12
|
+
}, [cancelTaskDetailLoad]);
|
|
8
13
|
const getPreferredTypeValue = useCallback(() => {
|
|
9
14
|
return activeTypes.find((entry) => String(entry.value || '').trim().length > 0)?.value || DEFAULT_TYPE_VALUE;
|
|
10
15
|
}, [activeTypes]);
|
|
11
16
|
const handleNavigation = useCallback(async (action) => {
|
|
17
|
+
const navigate = () => {
|
|
18
|
+
invalidatePendingEdit();
|
|
19
|
+
action();
|
|
20
|
+
};
|
|
12
21
|
if (editingTaskId) {
|
|
13
22
|
if (await flushPendingAutoSave()) {
|
|
14
|
-
|
|
23
|
+
navigate();
|
|
15
24
|
}
|
|
16
25
|
return;
|
|
17
26
|
}
|
|
@@ -22,10 +31,10 @@ export function useTaskEditorNavigation(input) {
|
|
|
22
31
|
comments.length > 0 ||
|
|
23
32
|
attachments.length > 0;
|
|
24
33
|
if (!hasChanges) {
|
|
25
|
-
|
|
34
|
+
navigate();
|
|
26
35
|
return;
|
|
27
36
|
}
|
|
28
|
-
setPendingNavigation(() =>
|
|
37
|
+
setPendingNavigation(() => navigate);
|
|
29
38
|
setUnsavedModalOpen(true);
|
|
30
39
|
}, [
|
|
31
40
|
attachments,
|
|
@@ -36,15 +45,18 @@ export function useTaskEditorNavigation(input) {
|
|
|
36
45
|
getDescriptionDraft,
|
|
37
46
|
getTitleDraft,
|
|
38
47
|
checklistItems,
|
|
48
|
+
invalidatePendingEdit,
|
|
39
49
|
newCommentText,
|
|
40
50
|
setPendingNavigation,
|
|
41
51
|
setUnsavedModalOpen,
|
|
42
52
|
title,
|
|
43
53
|
]);
|
|
44
54
|
const handleClose = useCallback(() => {
|
|
55
|
+
invalidatePendingEdit();
|
|
45
56
|
setIsOpen(false);
|
|
46
|
-
}, [setIsOpen]);
|
|
57
|
+
}, [invalidatePendingEdit, setIsOpen]);
|
|
47
58
|
const resetForm = useCallback(() => {
|
|
59
|
+
invalidatePendingEdit();
|
|
48
60
|
setEditingTaskId(null);
|
|
49
61
|
setTitle('');
|
|
50
62
|
setDescription('');
|
|
@@ -76,6 +88,7 @@ export function useTaskEditorNavigation(input) {
|
|
|
76
88
|
activeCategories,
|
|
77
89
|
getPreferredCategoryValue,
|
|
78
90
|
getPreferredTypeValue,
|
|
91
|
+
invalidatePendingEdit,
|
|
79
92
|
lastUsedCategory,
|
|
80
93
|
setAssignee,
|
|
81
94
|
setAttachments,
|
|
@@ -134,7 +147,7 @@ export function useTaskEditorNavigation(input) {
|
|
|
134
147
|
return exactTitle;
|
|
135
148
|
return null;
|
|
136
149
|
}, [workstreams]);
|
|
137
|
-
const
|
|
150
|
+
const applyTaskToEditor = useCallback((task, options) => {
|
|
138
151
|
if (!options?.preserveReturnTrail) {
|
|
139
152
|
setTaskReturnTrail([]);
|
|
140
153
|
}
|
|
@@ -157,6 +170,8 @@ export function useTaskEditorNavigation(input) {
|
|
|
157
170
|
setAttachments(task.attachments || []);
|
|
158
171
|
setAttachmentsDirty(false);
|
|
159
172
|
setActiveTab('add');
|
|
173
|
+
if (options?.adoptAsBaseline !== false)
|
|
174
|
+
onTaskApplied?.(task);
|
|
160
175
|
}, [
|
|
161
176
|
activeCategories,
|
|
162
177
|
getPreferredCategoryValue,
|
|
@@ -179,20 +194,62 @@ export function useTaskEditorNavigation(input) {
|
|
|
179
194
|
setTaskReturnTrail,
|
|
180
195
|
setTitle,
|
|
181
196
|
setType,
|
|
197
|
+
onTaskApplied,
|
|
182
198
|
workstreams
|
|
183
199
|
]);
|
|
200
|
+
const loadAndApplyTask = useCallback(async (task, options) => {
|
|
201
|
+
const requestSequence = editRequestSequenceRef.current + 1;
|
|
202
|
+
editRequestSequenceRef.current = requestSequence;
|
|
203
|
+
if (!loadTaskDetail) {
|
|
204
|
+
cancelTaskDetailLoad?.();
|
|
205
|
+
applyTaskToEditor(task, { preserveReturnTrail: options?.preserveReturnTrail });
|
|
206
|
+
return task;
|
|
207
|
+
}
|
|
208
|
+
if (!task.isSummary) {
|
|
209
|
+
cancelTaskDetailLoad?.();
|
|
210
|
+
const detail = options?.shouldApply
|
|
211
|
+
? await loadTaskDetail(task, { shouldCommit: options.shouldApply })
|
|
212
|
+
: await loadTaskDetail(task);
|
|
213
|
+
if (editRequestSequenceRef.current === requestSequence) {
|
|
214
|
+
const taskToApply = detail || task;
|
|
215
|
+
applyTaskToEditor(taskToApply, { preserveReturnTrail: options?.preserveReturnTrail });
|
|
216
|
+
return taskToApply;
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const detail = options?.shouldApply
|
|
221
|
+
? await loadTaskDetail(task, { shouldCommit: options.shouldApply })
|
|
222
|
+
: await loadTaskDetail(task);
|
|
223
|
+
if (detail
|
|
224
|
+
&& editRequestSequenceRef.current === requestSequence) {
|
|
225
|
+
applyTaskToEditor(detail, { preserveReturnTrail: options?.preserveReturnTrail });
|
|
226
|
+
return detail;
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}, [applyTaskToEditor, cancelTaskDetailLoad, loadTaskDetail]);
|
|
230
|
+
const handleEdit = useCallback((task, options) => {
|
|
231
|
+
void loadAndApplyTask(task, options);
|
|
232
|
+
}, [loadAndApplyTask]);
|
|
233
|
+
const refreshTaskDetail = useCallback((task, options) => loadAndApplyTask(task, options), [loadAndApplyTask]);
|
|
184
234
|
const handleOpenTaskById = useCallback((taskId) => {
|
|
185
235
|
const target = relationshipTasks.find((task) => task.id === taskId);
|
|
186
236
|
if (!target)
|
|
187
237
|
return;
|
|
238
|
+
const openTarget = () => {
|
|
239
|
+
if (activeTab === 'add' && editingTaskId && editingTaskId !== target.id) {
|
|
240
|
+
setTaskReturnTrail((prev) => prev[prev.length - 1] === editingTaskId ? prev : [...prev, editingTaskId]);
|
|
241
|
+
}
|
|
242
|
+
if (target.isArchived && !showArchive) {
|
|
243
|
+
setShowArchive(true);
|
|
244
|
+
}
|
|
245
|
+
handleEdit(target, { preserveReturnTrail: true });
|
|
246
|
+
};
|
|
188
247
|
if (activeTab === 'add' && editingTaskId && editingTaskId !== target.id) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (target.isArchived && !showArchive) {
|
|
192
|
-
setShowArchive(true);
|
|
248
|
+
void handleNavigation(openTarget);
|
|
249
|
+
return;
|
|
193
250
|
}
|
|
194
|
-
|
|
195
|
-
}, [activeTab, editingTaskId, handleEdit, relationshipTasks, setShowArchive, setTaskReturnTrail, showArchive]);
|
|
251
|
+
openTarget();
|
|
252
|
+
}, [activeTab, editingTaskId, handleEdit, handleNavigation, relationshipTasks, setShowArchive, setTaskReturnTrail, showArchive]);
|
|
196
253
|
const returnToPreviousTask = useCallback(() => {
|
|
197
254
|
if (activeTab !== 'add' || !editingTaskId || taskReturnTrail.length === 0)
|
|
198
255
|
return false;
|
|
@@ -224,6 +281,8 @@ export function useTaskEditorNavigation(input) {
|
|
|
224
281
|
resolveTaskIdInput,
|
|
225
282
|
resolveWorkstreamIdInput,
|
|
226
283
|
handleEdit,
|
|
284
|
+
refreshTaskDetail,
|
|
285
|
+
applyTaskToEditor,
|
|
227
286
|
handleOpenTaskById,
|
|
228
287
|
returnToPreviousTask,
|
|
229
288
|
clearReturnToParentTask
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AttachmentItem, CategoryConfig, ChecklistItem, TaskAssignee, TaskItem, TaskStatus, TaxonomyDefinition, WorkstreamItem } from '../../types';
|
|
2
2
|
import type { DurableTaskCreateMutationAdapter } from '../sync/useDurableTaskCreateMutation.js';
|
|
3
3
|
import { createCloudTaskHttpMutationClient } from './taskHttpMutation.js';
|
|
4
|
+
import type { TaskEditorDraft } from '../../utils/taskEditorReconciliation.js';
|
|
4
5
|
interface UseTaskFormActionsInput {
|
|
5
6
|
activeCategories: CategoryConfig[];
|
|
6
7
|
activeTab: 'add' | 'tasks' | 'settings';
|
|
@@ -20,6 +21,8 @@ interface UseTaskFormActionsInput {
|
|
|
20
21
|
taskHttpMutationClient?: ReturnType<typeof createCloudTaskHttpMutationClient>;
|
|
21
22
|
updateTask?: (taskId: string, updates: Partial<TaskItem>) => Promise<boolean | void>;
|
|
22
23
|
editingTaskId: string | null;
|
|
24
|
+
editorBaselineDraft?: TaskEditorDraft | null;
|
|
25
|
+
revisionConflict?: boolean;
|
|
23
26
|
fetchTasks: (isSilent?: boolean) => Promise<void>;
|
|
24
27
|
formTaxonomies: Record<string, string | number | string[] | number[]>;
|
|
25
28
|
getAttachmentsDraft?: () => Array<string | AttachmentItem>;
|
|
@@ -55,6 +58,9 @@ interface UseTaskFormActionsInput {
|
|
|
55
58
|
export type TaskAutoSaveState = 'idle' | 'saving' | 'saved' | 'error';
|
|
56
59
|
export declare function useTaskFormActions(input: UseTaskFormActionsInput): {
|
|
57
60
|
autoSaveState: TaskAutoSaveState;
|
|
61
|
+
hasUnsavedChanges: boolean;
|
|
62
|
+
hasUnsavedChangesNow: () => boolean;
|
|
63
|
+
hasPendingAutoSave: boolean;
|
|
58
64
|
flushAutoSave: () => Promise<boolean>;
|
|
59
65
|
scheduleWarningPrompt: {
|
|
60
66
|
dueDate: string;
|