@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.
Files changed (215) hide show
  1. package/README.md +0 -2
  2. package/dist/Taskforce.module.css +317 -27
  3. package/dist/TaskforceCore.js +1 -0
  4. package/dist/TaskforceCore.test.js +178 -0
  5. package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
  6. package/dist/components/activity/EntityActivityTimeline.js +81 -0
  7. package/dist/components/context/ContextAttachmentManager.js +15 -3
  8. package/dist/components/features/AgentsModule.d.ts +5 -1
  9. package/dist/components/features/AgentsModule.js +142 -39
  10. package/dist/components/features/AgentsModule.test.js +260 -11
  11. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
  12. package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
  13. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
  14. package/dist/components/features/DocumentIndex.d.ts +2 -2
  15. package/dist/components/features/DocumentIndex.js +3 -4
  16. package/dist/components/features/DocumentViewer.d.ts +3 -1
  17. package/dist/components/features/DocumentViewer.js +6 -5
  18. package/dist/components/features/DocumentWorkspace.d.ts +10 -2
  19. package/dist/components/features/DocumentWorkspace.js +15 -4
  20. package/dist/components/features/DocumentWorkspace.test.js +20 -3
  21. package/dist/components/features/TaskSettings.d.ts +2 -0
  22. package/dist/components/features/TaskSettings.js +83 -78
  23. package/dist/components/features/TaskSettings.test.js +171 -82
  24. package/dist/components/task/TaskActionHeader.js +11 -5
  25. package/dist/components/task/TaskActionHeader.test.js +10 -0
  26. package/dist/components/task/TaskCard.js +6 -2
  27. package/dist/components/task/TaskContextUpload.test.js +3 -3
  28. package/dist/components/task/TaskForm.js +14 -64
  29. package/dist/components/task/TaskKanban.js +0 -8
  30. package/dist/components/task/TaskList.js +5 -1
  31. package/dist/components/task/TaskList.test.js +4 -0
  32. package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
  33. package/dist/components/ui/HierarchyActionBadge.js +5 -0
  34. package/dist/components/views/AppShellHeader.d.ts +3 -1
  35. package/dist/components/views/AppShellHeader.js +27 -11
  36. package/dist/components/views/AppShellHeader.test.js +18 -1
  37. package/dist/components/views/PlanComparisonPage.js +9 -1
  38. package/dist/components/views/PlanComparisonPage.test.js +19 -0
  39. package/dist/components/views/PlansPage.js +1 -1
  40. package/dist/components/views/StandaloneLayout.js +256 -69
  41. package/dist/components/views/WidgetView.js +10 -8
  42. package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
  43. package/dist/components/views/panels/PlanningDrawer.js +259 -51
  44. package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
  45. package/dist/config/envSchema.js +1 -0
  46. package/dist/core/AiProfileService.d.ts +7 -0
  47. package/dist/core/AiProfileService.js +460 -35
  48. package/dist/core/AiProfiles.test.js +648 -2
  49. package/dist/core/AttachmentLinkService.d.ts +3 -0
  50. package/dist/core/AttachmentLinkService.js +58 -13
  51. package/dist/core/GlobalSettingsService.js +12 -0
  52. package/dist/core/McpTokenService.d.ts +12 -0
  53. package/dist/core/McpTokenService.js +44 -1
  54. package/dist/core/PlanningEntities.test.js +366 -4
  55. package/dist/core/TaskActivityService.d.ts +28 -1
  56. package/dist/core/TaskActivityService.js +227 -46
  57. package/dist/core/TaskActivitySync.test.d.ts +1 -0
  58. package/dist/core/TaskActivitySync.test.js +90 -0
  59. package/dist/core/TaskAuditTimeline.test.js +96 -0
  60. package/dist/core/Taskforce.d.ts +190 -26
  61. package/dist/core/Taskforce.ids.test.js +58 -0
  62. package/dist/core/Taskforce.js +775 -71
  63. package/dist/core/Taskforce.mcpAuth.test.js +109 -0
  64. package/dist/core/types.d.ts +7 -0
  65. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
  66. package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
  67. package/dist/hooks/bootstrapTimeouts.js +8 -0
  68. package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
  69. package/dist/hooks/bootstrapTimeouts.test.js +12 -0
  70. package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
  71. package/dist/hooks/sync/orchestratorShared.js +61 -1
  72. package/dist/hooks/sync/recovery.js +8 -0
  73. package/dist/hooks/sync/recovery.test.js +5 -5
  74. package/dist/hooks/sync/transfers.d.ts +2 -1
  75. package/dist/hooks/sync/transfers.js +36 -9
  76. package/dist/hooks/sync/transfers.test.js +534 -2
  77. package/dist/hooks/useFilterSort.js +13 -13
  78. package/dist/hooks/useFilterSort.test.js +87 -1
  79. package/dist/hooks/useSyncOrchestrator.js +50 -5
  80. package/dist/hooks/useTaskforce.d.ts +85 -50
  81. package/dist/hooks/useTaskforce.js +44 -14
  82. package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
  83. package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
  84. package/dist/hooks/useWorkspaceSyncController.js +7 -1
  85. package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
  86. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
  87. package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
  88. package/dist/mcp/canonicalAssetHelpers.js +18 -10
  89. package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
  90. package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
  91. package/dist/mcp/clientIntegrations.d.ts +1 -0
  92. package/dist/mcp/clientIntegrations.js +25 -0
  93. package/dist/mcp/clientIntegrations.test.js +14 -3
  94. package/dist/mcp/clientProfileDefaults.js +3 -3
  95. package/dist/mcp/collaborationRegistrar.js +7 -7
  96. package/dist/mcp/documentAssetRegistrar.js +113 -7
  97. package/dist/mcp/runtime.d.ts +1 -0
  98. package/dist/mcp/runtime.js +969 -122
  99. package/dist/mcp/runtime.test.js +1083 -11
  100. package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
  101. package/dist/mcp/taskPlanningRegistrar.js +48 -0
  102. package/dist/mcp/toolRegistry.d.ts +56 -0
  103. package/dist/mcp/toolRegistry.js +8 -0
  104. package/dist/mcp/toolRegistry.test.js +4 -0
  105. package/dist/migrations/taskSchemaMigrations.js +21 -0
  106. package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
  107. package/dist/server/aiProfileAvatarAssets.js +319 -0
  108. package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
  109. package/dist/server/aiProfileAvatarAssets.test.js +213 -0
  110. package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
  111. package/dist/server/index.js +4 -0
  112. package/dist/server/index.test.js +6 -1
  113. package/dist/server/routes/admin.js +187 -9
  114. package/dist/server/routes/annotatedAttachments.js +51 -0
  115. package/dist/server/routes/auth.d.ts +11 -0
  116. package/dist/server/routes/auth.js +189 -27
  117. package/dist/server/routes/authSupport.d.ts +4 -0
  118. package/dist/server/routes/authSupport.js +12 -1
  119. package/dist/server/routes/billing.js +11 -0
  120. package/dist/server/routes/billing.test.js +3 -0
  121. package/dist/server/routes/documents.js +10 -0
  122. package/dist/server/routes/shared.js +0 -10
  123. package/dist/server/routes/sync.integration.test.js +221 -0
  124. package/dist/server/routes/sync.js +13 -3
  125. package/dist/server/routes/syncSnapshotRoutes.js +2 -0
  126. package/dist/server/routes/tasks.js +12 -9
  127. package/dist/server/routes.js +87 -8
  128. package/dist/server/routes.test.js +890 -22
  129. package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
  130. package/dist/shared/aiProfileSurfaceType.js +10 -8
  131. package/dist/storage/objectStorage.d.ts +1 -0
  132. package/dist/storage/objectStorage.js +2 -1
  133. package/dist/storage/objectStorageClient.d.ts +1 -0
  134. package/dist/storage/objectStorageClient.js +41 -8
  135. package/dist/storage/objectStorageClient.test.js +26 -1
  136. package/dist/storage/objectStorageConfig.js +3 -1
  137. package/dist/storage/objectStorageConfig.test.js +13 -1
  138. package/dist/sync/collaborationSyncPayload.js +2 -0
  139. package/dist/sync/collaborationSyncPayload.test.js +1 -0
  140. package/dist/sync/collaborationSyncState.d.ts +3 -3
  141. package/dist/sync/collaborationSyncState.js +19 -12
  142. package/dist/sync/contentSyncPayload.d.ts +5 -1
  143. package/dist/sync/contentSyncPayload.js +9 -0
  144. package/dist/sync/contentSyncPayload.test.js +39 -0
  145. package/dist/sync/contentSyncState.d.ts +5 -1
  146. package/dist/sync/contentSyncState.js +231 -37
  147. package/dist/sync/planningSyncPayload.js +5 -0
  148. package/dist/sync/planningSyncPayload.test.js +24 -0
  149. package/dist/sync/syncApplyHandlers.d.ts +4 -4
  150. package/dist/sync/syncApplyHandlers.js +89 -31
  151. package/dist/sync/syncApplyHandlers.test.js +184 -4
  152. package/dist/sync/syncResourceAdapters.d.ts +2 -0
  153. package/dist/sync/syncResourceAdapters.js +2 -0
  154. package/dist/sync/syncService.d.ts +1 -0
  155. package/dist/sync/syncService.js +13 -1
  156. package/dist/sync/syncService.test.js +39 -0
  157. package/dist/sync/workspacePullFeed.d.ts +4 -0
  158. package/dist/sync/workspacePullFeed.js +129 -70
  159. package/dist/sync/workspacePullFeed.test.js +49 -0
  160. package/dist/sync/workspaceSyncModel.d.ts +28 -6
  161. package/dist/sync/workspaceSyncModel.js +159 -45
  162. package/dist/sync/workspaceSyncModel.test.js +269 -3
  163. package/dist/types.d.ts +33 -2
  164. package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
  165. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
  166. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
  167. package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
  168. package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
  169. package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
  170. package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
  171. package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
  172. package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-D4XseTwW.js} +1 -1
  173. package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
  174. package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
  175. package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
  176. package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
  177. package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-oOA9bcdK.js} +4 -4
  178. package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
  179. package/dist/ui/assets/index-607WPo_X.js +4 -0
  180. package/dist/ui/assets/index-CYyodXsg.css +1 -0
  181. package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
  182. package/dist/ui/index.html +3 -3
  183. package/dist/ui/product/agent manager.png +0 -0
  184. package/dist/ui/product/agent_manager_02.png +0 -0
  185. package/dist/utils/aiProfileEvents.d.ts +7 -0
  186. package/dist/utils/aiProfileEvents.js +6 -0
  187. package/dist/utils/assignees.d.ts +6 -0
  188. package/dist/utils/assignees.js +8 -1
  189. package/dist/utils/assignees.test.d.ts +1 -0
  190. package/dist/utils/assignees.test.js +25 -0
  191. package/dist/utils/avatarDisplay.d.ts +1 -0
  192. package/dist/utils/avatarDisplay.js +15 -0
  193. package/dist/utils/contextFiles.d.ts +3 -1
  194. package/dist/utils/contextFiles.js +2 -1
  195. package/dist/utils/taskActivity.d.ts +7 -1
  196. package/dist/utils/taskActivity.js +63 -1
  197. package/dist/utils/taskActivity.test.js +23 -1
  198. package/dist/utils/taskSearch.d.ts +22 -0
  199. package/dist/utils/taskSearch.js +87 -0
  200. package/dist/utils/taskSearch.test.d.ts +1 -0
  201. package/dist/utils/taskSearch.test.js +69 -0
  202. package/dist/utils/taskUtils.js +10 -3
  203. package/dist/utils/taskUtils.test.d.ts +1 -0
  204. package/dist/utils/taskUtils.test.js +19 -0
  205. package/package.json +1 -1
  206. package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
  207. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
  208. package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
  209. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
  210. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
  211. package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
  212. package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
  213. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
  214. package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
  215. 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 { getTaskReferenceLabel } from '../utils/taskReferences';
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 referenceLabel = getTaskReferenceLabel(t).toLowerCase();
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, searchQuery, filterCategories, filterPriorities, filterTypes, filterStatus, filterAssignees, filterAssigneesAllSelected, filterTaxonomies, sortBy, sortOrder, hasInitedFilters, filterableTaxonomies, activeCategories, activeTypes, priorities, isAllFilterValuesSelected, taxonomies]);
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 referenceLabel = getTaskReferenceLabel(t).toLowerCase();
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, searchQuery, filterCategories, filterPriorities, filterTypes, filterAssignees, filterAssigneesAllSelected, filterTaxonomies, sortBy, sortOrder, hasInitedFilters, activeCategories, activeTypes, priorities, filterableTaxonomies, isAllFilterValuesSelected, taxonomies]);
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 => {