@taskforcehq/taskforce 0.3.303 → 0.3.305
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -247
- package/dist/Taskforce.module.css +102 -5
- package/dist/TaskforceCore.js +635 -238
- package/dist/TaskforceCore.test.js +2243 -495
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/compat/workspaceSyncCompat.js +6 -0
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/DocumentIndex.d.ts +1 -1
- package/dist/components/features/DocumentIndex.js +1 -1
- package/dist/components/features/DocumentViewer.d.ts +1 -1
- package/dist/components/features/DocumentViewer.js +32 -9
- package/dist/components/features/DocumentWorkspace.d.ts +5 -22
- package/dist/components/features/DocumentWorkspace.js +12 -155
- package/dist/components/features/DocumentWorkspace.test.js +193 -6
- package/dist/components/features/TaskSettings.js +87 -41
- package/dist/components/features/TaskSettings.test.js +180 -40
- package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
- package/dist/components/features/documentWorkspaceModel.js +57 -0
- package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
- package/dist/components/features/useDocumentWorkspaceController.js +113 -0
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +9 -8
- package/dist/components/task/TaskForm.js +374 -219
- package/dist/components/task/TaskForm.test.js +482 -361
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +33 -161
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +9 -10
- package/dist/components/views/PlanComparisonPage.test.js +55 -41
- package/dist/components/views/PlansPage.d.ts +5 -1
- package/dist/components/views/PlansPage.js +150 -81
- package/dist/components/views/PlansPage.test.js +367 -41
- package/dist/components/views/StandaloneLayout.js +135 -54
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/panels/FilterToolbar.test.js +6 -4
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
- package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/config/envSchema.js +1 -1
- package/dist/core/AiProfileService.d.ts +6 -1
- package/dist/core/AiProfileService.js +161 -16
- package/dist/core/AiProfileService.test.js +3 -1
- package/dist/core/AiProfiles.test.js +200 -0
- package/dist/core/AuthTokenService.test.d.ts +1 -0
- package/dist/core/AuthTokenService.test.js +78 -0
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +75 -13
- package/dist/core/PlanEntitlementService.d.ts +9 -1
- package/dist/core/PlanEntitlementService.js +144 -19
- package/dist/core/PlanVersionPolicy.test.js +19 -26
- package/dist/core/SignupMonetizationSettings.test.js +75 -15
- package/dist/core/SystemAdmin.test.js +212 -56
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/TaskTaxonomyValidation.test.js +53 -0
- package/dist/core/Taskforce.d.ts +18 -11
- package/dist/core/Taskforce.js +93 -12
- package/dist/core/WorkspacePlanMode.test.js +73 -8
- package/dist/core/shared.d.ts +2 -0
- package/dist/core/shared.js +11 -0
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +63 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +123 -0
- package/dist/hooks/sync/transfers.js +435 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
- package/dist/hooks/useSyncOrchestrator.js +388 -879
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
- package/dist/hooks/useTaskMutations.d.ts +2 -1
- package/dist/hooks/useTaskMutations.js +57 -7
- package/dist/hooks/useTaskMutations.test.js +176 -0
- package/dist/hooks/useTaskforce.d.ts +13 -4
- package/dist/hooks/useTaskforce.js +302 -817
- package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
- package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
- package/dist/hooks/useWorkspaceSyncController.js +33 -11
- package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
- package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/collaborationRegistrar.d.ts +2 -0
- package/dist/mcp/collaborationRegistrar.js +71 -0
- package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
- package/dist/mcp/documentAssetRegistrar.js +346 -0
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +2455 -3925
- package/dist/mcp/runtime.test.js +240 -8
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
- package/dist/mcp/taskPlanningRegistrar.js +418 -0
- package/dist/mcp/toolCatalog.d.ts +35 -0
- package/dist/mcp/toolCatalog.js +3 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +46 -3
- package/dist/server/index.test.js +12 -1
- package/dist/server/routes/admin.d.ts +1 -5
- package/dist/server/routes/admin.js +151 -80
- package/dist/server/routes/annotatedAttachments.d.ts +1 -1
- package/dist/server/routes/annotatedAttachments.js +1 -1
- package/dist/server/routes/auth.d.ts +1 -4
- package/dist/server/routes/auth.js +59 -64
- package/dist/server/routes/authSupport.d.ts +30 -0
- package/dist/server/routes/authSupport.js +81 -0
- package/dist/server/routes/billing.d.ts +1 -1
- package/dist/server/routes/billing.js +122 -255
- package/dist/server/routes/billing.test.js +257 -86
- package/dist/server/routes/documentReviews.d.ts +1 -1
- package/dist/server/routes/documentReviews.js +1 -1
- package/dist/server/routes/documents.d.ts +4 -2
- package/dist/server/routes/documents.js +73 -7
- package/dist/server/routes/primitives.d.ts +11 -0
- package/dist/server/routes/primitives.js +73 -0
- package/dist/server/routes/resources.d.ts +1 -1
- package/dist/server/routes/resources.js +1 -1
- package/dist/server/routes/shared.d.ts +31 -1
- package/dist/server/routes/shared.js +26 -0
- package/dist/server/routes/sync.d.ts +1 -1
- package/dist/server/routes/sync.integration.test.js +20 -0
- package/dist/server/routes/sync.js +111 -238
- package/dist/server/routes/tasks.d.ts +1 -1
- package/dist/server/routes/tasks.js +50 -8
- package/dist/server/routes/workspaces.d.ts +1 -1
- package/dist/server/routes/workspaces.js +1 -1
- package/dist/server/routes.d.ts +2 -9
- package/dist/server/routes.js +84 -181
- package/dist/server/routes.test.js +912 -32
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.js +28 -17
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +379 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +44 -2
- package/dist/sync/syncService.test.js +62 -0
- package/dist/sync/workspacePullFeed.d.ts +1 -0
- package/dist/sync/workspacePullFeed.js +66 -59
- package/dist/sync/workspacePullFeed.test.js +72 -4
- package/dist/sync/workspaceSyncModel.d.ts +25 -1
- package/dist/sync/workspaceSyncModel.js +77 -3
- package/dist/sync/workspaceSyncModel.test.js +153 -0
- package/dist/sync/workspaceSyncState.d.ts +2 -0
- package/dist/sync/workspaceSyncState.js +1 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
- package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
- package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
- package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
- package/dist/ui/assets/index-C2-OXZV7.css +1 -0
- package/dist/ui/assets/index-DcSI5F86.js +6 -0
- package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
- package/dist/utils/commercialLifecyclePresentation.js +196 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +10 -1
- package/scripts/check-cloud-mcp.mjs +83 -0
- package/scripts/playwright-auth.sh +9 -1
- package/scripts/run-billing-performance-smoke.sh +24 -0
- package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
- package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
- package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
- package/dist/ui/assets/index-CLIMgR6g.js +0 -6
- package/dist/ui/assets/index-DneETxcu.css +0 -1
|
@@ -24,8 +24,9 @@ export interface UseTaskMutationsProps {
|
|
|
24
24
|
pushWorkspaceChangesToCloud: (sig: string) => void;
|
|
25
25
|
workspacePendingSignatureRef: MutableRefObject<string | null>;
|
|
26
26
|
workspaceDeletedTaskIdsRef: MutableRefObject<Set<string>>;
|
|
27
|
+
workspaceDeletedTaskWatermarksRef: MutableRefObject<Map<string, string>>;
|
|
27
28
|
}
|
|
28
|
-
export declare function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks, setArchivedTasks, setDeletedTasks, setError, resetForm, setActiveTab, fetchTasks, fetchArchive, fetchDeletedTasks, mergeTaskFromServer, pushNotice, cloudAuthConfigured, runtimeMode, isAuthenticated, workspaceCloudSyncEnabled, buildWorkspaceSyncSignature, pushWorkspaceChangesToCloud, workspacePendingSignatureRef, workspaceDeletedTaskIdsRef, }: UseTaskMutationsProps): {
|
|
29
|
+
export declare function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks, setArchivedTasks, setDeletedTasks, setError, resetForm, setActiveTab, fetchTasks, fetchArchive, fetchDeletedTasks, mergeTaskFromServer, pushNotice, cloudAuthConfigured, runtimeMode, isAuthenticated, workspaceCloudSyncEnabled, buildWorkspaceSyncSignature, pushWorkspaceChangesToCloud, workspacePendingSignatureRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, }: UseTaskMutationsProps): {
|
|
29
30
|
copiedId: string | null;
|
|
30
31
|
handleDelete: (id: string, archive?: boolean, options?: {
|
|
31
32
|
skipConfirm?: boolean;
|
|
@@ -1,21 +1,46 @@
|
|
|
1
|
-
import { useState, useCallback, useRef } from 'react';
|
|
1
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
2
2
|
import { normalizeTaskFromApi } from '../utils/taskNormalization.js';
|
|
3
|
-
export function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks, setArchivedTasks, setDeletedTasks, setError, resetForm, setActiveTab, fetchTasks, fetchArchive, fetchDeletedTasks, mergeTaskFromServer, pushNotice, cloudAuthConfigured, runtimeMode, isAuthenticated, workspaceCloudSyncEnabled, buildWorkspaceSyncSignature, pushWorkspaceChangesToCloud, workspacePendingSignatureRef, workspaceDeletedTaskIdsRef, }) {
|
|
3
|
+
export function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks, setArchivedTasks, setDeletedTasks, setError, resetForm, setActiveTab, fetchTasks, fetchArchive, fetchDeletedTasks, mergeTaskFromServer, pushNotice, cloudAuthConfigured, runtimeMode, isAuthenticated, workspaceCloudSyncEnabled, buildWorkspaceSyncSignature, pushWorkspaceChangesToCloud, workspacePendingSignatureRef, workspaceDeletedTaskIdsRef, workspaceDeletedTaskWatermarksRef, }) {
|
|
4
4
|
// Copy ID state + timer ref
|
|
5
5
|
const [copiedId, setCopiedId] = useState(null);
|
|
6
6
|
const copiedIdTimerRef = useRef(null);
|
|
7
|
+
const workspaceSyncTimerRef = useRef(null);
|
|
8
|
+
const workspaceSyncTargetAtRef = useRef(null);
|
|
9
|
+
useEffect(() => () => {
|
|
10
|
+
if (typeof window === 'undefined')
|
|
11
|
+
return;
|
|
12
|
+
if (workspaceSyncTimerRef.current !== null) {
|
|
13
|
+
window.clearTimeout(workspaceSyncTimerRef.current);
|
|
14
|
+
workspaceSyncTimerRef.current = null;
|
|
15
|
+
}
|
|
16
|
+
workspaceSyncTargetAtRef.current = null;
|
|
17
|
+
}, []);
|
|
7
18
|
const queueWorkspaceSyncFromAuthoritativeTaskState = useCallback((delayMs = 0) => {
|
|
8
19
|
if (!(cloudAuthConfigured && runtimeMode === 'local' && isAuthenticated && workspaceCloudSyncEnabled))
|
|
9
20
|
return;
|
|
10
21
|
if (typeof window === 'undefined')
|
|
11
22
|
return;
|
|
12
|
-
|
|
23
|
+
const normalizedDelayMs = Math.max(0, delayMs);
|
|
24
|
+
const requestedTargetAt = Date.now() + normalizedDelayMs;
|
|
25
|
+
const existingTargetAt = workspaceSyncTargetAtRef.current;
|
|
26
|
+
if (workspaceSyncTimerRef.current !== null) {
|
|
27
|
+
if (normalizedDelayMs <= 0)
|
|
28
|
+
return;
|
|
29
|
+
if (existingTargetAt !== null && existingTargetAt >= requestedTargetAt)
|
|
30
|
+
return;
|
|
31
|
+
window.clearTimeout(workspaceSyncTimerRef.current);
|
|
32
|
+
workspaceSyncTimerRef.current = null;
|
|
33
|
+
}
|
|
34
|
+
workspaceSyncTargetAtRef.current = requestedTargetAt;
|
|
35
|
+
workspaceSyncTimerRef.current = window.setTimeout(() => {
|
|
36
|
+
workspaceSyncTimerRef.current = null;
|
|
37
|
+
workspaceSyncTargetAtRef.current = null;
|
|
13
38
|
const signature = buildWorkspaceSyncSignature();
|
|
14
39
|
if (!signature)
|
|
15
40
|
return;
|
|
16
41
|
workspacePendingSignatureRef.current = signature;
|
|
17
42
|
void pushWorkspaceChangesToCloud(signature);
|
|
18
|
-
},
|
|
43
|
+
}, normalizedDelayMs);
|
|
19
44
|
}, [
|
|
20
45
|
cloudAuthConfigured,
|
|
21
46
|
runtimeMode,
|
|
@@ -65,9 +90,14 @@ export function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks
|
|
|
65
90
|
}
|
|
66
91
|
if (deletedRecord) {
|
|
67
92
|
setDeletedTasks(prev => [deletedRecord, ...prev.filter((entry) => entry.taskId !== id)]);
|
|
93
|
+
const deletedAt = String(deletedRecord.deletedAt || '').trim();
|
|
94
|
+
if (Number.isFinite(Date.parse(deletedAt))) {
|
|
95
|
+
workspaceDeletedTaskWatermarksRef.current.set(id, deletedAt);
|
|
96
|
+
}
|
|
68
97
|
}
|
|
69
98
|
else {
|
|
70
99
|
await fetchDeletedTasks(true);
|
|
100
|
+
workspaceDeletedTaskWatermarksRef.current.set(id, new Date().toISOString());
|
|
71
101
|
}
|
|
72
102
|
workspaceDeletedTaskIdsRef.current.add(id);
|
|
73
103
|
if (cloudAuthConfigured && runtimeMode === 'local' && isAuthenticated && workspaceCloudSyncEnabled) {
|
|
@@ -108,7 +138,13 @@ export function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks
|
|
|
108
138
|
await fetchTasks(true);
|
|
109
139
|
}
|
|
110
140
|
setDeletedTasks((prev) => prev.filter((entry) => entry.id !== id && entry.taskId !== id));
|
|
141
|
+
const restoredTaskId = String(restoredTask?.id || '').trim();
|
|
111
142
|
workspaceDeletedTaskIdsRef.current.delete(id);
|
|
143
|
+
workspaceDeletedTaskWatermarksRef.current.delete(id);
|
|
144
|
+
if (restoredTaskId) {
|
|
145
|
+
workspaceDeletedTaskIdsRef.current.delete(restoredTaskId);
|
|
146
|
+
workspaceDeletedTaskWatermarksRef.current.delete(restoredTaskId);
|
|
147
|
+
}
|
|
112
148
|
queueWorkspaceSyncFromAuthoritativeTaskState();
|
|
113
149
|
setError('');
|
|
114
150
|
pushNotice('Task restored from Trash.', 'info');
|
|
@@ -227,8 +263,20 @@ export function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks
|
|
|
227
263
|
}, [tasks, archivedTasks, pushNotice, mergeTaskFromServer, queueWorkspaceSyncFromAuthoritativeTaskState]);
|
|
228
264
|
const handleToggleComplete = async (task) => {
|
|
229
265
|
const newStatus = task.status === 'done' ? 'task' : 'done';
|
|
266
|
+
const completedAt = newStatus === 'done' ? new Date().toISOString() : undefined;
|
|
267
|
+
const optimisticUpdates = {
|
|
268
|
+
status: newStatus,
|
|
269
|
+
completedAt,
|
|
270
|
+
completed: newStatus === 'done',
|
|
271
|
+
inProgress: false,
|
|
272
|
+
readyForReview: false,
|
|
273
|
+
cancelled: false
|
|
274
|
+
};
|
|
275
|
+
const previousTasks = tasks;
|
|
276
|
+
const previousArchivedTasks = archivedTasks;
|
|
277
|
+
setTasks(prev => prev.map(t => t.id === task.id ? { ...t, ...optimisticUpdates } : t));
|
|
278
|
+
setArchivedTasks(prev => prev.map(t => t.id === task.id ? { ...t, ...optimisticUpdates } : t));
|
|
230
279
|
try {
|
|
231
|
-
const completedAt = newStatus === 'done' ? new Date().toISOString() : undefined;
|
|
232
280
|
const res = await fetch(`/api/taskforce/task/${task.id}`, {
|
|
233
281
|
method: 'PATCH',
|
|
234
282
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -239,7 +287,8 @@ export function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks
|
|
|
239
287
|
const message = data.error || 'Failed to update task status.';
|
|
240
288
|
setError(message);
|
|
241
289
|
pushNotice(message, 'error');
|
|
242
|
-
|
|
290
|
+
setTasks(previousTasks);
|
|
291
|
+
setArchivedTasks(previousArchivedTasks);
|
|
243
292
|
return;
|
|
244
293
|
}
|
|
245
294
|
const updatedTask = await res.json().catch(() => null);
|
|
@@ -257,7 +306,8 @@ export function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks
|
|
|
257
306
|
console.error("Failed to toggle complete", err);
|
|
258
307
|
setError('Failed to update task status.');
|
|
259
308
|
pushNotice('Failed to update task status.', 'error');
|
|
260
|
-
|
|
309
|
+
setTasks(previousTasks);
|
|
310
|
+
setArchivedTasks(previousArchivedTasks);
|
|
261
311
|
}
|
|
262
312
|
};
|
|
263
313
|
const handleToggleCancel = async (task) => {
|
|
@@ -6,9 +6,87 @@ describe('useTaskMutations', () => {
|
|
|
6
6
|
vi.stubGlobal('fetch', vi.fn());
|
|
7
7
|
});
|
|
8
8
|
afterEach(() => {
|
|
9
|
+
vi.useRealTimers();
|
|
9
10
|
vi.unstubAllGlobals();
|
|
10
11
|
vi.clearAllMocks();
|
|
11
12
|
});
|
|
13
|
+
const baseTask = {
|
|
14
|
+
id: 'task-1',
|
|
15
|
+
title: 'Test task',
|
|
16
|
+
description: '',
|
|
17
|
+
category: 'general',
|
|
18
|
+
type: 'task',
|
|
19
|
+
priority: 2,
|
|
20
|
+
complexity: 3,
|
|
21
|
+
status: 'task',
|
|
22
|
+
createdAt: '2026-04-03T10:00:00.000Z',
|
|
23
|
+
updatedAt: '2026-04-03T10:05:00.000Z'
|
|
24
|
+
};
|
|
25
|
+
const buildHook = (overrides = {}) => {
|
|
26
|
+
const setTasks = vi.fn();
|
|
27
|
+
const setArchivedTasks = vi.fn();
|
|
28
|
+
const setDeletedTasks = vi.fn();
|
|
29
|
+
const setError = vi.fn();
|
|
30
|
+
const resetForm = vi.fn();
|
|
31
|
+
const setActiveTab = vi.fn();
|
|
32
|
+
const fetchTasks = vi.fn();
|
|
33
|
+
const fetchArchive = vi.fn();
|
|
34
|
+
const fetchDeletedTasks = vi.fn();
|
|
35
|
+
const mergeTaskFromServer = vi.fn();
|
|
36
|
+
const pushNotice = vi.fn();
|
|
37
|
+
const buildWorkspaceSyncSignature = vi.fn(() => 'sig-1');
|
|
38
|
+
const pushWorkspaceChangesToCloud = vi.fn();
|
|
39
|
+
const workspacePendingSignatureRef = { current: null };
|
|
40
|
+
const workspaceDeletedTaskIdsRef = { current: new Set() };
|
|
41
|
+
const workspaceDeletedTaskWatermarksRef = { current: new Map() };
|
|
42
|
+
const hook = renderHook(() => useTaskMutations({
|
|
43
|
+
tasks: [baseTask],
|
|
44
|
+
archivedTasks: [],
|
|
45
|
+
editingTaskId: null,
|
|
46
|
+
setTasks,
|
|
47
|
+
setArchivedTasks,
|
|
48
|
+
setDeletedTasks,
|
|
49
|
+
setError,
|
|
50
|
+
resetForm,
|
|
51
|
+
setActiveTab,
|
|
52
|
+
fetchTasks,
|
|
53
|
+
fetchArchive,
|
|
54
|
+
fetchDeletedTasks,
|
|
55
|
+
mergeTaskFromServer,
|
|
56
|
+
pushNotice,
|
|
57
|
+
cloudAuthConfigured: true,
|
|
58
|
+
runtimeMode: 'local',
|
|
59
|
+
isAuthenticated: true,
|
|
60
|
+
workspaceCloudSyncEnabled: true,
|
|
61
|
+
buildWorkspaceSyncSignature,
|
|
62
|
+
pushWorkspaceChangesToCloud,
|
|
63
|
+
workspacePendingSignatureRef,
|
|
64
|
+
workspaceDeletedTaskIdsRef,
|
|
65
|
+
workspaceDeletedTaskWatermarksRef,
|
|
66
|
+
...overrides
|
|
67
|
+
}));
|
|
68
|
+
return {
|
|
69
|
+
...hook,
|
|
70
|
+
deps: {
|
|
71
|
+
setTasks,
|
|
72
|
+
setArchivedTasks,
|
|
73
|
+
setDeletedTasks,
|
|
74
|
+
setError,
|
|
75
|
+
resetForm,
|
|
76
|
+
setActiveTab,
|
|
77
|
+
fetchTasks,
|
|
78
|
+
fetchArchive,
|
|
79
|
+
fetchDeletedTasks,
|
|
80
|
+
mergeTaskFromServer,
|
|
81
|
+
pushNotice,
|
|
82
|
+
buildWorkspaceSyncSignature,
|
|
83
|
+
pushWorkspaceChangesToCloud,
|
|
84
|
+
workspacePendingSignatureRef,
|
|
85
|
+
workspaceDeletedTaskIdsRef,
|
|
86
|
+
workspaceDeletedTaskWatermarksRef
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
};
|
|
12
90
|
it('clears pending delete ids when a deleted task is restored', async () => {
|
|
13
91
|
const fetchMock = vi.mocked(fetch);
|
|
14
92
|
fetchMock.mockResolvedValue({
|
|
@@ -42,6 +120,7 @@ describe('useTaskMutations', () => {
|
|
|
42
120
|
const pushWorkspaceChangesToCloud = vi.fn();
|
|
43
121
|
const workspacePendingSignatureRef = { current: null };
|
|
44
122
|
const workspaceDeletedTaskIdsRef = { current: new Set(['task-1']) };
|
|
123
|
+
const workspaceDeletedTaskWatermarksRef = { current: new Map([['task-1', '2026-04-03T10:10:00.000Z']]) };
|
|
45
124
|
const { result } = renderHook(() => useTaskMutations({
|
|
46
125
|
tasks: [],
|
|
47
126
|
archivedTasks: [],
|
|
@@ -65,6 +144,7 @@ describe('useTaskMutations', () => {
|
|
|
65
144
|
pushWorkspaceChangesToCloud,
|
|
66
145
|
workspacePendingSignatureRef,
|
|
67
146
|
workspaceDeletedTaskIdsRef,
|
|
147
|
+
workspaceDeletedTaskWatermarksRef,
|
|
68
148
|
}));
|
|
69
149
|
await act(async () => {
|
|
70
150
|
await result.current.handleRestoreDeletedTask('task-1');
|
|
@@ -72,9 +152,105 @@ describe('useTaskMutations', () => {
|
|
|
72
152
|
await waitFor(() => {
|
|
73
153
|
expect(workspaceDeletedTaskIdsRef.current.has('task-1')).toBe(false);
|
|
74
154
|
});
|
|
155
|
+
expect(workspaceDeletedTaskWatermarksRef.current.has('task-1')).toBe(false);
|
|
75
156
|
expect(mergeTaskFromServer).toHaveBeenCalledWith(expect.objectContaining({ id: 'task-1' }));
|
|
76
157
|
expect(buildWorkspaceSyncSignature).toHaveBeenCalled();
|
|
77
158
|
expect(pushWorkspaceChangesToCloud).toHaveBeenCalledWith('sig-1');
|
|
78
159
|
expect(setDeletedTasks).toHaveBeenCalled();
|
|
79
160
|
});
|
|
161
|
+
it('records the delete watermark immediately when a task delete succeeds', async () => {
|
|
162
|
+
const fetchMock = vi.mocked(fetch);
|
|
163
|
+
fetchMock.mockResolvedValue({
|
|
164
|
+
ok: true,
|
|
165
|
+
status: 200,
|
|
166
|
+
json: async () => ({
|
|
167
|
+
deleted: {
|
|
168
|
+
id: 'deleted-task-1',
|
|
169
|
+
taskId: 'task-1',
|
|
170
|
+
deletedAt: '2026-04-03T10:10:00.000Z',
|
|
171
|
+
taskSnapshot: baseTask
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
const { result, deps } = buildHook();
|
|
176
|
+
await act(async () => {
|
|
177
|
+
await result.current.handleDelete('task-1', false, { skipConfirm: true });
|
|
178
|
+
});
|
|
179
|
+
expect(deps.workspaceDeletedTaskIdsRef.current.has('task-1')).toBe(true);
|
|
180
|
+
expect(deps.workspaceDeletedTaskWatermarksRef.current.get('task-1')).toBe('2026-04-03T10:10:00.000Z');
|
|
181
|
+
expect(deps.buildWorkspaceSyncSignature).toHaveBeenCalled();
|
|
182
|
+
expect(deps.pushWorkspaceChangesToCloud).toHaveBeenCalledWith('sig-1');
|
|
183
|
+
});
|
|
184
|
+
it('optimistically marks a task done before the server responds', async () => {
|
|
185
|
+
let resolveResponse;
|
|
186
|
+
const fetchMock = vi.mocked(fetch);
|
|
187
|
+
fetchMock.mockImplementation(() => new Promise((resolve) => {
|
|
188
|
+
resolveResponse = resolve;
|
|
189
|
+
}));
|
|
190
|
+
const { result, deps } = buildHook();
|
|
191
|
+
let togglePromise = null;
|
|
192
|
+
await act(async () => {
|
|
193
|
+
togglePromise = result.current.handleToggleComplete(baseTask);
|
|
194
|
+
});
|
|
195
|
+
expect(deps.setTasks).toHaveBeenCalledTimes(1);
|
|
196
|
+
const optimisticUpdater = deps.setTasks.mock.calls[0]?.[0];
|
|
197
|
+
const optimisticTasks = optimisticUpdater([baseTask]);
|
|
198
|
+
expect(optimisticTasks[0]?.status).toBe('done');
|
|
199
|
+
expect(optimisticTasks[0]?.completed).toBe(true);
|
|
200
|
+
resolveResponse({
|
|
201
|
+
ok: true,
|
|
202
|
+
status: 200,
|
|
203
|
+
json: async () => ({ ...baseTask, status: 'done', completedAt: '2026-04-14T12:00:00.000Z' })
|
|
204
|
+
});
|
|
205
|
+
await act(async () => {
|
|
206
|
+
await togglePromise;
|
|
207
|
+
});
|
|
208
|
+
expect(deps.mergeTaskFromServer).toHaveBeenCalledWith(expect.objectContaining({ id: 'task-1', status: 'done' }));
|
|
209
|
+
});
|
|
210
|
+
it('rolls back the optimistic done state when the server update fails', async () => {
|
|
211
|
+
const fetchMock = vi.mocked(fetch);
|
|
212
|
+
fetchMock.mockResolvedValue({
|
|
213
|
+
ok: false,
|
|
214
|
+
status: 500,
|
|
215
|
+
json: async () => ({ error: 'Server exploded' })
|
|
216
|
+
});
|
|
217
|
+
const { result, deps } = buildHook();
|
|
218
|
+
await act(async () => {
|
|
219
|
+
await result.current.handleToggleComplete(baseTask);
|
|
220
|
+
});
|
|
221
|
+
expect(deps.setTasks).toHaveBeenCalledTimes(2);
|
|
222
|
+
expect(deps.setTasks.mock.calls[1]?.[0]).toEqual([baseTask]);
|
|
223
|
+
expect(deps.setArchivedTasks.mock.calls[1]?.[0]).toEqual([]);
|
|
224
|
+
expect(deps.pushNotice).toHaveBeenCalledWith('Server exploded', 'error');
|
|
225
|
+
expect(deps.mergeTaskFromServer).not.toHaveBeenCalled();
|
|
226
|
+
});
|
|
227
|
+
it('coalesces pending workspace sync pushes and preserves attachment delay', async () => {
|
|
228
|
+
vi.useFakeTimers();
|
|
229
|
+
const fetchMock = vi.mocked(fetch);
|
|
230
|
+
fetchMock.mockResolvedValue({
|
|
231
|
+
ok: true,
|
|
232
|
+
status: 200,
|
|
233
|
+
json: async () => ({ ...baseTask, updatedAt: '2026-04-14T12:00:00.000Z' })
|
|
234
|
+
});
|
|
235
|
+
const { result, deps } = buildHook();
|
|
236
|
+
await act(async () => {
|
|
237
|
+
await result.current.handleUpdateTask(baseTask.id, { description: 'First update' });
|
|
238
|
+
});
|
|
239
|
+
await act(async () => {
|
|
240
|
+
await result.current.handleUpdateTask(baseTask.id, {
|
|
241
|
+
attachments: [{ id: 'asset-1', path: 'workspaces/workspace-local-active/assets/file.txt' }]
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
await act(async () => {
|
|
245
|
+
await vi.advanceTimersByTimeAsync(149);
|
|
246
|
+
});
|
|
247
|
+
expect(deps.pushWorkspaceChangesToCloud).not.toHaveBeenCalled();
|
|
248
|
+
await act(async () => {
|
|
249
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
250
|
+
});
|
|
251
|
+
expect(deps.buildWorkspaceSyncSignature).toHaveBeenCalledTimes(1);
|
|
252
|
+
expect(deps.workspacePendingSignatureRef.current).toBe('sig-1');
|
|
253
|
+
expect(deps.pushWorkspaceChangesToCloud).toHaveBeenCalledTimes(1);
|
|
254
|
+
expect(deps.pushWorkspaceChangesToCloud).toHaveBeenCalledWith('sig-1');
|
|
255
|
+
});
|
|
80
256
|
});
|
|
@@ -122,7 +122,7 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
|
|
|
122
122
|
authUserDisplayName: string;
|
|
123
123
|
authUserAvatarUrl: string;
|
|
124
124
|
authWorkspaceId: string;
|
|
125
|
-
userGlobalSyncStatus: "error" | "idle" | "
|
|
125
|
+
userGlobalSyncStatus: "error" | "idle" | "disconnected" | "syncing";
|
|
126
126
|
workspaceLastPullAt: string | null;
|
|
127
127
|
workspaceLastPushAt: string | null;
|
|
128
128
|
workspaceLastErrorAt: string | null;
|
|
@@ -130,6 +130,7 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
|
|
|
130
130
|
workspaceLastErrorMessage: string | null;
|
|
131
131
|
workspaceLastSuccessfulSyncAt: string | null;
|
|
132
132
|
workspaceSyncPhase: import("../sync/workspaceSyncState").WorkspaceSyncPhase;
|
|
133
|
+
workspaceSyncSetupIntent: import("../sync/workspaceSyncState").WorkspaceSyncSetupIntent;
|
|
133
134
|
workspaceSyncStatus: import("./useSyncOrchestrator").WorkspaceSyncStatus;
|
|
134
135
|
workspaceSyncSummary: string;
|
|
135
136
|
workspaceSyncRecommendedAction: string;
|
|
@@ -180,10 +181,10 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
|
|
|
180
181
|
success: boolean;
|
|
181
182
|
error?: string;
|
|
182
183
|
}>;
|
|
183
|
-
applyWorkspaceSyncStateSnapshot: (nextState: Partial<import("../sync/workspaceSyncState").WorkspaceSyncStateV2> & {
|
|
184
|
+
applyWorkspaceSyncStateSnapshot: (nextState: Parameters<(nextState: Partial<import("../sync/workspaceSyncState").WorkspaceSyncStateV2> & {
|
|
184
185
|
enabled: boolean;
|
|
185
186
|
phase?: import("../sync/workspaceSyncState").WorkspaceSyncPhase | null;
|
|
186
|
-
}) => void;
|
|
187
|
+
}) => void>[0]) => void;
|
|
187
188
|
authSessionResolved: boolean;
|
|
188
189
|
workspaceBootstrapPending: boolean;
|
|
189
190
|
bootstrapPhase: BootstrapPhase;
|
|
@@ -193,6 +194,12 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
|
|
|
193
194
|
runtimeCapabilities: RuntimeCapabilities | null;
|
|
194
195
|
refreshSetupContext: () => Promise<void>;
|
|
195
196
|
retryBootstrapChecks: () => Promise<void>;
|
|
197
|
+
continueAfterCommercialOnboarding: () => Promise<{
|
|
198
|
+
success: boolean;
|
|
199
|
+
destination: "app" | "setup" | "login";
|
|
200
|
+
error?: string;
|
|
201
|
+
code?: string;
|
|
202
|
+
}>;
|
|
196
203
|
saveWorkspaceProfile: (input: {
|
|
197
204
|
name: string;
|
|
198
205
|
description?: string;
|
|
@@ -206,7 +213,7 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
|
|
|
206
213
|
fetchWorkspaces: () => Promise<{
|
|
207
214
|
success: boolean;
|
|
208
215
|
error?: string;
|
|
209
|
-
workspaces?: WorkspaceSummary[];
|
|
216
|
+
workspaces?: import("./workspace/useTaskforceWorkspaceBootstrap").WorkspaceSummary[];
|
|
210
217
|
}>;
|
|
211
218
|
createWorkspace: (name: string, description?: string) => Promise<{
|
|
212
219
|
success: boolean;
|
|
@@ -274,6 +281,7 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
|
|
|
274
281
|
code?: string;
|
|
275
282
|
verificationToken?: string | null;
|
|
276
283
|
emailSent?: boolean;
|
|
284
|
+
deliveryAttempted?: boolean;
|
|
277
285
|
emailError?: string;
|
|
278
286
|
}>;
|
|
279
287
|
confirmEmailVerification: (token: string) => Promise<{
|
|
@@ -287,6 +295,7 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
|
|
|
287
295
|
code?: string;
|
|
288
296
|
resetToken?: string | null;
|
|
289
297
|
emailSent?: boolean;
|
|
298
|
+
deliveryAttempted?: boolean;
|
|
290
299
|
emailError?: string;
|
|
291
300
|
}>;
|
|
292
301
|
confirmPasswordReset: (token: string, password: string) => Promise<{
|