@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.
Files changed (237) hide show
  1. package/README.md +140 -247
  2. package/dist/Taskforce.module.css +102 -5
  3. package/dist/TaskforceCore.js +635 -238
  4. package/dist/TaskforceCore.test.js +2243 -495
  5. package/dist/annotatedAttachments/service.d.ts +1 -1
  6. package/dist/annotatedAttachments/types.d.ts +1 -1
  7. package/dist/compat/workspaceSyncCompat.js +6 -0
  8. package/dist/components/features/AgentsModule.js +1 -1
  9. package/dist/components/features/AgentsModule.test.js +1 -1
  10. package/dist/components/features/DocumentIndex.d.ts +1 -1
  11. package/dist/components/features/DocumentIndex.js +1 -1
  12. package/dist/components/features/DocumentViewer.d.ts +1 -1
  13. package/dist/components/features/DocumentViewer.js +32 -9
  14. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  15. package/dist/components/features/DocumentWorkspace.js +12 -155
  16. package/dist/components/features/DocumentWorkspace.test.js +193 -6
  17. package/dist/components/features/TaskSettings.js +87 -41
  18. package/dist/components/features/TaskSettings.test.js +180 -40
  19. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  20. package/dist/components/features/documentWorkspaceModel.js +57 -0
  21. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  22. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  23. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  24. package/dist/components/task/TaskActionHeader.js +3 -3
  25. package/dist/components/task/TaskActionHeader.test.js +8 -0
  26. package/dist/components/task/TaskCard.js +16 -1
  27. package/dist/components/task/TaskCard.test.js +31 -0
  28. package/dist/components/task/TaskForm.d.ts +9 -8
  29. package/dist/components/task/TaskForm.js +374 -219
  30. package/dist/components/task/TaskForm.test.js +482 -361
  31. package/dist/components/task/TaskKanban.d.ts +1 -1
  32. package/dist/components/task/TaskKanban.js +33 -161
  33. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  34. package/dist/components/task/TaskStatusActions.js +8 -3
  35. package/dist/components/task/TaskStatusActions.test.js +9 -0
  36. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  37. package/dist/components/task/taskKanbanDropRules.js +187 -0
  38. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  39. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  40. package/dist/components/ui/Modal.d.ts +1 -1
  41. package/dist/components/ui/Modal.js +1 -0
  42. package/dist/components/views/AppShellHeader.js +5 -1
  43. package/dist/components/views/AppShellHeader.test.js +4 -0
  44. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  45. package/dist/components/views/PlanComparisonPage.js +9 -10
  46. package/dist/components/views/PlanComparisonPage.test.js +55 -41
  47. package/dist/components/views/PlansPage.d.ts +5 -1
  48. package/dist/components/views/PlansPage.js +150 -81
  49. package/dist/components/views/PlansPage.test.js +367 -41
  50. package/dist/components/views/StandaloneLayout.js +135 -54
  51. package/dist/components/views/WidgetView.js +11 -2
  52. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  53. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
  54. package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
  55. package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
  56. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  57. package/dist/config/envSchema.js +1 -1
  58. package/dist/core/AiProfileService.d.ts +6 -1
  59. package/dist/core/AiProfileService.js +161 -16
  60. package/dist/core/AiProfileService.test.js +3 -1
  61. package/dist/core/AiProfiles.test.js +200 -0
  62. package/dist/core/AuthTokenService.test.d.ts +1 -0
  63. package/dist/core/AuthTokenService.test.js +78 -0
  64. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  65. package/dist/core/EntitlementsPolicy.test.js +75 -13
  66. package/dist/core/PlanEntitlementService.d.ts +9 -1
  67. package/dist/core/PlanEntitlementService.js +144 -19
  68. package/dist/core/PlanVersionPolicy.test.js +19 -26
  69. package/dist/core/SignupMonetizationSettings.test.js +75 -15
  70. package/dist/core/SystemAdmin.test.js +212 -56
  71. package/dist/core/TaskActivityService.d.ts +4 -1
  72. package/dist/core/TaskActivityService.js +45 -0
  73. package/dist/core/TaskAuditTimeline.test.js +52 -1
  74. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  75. package/dist/core/Taskforce.d.ts +18 -11
  76. package/dist/core/Taskforce.js +93 -12
  77. package/dist/core/WorkspacePlanMode.test.js +73 -8
  78. package/dist/core/shared.d.ts +2 -0
  79. package/dist/core/shared.js +11 -0
  80. package/dist/documentReviews/service.d.ts +1 -1
  81. package/dist/documentReviews/types.d.ts +1 -1
  82. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  83. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  84. package/dist/hooks/sync/auth.d.ts +37 -0
  85. package/dist/hooks/sync/auth.js +63 -0
  86. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  87. package/dist/hooks/sync/bootstrap.js +52 -0
  88. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  89. package/dist/hooks/sync/orchestratorShared.js +209 -0
  90. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  91. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  92. package/dist/hooks/sync/realtime.d.ts +25 -0
  93. package/dist/hooks/sync/realtime.js +60 -0
  94. package/dist/hooks/sync/recovery.d.ts +69 -0
  95. package/dist/hooks/sync/recovery.js +118 -0
  96. package/dist/hooks/sync/transfers.d.ts +123 -0
  97. package/dist/hooks/sync/transfers.js +435 -0
  98. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  99. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  100. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  101. package/dist/hooks/ui/useResourceExport.js +5 -2
  102. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  103. package/dist/hooks/useRealtimeSync.js +2 -1
  104. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  105. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  106. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  107. package/dist/hooks/useSyncOrchestrator.d.ts +12 -8
  108. package/dist/hooks/useSyncOrchestrator.js +388 -879
  109. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +641 -6
  110. package/dist/hooks/useTaskMutations.d.ts +2 -1
  111. package/dist/hooks/useTaskMutations.js +57 -7
  112. package/dist/hooks/useTaskMutations.test.js +176 -0
  113. package/dist/hooks/useTaskforce.d.ts +13 -4
  114. package/dist/hooks/useTaskforce.js +302 -817
  115. package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
  116. package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
  117. package/dist/hooks/useWorkspaceSyncController.js +33 -11
  118. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  119. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  120. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  121. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  122. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  123. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  124. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  125. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  126. package/dist/localization/locales/en-US.js +2 -2
  127. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  128. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  129. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  130. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  131. package/dist/mcp/clientIntegrations.d.ts +12 -1
  132. package/dist/mcp/clientIntegrations.js +161 -6
  133. package/dist/mcp/clientIntegrations.test.js +76 -4
  134. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  135. package/dist/mcp/collaborationRegistrar.js +71 -0
  136. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  137. package/dist/mcp/documentAssetRegistrar.js +346 -0
  138. package/dist/mcp/documentHelpers.d.ts +100 -0
  139. package/dist/mcp/documentHelpers.js +161 -0
  140. package/dist/mcp/runtime.js +2455 -3925
  141. package/dist/mcp/runtime.test.js +240 -8
  142. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  143. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  144. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  145. package/dist/mcp/taskAttachmentTypes.js +1 -0
  146. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  147. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  148. package/dist/mcp/toolCatalog.d.ts +35 -0
  149. package/dist/mcp/toolCatalog.js +3 -0
  150. package/dist/plugins/vite.js +1 -1
  151. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  152. package/dist/runtime/appGateDefinitions.js +1 -1
  153. package/dist/server/index.d.ts +1 -0
  154. package/dist/server/index.js +46 -3
  155. package/dist/server/index.test.js +12 -1
  156. package/dist/server/routes/admin.d.ts +1 -5
  157. package/dist/server/routes/admin.js +151 -80
  158. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  159. package/dist/server/routes/annotatedAttachments.js +1 -1
  160. package/dist/server/routes/auth.d.ts +1 -4
  161. package/dist/server/routes/auth.js +59 -64
  162. package/dist/server/routes/authSupport.d.ts +30 -0
  163. package/dist/server/routes/authSupport.js +81 -0
  164. package/dist/server/routes/billing.d.ts +1 -1
  165. package/dist/server/routes/billing.js +122 -255
  166. package/dist/server/routes/billing.test.js +257 -86
  167. package/dist/server/routes/documentReviews.d.ts +1 -1
  168. package/dist/server/routes/documentReviews.js +1 -1
  169. package/dist/server/routes/documents.d.ts +4 -2
  170. package/dist/server/routes/documents.js +73 -7
  171. package/dist/server/routes/primitives.d.ts +11 -0
  172. package/dist/server/routes/primitives.js +73 -0
  173. package/dist/server/routes/resources.d.ts +1 -1
  174. package/dist/server/routes/resources.js +1 -1
  175. package/dist/server/routes/shared.d.ts +31 -1
  176. package/dist/server/routes/shared.js +26 -0
  177. package/dist/server/routes/sync.d.ts +1 -1
  178. package/dist/server/routes/sync.integration.test.js +20 -0
  179. package/dist/server/routes/sync.js +111 -238
  180. package/dist/server/routes/tasks.d.ts +1 -1
  181. package/dist/server/routes/tasks.js +50 -8
  182. package/dist/server/routes/workspaces.d.ts +1 -1
  183. package/dist/server/routes/workspaces.js +1 -1
  184. package/dist/server/routes.d.ts +2 -9
  185. package/dist/server/routes.js +84 -181
  186. package/dist/server/routes.test.js +912 -32
  187. package/dist/shared/taskActor.d.ts +10 -0
  188. package/dist/shared/taskActor.js +1 -0
  189. package/dist/sync/collaborationSyncPayload.js +4 -0
  190. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  191. package/dist/sync/collaborationSyncState.d.ts +3 -1
  192. package/dist/sync/collaborationSyncState.js +19 -0
  193. package/dist/sync/contentSyncState.js +28 -17
  194. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  195. package/dist/sync/syncApplyHandlers.js +370 -8
  196. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  197. package/dist/sync/syncApplyHandlers.test.js +379 -0
  198. package/dist/sync/syncService.d.ts +9 -1
  199. package/dist/sync/syncService.js +44 -2
  200. package/dist/sync/syncService.test.js +62 -0
  201. package/dist/sync/workspacePullFeed.d.ts +1 -0
  202. package/dist/sync/workspacePullFeed.js +66 -59
  203. package/dist/sync/workspacePullFeed.test.js +72 -4
  204. package/dist/sync/workspaceSyncModel.d.ts +25 -1
  205. package/dist/sync/workspaceSyncModel.js +77 -3
  206. package/dist/sync/workspaceSyncModel.test.js +153 -0
  207. package/dist/sync/workspaceSyncState.d.ts +2 -0
  208. package/dist/sync/workspaceSyncState.js +1 -0
  209. package/dist/types.d.ts +2 -10
  210. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +1 -0
  211. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
  212. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
  213. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
  214. package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-Dhm7M8e7.js} +1 -1
  215. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  216. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
  217. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
  218. package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-BcS4afRn.js} +1 -1
  219. package/dist/ui/assets/index-C2-OXZV7.css +1 -0
  220. package/dist/ui/assets/index-DcSI5F86.js +6 -0
  221. package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-BSUaRwF8.js} +1 -1
  222. package/dist/ui/index.html +4 -4
  223. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  224. package/dist/utils/commercialLifecyclePresentation.js +196 -0
  225. package/dist/utils/taskActivity.js +4 -0
  226. package/dist/utils/taskActivity.test.js +25 -1
  227. package/package.json +10 -1
  228. package/scripts/check-cloud-mcp.mjs +83 -0
  229. package/scripts/playwright-auth.sh +9 -1
  230. package/scripts/run-billing-performance-smoke.sh +24 -0
  231. package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
  232. package/dist/ui/assets/DocumentWorkspace-C3GyzrWm.js +0 -250
  233. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  234. package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
  235. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
  236. package/dist/ui/assets/index-CLIMgR6g.js +0 -6
  237. package/dist/ui/assets/index-DneETxcu.css +0 -1
@@ -0,0 +1,379 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { applyCollaborationSyncChanges, applyContentSyncChanges, applyPreparedTaskSyncSnapshot, applyTaskSyncDeletes, classifyWorkspaceSyncChanges, prepareTaskSyncSnapshotApply, } from './syncApplyHandlers.js';
3
+ function createCoreFixture(overrides = {}) {
4
+ return {
5
+ resolveTaskIdentifier: vi.fn().mockReturnValue(null),
6
+ getTask: vi.fn().mockReturnValue(null),
7
+ deleteTaskForSync: vi.fn(),
8
+ applyWorkspaceSyncSnapshot: vi.fn().mockReturnValue({
9
+ total: 1,
10
+ created: 0,
11
+ updated: 1,
12
+ archived: 0,
13
+ unarchived: 0,
14
+ skipped: 0,
15
+ }),
16
+ notifyDocumentMetadataMutation: vi.fn(),
17
+ getDocumentWatermark: vi.fn().mockReturnValue(null),
18
+ getTenantId: vi.fn().mockReturnValue('tenant-1'),
19
+ getDocumentReviewSession: vi.fn().mockReturnValue(null),
20
+ upsertDocumentReviewSessionForSync: vi.fn(),
21
+ listDocumentReviewCommentsForSync: vi.fn().mockReturnValue([]),
22
+ upsertDocumentReviewCommentForSync: vi.fn(),
23
+ deleteDocumentReviewSessionForSync: vi.fn(),
24
+ deleteDocumentReviewCommentForSync: vi.fn(),
25
+ getAnnotatedAttachmentSession: vi.fn().mockReturnValue(null),
26
+ upsertAnnotatedAttachmentSessionForSync: vi.fn(),
27
+ deleteAnnotatedAttachmentSessionForSync: vi.fn(),
28
+ ...overrides,
29
+ };
30
+ }
31
+ describe('syncApplyHandlers', () => {
32
+ it('classifies task and workstream changes, then prepares and applies the task snapshot pipeline', () => {
33
+ const diagnostics = [];
34
+ const workspaceId = 'workspace-1';
35
+ const applyModel = classifyWorkspaceSyncChanges({
36
+ workspaceId,
37
+ incomingChanges: [
38
+ {
39
+ op: 'workstream-upsert',
40
+ workstream: {
41
+ id: 'workstream-1',
42
+ title: 'Delivery',
43
+ description: null,
44
+ createdAt: '2026-04-10T09:00:00.000Z',
45
+ updatedAt: '2026-04-10T09:00:00.000Z',
46
+ },
47
+ },
48
+ {
49
+ op: 'upsert',
50
+ archived: false,
51
+ task: {
52
+ id: 'task-1',
53
+ title: 'Superseded task',
54
+ status: 'task',
55
+ category: 'general',
56
+ type: 'feature',
57
+ createdAt: '2026-04-10T09:00:00.000Z',
58
+ updatedAt: '2026-04-10T10:00:00.000Z',
59
+ referenceNumber: 7,
60
+ },
61
+ },
62
+ {
63
+ op: 'upsert',
64
+ archived: true,
65
+ task: {
66
+ id: 'task-2',
67
+ title: 'Archived task',
68
+ status: 'done',
69
+ category: 'general',
70
+ type: 'feature',
71
+ createdAt: '2026-04-10T09:00:00.000Z',
72
+ updatedAt: '2026-04-10T10:30:00.000Z',
73
+ isArchived: true,
74
+ },
75
+ },
76
+ {
77
+ op: 'delete',
78
+ taskId: 'task-1',
79
+ deletedAt: '2026-04-10T11:00:00.000Z',
80
+ },
81
+ {
82
+ op: 'delete',
83
+ taskId: 'task-stale',
84
+ deletedAt: '2026-04-10T08:00:00.000Z',
85
+ },
86
+ ],
87
+ normalizeWorkspaceSyncDocumentPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
88
+ normalizeTaskAssetPath: (rawPath) => typeof rawPath === 'string' ? rawPath : null,
89
+ normalizeIncomingAiProfileSurfaceType: () => null,
90
+ normalizeTaskEventSyncPayload: () => null,
91
+ recordSyncDiagnostic: (targetWorkspaceId, eventType, message, details) => {
92
+ diagnostics.push({ workspaceId: targetWorkspaceId, eventType, message, details });
93
+ },
94
+ });
95
+ expect(applyModel.planning.incomingWorkstreams.get('workstream-1')).toEqual(expect.objectContaining({
96
+ id: 'workstream-1',
97
+ title: 'Delivery',
98
+ }));
99
+ expect(applyModel.taskMutations.upserts).toHaveLength(2);
100
+ expect(applyModel.taskMutations.deletes.get('task-1')).toBe('2026-04-10T11:00:00.000Z');
101
+ const core = createCoreFixture({
102
+ resolveTaskIdentifier: vi.fn().mockReturnValue({
103
+ id: 'task-local-7',
104
+ title: 'Local incumbent',
105
+ referenceNumber: 7,
106
+ }),
107
+ getTask: vi.fn().mockImplementation((taskId) => {
108
+ if (taskId === 'task-1') {
109
+ return {
110
+ id: 'task-1',
111
+ title: 'Superseded task',
112
+ createdAt: '2026-04-10T09:00:00.000Z',
113
+ updatedAt: '2026-04-10T10:00:00.000Z',
114
+ isArchived: false,
115
+ };
116
+ }
117
+ if (taskId === 'task-stale') {
118
+ return {
119
+ id: 'task-stale',
120
+ title: 'Keep local task',
121
+ createdAt: '2026-04-10T09:00:00.000Z',
122
+ updatedAt: '2026-04-10T09:30:00.000Z',
123
+ isArchived: false,
124
+ };
125
+ }
126
+ return null;
127
+ }),
128
+ });
129
+ const prepared = prepareTaskSyncSnapshotApply({
130
+ core,
131
+ workspaceId,
132
+ taskMutations: applyModel.taskMutations,
133
+ recordSyncDiagnostic: (targetWorkspaceId, eventType, message, details) => {
134
+ diagnostics.push({ workspaceId: targetWorkspaceId, eventType, message, details });
135
+ },
136
+ });
137
+ expect(prepared.tasks).toEqual([]);
138
+ expect(prepared.archived).toEqual([
139
+ expect.objectContaining({
140
+ id: 'task-2',
141
+ title: 'Archived task',
142
+ }),
143
+ ]);
144
+ expect(diagnostics).toEqual(expect.arrayContaining([
145
+ expect.objectContaining({
146
+ workspaceId,
147
+ eventType: 'apply',
148
+ message: 'Detected task reference number mismatch during normal sync.',
149
+ details: expect.objectContaining({
150
+ referenceLabel: 'T-7',
151
+ existingTaskId: 'task-local-7',
152
+ taskId: 'task-1',
153
+ }),
154
+ }),
155
+ ]));
156
+ const deleteCount = applyTaskSyncDeletes({
157
+ core,
158
+ workspaceId,
159
+ taskMutations: applyModel.taskMutations,
160
+ recordSyncDiagnostic: vi.fn(),
161
+ });
162
+ expect(deleteCount).toBe(2);
163
+ expect(core.deleteTaskForSync).toHaveBeenCalledTimes(1);
164
+ expect(core.deleteTaskForSync).toHaveBeenCalledWith('task-1', workspaceId, { persistBackup: false });
165
+ const result = applyPreparedTaskSyncSnapshot({
166
+ core,
167
+ workspaceId,
168
+ prepared,
169
+ });
170
+ expect(core.applyWorkspaceSyncSnapshot).toHaveBeenCalledWith({
171
+ tasks: [],
172
+ archived: [
173
+ expect.objectContaining({
174
+ id: 'task-2',
175
+ title: 'Archived task',
176
+ isArchived: true,
177
+ }),
178
+ ],
179
+ }, workspaceId);
180
+ expect(result).toEqual(expect.objectContaining({ total: 1 }));
181
+ });
182
+ it('applies document and asset changes through the content handler helpers', async () => {
183
+ const core = createCoreFixture();
184
+ const writeWorkspaceSyncDocument = vi.fn().mockResolvedValue(undefined);
185
+ const writeWorkspaceSyncBinaryAsset = vi.fn().mockResolvedValue(undefined);
186
+ const deleteWorkspaceSyncDocument = vi.fn();
187
+ const deleteWorkspaceSyncBinaryAsset = vi.fn();
188
+ await applyContentSyncChanges({
189
+ core,
190
+ workspaceId: 'workspace-1',
191
+ incomingDocuments: [
192
+ {
193
+ path: 'docs/spec.md',
194
+ updatedAt: '2026-04-10T10:00:00.000Z',
195
+ content: '# Spec',
196
+ assetId: 'asset-doc-1',
197
+ },
198
+ ],
199
+ incomingDocumentDeletes: new Map([
200
+ ['docs/stale.md', '2026-04-10T11:00:00.000Z'],
201
+ ]),
202
+ incomingAssets: [
203
+ {
204
+ path: 'assets/proof.png',
205
+ updatedAt: '2026-04-10T10:05:00.000Z',
206
+ contentBase64: Buffer.from('png-bytes').toString('base64'),
207
+ assetId: 'asset-image-1',
208
+ kind: 'image',
209
+ mimeType: 'image/png',
210
+ },
211
+ ],
212
+ incomingAssetDeletes: new Map([
213
+ ['assets/stale.png', '2026-04-10T11:05:00.000Z'],
214
+ ]),
215
+ basePath: '/tmp/taskforce-sync-content',
216
+ hasObjectStorageClient: () => false,
217
+ hasDocumentSyncMetadataDrift: () => false,
218
+ hasBinaryAssetSyncMetadataDrift: () => false,
219
+ getExistingDocument: () => null,
220
+ writeWorkspaceSyncDocument,
221
+ deleteWorkspaceSyncDocument,
222
+ writeWorkspaceSyncBinaryAsset,
223
+ deleteWorkspaceSyncBinaryAsset,
224
+ getExistingBinaryAsset: () => null,
225
+ recordSyncDiagnostic: vi.fn(),
226
+ });
227
+ expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md', assetId: 'asset-doc-1' }), 'workspace-1', { allowReferenceNumberReassignment: true });
228
+ expect(writeWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'assets/proof.png', assetId: 'asset-image-1' }), 'workspace-1', { allowReferenceNumberReassignment: true });
229
+ expect(deleteWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', 'docs/stale.md', 'workspace-1');
230
+ expect(deleteWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', 'assets/stale.png', 'workspace-1');
231
+ expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(1, 'workspace-1', ['docs/spec.md', 'assets/proof.png'], 'upsert');
232
+ expect(core.notifyDocumentMetadataMutation).toHaveBeenNthCalledWith(2, 'workspace-1', ['docs/stale.md', 'assets/stale.png'], 'delete');
233
+ });
234
+ it('only allows reference reassignment for content writes when the content changed or repair is explicit', async () => {
235
+ const core = createCoreFixture({
236
+ getDocumentWatermark: vi.fn().mockReturnValue(null),
237
+ });
238
+ const writeWorkspaceSyncDocument = vi.fn().mockResolvedValue(undefined);
239
+ const writeWorkspaceSyncBinaryAsset = vi.fn().mockResolvedValue(undefined);
240
+ await applyContentSyncChanges({
241
+ core,
242
+ workspaceId: 'workspace-1',
243
+ incomingDocuments: [
244
+ {
245
+ path: 'docs/spec.md',
246
+ updatedAt: '2026-04-10T10:00:00.000Z',
247
+ content: '# Spec',
248
+ assetId: 'asset-doc-1',
249
+ referenceNumber: 22,
250
+ },
251
+ ],
252
+ incomingDocumentDeletes: new Map(),
253
+ incomingAssets: [
254
+ {
255
+ path: 'assets/proof.png',
256
+ updatedAt: '2026-04-10T10:05:00.000Z',
257
+ contentBase64: Buffer.from('new-bytes').toString('base64'),
258
+ assetId: 'asset-image-1',
259
+ kind: 'image',
260
+ mimeType: 'image/png',
261
+ referenceNumber: 33,
262
+ },
263
+ ],
264
+ incomingAssetDeletes: new Map(),
265
+ basePath: '/tmp/taskforce-sync-content',
266
+ hasObjectStorageClient: () => false,
267
+ hasDocumentSyncMetadataDrift: () => true,
268
+ hasBinaryAssetSyncMetadataDrift: () => true,
269
+ getExistingDocument: () => ({
270
+ updatedAt: '2026-04-10T10:00:00.000Z',
271
+ contentHash: '6bb50a17e2dc2fc14caea95a15cd8572018b6d22d74fb7b9ad015b5a0a8a935e',
272
+ }),
273
+ writeWorkspaceSyncDocument,
274
+ deleteWorkspaceSyncDocument: vi.fn(),
275
+ writeWorkspaceSyncBinaryAsset,
276
+ deleteWorkspaceSyncBinaryAsset: vi.fn(),
277
+ getExistingBinaryAsset: () => ({
278
+ updatedAt: '2026-04-10T10:00:00.000Z',
279
+ contentSha256: 'old-hash',
280
+ }),
281
+ recordSyncDiagnostic: vi.fn(),
282
+ });
283
+ expect(writeWorkspaceSyncDocument).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'docs/spec.md' }), 'workspace-1', { allowReferenceNumberReassignment: false });
284
+ expect(writeWorkspaceSyncBinaryAsset).toHaveBeenCalledWith('/tmp/taskforce-sync-content', expect.objectContaining({ path: 'assets/proof.png' }), 'workspace-1', { allowReferenceNumberReassignment: true });
285
+ });
286
+ it('applies collaboration upserts and deletes through the collaboration handlers', () => {
287
+ const core = createCoreFixture();
288
+ applyCollaborationSyncChanges({
289
+ core,
290
+ workspaceId: 'workspace-1',
291
+ incomingDocumentReviewSessions: new Map([
292
+ ['session-1', {
293
+ id: 'session-1',
294
+ assetId: 'asset-doc-1',
295
+ documentId: 'doc-1',
296
+ documentVersion: 3,
297
+ title: 'Review round',
298
+ status: 'open',
299
+ comments: [],
300
+ createdByActorId: 'user-1',
301
+ updatedByActorId: 'user-1',
302
+ createdAt: '2026-04-10T09:00:00.000Z',
303
+ updatedAt: '2026-04-10T10:00:00.000Z',
304
+ deletedAt: null,
305
+ }],
306
+ ]),
307
+ incomingDocumentReviewSessionDeletes: new Map([
308
+ ['session-2', '2026-04-10T11:00:00.000Z'],
309
+ ]),
310
+ incomingDocumentReviewComments: new Map([
311
+ ['comment-1', {
312
+ id: 'comment-1',
313
+ sessionId: 'session-1',
314
+ body: 'Looks good',
315
+ anchor: { kind: 'document' },
316
+ order: 0,
317
+ authorActorId: 'user-1',
318
+ createdAt: '2026-04-10T09:05:00.000Z',
319
+ updatedAt: '2026-04-10T10:05:00.000Z',
320
+ deletedAt: null,
321
+ }],
322
+ ]),
323
+ incomingDocumentReviewCommentDeletes: new Map([
324
+ ['comment-2', '2026-04-10T11:05:00.000Z'],
325
+ ]),
326
+ incomingAnnotatedAttachmentSessions: new Map([
327
+ ['annotation-session-1', {
328
+ id: 'annotation-session-1',
329
+ workspaceId: 'workspace-1',
330
+ taskId: 'task-1',
331
+ baseImageAssetId: 'asset-image-1',
332
+ title: 'Annotations',
333
+ globalInstruction: 'Inspect issue',
334
+ annotations: [],
335
+ createdByActorId: 'user-1',
336
+ updatedByActorId: 'user-1',
337
+ createdAt: '2026-04-10T09:10:00.000Z',
338
+ updatedAt: '2026-04-10T10:10:00.000Z',
339
+ deletedAt: null,
340
+ }],
341
+ ]),
342
+ incomingAnnotatedAttachmentSessionDeletes: new Map([
343
+ ['annotation-session-2', '2026-04-10T11:10:00.000Z'],
344
+ ]),
345
+ recordSyncDiagnostic: vi.fn(),
346
+ createSyncRouteError: (message, details) => Object.assign(new Error(message), { details }),
347
+ });
348
+ expect(core.upsertDocumentReviewSessionForSync).toHaveBeenCalledWith(expect.objectContaining({
349
+ id: 'session-1',
350
+ workspaceId: 'workspace-1',
351
+ assetId: 'asset-doc-1',
352
+ }));
353
+ expect(core.deleteDocumentReviewSessionForSync).toHaveBeenCalledWith({
354
+ workspaceId: 'workspace-1',
355
+ sessionId: 'session-2',
356
+ deletedAt: '2026-04-10T11:00:00.000Z',
357
+ });
358
+ expect(core.upsertDocumentReviewCommentForSync).toHaveBeenCalledWith(expect.objectContaining({
359
+ id: 'comment-1',
360
+ sessionId: 'session-1',
361
+ workspaceId: 'workspace-1',
362
+ }));
363
+ expect(core.deleteDocumentReviewCommentForSync).toHaveBeenCalledWith({
364
+ workspaceId: 'workspace-1',
365
+ commentId: 'comment-2',
366
+ deletedAt: '2026-04-10T11:05:00.000Z',
367
+ });
368
+ expect(core.upsertAnnotatedAttachmentSessionForSync).toHaveBeenCalledWith(expect.objectContaining({
369
+ id: 'annotation-session-1',
370
+ workspaceId: 'workspace-1',
371
+ baseImageAssetId: 'asset-image-1',
372
+ }));
373
+ expect(core.deleteAnnotatedAttachmentSessionForSync).toHaveBeenCalledWith({
374
+ workspaceId: 'workspace-1',
375
+ sessionId: 'annotation-session-2',
376
+ deletedAt: '2026-04-10T11:10:00.000Z',
377
+ });
378
+ });
379
+ });
@@ -1,4 +1,4 @@
1
- import type { WorkspaceSyncPayload } from './workspaceSyncModel';
1
+ import type { WorkspaceSyncPayload, WorkspaceSyncTaskSnapshot } from './workspaceSyncModel';
2
2
  export declare function isTransientWorkspaceSyncStatus(status: number | null | undefined): boolean;
3
3
  export interface EnsureWorkspaceReadyInput {
4
4
  cloudAuthConfigured: boolean;
@@ -7,6 +7,7 @@ export interface EnsureWorkspaceReadyInput {
7
7
  resolveCloudAuthUrl: (path: string) => string;
8
8
  workspaceId: string;
9
9
  workspaceName: string;
10
+ allowProvisionOnMismatch?: boolean;
10
11
  }
11
12
  export interface EnsureWorkspaceReadyResult {
12
13
  success: boolean;
@@ -77,10 +78,16 @@ export interface WorkspacePushSyncResult {
77
78
  currentAnnotatedAttachmentSessionIds: Set<string>;
78
79
  deleteAnnotatedAttachmentSessionIds: Set<string>;
79
80
  currentAnnotatedAttachmentSessionWatermarks: Map<string, string>;
81
+ currentTaskEventWatermarks: Map<string, string>;
80
82
  /** Watermarks for every task upsert that was successfully pushed. */
81
83
  pushedWatermarks: Map<string, string>;
82
84
  /** Server-emitted realtime mutation event ids for this push apply. */
83
85
  emittedEventIds: string[];
86
+ /** Authoritative task snapshots returned immediately by the push route for incoming task upserts. */
87
+ appliedTaskUpserts: Array<{
88
+ archived: boolean;
89
+ task: WorkspaceSyncTaskSnapshot;
90
+ }>;
84
91
  }
85
92
  export declare function runWorkspacePushSyncService(input: WorkspacePushSyncInput): Promise<WorkspacePushSyncResult>;
86
93
  export type WorkspacePullFailureKind = 'pull_http' | 'apply_auth' | 'apply_all_candidates' | 'apply_payload' | 'exception';
@@ -113,6 +120,7 @@ export interface WorkspacePullSyncResult {
113
120
  pulledDeleteTaskIds: Set<string>;
114
121
  pulledActiveUpserts: boolean;
115
122
  pulledArchivedUpserts: boolean;
123
+ pulledTaskEventUpserts?: boolean;
116
124
  documentDecryptFailures?: string[];
117
125
  emittedEventIds?: string[];
118
126
  serverDiagnostics?: Record<string, unknown> | null;
@@ -45,6 +45,14 @@ export async function ensureCloudWorkspaceReadyForSyncService(input) {
45
45
  };
46
46
  }
47
47
  if (handshakeResult.status === 409 && data.code === 'WORKSPACE_ID_MISMATCH') {
48
+ if (input.allowProvisionOnMismatch === false) {
49
+ return {
50
+ success: false,
51
+ statusCode: 409,
52
+ transient: false,
53
+ error: String(data.error || 'Selected cloud workspace could not be attached to this local workspace.')
54
+ };
55
+ }
48
56
  const provisionResult = await provisionWorkspace(input.resolveCloudAuthUrl, {
49
57
  workspaceId: input.workspaceId,
50
58
  name: input.workspaceName || input.workspaceId
@@ -187,8 +195,10 @@ export async function runWorkspacePushSyncService(input) {
187
195
  currentAnnotatedAttachmentSessionIds: input.payload.currentAnnotatedAttachmentSessionIds,
188
196
  deleteAnnotatedAttachmentSessionIds: input.payload.deleteAnnotatedAttachmentSessionIds,
189
197
  currentAnnotatedAttachmentSessionWatermarks: input.payload.currentAnnotatedAttachmentSessionWatermarks,
198
+ currentTaskEventWatermarks: input.payload.currentTaskEventWatermarks,
190
199
  pushedWatermarks: new Map(),
191
- emittedEventIds: []
200
+ emittedEventIds: [],
201
+ appliedTaskUpserts: []
192
202
  };
193
203
  }
194
204
  const pushData = asObjectRecord(pushResult.data);
@@ -197,6 +207,23 @@ export async function runWorkspacePushSyncService(input) {
197
207
  .map((entry) => String(entry || '').trim())
198
208
  .filter(Boolean)))
199
209
  : [];
210
+ const appliedTaskUpserts = Array.isArray(pushData.appliedTaskUpserts)
211
+ ? pushData.appliedTaskUpserts
212
+ .map((entry) => {
213
+ const source = entry && typeof entry === 'object' ? entry : {};
214
+ const task = source.task && typeof source.task === 'object'
215
+ ? source.task
216
+ : null;
217
+ const taskId = String(task?.id || '').trim();
218
+ if (!task || !taskId)
219
+ return null;
220
+ return {
221
+ archived: source.archived === true,
222
+ task
223
+ };
224
+ })
225
+ .filter((entry) => Boolean(entry))
226
+ : [];
200
227
  return {
201
228
  success: true,
202
229
  syncedAt: new Date().toISOString(),
@@ -226,8 +253,10 @@ export async function runWorkspacePushSyncService(input) {
226
253
  currentAnnotatedAttachmentSessionIds: input.payload.currentAnnotatedAttachmentSessionIds,
227
254
  deleteAnnotatedAttachmentSessionIds: input.payload.deleteAnnotatedAttachmentSessionIds,
228
255
  currentAnnotatedAttachmentSessionWatermarks: input.payload.currentAnnotatedAttachmentSessionWatermarks,
256
+ currentTaskEventWatermarks: input.payload.currentTaskEventWatermarks,
229
257
  pushedWatermarks: input.payload.pushedWatermarks,
230
- emittedEventIds
258
+ emittedEventIds,
259
+ appliedTaskUpserts
231
260
  };
232
261
  }
233
262
  export async function runWorkspacePullSyncService(input) {
@@ -240,6 +269,7 @@ export async function runWorkspacePullSyncService(input) {
240
269
  const pulledDeleteTaskIds = new Set();
241
270
  let pulledActiveUpserts = false;
242
271
  let pulledArchivedUpserts = false;
272
+ let pulledTaskEventUpserts = false;
243
273
  const documentDecryptFailures = new Set();
244
274
  const emittedEventIds = new Set();
245
275
  const pulledAnnotatedAttachmentSessionIds = new Set();
@@ -280,6 +310,7 @@ export async function runWorkspacePullSyncService(input) {
280
310
  pulledDeleteTaskIds,
281
311
  pulledActiveUpserts,
282
312
  pulledArchivedUpserts,
313
+ pulledTaskEventUpserts,
283
314
  documentDecryptFailures: Array.from(documentDecryptFailures),
284
315
  serverDiagnostics: null
285
316
  };
@@ -309,6 +340,10 @@ export async function runWorkspacePullSyncService(input) {
309
340
  pulledAnnotatedAttachmentSessionIds.add(sessionId);
310
341
  continue;
311
342
  }
343
+ if (change.op === 'task-event-upsert') {
344
+ pulledTaskEventUpserts = true;
345
+ continue;
346
+ }
312
347
  if (change.op === 'document-upsert') {
313
348
  if (typeof change.content === 'string' && typeof process !== 'undefined' && process.env?.TASKFORCE_SYNC_KEY) {
314
349
  try {
@@ -371,6 +406,7 @@ export async function runWorkspacePullSyncService(input) {
371
406
  pulledDeleteTaskIds,
372
407
  pulledActiveUpserts,
373
408
  pulledArchivedUpserts,
409
+ pulledTaskEventUpserts,
374
410
  documentDecryptFailures: Array.from(documentDecryptFailures),
375
411
  serverDiagnostics
376
412
  };
@@ -398,6 +434,7 @@ export async function runWorkspacePullSyncService(input) {
398
434
  pulledDeleteTaskIds,
399
435
  pulledActiveUpserts,
400
436
  pulledArchivedUpserts,
437
+ pulledTaskEventUpserts,
401
438
  documentDecryptFailures: Array.from(documentDecryptFailures),
402
439
  emittedEventIds: Array.from(emittedEventIds),
403
440
  serverDiagnostics: lastServerDiagnostics
@@ -419,6 +456,7 @@ export async function runWorkspacePullSyncService(input) {
419
456
  pulledDeleteTaskIds,
420
457
  pulledActiveUpserts,
421
458
  pulledArchivedUpserts,
459
+ pulledTaskEventUpserts,
422
460
  documentDecryptFailures: Array.from(documentDecryptFailures),
423
461
  emittedEventIds: Array.from(emittedEventIds),
424
462
  serverDiagnostics: lastServerDiagnostics
@@ -469,6 +507,7 @@ export async function runWorkspacePullSyncService(input) {
469
507
  pulledDeleteTaskIds,
470
508
  pulledActiveUpserts,
471
509
  pulledArchivedUpserts,
510
+ pulledTaskEventUpserts,
472
511
  documentDecryptFailures: Array.from(documentDecryptFailures),
473
512
  emittedEventIds: Array.from(emittedEventIds),
474
513
  serverDiagnostics: lastServerDiagnostics
@@ -497,6 +536,7 @@ export async function runWorkspacePullSyncService(input) {
497
536
  pulledDeleteTaskIds,
498
537
  pulledActiveUpserts,
499
538
  pulledArchivedUpserts,
539
+ pulledTaskEventUpserts,
500
540
  documentDecryptFailures: Array.from(documentDecryptFailures),
501
541
  emittedEventIds: Array.from(emittedEventIds),
502
542
  serverDiagnostics: lastServerDiagnostics
@@ -515,6 +555,7 @@ export async function runWorkspacePullSyncService(input) {
515
555
  pulledDeleteTaskIds,
516
556
  pulledActiveUpserts,
517
557
  pulledArchivedUpserts,
558
+ pulledTaskEventUpserts,
518
559
  documentDecryptFailures: Array.from(documentDecryptFailures),
519
560
  emittedEventIds: Array.from(emittedEventIds),
520
561
  serverDiagnostics: lastServerDiagnostics
@@ -535,6 +576,7 @@ export async function runWorkspacePullSyncService(input) {
535
576
  pulledDeleteTaskIds,
536
577
  pulledActiveUpserts,
537
578
  pulledArchivedUpserts,
579
+ pulledTaskEventUpserts,
538
580
  documentDecryptFailures: Array.from(documentDecryptFailures),
539
581
  emittedEventIds: Array.from(emittedEventIds),
540
582
  serverDiagnostics: null
@@ -37,6 +37,7 @@ function buildPayload(overrides = {}) {
37
37
  currentAnnotatedAttachmentSessionIds: new Set(),
38
38
  deleteAnnotatedAttachmentSessionIds: new Set(),
39
39
  currentAnnotatedAttachmentSessionWatermarks: new Map(),
40
+ currentTaskEventWatermarks: new Map(),
40
41
  pushedWatermarks: new Map(),
41
42
  ...overrides
42
43
  };
@@ -500,6 +501,53 @@ describe('syncService', () => {
500
501
  delete process.env.TASKFORCE_SYNC_KEY;
501
502
  }
502
503
  });
504
+ it('tracks event-only task pulls so callers can refresh visible task history', async () => {
505
+ vi.mocked(pullWorkspaceChanges).mockResolvedValueOnce({
506
+ ok: true,
507
+ status: 200,
508
+ data: {
509
+ success: true,
510
+ changes: [{
511
+ op: 'task-event-upsert',
512
+ event: {
513
+ id: 'task-event-1',
514
+ taskId: 'task-1',
515
+ workspaceId: 'ws-1',
516
+ action: 'task-status-changed',
517
+ actor: 'ai-profile-1',
518
+ actorType: 'ai',
519
+ createdAt: '2026-04-13T10:05:00.000Z',
520
+ details: {
521
+ changes: {
522
+ status: {
523
+ from: 'in-progress',
524
+ to: 'done'
525
+ }
526
+ }
527
+ }
528
+ }
529
+ }],
530
+ nextCursor: null,
531
+ hasMore: false
532
+ }
533
+ });
534
+ vi.mocked(applyLocalWorkspaceChanges).mockResolvedValueOnce({
535
+ ok: true,
536
+ failures: [],
537
+ appliedCount: 1,
538
+ data: { success: true }
539
+ });
540
+ const res = await runWorkspacePullSyncService({
541
+ resolveCloudAuthUrl: mockResolveCloudAuthUrl,
542
+ workspaceId: 'ws-1',
543
+ cursor: null,
544
+ bootstrap: false
545
+ });
546
+ expect(res.success).toBe(true);
547
+ expect(res.pulledTaskEventUpserts).toBe(true);
548
+ expect(res.pulledActiveUpserts).toBe(false);
549
+ expect(res.pulledArchivedUpserts).toBe(false);
550
+ });
503
551
  it('zero-change bootstrap pull (no changes, hasMore false) returns success true', async () => {
504
552
  vi.mocked(pullWorkspaceChanges).mockResolvedValueOnce({
505
553
  ok: true,
@@ -695,6 +743,20 @@ describe('syncService', () => {
695
743
  expect(res.error).toContain('already linked to another cloud account');
696
744
  expect(res.error).toContain('grant you access');
697
745
  });
746
+ it('does not provision a new cloud workspace during attach-cloud mismatch handling', async () => {
747
+ vi.mocked(handshakeWorkspace).mockResolvedValueOnce({
748
+ ok: false,
749
+ status: 409,
750
+ data: { code: 'WORKSPACE_ID_MISMATCH', error: 'Workspace mismatch' }
751
+ });
752
+ const res = await ensureCloudWorkspaceReadyForSyncService({
753
+ ...baseHandshakeInput,
754
+ allowProvisionOnMismatch: false
755
+ });
756
+ expect(res.success).toBe(false);
757
+ expect(res.statusCode).toBe(409);
758
+ expect(provisionWorkspace).not.toHaveBeenCalled();
759
+ });
698
760
  });
699
761
  describe('syncUserGlobalSettingsService transient metadata', () => {
700
762
  beforeEach(() => {
@@ -18,6 +18,7 @@ export interface WorkspacePullFeedResult {
18
18
  diagnostics: {
19
19
  totalEntries: number;
20
20
  pageEntries: number;
21
+ skippedHydrationEntries: number;
21
22
  documentIndexCount: number;
22
23
  assetIndexCount: number;
23
24
  indexMs: number;