@taskforcehq/taskforce 0.3.306 → 0.3.307
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 +0 -2
- package/dist/Taskforce.module.css +317 -27
- package/dist/TaskforceCore.js +1 -0
- package/dist/TaskforceCore.test.js +178 -0
- package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
- package/dist/components/activity/EntityActivityTimeline.js +81 -0
- package/dist/components/context/ContextAttachmentManager.js +15 -3
- package/dist/components/features/AgentsModule.d.ts +5 -1
- package/dist/components/features/AgentsModule.js +142 -39
- package/dist/components/features/AgentsModule.test.js +260 -11
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
- package/dist/components/features/DocumentIndex.d.ts +2 -2
- package/dist/components/features/DocumentIndex.js +3 -4
- package/dist/components/features/DocumentViewer.d.ts +3 -1
- package/dist/components/features/DocumentViewer.js +6 -5
- package/dist/components/features/DocumentWorkspace.d.ts +10 -2
- package/dist/components/features/DocumentWorkspace.js +15 -4
- package/dist/components/features/DocumentWorkspace.test.js +20 -3
- package/dist/components/features/TaskSettings.d.ts +2 -0
- package/dist/components/features/TaskSettings.js +83 -78
- package/dist/components/features/TaskSettings.test.js +171 -82
- package/dist/components/task/TaskActionHeader.js +11 -5
- package/dist/components/task/TaskActionHeader.test.js +10 -0
- package/dist/components/task/TaskCard.js +6 -2
- package/dist/components/task/TaskContextUpload.test.js +3 -3
- package/dist/components/task/TaskForm.js +14 -64
- package/dist/components/task/TaskKanban.js +0 -8
- package/dist/components/task/TaskList.js +5 -1
- package/dist/components/task/TaskList.test.js +4 -0
- package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
- package/dist/components/ui/HierarchyActionBadge.js +5 -0
- package/dist/components/views/AppShellHeader.d.ts +3 -1
- package/dist/components/views/AppShellHeader.js +27 -11
- package/dist/components/views/AppShellHeader.test.js +18 -1
- package/dist/components/views/PlanComparisonPage.js +9 -1
- package/dist/components/views/PlanComparisonPage.test.js +19 -0
- package/dist/components/views/PlansPage.js +1 -1
- package/dist/components/views/StandaloneLayout.js +256 -69
- package/dist/components/views/WidgetView.js +10 -8
- package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
- package/dist/components/views/panels/PlanningDrawer.js +259 -51
- package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
- package/dist/config/envSchema.js +1 -0
- package/dist/core/AiProfileService.d.ts +7 -0
- package/dist/core/AiProfileService.js +460 -35
- package/dist/core/AiProfiles.test.js +648 -2
- package/dist/core/AttachmentLinkService.d.ts +3 -0
- package/dist/core/AttachmentLinkService.js +58 -13
- package/dist/core/GlobalSettingsService.js +12 -0
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanningEntities.test.js +366 -4
- package/dist/core/TaskActivityService.d.ts +28 -1
- package/dist/core/TaskActivityService.js +227 -46
- package/dist/core/TaskActivitySync.test.d.ts +1 -0
- package/dist/core/TaskActivitySync.test.js +90 -0
- package/dist/core/TaskAuditTimeline.test.js +96 -0
- package/dist/core/Taskforce.d.ts +190 -26
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +775 -71
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/types.d.ts +7 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
- package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
- package/dist/hooks/bootstrapTimeouts.js +8 -0
- package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
- package/dist/hooks/bootstrapTimeouts.test.js +12 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
- package/dist/hooks/sync/orchestratorShared.js +61 -1
- package/dist/hooks/sync/recovery.js +8 -0
- package/dist/hooks/sync/recovery.test.js +5 -5
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +36 -9
- package/dist/hooks/sync/transfers.test.js +534 -2
- package/dist/hooks/useFilterSort.js +13 -13
- package/dist/hooks/useFilterSort.test.js +87 -1
- package/dist/hooks/useSyncOrchestrator.js +50 -5
- package/dist/hooks/useTaskforce.d.ts +85 -50
- package/dist/hooks/useTaskforce.js +44 -14
- package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
- package/dist/hooks/useWorkspaceSyncController.js +7 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
- package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
- package/dist/mcp/canonicalAssetHelpers.js +18 -10
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
- package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
- package/dist/mcp/clientIntegrations.d.ts +1 -0
- package/dist/mcp/clientIntegrations.js +25 -0
- package/dist/mcp/clientIntegrations.test.js +14 -3
- package/dist/mcp/clientProfileDefaults.js +3 -3
- package/dist/mcp/collaborationRegistrar.js +7 -7
- package/dist/mcp/documentAssetRegistrar.js +113 -7
- package/dist/mcp/runtime.d.ts +1 -0
- package/dist/mcp/runtime.js +969 -122
- package/dist/mcp/runtime.test.js +1083 -11
- package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
- package/dist/mcp/taskPlanningRegistrar.js +48 -0
- package/dist/mcp/toolRegistry.d.ts +56 -0
- package/dist/mcp/toolRegistry.js +8 -0
- package/dist/mcp/toolRegistry.test.js +4 -0
- package/dist/migrations/taskSchemaMigrations.js +21 -0
- package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
- package/dist/server/aiProfileAvatarAssets.js +319 -0
- package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
- package/dist/server/aiProfileAvatarAssets.test.js +213 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
- package/dist/server/index.js +4 -0
- package/dist/server/index.test.js +6 -1
- package/dist/server/routes/admin.js +187 -9
- package/dist/server/routes/annotatedAttachments.js +51 -0
- package/dist/server/routes/auth.d.ts +11 -0
- package/dist/server/routes/auth.js +189 -27
- package/dist/server/routes/authSupport.d.ts +4 -0
- package/dist/server/routes/authSupport.js +12 -1
- package/dist/server/routes/billing.js +11 -0
- package/dist/server/routes/billing.test.js +3 -0
- package/dist/server/routes/documents.js +10 -0
- package/dist/server/routes/shared.js +0 -10
- package/dist/server/routes/sync.integration.test.js +221 -0
- package/dist/server/routes/sync.js +13 -3
- package/dist/server/routes/syncSnapshotRoutes.js +2 -0
- package/dist/server/routes/tasks.js +12 -9
- package/dist/server/routes.js +87 -8
- package/dist/server/routes.test.js +890 -22
- package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
- package/dist/shared/aiProfileSurfaceType.js +10 -8
- package/dist/storage/objectStorage.d.ts +1 -0
- package/dist/storage/objectStorage.js +2 -1
- package/dist/storage/objectStorageClient.d.ts +1 -0
- package/dist/storage/objectStorageClient.js +41 -8
- package/dist/storage/objectStorageClient.test.js +26 -1
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.js +13 -1
- package/dist/sync/collaborationSyncPayload.js +2 -0
- package/dist/sync/collaborationSyncPayload.test.js +1 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -3
- package/dist/sync/collaborationSyncState.js +19 -12
- package/dist/sync/contentSyncPayload.d.ts +5 -1
- package/dist/sync/contentSyncPayload.js +9 -0
- package/dist/sync/contentSyncPayload.test.js +39 -0
- package/dist/sync/contentSyncState.d.ts +5 -1
- package/dist/sync/contentSyncState.js +231 -37
- package/dist/sync/planningSyncPayload.js +5 -0
- package/dist/sync/planningSyncPayload.test.js +24 -0
- package/dist/sync/syncApplyHandlers.d.ts +4 -4
- package/dist/sync/syncApplyHandlers.js +89 -31
- package/dist/sync/syncApplyHandlers.test.js +184 -4
- package/dist/sync/syncResourceAdapters.d.ts +2 -0
- package/dist/sync/syncResourceAdapters.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +13 -1
- package/dist/sync/syncService.test.js +39 -0
- package/dist/sync/workspacePullFeed.d.ts +4 -0
- package/dist/sync/workspacePullFeed.js +129 -70
- package/dist/sync/workspacePullFeed.test.js +49 -0
- package/dist/sync/workspaceSyncModel.d.ts +28 -6
- package/dist/sync/workspaceSyncModel.js +159 -45
- package/dist/sync/workspaceSyncModel.test.js +269 -3
- package/dist/types.d.ts +33 -2
- package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
- package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-D4XseTwW.js} +1 -1
- package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
- package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
- package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
- package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-oOA9bcdK.js} +4 -4
- package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
- package/dist/ui/assets/index-607WPo_X.js +4 -0
- package/dist/ui/assets/index-CYyodXsg.css +1 -0
- package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/ui/product/agent manager.png +0 -0
- package/dist/ui/product/agent_manager_02.png +0 -0
- package/dist/utils/aiProfileEvents.d.ts +7 -0
- package/dist/utils/aiProfileEvents.js +6 -0
- package/dist/utils/assignees.d.ts +6 -0
- package/dist/utils/assignees.js +8 -1
- package/dist/utils/assignees.test.d.ts +1 -0
- package/dist/utils/assignees.test.js +25 -0
- package/dist/utils/avatarDisplay.d.ts +1 -0
- package/dist/utils/avatarDisplay.js +15 -0
- package/dist/utils/contextFiles.d.ts +3 -1
- package/dist/utils/contextFiles.js +2 -1
- package/dist/utils/taskActivity.d.ts +7 -1
- package/dist/utils/taskActivity.js +63 -1
- package/dist/utils/taskActivity.test.js +23 -1
- package/dist/utils/taskSearch.d.ts +22 -0
- package/dist/utils/taskSearch.js +87 -0
- package/dist/utils/taskSearch.test.d.ts +1 -0
- package/dist/utils/taskSearch.test.js +69 -0
- package/dist/utils/taskUtils.js +10 -3
- package/dist/utils/taskUtils.test.d.ts +1 -0
- package/dist/utils/taskUtils.test.js +19 -0
- package/package.json +1 -1
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
- package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
- package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
- package/dist/ui/assets/index-C6k75jr8.js +0 -6
|
@@ -46,9 +46,10 @@ function createCoordinator(overrides) {
|
|
|
46
46
|
refreshWorkspaceSyncMarkdownDocumentsSnapshot: vi.fn(async () => { }),
|
|
47
47
|
refreshWorkspaceSyncAiProfilesSnapshot: vi.fn(async () => { }),
|
|
48
48
|
refreshWorkspaceSyncAssetsSnapshot: vi.fn(async () => { }),
|
|
49
|
+
refreshWorkspaceSyncPlanningEntitiesSnapshot: overrides?.refreshWorkspaceSyncPlanningEntitiesSnapshot ?? vi.fn(async () => { }),
|
|
49
50
|
refreshWorkspaceSyncDocumentReviewSessionsSnapshot: vi.fn(async () => { }),
|
|
50
51
|
refreshWorkspaceSyncAnnotatedAttachmentSessionsSnapshot: vi.fn(async () => { }),
|
|
51
|
-
refreshLocalWorkspaceTaskCollections: vi.fn(async () => { }),
|
|
52
|
+
refreshLocalWorkspaceTaskCollections: overrides?.refreshLocalWorkspaceTaskCollections ?? vi.fn(async () => { }),
|
|
52
53
|
buildWorkspaceSyncPayload: (overrides?.buildWorkspaceSyncPayload ?? vi.fn(() => ({
|
|
53
54
|
changes: [],
|
|
54
55
|
currentTaskIds: new Set(),
|
|
@@ -77,7 +78,7 @@ function createCoordinator(overrides) {
|
|
|
77
78
|
currentTaskEventWatermarks: new Map(),
|
|
78
79
|
pushedWatermarks: new Map()
|
|
79
80
|
}))),
|
|
80
|
-
buildWorkspaceSyncSignature: vi.fn(() => 'sync-signature'),
|
|
81
|
+
buildWorkspaceSyncSignature: overrides?.buildWorkspaceSyncSignature ?? vi.fn(() => 'sync-signature'),
|
|
81
82
|
seedWorkspaceSnapshotPushBaseline: vi.fn(),
|
|
82
83
|
shouldSuppressAttachBootstrapPush: vi.fn(() => false),
|
|
83
84
|
recordSuppressedEventIds: vi.fn(),
|
|
@@ -136,6 +137,84 @@ describe('createWorkspaceTransferCoordinator', () => {
|
|
|
136
137
|
runWorkspacePullSyncServiceMock.mockReset();
|
|
137
138
|
runWorkspacePushSyncServiceMock.mockReset();
|
|
138
139
|
});
|
|
140
|
+
it('refreshes planning entity snapshots before building a push payload', async () => {
|
|
141
|
+
const callOrder = [];
|
|
142
|
+
const refreshWorkspaceSyncPlanningEntitiesSnapshot = vi.fn(async () => {
|
|
143
|
+
callOrder.push('planning-refresh');
|
|
144
|
+
});
|
|
145
|
+
const buildWorkspaceSyncPayload = vi.fn(() => {
|
|
146
|
+
callOrder.push('build-payload');
|
|
147
|
+
return {
|
|
148
|
+
changes: [],
|
|
149
|
+
currentTaskIds: new Set(),
|
|
150
|
+
deleteTaskIds: new Set(),
|
|
151
|
+
currentInitiativeIds: new Set(),
|
|
152
|
+
currentInitiativeWatermarks: new Map(),
|
|
153
|
+
currentWorkstreamIds: new Set(),
|
|
154
|
+
currentWorkstreamWatermarks: new Map(),
|
|
155
|
+
currentAiProfileIds: new Set(),
|
|
156
|
+
currentAiProfileWatermarks: new Map(),
|
|
157
|
+
currentDocumentPaths: new Set(),
|
|
158
|
+
deleteDocumentPaths: new Set(),
|
|
159
|
+
currentDocumentWatermarks: new Map(),
|
|
160
|
+
currentAssetPaths: new Set(),
|
|
161
|
+
deleteAssetPaths: new Set(),
|
|
162
|
+
currentAssetWatermarks: new Map(),
|
|
163
|
+
currentDocumentReviewSessionIds: new Set(),
|
|
164
|
+
deleteDocumentReviewSessionIds: new Set(),
|
|
165
|
+
currentDocumentReviewSessionWatermarks: new Map(),
|
|
166
|
+
currentDocumentReviewCommentIds: new Set(),
|
|
167
|
+
deleteDocumentReviewCommentIds: new Set(),
|
|
168
|
+
currentDocumentReviewCommentWatermarks: new Map(),
|
|
169
|
+
currentAnnotatedAttachmentSessionIds: new Set(),
|
|
170
|
+
deleteAnnotatedAttachmentSessionIds: new Set(),
|
|
171
|
+
currentAnnotatedAttachmentSessionWatermarks: new Map(),
|
|
172
|
+
currentTaskEventWatermarks: new Map(),
|
|
173
|
+
pushedWatermarks: new Map()
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
runWorkspacePushSyncServiceMock.mockResolvedValueOnce({
|
|
177
|
+
success: true,
|
|
178
|
+
syncedAt: '2026-04-27T16:00:00.000Z',
|
|
179
|
+
requestMs: 1,
|
|
180
|
+
changeCount: 0,
|
|
181
|
+
deleteCount: 0,
|
|
182
|
+
currentTaskIds: new Set(),
|
|
183
|
+
currentTaskWatermarks: new Map(),
|
|
184
|
+
currentTaskChangeKeys: new Map(),
|
|
185
|
+
deleteTaskIds: new Set(),
|
|
186
|
+
currentInitiativeIds: new Set(),
|
|
187
|
+
currentInitiativeWatermarks: new Map(),
|
|
188
|
+
currentWorkstreamIds: new Set(),
|
|
189
|
+
currentWorkstreamWatermarks: new Map(),
|
|
190
|
+
currentAiProfileIds: new Set(),
|
|
191
|
+
currentAiProfileWatermarks: new Map(),
|
|
192
|
+
currentAiProfileChangeKeys: new Map(),
|
|
193
|
+
currentDocumentPaths: new Set(),
|
|
194
|
+
currentDocumentWatermarks: new Map(),
|
|
195
|
+
currentAssetPaths: new Set(),
|
|
196
|
+
currentAssetWatermarks: new Map(),
|
|
197
|
+
currentDocumentReviewSessionIds: new Set(),
|
|
198
|
+
currentDocumentReviewSessionWatermarks: new Map(),
|
|
199
|
+
currentDocumentReviewCommentIds: new Set(),
|
|
200
|
+
currentDocumentReviewCommentWatermarks: new Map(),
|
|
201
|
+
currentAnnotatedAttachmentSessionIds: new Set(),
|
|
202
|
+
currentAnnotatedAttachmentSessionWatermarks: new Map(),
|
|
203
|
+
currentTaskEventWatermarks: new Map(),
|
|
204
|
+
pushedWatermarks: new Map(),
|
|
205
|
+
emittedEventIds: [],
|
|
206
|
+
appliedTaskUpserts: []
|
|
207
|
+
});
|
|
208
|
+
const { coordinator } = createCoordinator({
|
|
209
|
+
buildWorkspaceSyncPayload,
|
|
210
|
+
refreshWorkspaceSyncPlanningEntitiesSnapshot
|
|
211
|
+
});
|
|
212
|
+
const result = await coordinator.pushWorkspaceChangesToCloud('signature-1');
|
|
213
|
+
expect(result).toBe(true);
|
|
214
|
+
expect(refreshWorkspaceSyncPlanningEntitiesSnapshot).toHaveBeenCalledTimes(1);
|
|
215
|
+
expect(buildWorkspaceSyncPayload).toHaveBeenCalledTimes(1);
|
|
216
|
+
expect(callOrder).toEqual(['planning-refresh', 'build-payload']);
|
|
217
|
+
});
|
|
139
218
|
it('persists a cleared cursor before scheduling an invalid-cursor retry', async () => {
|
|
140
219
|
runWorkspacePullSyncServiceMock.mockResolvedValueOnce({
|
|
141
220
|
success: false,
|
|
@@ -162,6 +241,153 @@ describe('createWorkspaceTransferCoordinator', () => {
|
|
|
162
241
|
expect(persistWorkspaceSyncPatchSafely).toHaveBeenCalledWith({ pullCursor: null });
|
|
163
242
|
expect(scheduleWorkspaceSyncRetry).toHaveBeenCalledWith(150);
|
|
164
243
|
});
|
|
244
|
+
it('refreshes local planning collections after planning-only pull changes', async () => {
|
|
245
|
+
const refreshLocalWorkspaceTaskCollections = vi.fn(async () => { });
|
|
246
|
+
runWorkspacePullSyncServiceMock.mockResolvedValueOnce({
|
|
247
|
+
success: true,
|
|
248
|
+
syncedAt: '2026-04-27T22:00:00.000Z',
|
|
249
|
+
cursor: 'planning-cursor',
|
|
250
|
+
pages: 1,
|
|
251
|
+
appliedChanges: 1,
|
|
252
|
+
pullRequestMs: 1,
|
|
253
|
+
applyMs: 1,
|
|
254
|
+
pulledDeleteTaskIds: new Set(),
|
|
255
|
+
pulledActiveUpserts: false,
|
|
256
|
+
pulledArchivedUpserts: false,
|
|
257
|
+
pulledTaskEventUpserts: false,
|
|
258
|
+
pulledPlanningUpserts: true,
|
|
259
|
+
documentDecryptFailures: [],
|
|
260
|
+
emittedEventIds: []
|
|
261
|
+
});
|
|
262
|
+
const { coordinator } = createCoordinator({ refreshLocalWorkspaceTaskCollections });
|
|
263
|
+
const result = await coordinator.pullWorkspaceChangesFromCloud();
|
|
264
|
+
expect(result).toBe(true);
|
|
265
|
+
expect(refreshLocalWorkspaceTaskCollections).toHaveBeenCalledTimes(1);
|
|
266
|
+
});
|
|
267
|
+
it('reasserts all AI profiles after a full reconcile pull', async () => {
|
|
268
|
+
vi.useFakeTimers();
|
|
269
|
+
runWorkspacePullSyncServiceMock.mockResolvedValueOnce({
|
|
270
|
+
success: true,
|
|
271
|
+
syncedAt: '2026-03-15T19:30:00.000Z',
|
|
272
|
+
cursor: 'fresh-cursor',
|
|
273
|
+
pages: 1,
|
|
274
|
+
appliedChanges: 0,
|
|
275
|
+
pullRequestMs: 1,
|
|
276
|
+
applyMs: 0,
|
|
277
|
+
pulledDeleteTaskIds: new Set(),
|
|
278
|
+
pulledActiveUpserts: false,
|
|
279
|
+
pulledArchivedUpserts: false,
|
|
280
|
+
documentDecryptFailures: []
|
|
281
|
+
});
|
|
282
|
+
runWorkspacePushSyncServiceMock.mockResolvedValueOnce({
|
|
283
|
+
success: true,
|
|
284
|
+
syncedAt: '2026-03-15T19:30:01.000Z',
|
|
285
|
+
requestMs: 1,
|
|
286
|
+
changeCount: 1,
|
|
287
|
+
deleteCount: 0,
|
|
288
|
+
currentTaskIds: new Set(),
|
|
289
|
+
currentTaskWatermarks: new Map(),
|
|
290
|
+
currentTaskChangeKeys: new Map(),
|
|
291
|
+
deleteTaskIds: new Set(),
|
|
292
|
+
currentInitiativeIds: new Set(),
|
|
293
|
+
currentInitiativeWatermarks: new Map(),
|
|
294
|
+
currentWorkstreamIds: new Set(),
|
|
295
|
+
currentWorkstreamWatermarks: new Map(),
|
|
296
|
+
currentAiProfileIds: new Set(['ai-profile-john-drake']),
|
|
297
|
+
currentAiProfileWatermarks: new Map([['ai-profile-john-drake', '2026-03-15T19:00:00.000Z']]),
|
|
298
|
+
currentAiProfileChangeKeys: new Map([['ai-profile-john-drake', 'john-change-key']]),
|
|
299
|
+
currentDocumentPaths: new Set(),
|
|
300
|
+
currentDocumentWatermarks: new Map(),
|
|
301
|
+
currentAssetPaths: new Set(),
|
|
302
|
+
currentAssetWatermarks: new Map(),
|
|
303
|
+
currentDocumentReviewSessionIds: new Set(),
|
|
304
|
+
currentDocumentReviewSessionWatermarks: new Map(),
|
|
305
|
+
currentDocumentReviewCommentIds: new Set(),
|
|
306
|
+
currentDocumentReviewCommentWatermarks: new Map(),
|
|
307
|
+
currentAnnotatedAttachmentSessionIds: new Set(),
|
|
308
|
+
currentAnnotatedAttachmentSessionWatermarks: new Map(),
|
|
309
|
+
currentTaskEventWatermarks: new Map(),
|
|
310
|
+
pushedWatermarks: new Map(),
|
|
311
|
+
emittedEventIds: [],
|
|
312
|
+
appliedTaskUpserts: []
|
|
313
|
+
});
|
|
314
|
+
const buildWorkspaceSyncPayload = vi.fn(() => ({
|
|
315
|
+
changes: [{
|
|
316
|
+
op: 'ai-profile-upsert',
|
|
317
|
+
profile: {
|
|
318
|
+
id: 'ai-profile-john-drake',
|
|
319
|
+
workspaceId: 'workspace-local-active',
|
|
320
|
+
profileToken: 'token-john',
|
|
321
|
+
name: 'John Drake',
|
|
322
|
+
username: 'john-drake',
|
|
323
|
+
icon: 'Bot',
|
|
324
|
+
color: '#8b5cf6',
|
|
325
|
+
avatarUrl: null,
|
|
326
|
+
avatarSourceUrl: null,
|
|
327
|
+
description: null,
|
|
328
|
+
role: null,
|
|
329
|
+
provider: null,
|
|
330
|
+
model: null,
|
|
331
|
+
surfaceType: null,
|
|
332
|
+
seatScope: null,
|
|
333
|
+
providerMetadata: null,
|
|
334
|
+
archivedAt: null,
|
|
335
|
+
archivedBy: null,
|
|
336
|
+
archivedReason: null,
|
|
337
|
+
mergedIntoProfileId: null,
|
|
338
|
+
rosterStateUpdatedAt: null,
|
|
339
|
+
createdAt: '2026-03-15T19:00:00.000Z',
|
|
340
|
+
updatedAt: '2026-03-15T19:00:00.000Z',
|
|
341
|
+
lastActiveAt: null
|
|
342
|
+
}
|
|
343
|
+
}],
|
|
344
|
+
currentTaskIds: new Set(),
|
|
345
|
+
deleteTaskIds: new Set(),
|
|
346
|
+
currentInitiativeIds: new Set(),
|
|
347
|
+
currentInitiativeWatermarks: new Map(),
|
|
348
|
+
currentWorkstreamIds: new Set(),
|
|
349
|
+
currentWorkstreamWatermarks: new Map(),
|
|
350
|
+
currentAiProfileIds: new Set(['ai-profile-john-drake']),
|
|
351
|
+
currentAiProfileWatermarks: new Map([['ai-profile-john-drake', '2026-03-15T19:00:00.000Z']]),
|
|
352
|
+
currentAiProfileChangeKeys: new Map([['ai-profile-john-drake', 'john-change-key']]),
|
|
353
|
+
currentDocumentPaths: new Set(),
|
|
354
|
+
deleteDocumentPaths: new Set(),
|
|
355
|
+
currentDocumentWatermarks: new Map(),
|
|
356
|
+
currentAssetPaths: new Set(),
|
|
357
|
+
deleteAssetPaths: new Set(),
|
|
358
|
+
currentAssetWatermarks: new Map(),
|
|
359
|
+
currentDocumentReviewSessionIds: new Set(),
|
|
360
|
+
deleteDocumentReviewSessionIds: new Set(),
|
|
361
|
+
currentDocumentReviewSessionWatermarks: new Map(),
|
|
362
|
+
currentDocumentReviewCommentIds: new Set(),
|
|
363
|
+
deleteDocumentReviewCommentIds: new Set(),
|
|
364
|
+
currentDocumentReviewCommentWatermarks: new Map(),
|
|
365
|
+
currentAnnotatedAttachmentSessionIds: new Set(),
|
|
366
|
+
deleteAnnotatedAttachmentSessionIds: new Set(),
|
|
367
|
+
currentAnnotatedAttachmentSessionWatermarks: new Map(),
|
|
368
|
+
currentTaskEventWatermarks: new Map(),
|
|
369
|
+
pushedWatermarks: new Map()
|
|
370
|
+
}));
|
|
371
|
+
const { coordinator } = createCoordinator({ buildWorkspaceSyncPayload });
|
|
372
|
+
await coordinator.pullWorkspaceChangesFromCloud({ forceCursorNull: true });
|
|
373
|
+
await vi.advanceTimersByTimeAsync(130);
|
|
374
|
+
await Promise.resolve();
|
|
375
|
+
expect(runWorkspacePushSyncServiceMock).toHaveBeenCalledTimes(1);
|
|
376
|
+
expect(buildWorkspaceSyncPayload).toHaveBeenCalledWith({ forceAllAiProfiles: true });
|
|
377
|
+
expect(runWorkspacePushSyncServiceMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
378
|
+
payload: expect.objectContaining({
|
|
379
|
+
changes: expect.arrayContaining([
|
|
380
|
+
expect.objectContaining({
|
|
381
|
+
op: 'ai-profile-upsert',
|
|
382
|
+
profile: expect.objectContaining({
|
|
383
|
+
id: 'ai-profile-john-drake',
|
|
384
|
+
name: 'John Drake'
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
])
|
|
388
|
+
})
|
|
389
|
+
}));
|
|
390
|
+
});
|
|
165
391
|
it('reports push payload diagnostics when a push fails', async () => {
|
|
166
392
|
runWorkspacePushSyncServiceMock.mockResolvedValueOnce({
|
|
167
393
|
success: false,
|
|
@@ -393,4 +619,310 @@ describe('createWorkspaceTransferCoordinator', () => {
|
|
|
393
619
|
})
|
|
394
620
|
}));
|
|
395
621
|
});
|
|
622
|
+
it('rebuilds a transient replay payload when refreshed sync state changes the signature before retry', async () => {
|
|
623
|
+
const stalePayload = {
|
|
624
|
+
changes: [{
|
|
625
|
+
op: 'workstream-upsert',
|
|
626
|
+
workstream: {
|
|
627
|
+
id: 'workstream-1',
|
|
628
|
+
referenceNumber: 701,
|
|
629
|
+
title: 'Stale workstream',
|
|
630
|
+
description: null,
|
|
631
|
+
ownerId: null,
|
|
632
|
+
initiativeId: null,
|
|
633
|
+
createdAt: '2026-04-21T12:00:00.000Z',
|
|
634
|
+
updatedAt: '2026-04-21T12:00:00.000Z',
|
|
635
|
+
order: null,
|
|
636
|
+
attachments: [],
|
|
637
|
+
isArchived: false
|
|
638
|
+
}
|
|
639
|
+
}],
|
|
640
|
+
currentTaskIds: new Set(),
|
|
641
|
+
deleteTaskIds: new Set(),
|
|
642
|
+
currentInitiativeIds: new Set(),
|
|
643
|
+
currentInitiativeWatermarks: new Map(),
|
|
644
|
+
currentWorkstreamIds: new Set(['workstream-1']),
|
|
645
|
+
currentWorkstreamWatermarks: new Map([['workstream-1', '2026-04-21T12:00:00.000Z']]),
|
|
646
|
+
currentAiProfileIds: new Set(),
|
|
647
|
+
currentAiProfileWatermarks: new Map(),
|
|
648
|
+
currentDocumentPaths: new Set(),
|
|
649
|
+
deleteDocumentPaths: new Set(),
|
|
650
|
+
currentDocumentWatermarks: new Map(),
|
|
651
|
+
currentAssetPaths: new Set(),
|
|
652
|
+
deleteAssetPaths: new Set(),
|
|
653
|
+
currentAssetWatermarks: new Map(),
|
|
654
|
+
currentDocumentReviewSessionIds: new Set(),
|
|
655
|
+
deleteDocumentReviewSessionIds: new Set(),
|
|
656
|
+
currentDocumentReviewSessionWatermarks: new Map(),
|
|
657
|
+
currentDocumentReviewCommentIds: new Set(),
|
|
658
|
+
deleteDocumentReviewCommentIds: new Set(),
|
|
659
|
+
currentDocumentReviewCommentWatermarks: new Map(),
|
|
660
|
+
currentAnnotatedAttachmentSessionIds: new Set(),
|
|
661
|
+
deleteAnnotatedAttachmentSessionIds: new Set(),
|
|
662
|
+
currentAnnotatedAttachmentSessionWatermarks: new Map(),
|
|
663
|
+
currentTaskEventWatermarks: new Map(),
|
|
664
|
+
pushedWatermarks: new Map()
|
|
665
|
+
};
|
|
666
|
+
const refreshedPayload = {
|
|
667
|
+
...stalePayload,
|
|
668
|
+
changes: [{
|
|
669
|
+
op: 'workstream-upsert',
|
|
670
|
+
workstream: {
|
|
671
|
+
id: 'workstream-1',
|
|
672
|
+
referenceNumber: 701,
|
|
673
|
+
title: 'Stale workstream',
|
|
674
|
+
description: null,
|
|
675
|
+
ownerId: null,
|
|
676
|
+
initiativeId: null,
|
|
677
|
+
createdAt: '2026-04-21T12:00:00.000Z',
|
|
678
|
+
updatedAt: '2026-04-21T12:00:05.000Z',
|
|
679
|
+
order: null,
|
|
680
|
+
attachments: [{
|
|
681
|
+
path: '/api/taskforce/documents/asset-doc-1/content',
|
|
682
|
+
fsPath: 'workspaces/workspace-local-active/assets/documents/doc-1.md',
|
|
683
|
+
assetId: 'asset-doc-1',
|
|
684
|
+
caption: 'Plan',
|
|
685
|
+
timestamp: '2026-04-21T12:00:05.000Z'
|
|
686
|
+
}],
|
|
687
|
+
isArchived: false
|
|
688
|
+
}
|
|
689
|
+
}],
|
|
690
|
+
currentWorkstreamWatermarks: new Map([['workstream-1', '2026-04-21T12:00:05.000Z']])
|
|
691
|
+
};
|
|
692
|
+
const buildWorkspaceSyncPayload = vi.fn()
|
|
693
|
+
.mockReturnValueOnce(stalePayload)
|
|
694
|
+
.mockReturnValueOnce(refreshedPayload);
|
|
695
|
+
const buildWorkspaceSyncSignature = vi.fn()
|
|
696
|
+
.mockReturnValueOnce('signature-stale')
|
|
697
|
+
.mockReturnValueOnce('signature-fresh')
|
|
698
|
+
.mockReturnValue('signature-fresh');
|
|
699
|
+
runWorkspacePushSyncServiceMock
|
|
700
|
+
.mockResolvedValueOnce({
|
|
701
|
+
success: false,
|
|
702
|
+
status: 524,
|
|
703
|
+
error: '524 unknown',
|
|
704
|
+
transient: true,
|
|
705
|
+
requestMs: 125000,
|
|
706
|
+
changeCount: 1,
|
|
707
|
+
deleteCount: 0,
|
|
708
|
+
currentTaskIds: new Set(),
|
|
709
|
+
deleteTaskIds: new Set(),
|
|
710
|
+
currentInitiativeIds: new Set(),
|
|
711
|
+
currentInitiativeWatermarks: new Map(),
|
|
712
|
+
currentWorkstreamIds: new Set(['workstream-1']),
|
|
713
|
+
currentWorkstreamWatermarks: new Map([['workstream-1', '2026-04-21T12:00:00.000Z']]),
|
|
714
|
+
currentAiProfileIds: new Set(),
|
|
715
|
+
currentAiProfileWatermarks: new Map(),
|
|
716
|
+
currentDocumentPaths: new Set(),
|
|
717
|
+
deleteDocumentPaths: new Set(),
|
|
718
|
+
currentDocumentWatermarks: new Map(),
|
|
719
|
+
currentAssetPaths: new Set(),
|
|
720
|
+
deleteAssetPaths: new Set(),
|
|
721
|
+
currentAssetWatermarks: new Map(),
|
|
722
|
+
currentDocumentReviewSessionIds: new Set(),
|
|
723
|
+
currentDocumentReviewSessionWatermarks: new Map(),
|
|
724
|
+
currentDocumentReviewCommentIds: new Set(),
|
|
725
|
+
currentDocumentReviewCommentWatermarks: new Map(),
|
|
726
|
+
currentAnnotatedAttachmentSessionIds: new Set(),
|
|
727
|
+
currentAnnotatedAttachmentSessionWatermarks: new Map(),
|
|
728
|
+
currentTaskEventWatermarks: new Map(),
|
|
729
|
+
pushedWatermarks: new Map(),
|
|
730
|
+
emittedEventIds: [],
|
|
731
|
+
appliedTaskUpserts: []
|
|
732
|
+
})
|
|
733
|
+
.mockResolvedValueOnce({
|
|
734
|
+
success: true,
|
|
735
|
+
syncedAt: '2026-04-21T12:00:10.000Z',
|
|
736
|
+
requestMs: 200,
|
|
737
|
+
changeCount: 1,
|
|
738
|
+
deleteCount: 0,
|
|
739
|
+
currentTaskIds: new Set(),
|
|
740
|
+
deleteTaskIds: new Set(),
|
|
741
|
+
currentInitiativeIds: new Set(),
|
|
742
|
+
currentInitiativeWatermarks: new Map(),
|
|
743
|
+
currentWorkstreamIds: new Set(['workstream-1']),
|
|
744
|
+
currentWorkstreamWatermarks: new Map([['workstream-1', '2026-04-21T12:00:05.000Z']]),
|
|
745
|
+
currentAiProfileIds: new Set(),
|
|
746
|
+
currentAiProfileWatermarks: new Map(),
|
|
747
|
+
currentDocumentPaths: new Set(),
|
|
748
|
+
currentDocumentWatermarks: new Map(),
|
|
749
|
+
currentAssetPaths: new Set(),
|
|
750
|
+
currentAssetWatermarks: new Map(),
|
|
751
|
+
currentDocumentReviewSessionIds: new Set(),
|
|
752
|
+
currentDocumentReviewSessionWatermarks: new Map(),
|
|
753
|
+
currentDocumentReviewCommentIds: new Set(),
|
|
754
|
+
currentDocumentReviewCommentWatermarks: new Map(),
|
|
755
|
+
currentAnnotatedAttachmentSessionIds: new Set(),
|
|
756
|
+
currentAnnotatedAttachmentSessionWatermarks: new Map(),
|
|
757
|
+
currentTaskEventWatermarks: new Map(),
|
|
758
|
+
pushedWatermarks: new Map(),
|
|
759
|
+
emittedEventIds: [],
|
|
760
|
+
appliedTaskUpserts: []
|
|
761
|
+
});
|
|
762
|
+
const { coordinator, reportSyncEvent } = createCoordinator({
|
|
763
|
+
buildWorkspaceSyncPayload,
|
|
764
|
+
buildWorkspaceSyncSignature
|
|
765
|
+
});
|
|
766
|
+
const firstResult = await coordinator.pushWorkspaceChangesToCloud('signature-stale');
|
|
767
|
+
const secondResult = await coordinator.pushWorkspaceChangesToCloud('signature-stale');
|
|
768
|
+
expect(firstResult).toBe(false);
|
|
769
|
+
expect(secondResult).toBe(true);
|
|
770
|
+
expect(buildWorkspaceSyncPayload).toHaveBeenCalledTimes(2);
|
|
771
|
+
expect(runWorkspacePushSyncServiceMock.mock.calls[0]?.[0]?.payload).toBe(stalePayload);
|
|
772
|
+
expect(runWorkspacePushSyncServiceMock.mock.calls[1]?.[0]?.payload).toBe(refreshedPayload);
|
|
773
|
+
expect(reportSyncEvent).toHaveBeenLastCalledWith('workspace-local-active', expect.objectContaining({
|
|
774
|
+
eventType: 'push',
|
|
775
|
+
status: 'success',
|
|
776
|
+
details: expect.objectContaining({
|
|
777
|
+
replayedPayload: false
|
|
778
|
+
})
|
|
779
|
+
}));
|
|
780
|
+
});
|
|
781
|
+
it('rebuilds a transient replay payload when a later retry forces all AI profiles', async () => {
|
|
782
|
+
const firstPayload = {
|
|
783
|
+
changes: [],
|
|
784
|
+
currentTaskIds: new Set(),
|
|
785
|
+
deleteTaskIds: new Set(),
|
|
786
|
+
currentInitiativeIds: new Set(),
|
|
787
|
+
currentInitiativeWatermarks: new Map(),
|
|
788
|
+
currentWorkstreamIds: new Set(),
|
|
789
|
+
currentWorkstreamWatermarks: new Map(),
|
|
790
|
+
currentAiProfileIds: new Set(),
|
|
791
|
+
currentAiProfileWatermarks: new Map(),
|
|
792
|
+
currentDocumentPaths: new Set(),
|
|
793
|
+
deleteDocumentPaths: new Set(),
|
|
794
|
+
currentDocumentWatermarks: new Map(),
|
|
795
|
+
currentAssetPaths: new Set(),
|
|
796
|
+
deleteAssetPaths: new Set(),
|
|
797
|
+
currentAssetWatermarks: new Map(),
|
|
798
|
+
currentDocumentReviewSessionIds: new Set(),
|
|
799
|
+
deleteDocumentReviewSessionIds: new Set(),
|
|
800
|
+
currentDocumentReviewSessionWatermarks: new Map(),
|
|
801
|
+
currentDocumentReviewCommentIds: new Set(),
|
|
802
|
+
deleteDocumentReviewCommentIds: new Set(),
|
|
803
|
+
currentDocumentReviewCommentWatermarks: new Map(),
|
|
804
|
+
currentAnnotatedAttachmentSessionIds: new Set(),
|
|
805
|
+
deleteAnnotatedAttachmentSessionIds: new Set(),
|
|
806
|
+
currentAnnotatedAttachmentSessionWatermarks: new Map(),
|
|
807
|
+
currentTaskEventWatermarks: new Map(),
|
|
808
|
+
pushedWatermarks: new Map()
|
|
809
|
+
};
|
|
810
|
+
const forcedAiProfilePayload = {
|
|
811
|
+
...firstPayload,
|
|
812
|
+
changes: [{
|
|
813
|
+
op: 'ai-profile-upsert',
|
|
814
|
+
profile: {
|
|
815
|
+
id: 'ai-profile-codex',
|
|
816
|
+
workspaceId: 'workspace-local-active',
|
|
817
|
+
profileToken: 'tfp_codex',
|
|
818
|
+
name: 'Codex',
|
|
819
|
+
username: 'codex',
|
|
820
|
+
icon: 'Bot',
|
|
821
|
+
color: '#8b5cf6',
|
|
822
|
+
avatarUrl: null,
|
|
823
|
+
avatarSourceUrl: null,
|
|
824
|
+
description: null,
|
|
825
|
+
role: null,
|
|
826
|
+
provider: null,
|
|
827
|
+
model: null,
|
|
828
|
+
surfaceType: null,
|
|
829
|
+
seatScope: 'cloud_metered',
|
|
830
|
+
providerMetadata: null,
|
|
831
|
+
archivedAt: null,
|
|
832
|
+
archivedBy: null,
|
|
833
|
+
archivedReason: null,
|
|
834
|
+
mergedIntoProfileId: null,
|
|
835
|
+
rosterStateUpdatedAt: '2026-04-21T12:00:00.000Z',
|
|
836
|
+
createdAt: '2026-04-21T12:00:00.000Z',
|
|
837
|
+
updatedAt: '2026-04-21T12:00:00.000Z',
|
|
838
|
+
lastActiveAt: null
|
|
839
|
+
}
|
|
840
|
+
}],
|
|
841
|
+
currentAiProfileIds: new Set(['ai-profile-codex']),
|
|
842
|
+
currentAiProfileWatermarks: new Map([['ai-profile-codex', '2026-04-21T12:00:00.000Z']]),
|
|
843
|
+
currentAiProfileChangeKeys: new Map([['ai-profile-codex', 'codex-change-key']])
|
|
844
|
+
};
|
|
845
|
+
const buildWorkspaceSyncPayload = vi.fn()
|
|
846
|
+
.mockReturnValueOnce(firstPayload)
|
|
847
|
+
.mockReturnValueOnce(forcedAiProfilePayload);
|
|
848
|
+
runWorkspacePushSyncServiceMock
|
|
849
|
+
.mockResolvedValueOnce({
|
|
850
|
+
success: false,
|
|
851
|
+
status: 524,
|
|
852
|
+
error: '524 unknown',
|
|
853
|
+
transient: true,
|
|
854
|
+
requestMs: 125000,
|
|
855
|
+
changeCount: 0,
|
|
856
|
+
deleteCount: 0,
|
|
857
|
+
currentTaskIds: new Set(),
|
|
858
|
+
deleteTaskIds: new Set(),
|
|
859
|
+
currentInitiativeIds: new Set(),
|
|
860
|
+
currentInitiativeWatermarks: new Map(),
|
|
861
|
+
currentWorkstreamIds: new Set(),
|
|
862
|
+
currentWorkstreamWatermarks: new Map(),
|
|
863
|
+
currentAiProfileIds: new Set(),
|
|
864
|
+
currentAiProfileWatermarks: new Map(),
|
|
865
|
+
currentDocumentPaths: new Set(),
|
|
866
|
+
deleteDocumentPaths: new Set(),
|
|
867
|
+
currentDocumentWatermarks: new Map(),
|
|
868
|
+
currentAssetPaths: new Set(),
|
|
869
|
+
deleteAssetPaths: new Set(),
|
|
870
|
+
currentAssetWatermarks: new Map(),
|
|
871
|
+
currentDocumentReviewSessionIds: new Set(),
|
|
872
|
+
deleteDocumentReviewSessionIds: new Set(),
|
|
873
|
+
currentDocumentReviewSessionWatermarks: new Map(),
|
|
874
|
+
currentDocumentReviewCommentIds: new Set(),
|
|
875
|
+
deleteDocumentReviewCommentIds: new Set(),
|
|
876
|
+
currentDocumentReviewCommentWatermarks: new Map(),
|
|
877
|
+
currentAnnotatedAttachmentSessionIds: new Set(),
|
|
878
|
+
deleteAnnotatedAttachmentSessionIds: new Set(),
|
|
879
|
+
currentAnnotatedAttachmentSessionWatermarks: new Map(),
|
|
880
|
+
currentTaskEventWatermarks: new Map(),
|
|
881
|
+
pushedWatermarks: new Map(),
|
|
882
|
+
emittedEventIds: [],
|
|
883
|
+
appliedTaskUpserts: []
|
|
884
|
+
})
|
|
885
|
+
.mockResolvedValueOnce({
|
|
886
|
+
success: true,
|
|
887
|
+
syncedAt: '2026-04-21T12:00:00.000Z',
|
|
888
|
+
requestMs: 200,
|
|
889
|
+
changeCount: 1,
|
|
890
|
+
deleteCount: 0,
|
|
891
|
+
currentTaskIds: new Set(),
|
|
892
|
+
currentTaskWatermarks: new Map(),
|
|
893
|
+
currentTaskChangeKeys: new Map(),
|
|
894
|
+
deleteTaskIds: new Set(),
|
|
895
|
+
currentInitiativeIds: new Set(),
|
|
896
|
+
currentInitiativeWatermarks: new Map(),
|
|
897
|
+
currentWorkstreamIds: new Set(),
|
|
898
|
+
currentWorkstreamWatermarks: new Map(),
|
|
899
|
+
currentAiProfileIds: new Set(['ai-profile-codex']),
|
|
900
|
+
currentAiProfileWatermarks: new Map([['ai-profile-codex', '2026-04-21T12:00:00.000Z']]),
|
|
901
|
+
currentAiProfileChangeKeys: new Map([['ai-profile-codex', 'codex-change-key']]),
|
|
902
|
+
currentDocumentPaths: new Set(),
|
|
903
|
+
currentDocumentWatermarks: new Map(),
|
|
904
|
+
currentAssetPaths: new Set(),
|
|
905
|
+
currentAssetWatermarks: new Map(),
|
|
906
|
+
currentDocumentReviewSessionIds: new Set(),
|
|
907
|
+
currentDocumentReviewSessionWatermarks: new Map(),
|
|
908
|
+
currentDocumentReviewCommentIds: new Set(),
|
|
909
|
+
currentDocumentReviewCommentWatermarks: new Map(),
|
|
910
|
+
currentAnnotatedAttachmentSessionIds: new Set(),
|
|
911
|
+
currentAnnotatedAttachmentSessionWatermarks: new Map(),
|
|
912
|
+
currentTaskEventWatermarks: new Map(),
|
|
913
|
+
pushedWatermarks: new Map(),
|
|
914
|
+
emittedEventIds: [],
|
|
915
|
+
appliedTaskUpserts: []
|
|
916
|
+
});
|
|
917
|
+
const { coordinator } = createCoordinator({ buildWorkspaceSyncPayload });
|
|
918
|
+
const firstResult = await coordinator.pushWorkspaceChangesToCloud('signature-1');
|
|
919
|
+
const secondResult = await coordinator.pushWorkspaceChangesToCloud('signature-1', { forceAllAiProfiles: true });
|
|
920
|
+
expect(firstResult).toBe(false);
|
|
921
|
+
expect(secondResult).toBe(true);
|
|
922
|
+
expect(buildWorkspaceSyncPayload).toHaveBeenCalledTimes(2);
|
|
923
|
+
expect(buildWorkspaceSyncPayload).toHaveBeenNthCalledWith(1, { forceAllAiProfiles: false });
|
|
924
|
+
expect(buildWorkspaceSyncPayload).toHaveBeenNthCalledWith(2, { forceAllAiProfiles: true });
|
|
925
|
+
expect(runWorkspacePushSyncServiceMock.mock.calls[0]?.[0]?.payload).toBe(firstPayload);
|
|
926
|
+
expect(runWorkspacePushSyncServiceMock.mock.calls[1]?.[0]?.payload).toBe(forcedAiProfilePayload);
|
|
927
|
+
});
|
|
396
928
|
});
|
|
@@ -25,7 +25,7 @@ import { DEFAULT_TYPE_VALUE } from '../shared/taxonomyDefaults.js';
|
|
|
25
25
|
import { STATUS_OPTIONS } from '../utils/constants';
|
|
26
26
|
import { normalizeAssigneeOptions } from '../utils/assignees';
|
|
27
27
|
import { matchesPriorityFilter, normalizeCategoryFilters, normalizePriorityFilterValues } from '../utils/taskNormalization.js';
|
|
28
|
-
import {
|
|
28
|
+
import { normalizeTaskSearchQuery, taskMatchesSearch } from '../utils/taskSearch';
|
|
29
29
|
import { compareTasksBySortKey } from '../utils/taxonomySorting.js';
|
|
30
30
|
import { getFilterVisibleTaxonomyOptions } from '../utils/taxonomyFilterOptions.js';
|
|
31
31
|
import { getVisibleTaxonomiesForTasks } from '../utils/taxonomyLifecycle.js';
|
|
@@ -211,17 +211,21 @@ export function useFilterSort({ tasks, archivedTasks, activeCategories, activeTy
|
|
|
211
211
|
});
|
|
212
212
|
}, [assigneeFilterValues]);
|
|
213
213
|
// === SECTION: Derived Collections ===
|
|
214
|
+
const normalizedSearchQuery = useMemo(() => normalizeTaskSearchQuery(searchQuery), [searchQuery]);
|
|
215
|
+
const taskSearchContext = useMemo(() => ({
|
|
216
|
+
categories: activeCategories,
|
|
217
|
+
types: activeTypes,
|
|
218
|
+
priorities,
|
|
219
|
+
assigneeOptions: resolvedAssigneeOptions,
|
|
220
|
+
taxonomies
|
|
221
|
+
}), [activeCategories, activeTypes, priorities, resolvedAssigneeOptions, taxonomies]);
|
|
214
222
|
const filteredTasks = useMemo(() => {
|
|
215
223
|
const isAllCategoriesSelected = isAllFilterValuesSelected(filterCategories, activeCategories.map((category) => category.value));
|
|
216
224
|
const isAllTypesSelected = isAllFilterValuesSelected(filterTypes, activeTypes.map((type) => type.value));
|
|
217
225
|
const isAllPrioritiesSelected = isAllFilterValuesSelected(Array.from(new Set(filterPriorities.map((value) => Number(value)).filter((value) => Number.isFinite(value)))), priorities.map((priority) => Number(priority.value)).filter((value) => Number.isFinite(value)));
|
|
218
226
|
const isAllStatusesSelected = isAllFilterValuesSelected(filterStatus, STATUS_OPTIONS.map((status) => status.value));
|
|
219
227
|
return tasks.filter(t => {
|
|
220
|
-
const
|
|
221
|
-
const matchSearch = (t.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
222
|
-
(t.description?.toLowerCase() || '').includes(searchQuery.toLowerCase()) ||
|
|
223
|
-
t.id.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
224
|
-
referenceLabel.includes(searchQuery.toLowerCase()));
|
|
228
|
+
const matchSearch = taskMatchesSearch(t, normalizedSearchQuery, taskSearchContext);
|
|
225
229
|
const matchCategory = !hasInitedFilters || isAllCategoriesSelected || filterCategories.includes(t.category);
|
|
226
230
|
const matchPriority = !hasInitedFilters || isAllPrioritiesSelected || matchesPriorityFilter(t.priority, filterPriorities);
|
|
227
231
|
const matchType = !hasInitedFilters || isAllTypesSelected || filterTypes.includes(t.type || DEFAULT_TYPE_VALUE);
|
|
@@ -245,7 +249,7 @@ export function useFilterSort({ tasks, archivedTasks, activeCategories, activeTy
|
|
|
245
249
|
});
|
|
246
250
|
return matchSearch && matchCategory && matchPriority && matchType && matchStatus && matchAssignee && matchTaxonomies;
|
|
247
251
|
}).sort((a, b) => compareTasksBySortKey(a, b, sortBy, sortOrder, taxonomies));
|
|
248
|
-
}, [tasks, archivedTasks,
|
|
252
|
+
}, [tasks, archivedTasks, normalizedSearchQuery, taskSearchContext, filterCategories, filterPriorities, filterTypes, filterStatus, filterAssignees, filterAssigneesAllSelected, filterTaxonomies, sortBy, sortOrder, hasInitedFilters, filterableTaxonomies, activeCategories, activeTypes, priorities, isAllFilterValuesSelected, taxonomies]);
|
|
249
253
|
const searchAgnosticTasks = useMemo(() => {
|
|
250
254
|
const isAllCategoriesSelected = isAllFilterValuesSelected(filterCategories, activeCategories.map((category) => category.value));
|
|
251
255
|
const isAllTypesSelected = isAllFilterValuesSelected(filterTypes, activeTypes.map((type) => type.value));
|
|
@@ -280,11 +284,7 @@ export function useFilterSort({ tasks, archivedTasks, activeCategories, activeTy
|
|
|
280
284
|
const isAllTypesSelected = isAllFilterValuesSelected(filterTypes, activeTypes.map((type) => type.value));
|
|
281
285
|
const isAllPrioritiesSelected = isAllFilterValuesSelected(Array.from(new Set(filterPriorities.map((value) => Number(value)).filter((value) => Number.isFinite(value)))), priorities.map((priority) => Number(priority.value)).filter((value) => Number.isFinite(value)));
|
|
282
286
|
return archivedTasks.filter(t => {
|
|
283
|
-
const
|
|
284
|
-
const matchSearch = (t.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
285
|
-
(t.description?.toLowerCase() || '').includes(searchQuery.toLowerCase()) ||
|
|
286
|
-
t.id.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
287
|
-
referenceLabel.includes(searchQuery.toLowerCase()));
|
|
287
|
+
const matchSearch = taskMatchesSearch(t, normalizedSearchQuery, taskSearchContext);
|
|
288
288
|
const matchCategory = !hasInitedFilters || isAllCategoriesSelected || filterCategories.includes(t.category);
|
|
289
289
|
const matchPriority = !hasInitedFilters || isAllPrioritiesSelected || matchesPriorityFilter(t.priority, filterPriorities);
|
|
290
290
|
const matchType = !hasInitedFilters || isAllTypesSelected || filterTypes.includes(t.type || DEFAULT_TYPE_VALUE);
|
|
@@ -306,7 +306,7 @@ export function useFilterSort({ tasks, archivedTasks, activeCategories, activeTy
|
|
|
306
306
|
});
|
|
307
307
|
return matchSearch && matchCategory && matchPriority && matchType && matchAssignee && matchTaxonomies;
|
|
308
308
|
}).sort((a, b) => compareTasksBySortKey(a, b, sortBy, sortOrder, taxonomies));
|
|
309
|
-
}, [archivedTasks,
|
|
309
|
+
}, [archivedTasks, normalizedSearchQuery, taskSearchContext, filterCategories, filterPriorities, filterTypes, filterAssignees, filterAssigneesAllSelected, filterTaxonomies, sortBy, sortOrder, hasInitedFilters, activeCategories, activeTypes, priorities, filterableTaxonomies, isAllFilterValuesSelected, taxonomies]);
|
|
310
310
|
const groupedTasks = useMemo(() => {
|
|
311
311
|
const groups = {};
|
|
312
312
|
filteredTasks.forEach(task => {
|