@taskforcehq/taskforce 0.3.302 → 0.3.304

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 (192) hide show
  1. package/dist/Taskforce.module.css +113 -5
  2. package/dist/TaskforceCore.js +560 -208
  3. package/dist/TaskforceCore.test.js +1833 -91
  4. package/dist/annotatedAttachments/service.d.ts +1 -1
  5. package/dist/annotatedAttachments/types.d.ts +1 -1
  6. package/dist/components/features/AgentsModule.js +1 -1
  7. package/dist/components/features/AgentsModule.test.js +1 -1
  8. package/dist/components/features/TaskSettings.js +58 -38
  9. package/dist/components/features/TaskSettings.test.js +139 -40
  10. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  11. package/dist/components/task/TaskActionHeader.js +3 -3
  12. package/dist/components/task/TaskActionHeader.test.js +8 -0
  13. package/dist/components/task/TaskCard.js +16 -1
  14. package/dist/components/task/TaskCard.test.js +31 -0
  15. package/dist/components/task/TaskForm.d.ts +2 -1
  16. package/dist/components/task/TaskForm.js +22 -4
  17. package/dist/components/task/TaskForm.test.js +46 -0
  18. package/dist/components/task/TaskKanban.d.ts +1 -1
  19. package/dist/components/task/TaskKanban.js +31 -160
  20. package/dist/components/task/TaskKanban.test.js +17 -0
  21. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  22. package/dist/components/task/TaskStatusActions.js +8 -3
  23. package/dist/components/task/TaskStatusActions.test.js +9 -0
  24. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  25. package/dist/components/task/taskKanbanDropRules.js +187 -0
  26. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  27. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  28. package/dist/components/ui/Modal.d.ts +1 -1
  29. package/dist/components/ui/Modal.js +1 -0
  30. package/dist/components/views/AppShellHeader.js +5 -1
  31. package/dist/components/views/AppShellHeader.test.js +4 -0
  32. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  33. package/dist/components/views/PlanComparisonPage.js +4 -2
  34. package/dist/components/views/PlanComparisonPage.test.js +4 -0
  35. package/dist/components/views/PlansPage.d.ts +7 -1
  36. package/dist/components/views/PlansPage.js +139 -53
  37. package/dist/components/views/PlansPage.test.d.ts +1 -0
  38. package/dist/components/views/PlansPage.test.js +157 -0
  39. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  40. package/dist/components/views/StandaloneLayout.js +216 -186
  41. package/dist/components/views/WidgetView.js +11 -2
  42. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
  43. package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
  44. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  45. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  46. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  47. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  48. package/dist/core/EntitlementsPolicy.test.js +2 -2
  49. package/dist/core/GlobalSettingsService.js +78 -18
  50. package/dist/core/PlanEntitlementService.d.ts +31 -5
  51. package/dist/core/PlanEntitlementService.js +350 -222
  52. package/dist/core/SignupMonetizationSettings.test.js +208 -49
  53. package/dist/core/SystemAdmin.test.js +186 -96
  54. package/dist/core/TaskActivityService.d.ts +4 -1
  55. package/dist/core/TaskActivityService.js +45 -0
  56. package/dist/core/TaskAuditTimeline.test.js +52 -1
  57. package/dist/core/Taskforce.d.ts +65 -20
  58. package/dist/core/Taskforce.js +261 -51
  59. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  60. package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
  61. package/dist/core/WorkspacePlanMode.test.js +65 -0
  62. package/dist/core/types.d.ts +11 -12
  63. package/dist/documentReviews/service.d.ts +1 -1
  64. package/dist/documentReviews/types.d.ts +1 -1
  65. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  66. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  67. package/dist/hooks/sync/auth.d.ts +37 -0
  68. package/dist/hooks/sync/auth.js +61 -0
  69. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  70. package/dist/hooks/sync/bootstrap.js +52 -0
  71. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  72. package/dist/hooks/sync/orchestratorShared.js +209 -0
  73. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  74. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  75. package/dist/hooks/sync/realtime.d.ts +25 -0
  76. package/dist/hooks/sync/realtime.js +60 -0
  77. package/dist/hooks/sync/recovery.d.ts +69 -0
  78. package/dist/hooks/sync/recovery.js +118 -0
  79. package/dist/hooks/sync/transfers.d.ts +122 -0
  80. package/dist/hooks/sync/transfers.js +431 -0
  81. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  82. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  83. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  84. package/dist/hooks/ui/useResourceExport.js +5 -2
  85. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  86. package/dist/hooks/useRealtimeSync.js +2 -1
  87. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  88. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  89. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  90. package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
  91. package/dist/hooks/useSyncOrchestrator.js +315 -860
  92. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
  93. package/dist/hooks/useTaskMutations.js +45 -6
  94. package/dist/hooks/useTaskMutations.test.js +147 -0
  95. package/dist/hooks/useTaskforce.d.ts +11 -2
  96. package/dist/hooks/useTaskforce.js +290 -812
  97. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  98. package/dist/hooks/useWorkspaceSyncController.js +22 -10
  99. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  100. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  101. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  102. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  103. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  104. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  105. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  106. package/dist/localization/locales/en-US.js +2 -2
  107. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  108. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  109. package/dist/mcp/clientIntegrations.d.ts +12 -1
  110. package/dist/mcp/clientIntegrations.js +161 -6
  111. package/dist/mcp/clientIntegrations.test.js +76 -4
  112. package/dist/mcp/documentHelpers.d.ts +100 -0
  113. package/dist/mcp/documentHelpers.js +161 -0
  114. package/dist/mcp/runtime.js +264 -505
  115. package/dist/mcp/runtime.test.js +353 -89
  116. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  117. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  118. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  119. package/dist/mcp/taskAttachmentTypes.js +1 -0
  120. package/dist/migrations/billingSchemaParity.test.js +3 -0
  121. package/dist/migrations/taskSchemaMigrations.js +15 -0
  122. package/dist/plugins/vite.js +1 -1
  123. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  124. package/dist/runtime/appGateDefinitions.js +1 -1
  125. package/dist/server/index.cookieProxy.test.js +1 -0
  126. package/dist/server/index.d.ts +1 -0
  127. package/dist/server/index.js +48 -3
  128. package/dist/server/index.test.js +14 -2
  129. package/dist/server/mockStripe.d.ts +1 -0
  130. package/dist/server/mockStripe.js +2 -0
  131. package/dist/server/routes/admin.js +91 -40
  132. package/dist/server/routes/auth.d.ts +1 -0
  133. package/dist/server/routes/auth.js +231 -55
  134. package/dist/server/routes/billing.js +473 -39
  135. package/dist/server/routes/billing.test.js +972 -37
  136. package/dist/server/routes/shared.d.ts +30 -0
  137. package/dist/server/routes/shared.js +31 -3
  138. package/dist/server/routes/sync.integration.test.js +180 -0
  139. package/dist/server/routes/sync.js +125 -242
  140. package/dist/server/routes/tasks.js +49 -7
  141. package/dist/server/routes.js +11 -22
  142. package/dist/server/routes.test.js +530 -81
  143. package/dist/shared/taskActor.d.ts +10 -0
  144. package/dist/shared/taskActor.js +1 -0
  145. package/dist/sync/cloudSyncApi.d.ts +1 -0
  146. package/dist/sync/collaborationSyncPayload.js +4 -0
  147. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  148. package/dist/sync/collaborationSyncState.d.ts +3 -1
  149. package/dist/sync/collaborationSyncState.js +19 -0
  150. package/dist/sync/contentSyncState.d.ts +10 -0
  151. package/dist/sync/contentSyncState.js +277 -14
  152. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  153. package/dist/sync/syncApplyHandlers.js +370 -8
  154. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  155. package/dist/sync/syncApplyHandlers.test.js +377 -0
  156. package/dist/sync/syncService.d.ts +9 -1
  157. package/dist/sync/syncService.js +57 -10
  158. package/dist/sync/syncService.test.js +48 -0
  159. package/dist/sync/workspacePullFeed.d.ts +11 -0
  160. package/dist/sync/workspacePullFeed.js +123 -68
  161. package/dist/sync/workspacePullFeed.test.js +180 -0
  162. package/dist/sync/workspaceSyncModel.d.ts +22 -1
  163. package/dist/sync/workspaceSyncModel.js +65 -0
  164. package/dist/sync/workspaceSyncModel.test.js +119 -0
  165. package/dist/types.d.ts +2 -10
  166. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
  167. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
  168. package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
  169. package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
  170. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  171. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
  172. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
  173. package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
  174. package/dist/ui/assets/index-DXUdtH1B.js +6 -0
  175. package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
  176. package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
  177. package/dist/ui/index.html +4 -4
  178. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  179. package/dist/utils/accountProfileSummaryCache.js +97 -0
  180. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  181. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  182. package/dist/utils/taskActivity.js +4 -0
  183. package/dist/utils/taskActivity.test.js +25 -1
  184. package/package.json +5 -3
  185. package/scripts/playwright-auth.sh +4 -0
  186. package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
  187. package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
  188. package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
  189. package/dist/ui/assets/index-MXeWoebC.css +0 -1
  190. package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
  191. package/dist/utils/billingStatusCache.d.ts +0 -21
  192. package/dist/utils/billingStatusCache.js +0 -70
@@ -1,21 +1,46 @@
1
- import { useState, useCallback, useRef } from 'react';
1
+ import { useState, useCallback, useEffect, useRef } from 'react';
2
2
  import { normalizeTaskFromApi } from '../utils/taskNormalization.js';
3
3
  export function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks, setArchivedTasks, setDeletedTasks, setError, resetForm, setActiveTab, fetchTasks, fetchArchive, fetchDeletedTasks, mergeTaskFromServer, pushNotice, cloudAuthConfigured, runtimeMode, isAuthenticated, workspaceCloudSyncEnabled, buildWorkspaceSyncSignature, pushWorkspaceChangesToCloud, workspacePendingSignatureRef, workspaceDeletedTaskIdsRef, }) {
4
4
  // Copy ID state + timer ref
5
5
  const [copiedId, setCopiedId] = useState(null);
6
6
  const copiedIdTimerRef = useRef(null);
7
+ const workspaceSyncTimerRef = useRef(null);
8
+ const workspaceSyncTargetAtRef = useRef(null);
9
+ useEffect(() => () => {
10
+ if (typeof window === 'undefined')
11
+ return;
12
+ if (workspaceSyncTimerRef.current !== null) {
13
+ window.clearTimeout(workspaceSyncTimerRef.current);
14
+ workspaceSyncTimerRef.current = null;
15
+ }
16
+ workspaceSyncTargetAtRef.current = null;
17
+ }, []);
7
18
  const queueWorkspaceSyncFromAuthoritativeTaskState = useCallback((delayMs = 0) => {
8
19
  if (!(cloudAuthConfigured && runtimeMode === 'local' && isAuthenticated && workspaceCloudSyncEnabled))
9
20
  return;
10
21
  if (typeof window === 'undefined')
11
22
  return;
12
- window.setTimeout(() => {
23
+ const normalizedDelayMs = Math.max(0, delayMs);
24
+ const requestedTargetAt = Date.now() + normalizedDelayMs;
25
+ const existingTargetAt = workspaceSyncTargetAtRef.current;
26
+ if (workspaceSyncTimerRef.current !== null) {
27
+ if (normalizedDelayMs <= 0)
28
+ return;
29
+ if (existingTargetAt !== null && existingTargetAt >= requestedTargetAt)
30
+ return;
31
+ window.clearTimeout(workspaceSyncTimerRef.current);
32
+ workspaceSyncTimerRef.current = null;
33
+ }
34
+ workspaceSyncTargetAtRef.current = requestedTargetAt;
35
+ workspaceSyncTimerRef.current = window.setTimeout(() => {
36
+ workspaceSyncTimerRef.current = null;
37
+ workspaceSyncTargetAtRef.current = null;
13
38
  const signature = buildWorkspaceSyncSignature();
14
39
  if (!signature)
15
40
  return;
16
41
  workspacePendingSignatureRef.current = signature;
17
42
  void pushWorkspaceChangesToCloud(signature);
18
- }, Math.max(0, delayMs));
43
+ }, normalizedDelayMs);
19
44
  }, [
20
45
  cloudAuthConfigured,
21
46
  runtimeMode,
@@ -227,8 +252,20 @@ export function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks
227
252
  }, [tasks, archivedTasks, pushNotice, mergeTaskFromServer, queueWorkspaceSyncFromAuthoritativeTaskState]);
228
253
  const handleToggleComplete = async (task) => {
229
254
  const newStatus = task.status === 'done' ? 'task' : 'done';
255
+ const completedAt = newStatus === 'done' ? new Date().toISOString() : undefined;
256
+ const optimisticUpdates = {
257
+ status: newStatus,
258
+ completedAt,
259
+ completed: newStatus === 'done',
260
+ inProgress: false,
261
+ readyForReview: false,
262
+ cancelled: false
263
+ };
264
+ const previousTasks = tasks;
265
+ const previousArchivedTasks = archivedTasks;
266
+ setTasks(prev => prev.map(t => t.id === task.id ? { ...t, ...optimisticUpdates } : t));
267
+ setArchivedTasks(prev => prev.map(t => t.id === task.id ? { ...t, ...optimisticUpdates } : t));
230
268
  try {
231
- const completedAt = newStatus === 'done' ? new Date().toISOString() : undefined;
232
269
  const res = await fetch(`/api/taskforce/task/${task.id}`, {
233
270
  method: 'PATCH',
234
271
  headers: { 'Content-Type': 'application/json' },
@@ -239,7 +276,8 @@ export function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks
239
276
  const message = data.error || 'Failed to update task status.';
240
277
  setError(message);
241
278
  pushNotice(message, 'error');
242
- await fetchTasks(true);
279
+ setTasks(previousTasks);
280
+ setArchivedTasks(previousArchivedTasks);
243
281
  return;
244
282
  }
245
283
  const updatedTask = await res.json().catch(() => null);
@@ -257,7 +295,8 @@ export function useTaskMutations({ tasks, archivedTasks, editingTaskId, setTasks
257
295
  console.error("Failed to toggle complete", err);
258
296
  setError('Failed to update task status.');
259
297
  pushNotice('Failed to update task status.', 'error');
260
- fetchTasks();
298
+ setTasks(previousTasks);
299
+ setArchivedTasks(previousArchivedTasks);
261
300
  }
262
301
  };
263
302
  const handleToggleCancel = async (task) => {
@@ -6,9 +6,84 @@ describe('useTaskMutations', () => {
6
6
  vi.stubGlobal('fetch', vi.fn());
7
7
  });
8
8
  afterEach(() => {
9
+ vi.useRealTimers();
9
10
  vi.unstubAllGlobals();
10
11
  vi.clearAllMocks();
11
12
  });
13
+ const baseTask = {
14
+ id: 'task-1',
15
+ title: 'Test task',
16
+ description: '',
17
+ category: 'general',
18
+ type: 'task',
19
+ priority: 2,
20
+ complexity: 3,
21
+ status: 'task',
22
+ createdAt: '2026-04-03T10:00:00.000Z',
23
+ updatedAt: '2026-04-03T10:05:00.000Z'
24
+ };
25
+ const buildHook = (overrides = {}) => {
26
+ const setTasks = vi.fn();
27
+ const setArchivedTasks = vi.fn();
28
+ const setDeletedTasks = vi.fn();
29
+ const setError = vi.fn();
30
+ const resetForm = vi.fn();
31
+ const setActiveTab = vi.fn();
32
+ const fetchTasks = vi.fn();
33
+ const fetchArchive = vi.fn();
34
+ const fetchDeletedTasks = vi.fn();
35
+ const mergeTaskFromServer = vi.fn();
36
+ const pushNotice = vi.fn();
37
+ const buildWorkspaceSyncSignature = vi.fn(() => 'sig-1');
38
+ const pushWorkspaceChangesToCloud = vi.fn();
39
+ const workspacePendingSignatureRef = { current: null };
40
+ const workspaceDeletedTaskIdsRef = { current: new Set() };
41
+ const hook = renderHook(() => useTaskMutations({
42
+ tasks: [baseTask],
43
+ archivedTasks: [],
44
+ editingTaskId: null,
45
+ setTasks,
46
+ setArchivedTasks,
47
+ setDeletedTasks,
48
+ setError,
49
+ resetForm,
50
+ setActiveTab,
51
+ fetchTasks,
52
+ fetchArchive,
53
+ fetchDeletedTasks,
54
+ mergeTaskFromServer,
55
+ pushNotice,
56
+ cloudAuthConfigured: true,
57
+ runtimeMode: 'local',
58
+ isAuthenticated: true,
59
+ workspaceCloudSyncEnabled: true,
60
+ buildWorkspaceSyncSignature,
61
+ pushWorkspaceChangesToCloud,
62
+ workspacePendingSignatureRef,
63
+ workspaceDeletedTaskIdsRef,
64
+ ...overrides
65
+ }));
66
+ return {
67
+ ...hook,
68
+ deps: {
69
+ setTasks,
70
+ setArchivedTasks,
71
+ setDeletedTasks,
72
+ setError,
73
+ resetForm,
74
+ setActiveTab,
75
+ fetchTasks,
76
+ fetchArchive,
77
+ fetchDeletedTasks,
78
+ mergeTaskFromServer,
79
+ pushNotice,
80
+ buildWorkspaceSyncSignature,
81
+ pushWorkspaceChangesToCloud,
82
+ workspacePendingSignatureRef,
83
+ workspaceDeletedTaskIdsRef
84
+ }
85
+ };
86
+ };
12
87
  it('clears pending delete ids when a deleted task is restored', async () => {
13
88
  const fetchMock = vi.mocked(fetch);
14
89
  fetchMock.mockResolvedValue({
@@ -77,4 +152,76 @@ describe('useTaskMutations', () => {
77
152
  expect(pushWorkspaceChangesToCloud).toHaveBeenCalledWith('sig-1');
78
153
  expect(setDeletedTasks).toHaveBeenCalled();
79
154
  });
155
+ it('optimistically marks a task done before the server responds', async () => {
156
+ let resolveResponse;
157
+ const fetchMock = vi.mocked(fetch);
158
+ fetchMock.mockImplementation(() => new Promise((resolve) => {
159
+ resolveResponse = resolve;
160
+ }));
161
+ const { result, deps } = buildHook();
162
+ let togglePromise = null;
163
+ await act(async () => {
164
+ togglePromise = result.current.handleToggleComplete(baseTask);
165
+ });
166
+ expect(deps.setTasks).toHaveBeenCalledTimes(1);
167
+ const optimisticUpdater = deps.setTasks.mock.calls[0]?.[0];
168
+ const optimisticTasks = optimisticUpdater([baseTask]);
169
+ expect(optimisticTasks[0]?.status).toBe('done');
170
+ expect(optimisticTasks[0]?.completed).toBe(true);
171
+ resolveResponse({
172
+ ok: true,
173
+ status: 200,
174
+ json: async () => ({ ...baseTask, status: 'done', completedAt: '2026-04-14T12:00:00.000Z' })
175
+ });
176
+ await act(async () => {
177
+ await togglePromise;
178
+ });
179
+ expect(deps.mergeTaskFromServer).toHaveBeenCalledWith(expect.objectContaining({ id: 'task-1', status: 'done' }));
180
+ });
181
+ it('rolls back the optimistic done state when the server update fails', async () => {
182
+ const fetchMock = vi.mocked(fetch);
183
+ fetchMock.mockResolvedValue({
184
+ ok: false,
185
+ status: 500,
186
+ json: async () => ({ error: 'Server exploded' })
187
+ });
188
+ const { result, deps } = buildHook();
189
+ await act(async () => {
190
+ await result.current.handleToggleComplete(baseTask);
191
+ });
192
+ expect(deps.setTasks).toHaveBeenCalledTimes(2);
193
+ expect(deps.setTasks.mock.calls[1]?.[0]).toEqual([baseTask]);
194
+ expect(deps.setArchivedTasks.mock.calls[1]?.[0]).toEqual([]);
195
+ expect(deps.pushNotice).toHaveBeenCalledWith('Server exploded', 'error');
196
+ expect(deps.mergeTaskFromServer).not.toHaveBeenCalled();
197
+ });
198
+ it('coalesces pending workspace sync pushes and preserves attachment delay', async () => {
199
+ vi.useFakeTimers();
200
+ const fetchMock = vi.mocked(fetch);
201
+ fetchMock.mockResolvedValue({
202
+ ok: true,
203
+ status: 200,
204
+ json: async () => ({ ...baseTask, updatedAt: '2026-04-14T12:00:00.000Z' })
205
+ });
206
+ const { result, deps } = buildHook();
207
+ await act(async () => {
208
+ await result.current.handleUpdateTask(baseTask.id, { description: 'First update' });
209
+ });
210
+ await act(async () => {
211
+ await result.current.handleUpdateTask(baseTask.id, {
212
+ attachments: [{ id: 'asset-1', path: 'workspaces/workspace-local-active/assets/file.txt' }]
213
+ });
214
+ });
215
+ await act(async () => {
216
+ await vi.advanceTimersByTimeAsync(149);
217
+ });
218
+ expect(deps.pushWorkspaceChangesToCloud).not.toHaveBeenCalled();
219
+ await act(async () => {
220
+ await vi.advanceTimersByTimeAsync(1);
221
+ });
222
+ expect(deps.buildWorkspaceSyncSignature).toHaveBeenCalledTimes(1);
223
+ expect(deps.workspacePendingSignatureRef.current).toBe('sig-1');
224
+ expect(deps.pushWorkspaceChangesToCloud).toHaveBeenCalledTimes(1);
225
+ expect(deps.pushWorkspaceChangesToCloud).toHaveBeenCalledWith('sig-1');
226
+ });
80
227
  });
@@ -122,7 +122,7 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
122
122
  authUserDisplayName: string;
123
123
  authUserAvatarUrl: string;
124
124
  authWorkspaceId: string;
125
- userGlobalSyncStatus: "error" | "idle" | "syncing" | "disconnected";
125
+ userGlobalSyncStatus: "error" | "idle" | "disconnected" | "syncing";
126
126
  workspaceLastPullAt: string | null;
127
127
  workspaceLastPushAt: string | null;
128
128
  workspaceLastErrorAt: string | null;
@@ -193,6 +193,12 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
193
193
  runtimeCapabilities: RuntimeCapabilities | null;
194
194
  refreshSetupContext: () => Promise<void>;
195
195
  retryBootstrapChecks: () => Promise<void>;
196
+ continueAfterCommercialOnboarding: () => Promise<{
197
+ success: boolean;
198
+ destination: "app" | "setup" | "login";
199
+ error?: string;
200
+ code?: string;
201
+ }>;
196
202
  saveWorkspaceProfile: (input: {
197
203
  name: string;
198
204
  description?: string;
@@ -206,7 +212,7 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
206
212
  fetchWorkspaces: () => Promise<{
207
213
  success: boolean;
208
214
  error?: string;
209
- workspaces?: WorkspaceSummary[];
215
+ workspaces?: import("./workspace/useTaskforceWorkspaceBootstrap").WorkspaceSummary[];
210
216
  }>;
211
217
  createWorkspace: (name: string, description?: string) => Promise<{
212
218
  success: boolean;
@@ -249,6 +255,9 @@ export declare function useTaskforce({ config, initialTaskId, onTaskCountChange,
249
255
  emailSent?: boolean;
250
256
  emailError?: string;
251
257
  workspaceSetupRequired?: boolean;
258
+ planSelectionRequired?: boolean;
259
+ checkoutPending?: boolean;
260
+ commercialState?: string | null;
252
261
  }>;
253
262
  updateCurrentUserProfile: (input: {
254
263
  displayName: string;