@taskforcehq/taskforce 0.3.304 → 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 (138) hide show
  1. package/README.md +140 -247
  2. package/dist/TaskforceCore.js +165 -70
  3. package/dist/TaskforceCore.test.js +799 -90
  4. package/dist/compat/workspaceSyncCompat.js +6 -0
  5. package/dist/components/features/DocumentIndex.d.ts +1 -1
  6. package/dist/components/features/DocumentIndex.js +1 -1
  7. package/dist/components/features/DocumentViewer.d.ts +1 -1
  8. package/dist/components/features/DocumentViewer.js +32 -9
  9. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  10. package/dist/components/features/DocumentWorkspace.js +12 -155
  11. package/dist/components/features/DocumentWorkspace.test.js +193 -6
  12. package/dist/components/features/TaskSettings.js +29 -3
  13. package/dist/components/features/TaskSettings.test.js +42 -1
  14. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  15. package/dist/components/features/documentWorkspaceModel.js +57 -0
  16. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  17. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  18. package/dist/components/task/TaskForm.d.ts +8 -8
  19. package/dist/components/task/TaskForm.js +358 -221
  20. package/dist/components/task/TaskForm.test.js +456 -381
  21. package/dist/components/task/TaskKanban.js +2 -1
  22. package/dist/components/views/PlanComparisonPage.js +5 -8
  23. package/dist/components/views/PlanComparisonPage.test.js +51 -41
  24. package/dist/components/views/PlansPage.js +126 -84
  25. package/dist/components/views/PlansPage.test.js +308 -39
  26. package/dist/components/views/StandaloneLayout.js +28 -22
  27. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  28. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +2 -14
  29. package/dist/components/views/standalone/modals/AccountHubModal.js +12 -14
  30. package/dist/components/views/standalone/modals/AccountHubModal.test.js +24 -1
  31. package/dist/config/envSchema.js +1 -1
  32. package/dist/core/AiProfileService.d.ts +6 -1
  33. package/dist/core/AiProfileService.js +161 -16
  34. package/dist/core/AiProfileService.test.js +3 -1
  35. package/dist/core/AiProfiles.test.js +200 -0
  36. package/dist/core/AuthTokenService.test.d.ts +1 -0
  37. package/dist/core/AuthTokenService.test.js +78 -0
  38. package/dist/core/EntitlementsPolicy.test.js +75 -13
  39. package/dist/core/PlanEntitlementService.d.ts +7 -1
  40. package/dist/core/PlanEntitlementService.js +117 -42
  41. package/dist/core/PlanVersionPolicy.test.js +19 -26
  42. package/dist/core/SignupMonetizationSettings.test.js +46 -21
  43. package/dist/core/SystemAdmin.test.js +212 -56
  44. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  45. package/dist/core/Taskforce.d.ts +3 -0
  46. package/dist/core/Taskforce.js +50 -10
  47. package/dist/core/WorkspacePlanMode.test.js +12 -12
  48. package/dist/core/shared.d.ts +2 -0
  49. package/dist/core/shared.js +11 -0
  50. package/dist/hooks/sync/auth.js +3 -1
  51. package/dist/hooks/sync/transfers.d.ts +2 -1
  52. package/dist/hooks/sync/transfers.js +8 -4
  53. package/dist/hooks/useSyncOrchestrator.d.ts +7 -4
  54. package/dist/hooks/useSyncOrchestrator.js +75 -7
  55. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +80 -10
  56. package/dist/hooks/useTaskMutations.d.ts +2 -1
  57. package/dist/hooks/useTaskMutations.js +12 -1
  58. package/dist/hooks/useTaskMutations.test.js +30 -1
  59. package/dist/hooks/useTaskforce.d.ts +5 -2
  60. package/dist/hooks/useTaskforce.js +17 -5
  61. package/dist/hooks/useTaskforce.sync-behavior.test.js +78 -0
  62. package/dist/hooks/useWorkspaceSyncController.d.ts +3 -1
  63. package/dist/hooks/useWorkspaceSyncController.js +11 -1
  64. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  65. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  66. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  67. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  68. package/dist/mcp/collaborationRegistrar.js +71 -0
  69. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  70. package/dist/mcp/documentAssetRegistrar.js +346 -0
  71. package/dist/mcp/runtime.js +2362 -3530
  72. package/dist/mcp/runtime.test.js +159 -0
  73. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  74. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  75. package/dist/mcp/toolCatalog.d.ts +35 -0
  76. package/dist/mcp/toolCatalog.js +3 -0
  77. package/dist/server/routes/admin.d.ts +1 -5
  78. package/dist/server/routes/admin.js +138 -61
  79. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  80. package/dist/server/routes/annotatedAttachments.js +1 -1
  81. package/dist/server/routes/auth.d.ts +1 -4
  82. package/dist/server/routes/auth.js +23 -63
  83. package/dist/server/routes/authSupport.d.ts +30 -0
  84. package/dist/server/routes/authSupport.js +81 -0
  85. package/dist/server/routes/billing.d.ts +1 -1
  86. package/dist/server/routes/billing.js +104 -188
  87. package/dist/server/routes/billing.test.js +214 -86
  88. package/dist/server/routes/documentReviews.d.ts +1 -1
  89. package/dist/server/routes/documentReviews.js +1 -1
  90. package/dist/server/routes/documents.d.ts +4 -2
  91. package/dist/server/routes/documents.js +73 -7
  92. package/dist/server/routes/primitives.d.ts +11 -0
  93. package/dist/server/routes/primitives.js +73 -0
  94. package/dist/server/routes/resources.d.ts +1 -1
  95. package/dist/server/routes/resources.js +1 -1
  96. package/dist/server/routes/shared.d.ts +1 -1
  97. package/dist/server/routes/sync.d.ts +1 -1
  98. package/dist/server/routes/sync.js +1 -1
  99. package/dist/server/routes/tasks.d.ts +1 -1
  100. package/dist/server/routes/tasks.js +1 -1
  101. package/dist/server/routes/workspaces.d.ts +1 -1
  102. package/dist/server/routes/workspaces.js +1 -1
  103. package/dist/server/routes.d.ts +2 -9
  104. package/dist/server/routes.js +76 -162
  105. package/dist/server/routes.test.js +672 -30
  106. package/dist/sync/contentSyncState.js +16 -7
  107. package/dist/sync/syncApplyHandlers.test.js +2 -0
  108. package/dist/sync/syncService.d.ts +1 -0
  109. package/dist/sync/syncService.js +8 -0
  110. package/dist/sync/syncService.test.js +14 -0
  111. package/dist/sync/workspaceSyncModel.d.ts +3 -0
  112. package/dist/sync/workspaceSyncModel.js +12 -3
  113. package/dist/sync/workspaceSyncModel.test.js +34 -0
  114. package/dist/sync/workspaceSyncState.d.ts +2 -0
  115. package/dist/sync/workspaceSyncState.js +1 -0
  116. package/dist/ui/assets/{AgentsModule-BLWVbuKP.js → AgentsModule-N2MnQBZk.js} +1 -1
  117. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-BG6P_GVW.js} +1 -1
  118. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +1 -0
  119. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +252 -0
  120. package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-Dhm7M8e7.js} +1 -1
  121. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +1 -0
  122. package/dist/ui/assets/TaskSettings-ln0aF7xc.js +9 -0
  123. package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BcS4afRn.js} +1 -1
  124. package/dist/ui/assets/{index-Ii0B0rTO.css → index-C2-OXZV7.css} +1 -1
  125. package/dist/ui/assets/index-DcSI5F86.js +6 -0
  126. package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-BSUaRwF8.js} +1 -1
  127. package/dist/ui/index.html +3 -3
  128. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  129. package/dist/utils/commercialLifecyclePresentation.js +196 -0
  130. package/package.json +8 -1
  131. package/scripts/check-cloud-mcp.mjs +83 -0
  132. package/scripts/playwright-auth.sh +5 -1
  133. package/scripts/run-billing-performance-smoke.sh +24 -0
  134. package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
  135. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  136. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
  137. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
  138. package/dist/ui/assets/index-DXUdtH1B.js +0 -6
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { MemoryRouter, Navigate, useInRouterContext, useLocation, useNavigate } from 'react-router-dom';
4
4
  import { useTaskforce } from './hooks/useTaskforce';
5
5
  import { WidgetView } from './components/views/WidgetView';
@@ -92,6 +92,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
92
92
  const [syncNewLocalWorkspaceToCloud, setSyncNewLocalWorkspaceToCloud] = useState(false);
93
93
  const [pendingCloudWorkspaceImport, setPendingCloudWorkspaceImport] = useState(null);
94
94
  const [cloudWorkspaceImportFinalizing, setCloudWorkspaceImportFinalizing] = useState(false);
95
+ const cloudWorkspaceImportFinalizingRef = useRef(false);
95
96
  const fallbackSetupLibraryPacks = useMemo(() => listBuiltInTaxonomyLibraryPacks(), []);
96
97
  const [setupLibraryPacks, setSetupLibraryPacks] = useState(fallbackSetupLibraryPacks);
97
98
  const [selectedSetupPackId, setSelectedSetupPackId] = useState(() => (fallbackSetupLibraryPacks.find((pack) => pack.isDefaultStarter)?.id || fallbackSetupLibraryPacks[0]?.id || ''));
@@ -108,7 +109,24 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
108
109
  onTaskCountChange,
109
110
  onClose
110
111
  });
111
- const { runtimeMode, workspaceSwitchingEnabled, currentWorkspaceId, configLoaded, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, hasBetaAccess, authSessionResolved, workspaceBootstrapPending, bootstrapPhase, bootstrapError, setupState, runtimeCapabilities, refreshSetupContext, retryBootstrapChecks, createWorkspace, saveWorkspaceProfile, fetchWorkspaces, applyWorkspaceSyncStateSnapshot, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceLastErrorMessage, retryWorkspaceCloudSync, settingsModel, fetchTasks, loginWithCredentials, registerWithCredentials, requestEmailVerification, confirmEmailVerification, requestPasswordReset, confirmPasswordReset, inspectInviteAcceptance, acceptInviteWithToken, joinInviteWithToken, authUserEmail, currentTheme, logout } = taskforceState;
112
+ const { runtimeMode, workspaceSwitchingEnabled, currentWorkspaceId, configLoaded, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, hasBetaAccess, authSessionResolved, workspaceBootstrapPending, bootstrapPhase, bootstrapError, setupState, runtimeCapabilities, refreshSetupContext, retryBootstrapChecks, createWorkspace, saveWorkspaceProfile, fetchWorkspaces, applyWorkspaceSyncStateSnapshot, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncSetupIntent, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceLastErrorMessage, retryWorkspaceCloudSync, settingsModel, fetchTasks, loginWithCredentials, registerWithCredentials, requestEmailVerification, confirmEmailVerification, requestPasswordReset, confirmPasswordReset, inspectInviteAcceptance, acceptInviteWithToken, joinInviteWithToken, authUserEmail, currentTheme, logout } = taskforceState;
113
+ const persistWorkspaceSyncStatePatch = useCallback(async (workspaceId, patch) => {
114
+ const response = await fetch('/api/taskforce/ui-state', {
115
+ method: 'POST',
116
+ headers: { 'Content-Type': 'application/json' },
117
+ credentials: 'include',
118
+ body: JSON.stringify({
119
+ workspaceId,
120
+ stateKey: 'workspace-sync',
121
+ patch
122
+ })
123
+ });
124
+ const payload = await response.json().catch(() => ({}));
125
+ if (!response.ok || payload?.success === false) {
126
+ throw new Error(payload?.error || `Failed to configure workspace sync (${response.status})`);
127
+ }
128
+ return payload;
129
+ }, []);
112
130
  const selectedSetupPack = useMemo(() => {
113
131
  return setupLibraryPacks.find((pack) => pack.id === selectedSetupPackId)
114
132
  || setupLibraryPacks.find((pack) => pack.isDefaultStarter)
@@ -131,20 +149,35 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
131
149
  const isLoginRoute = location.pathname === '/login';
132
150
  const isSetupRoute = location.pathname === '/setup';
133
151
  const isComingSoonRoute = location.pathname === '/coming-soon';
152
+ const cloudWorkspaceSelectionPath = '/setup?step=workspace&source=cloud&postAuth=select-cloud-workspace';
134
153
  const wantsCloudWorkspaceSetup = !isCloudRuntime && workspaceSetupSource === 'cloud';
135
154
  const isCloudWorkspaceSetupFlow = wantsCloudWorkspaceSetup && canUseCloudWorkspaceSetup;
136
- const cloudWorkspaceImportMatchesCurrentWorkspace = Boolean(pendingCloudWorkspaceImport
137
- && currentWorkspaceId === pendingCloudWorkspaceImport.workspaceId);
138
- const isCloudWorkspaceImportErrored = Boolean(pendingCloudWorkspaceImport
155
+ const activeCloudWorkspaceImport = useMemo(() => {
156
+ if (pendingCloudWorkspaceImport)
157
+ return pendingCloudWorkspaceImport;
158
+ if (workspaceSyncSetupIntent !== 'attach-cloud-import')
159
+ return null;
160
+ const workspaceId = String(currentWorkspaceId || '').trim();
161
+ if (!workspaceId || workspaceId.toLowerCase() === 'default')
162
+ return null;
163
+ const matchingWorkspace = cloudWorkspaceOptions.find((workspace) => workspace.id === workspaceId);
164
+ return {
165
+ workspaceId,
166
+ name: matchingWorkspace?.name || workspaceId
167
+ };
168
+ }, [cloudWorkspaceOptions, currentWorkspaceId, pendingCloudWorkspaceImport, workspaceSyncSetupIntent]);
169
+ const cloudWorkspaceImportMatchesCurrentWorkspace = Boolean(activeCloudWorkspaceImport
170
+ && currentWorkspaceId === activeCloudWorkspaceImport.workspaceId);
171
+ const isCloudWorkspaceImportErrored = Boolean(activeCloudWorkspaceImport
139
172
  && cloudWorkspaceImportMatchesCurrentWorkspace
140
173
  && workspaceSyncPhase === 'error');
141
- const isCloudWorkspaceImportReady = Boolean(pendingCloudWorkspaceImport
174
+ const isCloudWorkspaceImportReady = Boolean(activeCloudWorkspaceImport
142
175
  && cloudWorkspaceImportMatchesCurrentWorkspace
143
176
  && workspaceCloudSyncEnabled
144
177
  && workspaceSyncPhase === 'active'
145
178
  && !workspaceSyncBusy);
146
179
  const cloudWorkspaceImportProgressLines = useMemo(() => {
147
- if (!pendingCloudWorkspaceImport)
180
+ if (!activeCloudWorkspaceImport)
148
181
  return [];
149
182
  const connectingState = setupBusy ? 'In progress' : 'Complete';
150
183
  const importingState = isCloudWorkspaceImportErrored
@@ -163,7 +196,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
163
196
  `Open local workspace: ${finishingState}`
164
197
  ];
165
198
  }, [
166
- pendingCloudWorkspaceImport,
199
+ activeCloudWorkspaceImport,
167
200
  setupBusy,
168
201
  isCloudWorkspaceImportErrored,
169
202
  workspaceSyncBusy,
@@ -340,6 +373,11 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
340
373
  if (!isSetupRoute || setupStep !== 'workspace')
341
374
  return;
342
375
  setWorkspaceSetupPage('details');
376
+ if (activeCloudWorkspaceImport && canUseCloudWorkspaceSetup) {
377
+ setWorkspaceSetupSource('cloud');
378
+ setSyncNewLocalWorkspaceToCloud(false);
379
+ return;
380
+ }
343
381
  const source = String(new URLSearchParams(location.search).get('source') || '').trim().toLowerCase();
344
382
  if (source === 'cloud' && canUseCloudWorkspaceSetup) {
345
383
  setWorkspaceSetupSource('cloud');
@@ -347,7 +385,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
347
385
  return;
348
386
  }
349
387
  setWorkspaceSetupSource('local');
350
- }, [canUseCloudWorkspaceSetup, isSetupRoute, location.search, setupStep]);
388
+ }, [activeCloudWorkspaceImport, canUseCloudWorkspaceSetup, isSetupRoute, location.search, setupStep]);
351
389
  const fetchCloudWorkspaceOptions = useCallback(async () => {
352
390
  if (!canUseCloudWorkspaceSetup || !isAuthenticated) {
353
391
  setCloudWorkspaceOptions([]);
@@ -410,7 +448,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
410
448
  })
411
449
  .then(async (res) => {
412
450
  if (!res.ok)
413
- throw new Error(`Failed to load starter libraries (${res.status})`);
451
+ throw new Error(`Failed to load setup libraries (${res.status})`);
414
452
  const payload = await res.json().catch(() => ({}));
415
453
  const nextPacks = Array.isArray(payload?.packs) ? payload.packs : [];
416
454
  if (cancelled || nextPacks.length === 0)
@@ -424,7 +462,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
424
462
  if (cancelled)
425
463
  return;
426
464
  setSetupLibraryPacks(fallbackSetupLibraryPacks);
427
- setSetupLibraryError(error instanceof Error ? error.message : 'Failed to load starter libraries.');
465
+ setSetupLibraryError(error instanceof Error ? error.message : 'Failed to load setup libraries.');
428
466
  })
429
467
  .finally(() => {
430
468
  if (!cancelled)
@@ -446,22 +484,24 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
446
484
  if (!isCloudWorkspaceSetupFlow) {
447
485
  setPendingCloudWorkspaceImport(null);
448
486
  setCloudWorkspaceImportFinalizing(false);
487
+ cloudWorkspaceImportFinalizingRef.current = false;
449
488
  }
450
489
  }, [isCloudWorkspaceSetupFlow]);
451
490
  useEffect(() => {
452
- if (!isCloudWorkspaceImportErrored || !pendingCloudWorkspaceImport)
491
+ if (!isCloudWorkspaceImportErrored || !activeCloudWorkspaceImport)
453
492
  return;
454
493
  setSetupNotice(null);
455
494
  setSetupError(workspaceLastErrorMessage
456
- || `Failed to import "${pendingCloudWorkspaceImport.name}" from cloud. Retry import to continue.`);
457
- }, [isCloudWorkspaceImportErrored, pendingCloudWorkspaceImport, workspaceLastErrorMessage]);
495
+ || `Failed to import "${activeCloudWorkspaceImport.name}" from cloud. Retry import to continue.`);
496
+ }, [activeCloudWorkspaceImport, isCloudWorkspaceImportErrored, workspaceLastErrorMessage]);
458
497
  useEffect(() => {
459
- if (!isCloudWorkspaceImportReady || !pendingCloudWorkspaceImport || cloudWorkspaceImportFinalizing)
498
+ if (!isCloudWorkspaceImportReady || !activeCloudWorkspaceImport || cloudWorkspaceImportFinalizingRef.current)
460
499
  return;
461
500
  let cancelled = false;
501
+ cloudWorkspaceImportFinalizingRef.current = true;
462
502
  setCloudWorkspaceImportFinalizing(true);
463
503
  setSetupError(null);
464
- setSetupNotice(`Workspace import complete. Opening ${pendingCloudWorkspaceImport.name}...`);
504
+ setSetupNotice(`Workspace import complete. Opening ${activeCloudWorkspaceImport.name}...`);
465
505
  void (async () => {
466
506
  try {
467
507
  await refreshSetupContext();
@@ -471,6 +511,12 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
471
511
  ]);
472
512
  if (cancelled)
473
513
  return;
514
+ await persistWorkspaceSyncStatePatch(activeCloudWorkspaceImport.workspaceId, { setupIntent: null });
515
+ applyWorkspaceSyncStateSnapshot({
516
+ enabled: workspaceCloudSyncEnabled,
517
+ phase: workspaceSyncPhase,
518
+ setupIntent: null
519
+ });
474
520
  setPendingCloudWorkspaceImport(null);
475
521
  navigate('/', { replace: true });
476
522
  }
@@ -478,20 +524,22 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
478
524
  if (cancelled)
479
525
  return;
480
526
  setSetupNotice(null);
481
- setSetupError(`Imported "${pendingCloudWorkspaceImport.name}" but failed to finish opening it.`);
527
+ setSetupError(`Imported "${activeCloudWorkspaceImport.name}" but failed to finish opening it.`);
482
528
  setCloudWorkspaceImportFinalizing(false);
529
+ cloudWorkspaceImportFinalizingRef.current = false;
483
530
  }
484
531
  })();
485
532
  return () => {
486
533
  cancelled = true;
487
534
  };
488
535
  }, [
489
- cloudWorkspaceImportFinalizing,
536
+ activeCloudWorkspaceImport,
537
+ applyWorkspaceSyncStateSnapshot,
490
538
  fetchTasks,
491
539
  fetchWorkspaces,
492
540
  isCloudWorkspaceImportReady,
493
541
  navigate,
494
- pendingCloudWorkspaceImport,
542
+ persistWorkspaceSyncStatePatch,
495
543
  refreshSetupContext
496
544
  ]);
497
545
  const nextPath = useMemo(() => {
@@ -502,7 +550,6 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
502
550
  return '/';
503
551
  return raw;
504
552
  }, [location.search]);
505
- const cloudWorkspaceSelectionPath = '/setup?step=workspace&source=cloud&postAuth=select-cloud-workspace';
506
553
  const shouldReturnToCloudWorkspaceSelection = useMemo(() => {
507
554
  try {
508
555
  const parsed = new URL(nextPath, 'https://taskforce.local');
@@ -688,6 +735,24 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
688
735
  const code = result.code || result.state;
689
736
  return code ? `[${code}] ${message}` : message;
690
737
  };
738
+ const buildVerificationRequestNotice = (input) => {
739
+ if (input?.deliveryAttempted === false) {
740
+ return input?.intro || 'If that account exists and still needs verification, we sent an email.';
741
+ }
742
+ if (input?.emailSent === false) {
743
+ return `Verification email failed to send. ${input?.emailError || 'Try Resend Verification again.'}`;
744
+ }
745
+ return 'Verification email resent.';
746
+ };
747
+ const buildPasswordResetNotice = (input) => {
748
+ if (input?.deliveryAttempted === false) {
749
+ return 'If that account exists, we sent password reset instructions.';
750
+ }
751
+ if (input?.emailSent === false) {
752
+ return `Password reset email failed to send. ${input?.emailError || 'Try again in a minute.'}`;
753
+ }
754
+ return 'Password reset instructions sent.';
755
+ };
691
756
  useEffect(() => {
692
757
  if (mode === 'widget')
693
758
  return;
@@ -738,7 +803,13 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
738
803
  }
739
804
  return;
740
805
  }
741
- if (isSetupRoute && pendingCloudWorkspaceImport) {
806
+ if (activeCloudWorkspaceImport) {
807
+ const isCloudImportRoute = isSetupRoute
808
+ && setupStep === 'workspace'
809
+ && new URLSearchParams(location.search).get('source') === 'cloud';
810
+ if (!isCloudImportRoute) {
811
+ navigate(cloudWorkspaceSelectionPath, { replace: true });
812
+ }
742
813
  return;
743
814
  }
744
815
  if (setupGateActive) {
@@ -785,7 +856,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
785
856
  setupState,
786
857
  hasSetupReadError,
787
858
  isCreateWorkspaceIntent,
788
- pendingCloudWorkspaceImport,
859
+ activeCloudWorkspaceImport,
789
860
  needsGlobalSetup,
790
861
  needsWorkspaceSetup,
791
862
  setupStep,
@@ -798,7 +869,8 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
798
869
  location.hash,
799
870
  navigate,
800
871
  nextPath,
801
- pendingPostAuthRedirect
872
+ pendingPostAuthRedirect,
873
+ cloudWorkspaceSelectionPath
802
874
  ]);
803
875
  useEffect(() => {
804
876
  if (mode === 'widget')
@@ -878,7 +950,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
878
950
  setLoginMode('verify');
879
951
  if (sent.verificationToken)
880
952
  setVerificationToken(sent.verificationToken);
881
- setLoginNotice('Verification email resent.');
953
+ setLoginNotice(buildVerificationRequestNotice(sent));
882
954
  setLoginError(null);
883
955
  setLoginErrorCode(null);
884
956
  }
@@ -950,7 +1022,10 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
950
1022
  setLoginMode('verify');
951
1023
  if (sent.verificationToken)
952
1024
  setVerificationToken(sent.verificationToken);
953
- setLoginNotice('Email not verified. A verification token was sent.');
1025
+ setLoginNotice(buildVerificationRequestNotice({
1026
+ ...sent,
1027
+ intro: 'If that account exists and still needs verification, we sent an email.'
1028
+ }));
954
1029
  setLoginError(null);
955
1030
  setLoginErrorCode(null);
956
1031
  }
@@ -1003,13 +1078,14 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1003
1078
  });
1004
1079
  }
1005
1080
  else if (authMode === 'forgot') {
1081
+ const passwordResetDeliveryFailed = result.deliveryAttempted === true && result.emailSent === false;
1006
1082
  if (result.resetToken)
1007
1083
  setResetToken(result.resetToken);
1008
- setResetCooldownUntil(Date.now() + 30_000);
1009
- setLoginMode('reset');
1010
- setLoginNotice(result.emailSent === false
1011
- ? `Password reset email failed to send. ${result.emailError || 'Try again in a minute.'}`
1012
- : 'Password reset instructions sent.');
1084
+ setLoginNotice(buildPasswordResetNotice(result));
1085
+ if (!passwordResetDeliveryFailed) {
1086
+ setResetCooldownUntil(Date.now() + 30_000);
1087
+ setLoginMode('reset');
1088
+ }
1013
1089
  }
1014
1090
  else if (authMode === 'reset') {
1015
1091
  setLoginMode('login');
@@ -1017,9 +1093,31 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1017
1093
  }
1018
1094
  else if (authMode === 'invite') {
1019
1095
  if (inviteResolution === 'existing_user_signed_out') {
1020
- setAuthMode('invite');
1096
+ const normalizedInviteEmail = String(inviteEmail || '').trim().toLowerCase();
1097
+ const normalizedLoginEmail = loginEmail.trim().toLowerCase();
1098
+ // Make the invite flow deterministically join-ready as soon as
1099
+ // the invited account signs in successfully, instead of waiting
1100
+ // for a later invite inspection effect to catch up.
1101
+ if (normalizedInviteEmail && normalizedLoginEmail === normalizedInviteEmail) {
1102
+ setAuthMode('invite');
1103
+ setInviteResolution('existing_user_ready');
1104
+ setLoginPassword('');
1105
+ setLoginNotice('Signed in. Review the invite details to continue.');
1106
+ setLoginError(null);
1107
+ setLoginErrorCode(null);
1108
+ setLoginBusy(false);
1109
+ return;
1110
+ }
1111
+ if (normalizedInviteEmail) {
1112
+ setLoginEmail(inviteEmail || '');
1113
+ }
1114
+ setInviteResolution('existing_user_signed_out');
1021
1115
  setLoginPassword('');
1022
- setLoginNotice('Signed in. Review the invite details to continue.');
1116
+ setLoginNotice(normalizedInviteEmail
1117
+ ? `Signed in, but this invite is for ${inviteEmail}. Sign in with that account to continue.`
1118
+ : 'Signed in, but this invite requires the invited account. Sign in with that account to continue.');
1119
+ setLoginError('Sign in with the invited account to join this workspace.');
1120
+ setLoginErrorCode('INVITE_ACCOUNT_MISMATCH');
1023
1121
  setLoginBusy(false);
1024
1122
  return;
1025
1123
  }
@@ -1079,7 +1177,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1079
1177
  setVerifyCooldownUntil(Date.now() + 30_000);
1080
1178
  if (sent.verificationToken)
1081
1179
  setVerificationToken(sent.verificationToken);
1082
- setLoginNotice('Verification email resent.');
1180
+ setLoginNotice(buildVerificationRequestNotice(sent));
1083
1181
  }
1084
1182
  else {
1085
1183
  setLoginError(formatAuthError(sent));
@@ -1096,7 +1194,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1096
1194
  }, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsx("h1", { className: authStyles.loginTitle, children: "Private Beta" })] }), _jsx("p", { className: authStyles.loginSubtitle, children: "You are signed in, but your account is not in the beta allowlist yet." }), _jsx("p", { className: authStyles.loginSubtitle, children: "You will see the full app as soon as beta access is enabled." }), _jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: "Sign Out" })] }) }) }));
1097
1195
  }
1098
1196
  const showGuidedWorkspaceSetup = isCreateWorkspaceIntent
1099
- || (isSetupRoute && Boolean(pendingCloudWorkspaceImport))
1197
+ || (isSetupRoute && Boolean(activeCloudWorkspaceImport))
1100
1198
  || (isSetupRoute && setupGateActive && (hasSetupReadError || needsGlobalSetup || needsWorkspaceSetup));
1101
1199
  if (showGuidedWorkspaceSetup) {
1102
1200
  const isError = !isCreateWorkspaceIntent && (hasSetupReadError || setupStep === 'error');
@@ -1114,11 +1212,11 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1114
1212
  const workspaceLabel = 'Workspace';
1115
1213
  const isLocalWorkspaceSetup = isCloudRuntime || workspaceSetupSource !== 'cloud';
1116
1214
  const isWorkspaceDetailsPage = isLocalWorkspaceSetup && workspaceSetupPage === 'details';
1117
- const isWorkspaceStarterPage = isLocalWorkspaceSetup && workspaceSetupPage === 'starter';
1215
+ const isWorkspaceLibraryPage = isLocalWorkspaceSetup && workspaceSetupPage === 'library';
1118
1216
  const showWorkspaceSourceSelector = !isCloudRuntime
1119
1217
  && canUseCloudWorkspaceSetup
1120
1218
  && isWorkspaceDetailsPage
1121
- && !pendingCloudWorkspaceImport;
1219
+ && !activeCloudWorkspaceImport;
1122
1220
  const runtimeIcon = runtimeCapabilities?.runtimeMode === 'cloud' ? Cloud : LaptopMinimal;
1123
1221
  const starterCategoryItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.categories) : '';
1124
1222
  const starterTypeItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.types) : '';
@@ -1126,23 +1224,23 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1126
1224
  const setupPrimaryActionLabel = setupBusy
1127
1225
  ? t('setup.saving')
1128
1226
  : wantsCloudWorkspaceSetup
1129
- ? (pendingCloudWorkspaceImport
1227
+ ? (activeCloudWorkspaceImport
1130
1228
  ? (isCloudWorkspaceImportErrored ? 'Retry Import' : 'Importing Workspace...')
1131
1229
  : 'Connect Workspace')
1132
1230
  : isWorkspaceDetailsPage
1133
1231
  ? 'Continue'
1134
- : isWorkspaceStarterPage
1232
+ : isWorkspaceLibraryPage
1135
1233
  ? 'Create Workspace'
1136
1234
  : 'Connect Workspace';
1137
1235
  return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
1138
1236
  backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
1139
- }, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.setupHeaderRow, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsxs("h1", { className: authStyles.loginTitle, children: ["TASKFORCE ", _jsx("span", { className: authStyles.loginTitleAccent, children: "HQ" })] })] }), runtimeCapabilities && (_jsx("span", { className: authStyles.runtimeIconBadge, title: runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', "aria-label": runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', children: React.createElement(runtimeIcon, { size: 16, 'aria-hidden': true }) }))] }), _jsx("p", { className: authStyles.loginSubtitle, children: _jsx("strong", { children: heading }) }), subtitle ? _jsx("p", { className: authStyles.loginSubtitle, children: subtitle }) : null, setupNotice && _jsx("p", { className: authStyles.loginSubtitle, children: setupNotice }), setupError && _jsx("p", { className: authStyles.loginError, children: setupError }), isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "core", checked: setupModeChoice === 'core', onChange: () => setSetupModeChoice('core'), disabled: setupBusy }), _jsx("span", { children: t('setup.coreModeOption', { workspaceLabel }) })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "operations", checked: setupModeChoice === 'operations', onChange: () => setSetupModeChoice('operations'), disabled: setupBusy }), _jsx("span", { children: t('setup.operationsModeOption') })] })] })), !isError && !isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [showWorkspaceSourceSelector && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "local", checked: workspaceSetupSource === 'local', onChange: () => setWorkspaceSetupSource('local'), disabled: setupBusy }), _jsxs("span", { children: ["Create new local ", workspaceLabel.toLowerCase()] })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "cloud", checked: workspaceSetupSource === 'cloud', onChange: () => setWorkspaceSetupSource('cloud'), disabled: setupBusy }), _jsxs("span", { children: ["Sync existing cloud ", workspaceLabel.toLowerCase()] })] })] })), wantsCloudWorkspaceSetup ? (_jsxs("div", { className: authStyles.optionGroup, children: [!pendingCloudWorkspaceImport && !canUseCloudWorkspaceSetup && (_jsx("p", { className: authStyles.loginError, children: "Cloud workspace sync is unavailable right now. Switch back to local setup or refresh runtime configuration." })), !pendingCloudWorkspaceImport && canUseCloudWorkspaceSetup && !isAuthenticated && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.inlineHint, children: "Sign in to choose one of your cloud projects." }), _jsxs("div", { className: authStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, disabled: setupBusy, onClick: () => {
1237
+ }, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.setupHeaderRow, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsxs("h1", { className: authStyles.loginTitle, children: ["TASKFORCE ", _jsx("span", { className: authStyles.loginTitleAccent, children: "HQ" })] })] }), runtimeCapabilities && (_jsx("span", { className: authStyles.runtimeIconBadge, title: runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', "aria-label": runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', children: React.createElement(runtimeIcon, { size: 16, 'aria-hidden': true }) }))] }), _jsx("p", { className: authStyles.loginSubtitle, children: _jsx("strong", { children: heading }) }), subtitle ? _jsx("p", { className: authStyles.loginSubtitle, children: subtitle }) : null, setupNotice && _jsx("p", { className: authStyles.loginSubtitle, children: setupNotice }), setupError && _jsx("p", { className: authStyles.loginError, children: setupError }), isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "core", checked: setupModeChoice === 'core', onChange: () => setSetupModeChoice('core'), disabled: setupBusy }), _jsx("span", { children: t('setup.coreModeOption', { workspaceLabel }) })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "operations", checked: setupModeChoice === 'operations', onChange: () => setSetupModeChoice('operations'), disabled: setupBusy }), _jsx("span", { children: t('setup.operationsModeOption') })] })] })), !isError && !isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [showWorkspaceSourceSelector && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "local", checked: workspaceSetupSource === 'local', onChange: () => setWorkspaceSetupSource('local'), disabled: setupBusy }), _jsxs("span", { children: ["Create new local ", workspaceLabel.toLowerCase()] })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "cloud", checked: workspaceSetupSource === 'cloud', onChange: () => setWorkspaceSetupSource('cloud'), disabled: setupBusy }), _jsxs("span", { children: ["Sync existing cloud ", workspaceLabel.toLowerCase()] })] })] })), wantsCloudWorkspaceSetup ? (_jsxs("div", { className: authStyles.optionGroup, children: [!activeCloudWorkspaceImport && !canUseCloudWorkspaceSetup && (_jsx("p", { className: authStyles.loginError, children: "Cloud workspace sync is unavailable right now. Switch back to local setup or refresh runtime configuration." })), !activeCloudWorkspaceImport && canUseCloudWorkspaceSetup && !isAuthenticated && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.inlineHint, children: "Sign in to choose one of your cloud projects." }), _jsxs("div", { className: authStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, disabled: setupBusy, onClick: () => {
1140
1238
  navigate(`/login?mode=login&next=${encodeURIComponent(cloudWorkspaceSelectionPath)}`, { replace: true });
1141
1239
  }, children: "Sign In" }), directRegistrationEnabled && (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: () => {
1142
1240
  navigate(`/login?mode=register&next=${encodeURIComponent(cloudWorkspaceSelectionPath)}`, { replace: true });
1143
- }, children: "Register" }))] })] })), !pendingCloudWorkspaceImport && canUseCloudWorkspaceSetup && isAuthenticated && (_jsxs(_Fragment, { children: [_jsxs("select", { className: styles.input, value: cloudWorkspaceSelection, onChange: (event) => setCloudWorkspaceSelection(event.target.value), disabled: setupBusy || cloudWorkspaceLoading || cloudWorkspaceOptions.length === 0, children: [cloudWorkspaceOptions.length === 0 && (_jsx("option", { value: "", children: cloudWorkspaceLoading ? 'Loading cloud projects...' : 'No cloud projects found' })), cloudWorkspaceOptions.map((workspace) => (_jsxs("option", { value: workspace.id, children: [workspace.name, " (", workspace.id, ")"] }, workspace.id)))] }), _jsx("div", { className: authStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, disabled: setupBusy || cloudWorkspaceLoading, onClick: () => void fetchCloudWorkspaceOptions(), children: "Refresh Cloud Projects" }) }), cloudWorkspaceError && _jsx("p", { className: authStyles.loginError, children: cloudWorkspaceError })] })), pendingCloudWorkspaceImport && (_jsxs(_Fragment, { children: [_jsxs("p", { className: authStyles.inlineHint, children: ["Importing \"", pendingCloudWorkspaceImport.name, "\" from cloud into local."] }), _jsx("div", { className: authStyles.optionGroup, children: cloudWorkspaceImportProgressLines.map((line) => (_jsx("p", { className: authStyles.inlineHint, children: line }, line))) }), _jsx("p", { className: authStyles.inlineHint, children: cloudWorkspaceImportFinalizing
1241
+ }, children: "Register" }))] })] })), !activeCloudWorkspaceImport && canUseCloudWorkspaceSetup && isAuthenticated && (_jsxs(_Fragment, { children: [_jsxs("select", { className: styles.input, value: cloudWorkspaceSelection, onChange: (event) => setCloudWorkspaceSelection(event.target.value), disabled: setupBusy || cloudWorkspaceLoading || cloudWorkspaceOptions.length === 0, children: [cloudWorkspaceOptions.length === 0 && (_jsx("option", { value: "", children: cloudWorkspaceLoading ? 'Loading cloud projects...' : 'No cloud projects found' })), cloudWorkspaceOptions.map((workspace) => (_jsxs("option", { value: workspace.id, children: [workspace.name, " (", workspace.id, ")"] }, workspace.id)))] }), _jsx("div", { className: authStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, disabled: setupBusy || cloudWorkspaceLoading, onClick: () => void fetchCloudWorkspaceOptions(), children: "Refresh Cloud Projects" }) }), cloudWorkspaceError && _jsx("p", { className: authStyles.loginError, children: cloudWorkspaceError })] })), activeCloudWorkspaceImport && (_jsxs(_Fragment, { children: [_jsxs("p", { className: authStyles.inlineHint, children: ["Importing \"", activeCloudWorkspaceImport.name, "\" from cloud into local."] }), _jsx("div", { className: authStyles.optionGroup, children: cloudWorkspaceImportProgressLines.map((line) => (_jsx("p", { className: authStyles.inlineHint, children: line }, line))) }), _jsx("p", { className: authStyles.inlineHint, children: cloudWorkspaceImportFinalizing
1144
1242
  ? 'Finishing setup and opening your workspace.'
1145
- : (workspaceSyncSummary || 'Pulling tasks, taxonomies, and settings into local.') }), !cloudWorkspaceImportFinalizing && workspaceSyncRecommendedAction && (_jsx("p", { className: authStyles.inlineHint, children: workspaceSyncRecommendedAction }))] }))] })) : (_jsxs(_Fragment, { children: [isWorkspaceDetailsPage && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.loginSubtitle, children: "Workspace name" }), _jsx("input", { className: styles.input, type: "text", value: workspaceNameInput, placeholder: "Workspace name", onChange: (event) => setWorkspaceNameInput(event.target.value), disabled: setupBusy }), _jsx("p", { className: authStyles.loginSubtitle, children: "Workspace description" }), _jsx("textarea", { className: styles.textarea, value: workspaceDescriptionInput, placeholder: "Workspace description (optional)", onChange: (event) => setWorkspaceDescriptionInput(event.target.value), disabled: setupBusy, rows: 4 })] })), isWorkspaceStarterPage && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "What are you working on?" }), _jsxs("select", { className: styles.input, value: selectedSetupPackId, onChange: (event) => setSelectedSetupPackId(event.target.value), disabled: setupBusy || setupLibraryLoading || setupLibraryPacks.length === 0, children: [setupLibraryPacks.length === 0 && (_jsx("option", { value: "", children: setupLibraryLoading ? 'Loading starter options...' : 'No starter options available' })), setupLibraryPacks.map((pack) => (_jsx("option", { value: pack.id, children: pack.label }, pack.id)))] }), selectedSetupPack ? (_jsx(_Fragment, { children: selectedSetupPack.description ? (_jsx("p", { className: authStyles.inlineHint, children: selectedSetupPack.description })) : null })) : null, _jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "Apply Starter Sections" }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.categories, onChange: () => setSetupTaxonomySections((current) => ({ ...current, categories: !current.categories })), disabled: setupBusy || !selectedSetupPack?.categories.length }), _jsx("span", { children: "Categories" })] }), starterCategoryItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterCategoryItems })) : null, _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.types, onChange: () => setSetupTaxonomySections((current) => ({ ...current, types: !current.types })), disabled: setupBusy || !selectedSetupPack?.types.length }), _jsx("span", { children: "Task Types" })] }), starterTypeItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterTypeItems })) : null, _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.priorities, onChange: () => setSetupTaxonomySections((current) => ({ ...current, priorities: !current.priorities })), disabled: setupBusy || !selectedSetupPack?.priorities.length }), _jsx("span", { children: "Priorities" })] }), starterPriorityItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterPriorityItems })) : null] }), setupLibraryLoading ? (_jsx("p", { className: authStyles.inlineHint, children: "Loading starter options\u2026" })) : null, setupLibraryError ? (_jsx("p", { className: authStyles.loginError, children: setupLibraryError })) : null] })), !isCloudRuntime && canUseCloudWorkspaceSetup && isAuthenticated && isWorkspaceDetailsPage && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: syncNewLocalWorkspaceToCloud, onChange: (event) => setSyncNewLocalWorkspaceToCloud(event.target.checked), disabled: setupBusy }), _jsx("span", { children: "Sync to cloud after setup" })] }), _jsx("p", { className: authStyles.inlineHint, children: "When enabled, this project will connect to cloud sync as soon as setup finishes." })] }))] }))] })), _jsxs("div", { className: authStyles.authModeLinks, children: [isWorkspaceStarterPage ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
1243
+ : (workspaceSyncSummary || 'Pulling tasks, taxonomies, and settings into local.') }), !cloudWorkspaceImportFinalizing && workspaceSyncRecommendedAction && (_jsx("p", { className: authStyles.inlineHint, children: workspaceSyncRecommendedAction }))] }))] })) : (_jsxs(_Fragment, { children: [isWorkspaceDetailsPage && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.loginSubtitle, children: "Workspace name" }), _jsx("input", { className: styles.input, type: "text", value: workspaceNameInput, placeholder: "Workspace name", onChange: (event) => setWorkspaceNameInput(event.target.value), disabled: setupBusy }), _jsx("p", { className: authStyles.loginSubtitle, children: "Workspace description" }), _jsx("textarea", { className: styles.textarea, value: workspaceDescriptionInput, placeholder: "Workspace description (optional)", onChange: (event) => setWorkspaceDescriptionInput(event.target.value), disabled: setupBusy, rows: 4 })] })), isWorkspaceLibraryPage && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "What are you working on?" }), _jsxs("select", { className: styles.input, value: selectedSetupPackId, onChange: (event) => setSelectedSetupPackId(event.target.value), disabled: setupBusy || setupLibraryLoading || setupLibraryPacks.length === 0, children: [setupLibraryPacks.length === 0 && (_jsx("option", { value: "", children: setupLibraryLoading ? 'Loading setup options...' : 'No setup options available' })), setupLibraryPacks.map((pack) => (_jsx("option", { value: pack.id, children: pack.label }, pack.id)))] }), selectedSetupPack ? (_jsx(_Fragment, { children: selectedSetupPack.description ? (_jsx("p", { className: authStyles.inlineHint, children: selectedSetupPack.description })) : null })) : null, _jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "Apply Setup Sections" }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.categories, onChange: () => setSetupTaxonomySections((current) => ({ ...current, categories: !current.categories })), disabled: setupBusy || !selectedSetupPack?.categories.length }), _jsx("span", { children: "Categories" })] }), starterCategoryItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterCategoryItems })) : null, _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.types, onChange: () => setSetupTaxonomySections((current) => ({ ...current, types: !current.types })), disabled: setupBusy || !selectedSetupPack?.types.length }), _jsx("span", { children: "Task Types" })] }), starterTypeItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterTypeItems })) : null, _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.priorities, onChange: () => setSetupTaxonomySections((current) => ({ ...current, priorities: !current.priorities })), disabled: setupBusy || !selectedSetupPack?.priorities.length }), _jsx("span", { children: "Priorities" })] }), starterPriorityItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterPriorityItems })) : null] }), setupLibraryLoading ? (_jsx("p", { className: authStyles.inlineHint, children: "Loading setup options\u2026" })) : null, setupLibraryError ? (_jsx("p", { className: authStyles.loginError, children: setupLibraryError })) : null] })), !isCloudRuntime && canUseCloudWorkspaceSetup && isAuthenticated && isWorkspaceDetailsPage && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: syncNewLocalWorkspaceToCloud, onChange: (event) => setSyncNewLocalWorkspaceToCloud(event.target.checked), disabled: setupBusy }), _jsx("span", { children: "Sync to cloud after setup" })] }), _jsx("p", { className: authStyles.inlineHint, children: "When enabled, this project will connect to cloud sync as soon as setup finishes." })] }))] }))] })), _jsxs("div", { className: authStyles.authModeLinks, children: [isWorkspaceLibraryPage ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
1146
1244
  setSetupError(null);
1147
1245
  setSetupNotice(null);
1148
1246
  setWorkspaceSetupPage('details');
@@ -1150,7 +1248,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1150
1248
  setSetupError(null);
1151
1249
  setSetupNotice(null);
1152
1250
  navigate('/', { replace: true });
1153
- }, children: "Cancel" })) : runtimeMode === 'cloud' ? (_jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: t('setup.signOut') })) : null, isGlobal ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy || (isCloudWorkspaceSetupFlow && Boolean(pendingCloudWorkspaceImport) && !isCloudWorkspaceImportErrored), onClick: async () => {
1251
+ }, children: "Cancel" })) : runtimeMode === 'cloud' ? (_jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: t('setup.signOut') })) : null, isGlobal ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy || (isCloudWorkspaceSetupFlow && Boolean(activeCloudWorkspaceImport) && !isCloudWorkspaceImportErrored), onClick: async () => {
1154
1252
  setSetupBusy(true);
1155
1253
  setSetupNotice(null);
1156
1254
  setSetupError(null);
@@ -1177,19 +1275,19 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1177
1275
  setSetupBusy(false);
1178
1276
  }
1179
1277
  }, children: setupBusy ? t('setup.saving') : t('setup.saveGlobalSetup') })) : !isError ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy
1180
- || (isCloudWorkspaceSetupFlow && !pendingCloudWorkspaceImport && !isAuthenticated), onClick: async () => {
1181
- if (isCloudWorkspaceSetupFlow && pendingCloudWorkspaceImport) {
1278
+ || (isCloudWorkspaceSetupFlow && !activeCloudWorkspaceImport && !isAuthenticated), onClick: async () => {
1279
+ if (isCloudWorkspaceSetupFlow && activeCloudWorkspaceImport) {
1182
1280
  if (!isCloudWorkspaceImportErrored)
1183
1281
  return;
1184
1282
  setSetupBusy(true);
1185
- setSetupNotice(`Retrying import for ${pendingCloudWorkspaceImport.name}...`);
1283
+ setSetupNotice(`Retrying import for ${activeCloudWorkspaceImport.name}...`);
1186
1284
  setSetupError(null);
1187
1285
  try {
1188
1286
  await retryWorkspaceCloudSync();
1189
1287
  }
1190
1288
  catch {
1191
1289
  setSetupNotice(null);
1192
- setSetupError(`Failed to retry import for "${pendingCloudWorkspaceImport.name}".`);
1290
+ setSetupError(`Failed to retry import for "${activeCloudWorkspaceImport.name}".`);
1193
1291
  }
1194
1292
  finally {
1195
1293
  setSetupBusy(false);
@@ -1229,33 +1327,28 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1229
1327
  setSetupBusy(false);
1230
1328
  return;
1231
1329
  }
1232
- const stateRes = await fetch('/api/taskforce/ui-state', {
1233
- method: 'POST',
1234
- headers: { 'Content-Type': 'application/json' },
1235
- credentials: 'include',
1236
- body: JSON.stringify({
1237
- workspaceId: workspaceIdForSync,
1238
- stateKey: 'workspace-sync',
1239
- patch: {
1240
- version: 2,
1241
- enabled: true,
1242
- phase: 'attach-cloud',
1243
- pullCursor: null,
1244
- lastPullAt: null,
1245
- lastPushAt: null,
1246
- lastSyncedAt: null
1247
- }
1248
- })
1249
- });
1250
- const statePayload = await stateRes.json().catch(() => ({}));
1251
- if (!stateRes.ok || statePayload?.success === false) {
1252
- setSetupError(statePayload?.error || `Failed to configure workspace sync (${stateRes.status})`);
1330
+ try {
1331
+ await persistWorkspaceSyncStatePatch(workspaceIdForSync, {
1332
+ version: 2,
1333
+ enabled: true,
1334
+ phase: 'attach-cloud',
1335
+ setupIntent: 'attach-cloud-import',
1336
+ pullCursor: null,
1337
+ lastPullAt: null,
1338
+ lastPushAt: null,
1339
+ lastSyncedAt: null,
1340
+ lastErrorMessage: null
1341
+ });
1342
+ }
1343
+ catch (error) {
1344
+ setSetupError(error instanceof Error ? error.message : 'Failed to configure workspace sync.');
1253
1345
  setSetupBusy(false);
1254
1346
  return;
1255
1347
  }
1256
1348
  applyWorkspaceSyncStateSnapshot({
1257
1349
  enabled: true,
1258
1350
  phase: 'attach-cloud',
1351
+ setupIntent: 'attach-cloud-import',
1259
1352
  pullCursor: null
1260
1353
  });
1261
1354
  await refreshSetupContext();
@@ -1276,10 +1369,10 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1276
1369
  }
1277
1370
  setSetupError(null);
1278
1371
  setSetupNotice(null);
1279
- setWorkspaceSetupPage('starter');
1372
+ setWorkspaceSetupPage('library');
1280
1373
  return;
1281
1374
  }
1282
- if (isWorkspaceStarterPage) {
1375
+ if (isWorkspaceLibraryPage) {
1283
1376
  // fall through to final create/save behavior below
1284
1377
  }
1285
1378
  setSetupBusy(true);
@@ -1324,7 +1417,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1324
1417
  workspaceIdOverride: workspaceIdForSetup
1325
1418
  });
1326
1419
  if (!applyResult?.success) {
1327
- setSetupError(applyResult?.error || 'Failed to apply starter taxonomy library.');
1420
+ setSetupError(applyResult?.error || 'Failed to apply setup library.');
1328
1421
  setSetupBusy(false);
1329
1422
  return;
1330
1423
  }
@@ -1342,6 +1435,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1342
1435
  version: 2,
1343
1436
  enabled: shouldEnableCloudSync,
1344
1437
  phase: shouldEnableCloudSync ? 'provision-local' : 'idle',
1438
+ setupIntent: null,
1345
1439
  pullCursor: null,
1346
1440
  lastPullAt: null,
1347
1441
  lastPushAt: null,
@@ -1358,6 +1452,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1358
1452
  applyWorkspaceSyncStateSnapshot({
1359
1453
  enabled: shouldEnableCloudSync,
1360
1454
  phase: shouldEnableCloudSync ? 'provision-local' : 'idle',
1455
+ setupIntent: null,
1361
1456
  pullCursor: null
1362
1457
  });
1363
1458
  if (shouldEnableCloudSync) {