@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
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { 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';
@@ -14,6 +14,7 @@ import logoLight from './assets/branding/logos/logo_light.svg';
14
14
  import logoDark from './assets/branding/logos/logo_dark.svg';
15
15
  import taskforceBg from './assets/images/taskforce.png';
16
16
  import { isLightThemeFamily } from './utils/theme';
17
+ import { Cloud, LaptopMinimal } from 'lucide-react';
17
18
  function isDebugModeEnabled() {
18
19
  const runtimeOverride = globalThis.__DEBUG_MODE__;
19
20
  if (typeof runtimeOverride === 'boolean')
@@ -27,6 +28,12 @@ function defaultSetupTaxonomySections(pack) {
27
28
  priorities: Boolean(pack?.priorities?.length),
28
29
  };
29
30
  }
31
+ function formatStarterSectionItems(values) {
32
+ return (Array.isArray(values) ? values : [])
33
+ .map((entry) => String(entry.label || entry.value || '').trim())
34
+ .filter(Boolean)
35
+ .join(', ');
36
+ }
30
37
  function buildPlansPath(params = {}) {
31
38
  const query = new URLSearchParams();
32
39
  query.set('screen', 'plans');
@@ -54,7 +61,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
54
61
  const [authMode, setAuthMode] = useState('login');
55
62
  const [verificationToken, setVerificationToken] = useState('');
56
63
  const [pendingVerificationWorkspaceSetupRequired, setPendingVerificationWorkspaceSetupRequired] = useState(false);
64
+ const [pendingVerificationCommercialState, setPendingVerificationCommercialState] = useState(null);
57
65
  const [pendingVerificationRedirectPath, setPendingVerificationRedirectPath] = useState(null);
66
+ const [pendingPostAuthRedirect, setPendingPostAuthRedirect] = useState(null);
58
67
  const [resetToken, setResetToken] = useState('');
59
68
  const [inviteToken, setInviteToken] = useState('');
60
69
  const [inviteEmail, setInviteEmail] = useState(null);
@@ -75,11 +84,15 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
75
84
  const [workspaceNameInput, setWorkspaceNameInput] = useState('');
76
85
  const [workspaceDescriptionInput, setWorkspaceDescriptionInput] = useState('');
77
86
  const [workspaceSetupSource, setWorkspaceSetupSource] = useState('local');
87
+ const [workspaceSetupPage, setWorkspaceSetupPage] = useState('details');
78
88
  const [cloudWorkspaceOptions, setCloudWorkspaceOptions] = useState([]);
79
89
  const [cloudWorkspaceSelection, setCloudWorkspaceSelection] = useState('');
80
90
  const [cloudWorkspaceLoading, setCloudWorkspaceLoading] = useState(false);
81
91
  const [cloudWorkspaceError, setCloudWorkspaceError] = useState(null);
82
92
  const [syncNewLocalWorkspaceToCloud, setSyncNewLocalWorkspaceToCloud] = useState(false);
93
+ const [pendingCloudWorkspaceImport, setPendingCloudWorkspaceImport] = useState(null);
94
+ const [cloudWorkspaceImportFinalizing, setCloudWorkspaceImportFinalizing] = useState(false);
95
+ const cloudWorkspaceImportFinalizingRef = useRef(false);
83
96
  const fallbackSetupLibraryPacks = useMemo(() => listBuiltInTaxonomyLibraryPacks(), []);
84
97
  const [setupLibraryPacks, setSetupLibraryPacks] = useState(fallbackSetupLibraryPacks);
85
98
  const [selectedSetupPackId, setSelectedSetupPackId] = useState(() => (fallbackSetupLibraryPacks.find((pack) => pack.isDefaultStarter)?.id || fallbackSetupLibraryPacks[0]?.id || ''));
@@ -88,6 +101,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
88
101
  const [setupLibraryError, setSetupLibraryError] = useState(null);
89
102
  const [bootstrapRetryBusy, setBootstrapRetryBusy] = useState(false);
90
103
  const [accountAccessGate, setAccountAccessGate] = useState(null);
104
+ const [accountAccessGateResolved, setAccountAccessGateResolved] = useState(false);
91
105
  // Initialize the hook
92
106
  const taskforceState = useTaskforce({
93
107
  config,
@@ -95,7 +109,24 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
95
109
  onTaskCountChange,
96
110
  onClose
97
111
  });
98
- const { runtimeMode, workspaceSwitchingEnabled, currentWorkspaceId, configLoaded, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, hasBetaAccess, authSessionResolved, workspaceBootstrapPending, bootstrapPhase, bootstrapError, setupState, runtimeCapabilities, refreshSetupContext, retryBootstrapChecks, createWorkspace, saveWorkspaceProfile, fetchWorkspaces, applyWorkspaceSyncStateSnapshot, 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
+ }, []);
99
130
  const selectedSetupPack = useMemo(() => {
100
131
  return setupLibraryPacks.find((pack) => pack.id === selectedSetupPackId)
101
132
  || setupLibraryPacks.find((pack) => pack.isDefaultStarter)
@@ -118,6 +149,61 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
118
149
  const isLoginRoute = location.pathname === '/login';
119
150
  const isSetupRoute = location.pathname === '/setup';
120
151
  const isComingSoonRoute = location.pathname === '/coming-soon';
152
+ const cloudWorkspaceSelectionPath = '/setup?step=workspace&source=cloud&postAuth=select-cloud-workspace';
153
+ const wantsCloudWorkspaceSetup = !isCloudRuntime && workspaceSetupSource === 'cloud';
154
+ const isCloudWorkspaceSetupFlow = wantsCloudWorkspaceSetup && canUseCloudWorkspaceSetup;
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
172
+ && cloudWorkspaceImportMatchesCurrentWorkspace
173
+ && workspaceSyncPhase === 'error');
174
+ const isCloudWorkspaceImportReady = Boolean(activeCloudWorkspaceImport
175
+ && cloudWorkspaceImportMatchesCurrentWorkspace
176
+ && workspaceCloudSyncEnabled
177
+ && workspaceSyncPhase === 'active'
178
+ && !workspaceSyncBusy);
179
+ const cloudWorkspaceImportProgressLines = useMemo(() => {
180
+ if (!activeCloudWorkspaceImport)
181
+ return [];
182
+ const connectingState = setupBusy ? 'In progress' : 'Complete';
183
+ const importingState = isCloudWorkspaceImportErrored
184
+ ? 'Needs attention'
185
+ : (workspaceSyncBusy || workspaceSyncPhase === 'attach-cloud')
186
+ ? 'In progress'
187
+ : (workspaceSyncPhase === 'active' ? 'Complete' : 'Waiting');
188
+ const finishingState = cloudWorkspaceImportFinalizing
189
+ ? 'In progress'
190
+ : isCloudWorkspaceImportReady
191
+ ? 'Ready'
192
+ : 'Waiting';
193
+ return [
194
+ `Connect workspace: ${connectingState}`,
195
+ `Import cloud data: ${importingState}`,
196
+ `Open local workspace: ${finishingState}`
197
+ ];
198
+ }, [
199
+ activeCloudWorkspaceImport,
200
+ setupBusy,
201
+ isCloudWorkspaceImportErrored,
202
+ workspaceSyncBusy,
203
+ workspaceSyncPhase,
204
+ cloudWorkspaceImportFinalizing,
205
+ isCloudWorkspaceImportReady
206
+ ]);
121
207
  const shouldForceLogin = isLikelyCloudAppHost
122
208
  ? ((!isAuthenticated || authBlocked) && !isPricingRoute && !isPlansRoute && !isPlansScreen)
123
209
  : (((isCloudAuthRequired && !isAuthenticated) || shouldForceForAuthBlocked) && !isPricingRoute && !isPlansRoute && !isPlansScreen);
@@ -131,6 +217,23 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
131
217
  && !isPlansRoute
132
218
  && !isPlansScreen;
133
219
  const directRegistrationEnabled = cloudAuthConfigured;
220
+ const authPageTitle = useMemo(() => {
221
+ switch (authMode) {
222
+ case 'register':
223
+ return 'Create Account | Taskforce';
224
+ case 'verify':
225
+ return 'Verify Email | Taskforce';
226
+ case 'forgot':
227
+ return 'Reset Password | Taskforce';
228
+ case 'reset':
229
+ return 'Set New Password | Taskforce';
230
+ case 'invite':
231
+ return 'Accept Invite | Taskforce';
232
+ case 'login':
233
+ default:
234
+ return 'Sign In | Taskforce';
235
+ }
236
+ }, [authMode]);
134
237
  const setupStep = useMemo(() => String(new URLSearchParams(location.search).get('step') || '').trim().toLowerCase(), [location.search]);
135
238
  const setupIntent = useMemo(() => String(new URLSearchParams(location.search).get('intent') || '').trim().toLowerCase(), [location.search]);
136
239
  const isCreateWorkspaceIntent = isSetupRoute && setupStep === 'workspace' && setupIntent === 'create-workspace';
@@ -198,9 +301,11 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
198
301
  useEffect(() => {
199
302
  if (runtimeMode !== 'cloud' || !isAuthenticated || !authSessionResolved) {
200
303
  setAccountAccessGate(null);
304
+ setAccountAccessGateResolved(false);
201
305
  return;
202
306
  }
203
307
  setAccountAccessGate(null);
308
+ setAccountAccessGateResolved(false);
204
309
  let cancelled = false;
205
310
  (async () => {
206
311
  try {
@@ -209,11 +314,17 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
209
314
  credentials: 'include'
210
315
  });
211
316
  const payload = await res.json().catch(() => ({}));
212
- if (cancelled || !res.ok)
317
+ if (cancelled)
318
+ return;
319
+ if (!res.ok) {
320
+ setAccountAccessGate(null);
321
+ setAccountAccessGateResolved(true);
213
322
  return;
323
+ }
214
324
  const gate = String(payload?.gate || '').trim().toLowerCase();
215
325
  if (gate !== 'ok' && gate !== 'plan_selection_required' && gate !== 'checkout_pending' && gate !== 'misconfigured' && gate !== 'missing_entitlement') {
216
326
  setAccountAccessGate(null);
327
+ setAccountAccessGateResolved(true);
217
328
  return;
218
329
  }
219
330
  setAccountAccessGate({
@@ -222,10 +333,13 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
222
333
  canAccessPlans: payload?.canAccessPlans !== false,
223
334
  message: typeof payload?.message === 'string' ? payload.message : null
224
335
  });
336
+ setAccountAccessGateResolved(true);
225
337
  }
226
338
  catch {
227
- if (!cancelled)
339
+ if (!cancelled) {
228
340
  setAccountAccessGate(null);
341
+ setAccountAccessGateResolved(true);
342
+ }
229
343
  }
230
344
  })();
231
345
  return () => {
@@ -258,6 +372,12 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
258
372
  useEffect(() => {
259
373
  if (!isSetupRoute || setupStep !== 'workspace')
260
374
  return;
375
+ setWorkspaceSetupPage('details');
376
+ if (activeCloudWorkspaceImport && canUseCloudWorkspaceSetup) {
377
+ setWorkspaceSetupSource('cloud');
378
+ setSyncNewLocalWorkspaceToCloud(false);
379
+ return;
380
+ }
261
381
  const source = String(new URLSearchParams(location.search).get('source') || '').trim().toLowerCase();
262
382
  if (source === 'cloud' && canUseCloudWorkspaceSetup) {
263
383
  setWorkspaceSetupSource('cloud');
@@ -265,7 +385,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
265
385
  return;
266
386
  }
267
387
  setWorkspaceSetupSource('local');
268
- }, [canUseCloudWorkspaceSetup, isSetupRoute, location.search, setupStep]);
388
+ }, [activeCloudWorkspaceImport, canUseCloudWorkspaceSetup, isSetupRoute, location.search, setupStep]);
269
389
  const fetchCloudWorkspaceOptions = useCallback(async () => {
270
390
  if (!canUseCloudWorkspaceSetup || !isAuthenticated) {
271
391
  setCloudWorkspaceOptions([]);
@@ -328,7 +448,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
328
448
  })
329
449
  .then(async (res) => {
330
450
  if (!res.ok)
331
- throw new Error(`Failed to load starter libraries (${res.status})`);
451
+ throw new Error(`Failed to load setup libraries (${res.status})`);
332
452
  const payload = await res.json().catch(() => ({}));
333
453
  const nextPacks = Array.isArray(payload?.packs) ? payload.packs : [];
334
454
  if (cancelled || nextPacks.length === 0)
@@ -342,7 +462,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
342
462
  if (cancelled)
343
463
  return;
344
464
  setSetupLibraryPacks(fallbackSetupLibraryPacks);
345
- setSetupLibraryError(error instanceof Error ? error.message : 'Failed to load starter libraries.');
465
+ setSetupLibraryError(error instanceof Error ? error.message : 'Failed to load setup libraries.');
346
466
  })
347
467
  .finally(() => {
348
468
  if (!cancelled)
@@ -355,6 +475,73 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
355
475
  useEffect(() => {
356
476
  setSetupTaxonomySections(defaultSetupTaxonomySections(selectedSetupPack));
357
477
  }, [selectedSetupPackId, selectedSetupPack]);
478
+ useEffect(() => {
479
+ if (workspaceSetupSource === 'cloud') {
480
+ setWorkspaceSetupPage('details');
481
+ }
482
+ }, [workspaceSetupSource]);
483
+ useEffect(() => {
484
+ if (!isCloudWorkspaceSetupFlow) {
485
+ setPendingCloudWorkspaceImport(null);
486
+ setCloudWorkspaceImportFinalizing(false);
487
+ cloudWorkspaceImportFinalizingRef.current = false;
488
+ }
489
+ }, [isCloudWorkspaceSetupFlow]);
490
+ useEffect(() => {
491
+ if (!isCloudWorkspaceImportErrored || !activeCloudWorkspaceImport)
492
+ return;
493
+ setSetupNotice(null);
494
+ setSetupError(workspaceLastErrorMessage
495
+ || `Failed to import "${activeCloudWorkspaceImport.name}" from cloud. Retry import to continue.`);
496
+ }, [activeCloudWorkspaceImport, isCloudWorkspaceImportErrored, workspaceLastErrorMessage]);
497
+ useEffect(() => {
498
+ if (!isCloudWorkspaceImportReady || !activeCloudWorkspaceImport || cloudWorkspaceImportFinalizingRef.current)
499
+ return;
500
+ let cancelled = false;
501
+ cloudWorkspaceImportFinalizingRef.current = true;
502
+ setCloudWorkspaceImportFinalizing(true);
503
+ setSetupError(null);
504
+ setSetupNotice(`Workspace import complete. Opening ${activeCloudWorkspaceImport.name}...`);
505
+ void (async () => {
506
+ try {
507
+ await refreshSetupContext();
508
+ await Promise.all([
509
+ fetchWorkspaces(),
510
+ fetchTasks(true)
511
+ ]);
512
+ if (cancelled)
513
+ return;
514
+ await persistWorkspaceSyncStatePatch(activeCloudWorkspaceImport.workspaceId, { setupIntent: null });
515
+ applyWorkspaceSyncStateSnapshot({
516
+ enabled: workspaceCloudSyncEnabled,
517
+ phase: workspaceSyncPhase,
518
+ setupIntent: null
519
+ });
520
+ setPendingCloudWorkspaceImport(null);
521
+ navigate('/', { replace: true });
522
+ }
523
+ catch {
524
+ if (cancelled)
525
+ return;
526
+ setSetupNotice(null);
527
+ setSetupError(`Imported "${activeCloudWorkspaceImport.name}" but failed to finish opening it.`);
528
+ setCloudWorkspaceImportFinalizing(false);
529
+ cloudWorkspaceImportFinalizingRef.current = false;
530
+ }
531
+ })();
532
+ return () => {
533
+ cancelled = true;
534
+ };
535
+ }, [
536
+ activeCloudWorkspaceImport,
537
+ applyWorkspaceSyncStateSnapshot,
538
+ fetchTasks,
539
+ fetchWorkspaces,
540
+ isCloudWorkspaceImportReady,
541
+ navigate,
542
+ persistWorkspaceSyncStatePatch,
543
+ refreshSetupContext
544
+ ]);
358
545
  const nextPath = useMemo(() => {
359
546
  const raw = new URLSearchParams(location.search).get('next') || '/';
360
547
  if (!raw.startsWith('/'))
@@ -363,7 +550,28 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
363
550
  return '/';
364
551
  return raw;
365
552
  }, [location.search]);
553
+ const shouldReturnToCloudWorkspaceSelection = useMemo(() => {
554
+ try {
555
+ const parsed = new URL(nextPath, 'https://taskforce.local');
556
+ return parsed.pathname === '/setup'
557
+ && parsed.searchParams.get('step') === 'workspace'
558
+ && parsed.searchParams.get('source') === 'cloud'
559
+ && parsed.searchParams.get('postAuth') === 'select-cloud-workspace';
560
+ }
561
+ catch {
562
+ return false;
563
+ }
564
+ }, [nextPath]);
565
+ const resolveWorkspaceSetupPath = useCallback(() => (shouldReturnToCloudWorkspaceSelection
566
+ ? cloudWorkspaceSelectionPath
567
+ : '/setup?step=workspace'), [shouldReturnToCloudWorkspaceSelection]);
366
568
  const resolvedSignupInterval = selectedSignupInterval === 'year' ? 'year' : 'month';
569
+ const hasCommercialOnboardingGate = useCallback((input) => {
570
+ return Boolean(input?.planSelectionRequired
571
+ || input?.checkoutPending
572
+ || input?.commercialState === 'pending_plan_selection'
573
+ || input?.commercialState === 'checkout_pending');
574
+ }, []);
367
575
  const resolvePostRegistrationPath = useCallback((input) => {
368
576
  if (input?.planSelectionRequired || input?.commercialState === 'pending_plan_selection') {
369
577
  return planSelectionPath;
@@ -408,26 +616,6 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
408
616
  const query = params.toString();
409
617
  navigate(`/login${query ? `?${query}` : ''}`, { replace: true });
410
618
  }, [directRegistrationEnabled, location.search, navigate]);
411
- const routeToWorkspaceSetupIfNeeded = useCallback(async (hintWorkspaceSetupRequired) => {
412
- if (!workspaceSwitchingEnabled)
413
- return false;
414
- const workspacesResult = await fetchWorkspaces();
415
- if (workspacesResult.success) {
416
- const workspaceCount = Array.isArray(workspacesResult.workspaces)
417
- ? workspacesResult.workspaces.length
418
- : 0;
419
- if (workspaceCount === 0) {
420
- navigate('/setup?step=workspace', { replace: true });
421
- return true;
422
- }
423
- return false;
424
- }
425
- if (hintWorkspaceSetupRequired) {
426
- navigate('/setup?step=workspace', { replace: true });
427
- return true;
428
- }
429
- return false;
430
- }, [fetchWorkspaces, navigate, workspaceSwitchingEnabled]);
431
619
  useEffect(() => {
432
620
  if (!isLoginRoute)
433
621
  return;
@@ -445,8 +633,40 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
445
633
  if (isLoginRoute && loginRouteMode === 'verify')
446
634
  return;
447
635
  setPendingVerificationWorkspaceSetupRequired(false);
636
+ setPendingVerificationCommercialState(null);
448
637
  setPendingVerificationRedirectPath(null);
449
638
  }, [authMode, isLoginRoute, loginRouteMode]);
639
+ useEffect(() => {
640
+ if (!isLoginRoute || !pendingPostAuthRedirect || !authSessionResolved || !isAuthenticated)
641
+ return;
642
+ if (runtimeMode === 'cloud' && !pendingPostAuthRedirect.commercialOnboardingGate && !accountAccessGateResolved) {
643
+ return;
644
+ }
645
+ if (!pendingPostAuthRedirect.commercialOnboardingGate && pendingPostAuthRedirect.workspaceSetupRequired) {
646
+ navigate(resolveWorkspaceSetupPath(), { replace: true });
647
+ }
648
+ else {
649
+ navigate(pendingPostAuthRedirect.path, { replace: true });
650
+ }
651
+ setLoginEmail('');
652
+ setLoginDisplayName('');
653
+ setLoginPassword('');
654
+ setPendingPostAuthRedirect(null);
655
+ }, [
656
+ accountAccessGateResolved,
657
+ authSessionResolved,
658
+ isAuthenticated,
659
+ isLoginRoute,
660
+ navigate,
661
+ pendingPostAuthRedirect,
662
+ resolveWorkspaceSetupPath,
663
+ runtimeMode
664
+ ]);
665
+ useEffect(() => {
666
+ if (!isLoginRoute)
667
+ return;
668
+ document.title = authPageTitle;
669
+ }, [authPageTitle, isLoginRoute]);
450
670
  useEffect(() => {
451
671
  if (!isLoginRoute || authMode !== 'invite' || !inviteToken.trim())
452
672
  return;
@@ -515,6 +735,24 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
515
735
  const code = result.code || result.state;
516
736
  return code ? `[${code}] ${message}` : message;
517
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
+ };
518
756
  useEffect(() => {
519
757
  if (mode === 'widget')
520
758
  return;
@@ -533,6 +771,17 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
533
771
  const shouldStayOnInviteRoute = isLoginRoute
534
772
  && authMode === 'invite'
535
773
  && inviteToken.trim().length > 0;
774
+ const shouldDelayAuthenticatedLoginRedirect = isLoginRoute
775
+ && isAuthenticated
776
+ && !shouldStayOnInviteRoute
777
+ && (Boolean(pendingPostAuthRedirect)
778
+ || workspaceBootstrapPending
779
+ || authMode === 'register'
780
+ || authMode === 'verify'
781
+ || (runtimeMode === 'cloud' && (!configLoaded || !accountAccessGateResolved)));
782
+ if (shouldDelayAuthenticatedLoginRedirect) {
783
+ return;
784
+ }
536
785
  if (isLoginRoute && isAuthenticated && !shouldStayOnInviteRoute)
537
786
  navigate(nextPath, { replace: true });
538
787
  }
@@ -544,6 +793,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
544
793
  }
545
794
  return;
546
795
  }
796
+ if (isLoginRoute && !isAuthenticated) {
797
+ return;
798
+ }
547
799
  if (shouldForceAccountAccessGate) {
548
800
  if (!isPlansScreen) {
549
801
  const gate = encodeURIComponent(String(accountAccessGate?.gate || 'plan_selection_required'));
@@ -551,6 +803,15 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
551
803
  }
552
804
  return;
553
805
  }
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
+ }
813
+ return;
814
+ }
554
815
  if (setupGateActive) {
555
816
  if (hasSetupReadError) {
556
817
  if (!isSetupRoute || setupStep !== 'error') {
@@ -566,7 +827,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
566
827
  }
567
828
  if (needsWorkspaceSetup) {
568
829
  if (!isSetupRoute || setupStep !== 'workspace') {
569
- navigate('/setup?step=workspace', { replace: true });
830
+ navigate(resolveWorkspaceSetupPath(), { replace: true });
570
831
  }
571
832
  return;
572
833
  }
@@ -589,19 +850,27 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
589
850
  isPlansRoute,
590
851
  isPlansScreen,
591
852
  accountAccessGate?.gate,
853
+ accountAccessGateResolved,
854
+ authMode,
592
855
  setupGateActive,
593
856
  setupState,
594
857
  hasSetupReadError,
595
858
  isCreateWorkspaceIntent,
859
+ activeCloudWorkspaceImport,
596
860
  needsGlobalSetup,
597
861
  needsWorkspaceSetup,
598
862
  setupStep,
599
863
  workspaceBootstrapPending,
864
+ configLoaded,
865
+ runtimeMode,
866
+ inviteToken,
600
867
  location.pathname,
601
868
  location.search,
602
869
  location.hash,
603
870
  navigate,
604
- nextPath
871
+ nextPath,
872
+ pendingPostAuthRedirect,
873
+ cloudWorkspaceSelectionPath
605
874
  ]);
606
875
  useEffect(() => {
607
876
  if (mode === 'widget')
@@ -653,18 +922,26 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
653
922
  if (isLoginRoute) {
654
923
  return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
655
924
  backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
656
- }, children: _jsxs("div", { className: authStyles.loginCard, children: [!isCloudRuntime && !shouldForceLogin && (_jsx("button", { className: authStyles.loginCloseBtn, "aria-label": "Close sign in", onClick: () => navigate(nextPath, { replace: true }), children: "\u00D7" })), _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" })] })] }), _jsxs("p", { className: authStyles.loginSubtitle, children: [authMode === 'login' && 'Sign in with your account credentials.', authMode === 'register' && 'Create your Taskforce account.', authMode === 'verify' && 'Enter your verification token.', authMode === 'forgot' && 'Request a password reset link.', authMode === 'reset' && 'Set a new password using your reset token.', authMode === 'invite' && (inviteResolution === 'existing_user_ready'
925
+ }, children: _jsxs("div", { className: authStyles.loginCard, children: [!isCloudRuntime && !shouldForceLogin && (_jsx("button", { className: authStyles.loginCloseBtn, "aria-label": "Close sign in", "data-testid": "auth-close-button", onClick: () => navigate(nextPath, { replace: true }), children: "\u00D7" })), _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" })] })] }), _jsxs("p", { className: authStyles.loginSubtitle, children: [authMode === 'login' && 'Sign in with your account credentials.', authMode === 'register' && 'Create your Taskforce account.', authMode === 'verify' && 'Enter your verification token.', authMode === 'forgot' && 'Request a password reset link.', authMode === 'reset' && 'Set a new password using your reset token.', authMode === 'invite' && (inviteResolution === 'existing_user_ready'
657
926
  ? `You've been invited to join ${inviteWorkspaceId || 'this workspace'}.`
658
927
  : inviteResolution === 'existing_user_signed_out'
659
928
  ? 'Sign in to join this workspace.'
660
- : 'Create your account to join this workspace.')] }), (authMode === 'login' || authMode === 'register' || authMode === 'forgot' || (authMode === 'invite' && inviteResolution === 'existing_user_signed_out')) && (_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "email", value: loginEmail, placeholder: "Email", onChange: (event) => setLoginEmail(event.target.value) })), authMode === 'register' && (_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: loginDisplayName, placeholder: "Display name", onChange: (event) => setLoginDisplayName(event.target.value) })), (authMode === 'login'
929
+ : 'Create your account to join this workspace.')] }), (authMode === 'login'
930
+ || authMode === 'register'
931
+ || authMode === 'verify'
932
+ || authMode === 'forgot'
933
+ || (authMode === 'invite' && inviteResolution === 'existing_user_signed_out')) && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: "Email" }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "email", value: loginEmail, placeholder: "Email", autoComplete: "email", "data-testid": "auth-email-input", onChange: (event) => setLoginEmail(event.target.value) })] })), authMode === 'register' && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: "Display name" }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: loginDisplayName, placeholder: "Display name", autoComplete: "nickname", "data-testid": "auth-display-name-input", onChange: (event) => setLoginDisplayName(event.target.value) })] })), (authMode === 'login'
661
934
  || authMode === 'register'
662
935
  || authMode === 'reset'
663
- || (authMode === 'invite' && (invitePasswordRequired || inviteResolution === 'existing_user_signed_out'))) && (_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "password", value: loginPassword, placeholder: authMode === 'reset'
664
- ? 'New password'
665
- : (authMode === 'invite'
666
- ? (invitePasswordRequired ? 'Create password' : 'Password')
667
- : 'Password'), onChange: (event) => setLoginPassword(event.target.value) })), authMode === 'verify' && (_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: verificationToken, placeholder: "Verification token", onChange: (event) => setVerificationToken(event.target.value) })), authMode === 'reset' && (_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: resetToken, placeholder: "Reset token", onChange: (event) => setResetToken(event.target.value) })), authMode === 'invite' && (_jsxs(_Fragment, { children: [_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: inviteToken, placeholder: "Invite token", onChange: (event) => setInviteToken(event.target.value) }), inviteEmail && (_jsxs("p", { className: authStyles.loginSubtitle, children: ["Invite for: ", _jsx("strong", { children: inviteEmail }), inviteWorkspaceId ? ` · Workspace: ${inviteWorkspaceId}` : ''] }))] })), loginNotice && _jsx("p", { className: authStyles.loginSubtitle, children: loginNotice }), loginError && _jsx("p", { className: authStyles.loginError, children: loginError }), authMode === 'login' && loginErrorCode === 'EMAIL_NOT_VERIFIED' && loginEmail.trim() && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || verifyCooldownActive, onClick: async () => {
936
+ || (authMode === 'invite' && (invitePasswordRequired || inviteResolution === 'existing_user_signed_out'))) && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: authMode === 'reset'
937
+ ? 'New password'
938
+ : (authMode === 'invite' && invitePasswordRequired ? 'Create password' : 'Password') }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "password", value: loginPassword, placeholder: authMode === 'reset'
939
+ ? 'New password'
940
+ : (authMode === 'invite'
941
+ ? (invitePasswordRequired ? 'Create password' : 'Password')
942
+ : 'Password'), autoComplete: authMode === 'register' || authMode === 'reset' || (authMode === 'invite' && invitePasswordRequired)
943
+ ? 'new-password'
944
+ : 'current-password', "data-testid": "auth-password-input", onChange: (event) => setLoginPassword(event.target.value) })] })), authMode === 'verify' && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: "Verification token" }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: verificationToken, placeholder: "Verification token", autoComplete: "one-time-code", "data-testid": "auth-verification-token-input", onChange: (event) => setVerificationToken(event.target.value) })] })), authMode === 'reset' && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: "Reset token" }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: resetToken, placeholder: "Reset token", autoComplete: "one-time-code", "data-testid": "auth-reset-token-input", onChange: (event) => setResetToken(event.target.value) })] })), authMode === 'invite' && (_jsxs(_Fragment, { children: [_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: "Invite token" }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: inviteToken, placeholder: "Invite token", "data-testid": "auth-invite-token-input", onChange: (event) => setInviteToken(event.target.value) })] }), inviteEmail && (_jsxs("p", { className: authStyles.loginSubtitle, children: ["Invite for: ", _jsx("strong", { children: inviteEmail }), inviteWorkspaceId ? ` · Workspace: ${inviteWorkspaceId}` : ''] }))] })), loginNotice && _jsx("p", { className: authStyles.loginSubtitle, children: loginNotice }), loginError && _jsx("p", { className: authStyles.loginError, children: loginError }), authMode === 'login' && loginErrorCode === 'EMAIL_NOT_VERIFIED' && loginEmail.trim() && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || verifyCooldownActive, onClick: async () => {
668
945
  setLoginBusy(true);
669
946
  setLoginNotice(null);
670
947
  const sent = await requestEmailVerification(loginEmail);
@@ -673,7 +950,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
673
950
  setLoginMode('verify');
674
951
  if (sent.verificationToken)
675
952
  setVerificationToken(sent.verificationToken);
676
- setLoginNotice('Verification email resent.');
953
+ setLoginNotice(buildVerificationRequestNotice(sent));
677
954
  setLoginError(null);
678
955
  setLoginErrorCode(null);
679
956
  }
@@ -681,180 +958,232 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
681
958
  setLoginError(formatAuthError(sent));
682
959
  }
683
960
  setLoginBusy(false);
684
- }, children: verifyCooldownActive ? `Resend in ${verifyCooldownSeconds}s` : 'Resend Verification Email' })), _jsx("button", { className: `${styles.submitBtn} ${authStyles.loginPrimaryBtn}`, disabled: loginBusy || (authMode === 'verify' && verifyCooldownActive) || (authMode === 'forgot' && resetCooldownActive), onClick: async () => {
685
- setLoginBusy(true);
686
- setLoginNotice(null);
687
- setLoginError(null);
688
- setLoginErrorCode(null);
689
- if (authMode === 'register' && !loginDisplayName.trim()) {
690
- setLoginError('Display name is required.');
691
- setLoginBusy(false);
692
- return;
693
- }
694
- let result = { success: false };
695
- if (authMode === 'login') {
696
- result = await loginWithCredentials(loginEmail, loginPassword);
697
- }
698
- else if (authMode === 'register') {
699
- result = await registerWithCredentials(loginEmail, loginPassword, {
700
- displayName: loginDisplayName,
701
- planId: selectedSignupPlanId || undefined,
702
- planVersionId: selectedSignupPlanVersionId || undefined,
703
- interval: selectedSignupInterval || undefined
704
- });
705
- }
706
- else if (authMode === 'verify') {
707
- result = await confirmEmailVerification(verificationToken);
708
- }
709
- else if (authMode === 'forgot') {
710
- result = await requestPasswordReset(loginEmail);
711
- }
712
- else if (authMode === 'reset') {
713
- result = await confirmPasswordReset(resetToken, loginPassword);
714
- }
715
- else if (authMode === 'invite') {
716
- if (inviteResolution === 'existing_user_signed_out') {
961
+ }, children: verifyCooldownActive ? `Resend in ${verifyCooldownSeconds}s` : 'Resend Verification Email' })), _jsx("div", { className: authStyles.authPrimaryActions, role: "group", "aria-label": "Primary authentication action", children: _jsx("button", { className: `${styles.submitBtn} ${authStyles.loginPrimaryBtn}`, disabled: loginBusy || (authMode === 'forgot' && resetCooldownActive), "data-testid": "auth-submit-button", onClick: async () => {
962
+ setLoginBusy(true);
963
+ setLoginNotice(null);
964
+ setLoginError(null);
965
+ setLoginErrorCode(null);
966
+ if (authMode === 'register' && !loginDisplayName.trim()) {
967
+ setLoginError('Display name is required.');
968
+ setLoginBusy(false);
969
+ return;
970
+ }
971
+ let result = { success: false };
972
+ if (authMode === 'login') {
717
973
  result = await loginWithCredentials(loginEmail, loginPassword);
718
974
  }
719
- else {
720
- result = invitePasswordRequired
721
- ? await acceptInviteWithToken(inviteToken, loginPassword)
722
- : await joinInviteWithToken(inviteToken);
975
+ else if (authMode === 'register') {
976
+ result = await registerWithCredentials(loginEmail, loginPassword, {
977
+ displayName: loginDisplayName,
978
+ planId: selectedSignupPlanId || undefined,
979
+ planVersionId: selectedSignupPlanVersionId || undefined,
980
+ interval: selectedSignupInterval || undefined
981
+ });
723
982
  }
724
- }
725
- if (!result.success) {
726
- if (authMode === 'register'
727
- && (result.code === 'SIGNUP_PLAN_NOT_ENABLED'
728
- || result.code === 'SIGNUP_PLAN_VERSION_NOT_FOUND'
729
- || result.code === 'SIGNUP_PLAN_VERSION_REQUIRED')) {
730
- navigate(planSelectionPath, { replace: true });
731
- setLoginBusy(false);
732
- return;
983
+ else if (authMode === 'verify') {
984
+ result = await confirmEmailVerification(verificationToken);
733
985
  }
734
- setLoginError(formatAuthError(result));
735
- setLoginErrorCode(result.code || null);
736
- if (authMode === 'login' && (result.code === 'WORKSPACE_NOT_FOUND' || result.code === 'WORKSPACE_ID_REQUIRED')) {
737
- navigate('/setup?step=workspace', { replace: true });
738
- setLoginBusy(false);
739
- return;
986
+ else if (authMode === 'forgot') {
987
+ result = await requestPasswordReset(loginEmail);
988
+ }
989
+ else if (authMode === 'reset') {
990
+ result = await confirmPasswordReset(resetToken, loginPassword);
991
+ }
992
+ else if (authMode === 'invite') {
993
+ if (inviteResolution === 'existing_user_signed_out') {
994
+ result = await loginWithCredentials(loginEmail, loginPassword);
995
+ }
996
+ else {
997
+ result = invitePasswordRequired
998
+ ? await acceptInviteWithToken(inviteToken, loginPassword)
999
+ : await joinInviteWithToken(inviteToken);
1000
+ }
740
1001
  }
741
- if (authMode === 'login' && result.code === 'EMAIL_NOT_VERIFIED' && loginEmail.trim()) {
742
- const sent = await requestEmailVerification(loginEmail);
743
- if (sent.success) {
1002
+ if (!result.success) {
1003
+ if (authMode === 'register'
1004
+ && (result.code === 'SIGNUP_PLAN_NOT_ENABLED'
1005
+ || result.code === 'SIGNUP_PLAN_VERSION_NOT_FOUND'
1006
+ || result.code === 'SIGNUP_PLAN_VERSION_REQUIRED')) {
1007
+ navigate(planSelectionPath, { replace: true });
1008
+ setLoginBusy(false);
1009
+ return;
1010
+ }
1011
+ setLoginError(formatAuthError(result));
1012
+ setLoginErrorCode(result.code || null);
1013
+ if (authMode === 'login' && (result.code === 'WORKSPACE_NOT_FOUND' || result.code === 'WORKSPACE_ID_REQUIRED')) {
1014
+ navigate('/setup?step=workspace', { replace: true });
1015
+ setLoginBusy(false);
1016
+ return;
1017
+ }
1018
+ if (authMode === 'login' && result.code === 'EMAIL_NOT_VERIFIED' && loginEmail.trim()) {
1019
+ const sent = await requestEmailVerification(loginEmail);
1020
+ if (sent.success) {
1021
+ setVerifyCooldownUntil(Date.now() + 30_000);
1022
+ setLoginMode('verify');
1023
+ if (sent.verificationToken)
1024
+ setVerificationToken(sent.verificationToken);
1025
+ setLoginNotice(buildVerificationRequestNotice({
1026
+ ...sent,
1027
+ intro: 'If that account exists and still needs verification, we sent an email.'
1028
+ }));
1029
+ setLoginError(null);
1030
+ setLoginErrorCode(null);
1031
+ }
1032
+ }
1033
+ if (authMode === 'register' && result.code === 'EMAIL_ALREADY_EXISTS_UNVERIFIED' && loginEmail.trim()) {
744
1034
  setVerifyCooldownUntil(Date.now() + 30_000);
745
1035
  setLoginMode('verify');
746
- if (sent.verificationToken)
747
- setVerificationToken(sent.verificationToken);
748
- setLoginNotice('Email not verified. A verification token was sent.');
1036
+ if (result.verificationToken)
1037
+ setVerificationToken(result.verificationToken);
1038
+ setLoginNotice(result.emailSent === false
1039
+ ? `This email already has an unverified account, but verification email delivery failed. ${result.emailError || 'Try Resend Verification again.'}`
1040
+ : 'This email already has an unverified account. Check your email for verification instructions.');
749
1041
  setLoginError(null);
750
1042
  setLoginErrorCode(null);
751
1043
  }
752
1044
  }
753
- }
754
- else if (authMode === 'register') {
755
- const postRegistrationPath = resolvePostRegistrationPath(result);
756
- if (result.verificationRequired) {
757
- setPendingVerificationWorkspaceSetupRequired(Boolean(result.workspaceSetupRequired));
758
- setPendingVerificationRedirectPath(postRegistrationPath);
759
- if (result.verificationToken)
760
- setVerificationToken(result.verificationToken);
761
- setVerifyCooldownUntil(Date.now() + 30_000);
762
- setLoginMode('verify');
763
- setLoginNotice(result.emailSent === false
764
- ? `Account created, but verification email failed to send. ${result.emailError || 'Try Resend Verification again.'}`
765
- : 'Account created. Check your email for verification instructions.');
1045
+ else if (authMode === 'register') {
1046
+ const postRegistrationPath = resolvePostRegistrationPath(result);
1047
+ const commercialOnboardingGate = hasCommercialOnboardingGate(result);
1048
+ if (result.verificationRequired) {
1049
+ setPendingVerificationWorkspaceSetupRequired(Boolean(result.workspaceSetupRequired));
1050
+ setPendingVerificationCommercialState(commercialOnboardingGate
1051
+ ? String(result.commercialState || '').trim() || (result.planSelectionRequired ? 'pending_plan_selection' : 'checkout_pending')
1052
+ : null);
1053
+ setPendingVerificationRedirectPath(postRegistrationPath);
1054
+ if (result.verificationToken)
1055
+ setVerificationToken(result.verificationToken);
1056
+ setVerifyCooldownUntil(Date.now() + 30_000);
1057
+ setLoginMode('verify');
1058
+ setLoginNotice(result.emailSent === false
1059
+ ? `Account created, but verification email failed to send. ${result.emailError || 'Try Resend Verification again.'}`
1060
+ : 'Account created. Check your email for verification instructions.');
1061
+ }
1062
+ else {
1063
+ setPendingPostAuthRedirect({
1064
+ path: postRegistrationPath,
1065
+ workspaceSetupRequired: Boolean(result.workspaceSetupRequired),
1066
+ commercialOnboardingGate
1067
+ });
1068
+ }
766
1069
  }
767
- else {
1070
+ else if (authMode === 'verify') {
768
1071
  await retryBootstrapChecks();
769
- if (await routeToWorkspaceSetupIfNeeded(result.workspaceSetupRequired)) {
1072
+ const commercialOnboardingGate = pendingVerificationCommercialState === 'pending_plan_selection'
1073
+ || pendingVerificationCommercialState === 'checkout_pending';
1074
+ setPendingPostAuthRedirect({
1075
+ path: pendingVerificationRedirectPath || nextPath,
1076
+ workspaceSetupRequired: pendingVerificationWorkspaceSetupRequired,
1077
+ commercialOnboardingGate
1078
+ });
1079
+ }
1080
+ else if (authMode === 'forgot') {
1081
+ const passwordResetDeliveryFailed = result.deliveryAttempted === true && result.emailSent === false;
1082
+ if (result.resetToken)
1083
+ setResetToken(result.resetToken);
1084
+ setLoginNotice(buildPasswordResetNotice(result));
1085
+ if (!passwordResetDeliveryFailed) {
1086
+ setResetCooldownUntil(Date.now() + 30_000);
1087
+ setLoginMode('reset');
1088
+ }
1089
+ }
1090
+ else if (authMode === 'reset') {
1091
+ setLoginMode('login');
1092
+ setLoginNotice('Password updated. Sign in with your new password.');
1093
+ }
1094
+ else if (authMode === 'invite') {
1095
+ if (inviteResolution === 'existing_user_signed_out') {
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');
1115
+ setLoginPassword('');
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');
1121
+ setLoginBusy(false);
1122
+ return;
1123
+ }
1124
+ if (result.workspaceSetupRequired) {
1125
+ navigate(resolveWorkspaceSetupPath(), { replace: true });
1126
+ setLoginBusy(false);
1127
+ return;
1128
+ }
1129
+ navigate(nextPath, { replace: true });
1130
+ }
1131
+ else if (authMode === 'login') {
1132
+ if (inviteToken.trim()) {
1133
+ setAuthMode('invite');
1134
+ setLoginPassword('');
1135
+ setLoginNotice('Signed in. Review the invite details to continue.');
1136
+ setLoginBusy(false);
1137
+ return;
1138
+ }
1139
+ if (result.workspaceSetupRequired) {
1140
+ navigate(resolveWorkspaceSetupPath(), { replace: true });
770
1141
  setLoginBusy(false);
771
1142
  return;
772
1143
  }
773
1144
  setLoginEmail('');
774
1145
  setLoginDisplayName('');
775
1146
  setLoginPassword('');
776
- navigate(postRegistrationPath, { replace: true });
777
- }
778
- }
779
- else if (authMode === 'verify') {
780
- await retryBootstrapChecks();
781
- if (await routeToWorkspaceSetupIfNeeded(pendingVerificationWorkspaceSetupRequired)) {
782
- setLoginBusy(false);
783
- return;
784
- }
785
- setLoginEmail('');
786
- setLoginPassword('');
787
- navigate(pendingVerificationRedirectPath || nextPath, { replace: true });
788
- }
789
- else if (authMode === 'forgot') {
790
- if (result.resetToken)
791
- setResetToken(result.resetToken);
792
- setResetCooldownUntil(Date.now() + 30_000);
793
- setLoginMode('reset');
794
- setLoginNotice(result.emailSent === false
795
- ? `Password reset email failed to send. ${result.emailError || 'Try again in a minute.'}`
796
- : 'Password reset instructions sent.');
797
- }
798
- else if (authMode === 'reset') {
799
- setLoginMode('login');
800
- setLoginNotice('Password updated. Sign in with your new password.');
801
- }
802
- else if (authMode === 'invite') {
803
- if (inviteResolution === 'existing_user_signed_out') {
804
- setAuthMode('invite');
805
- setLoginPassword('');
806
- setLoginNotice('Signed in. Review the invite details to continue.');
807
- setLoginBusy(false);
808
- return;
1147
+ navigate(nextPath, { replace: true });
809
1148
  }
810
- if (result.workspaceSetupRequired) {
811
- navigate('/setup?step=workspace', { replace: true });
812
- setLoginBusy(false);
813
- return;
814
- }
815
- navigate(nextPath, { replace: true });
816
- }
817
- else if (authMode === 'login') {
818
- if (inviteToken.trim()) {
819
- setAuthMode('invite');
1149
+ else {
1150
+ setLoginEmail('');
1151
+ setLoginDisplayName('');
820
1152
  setLoginPassword('');
821
- setLoginNotice('Signed in. Review the invite details to continue.');
822
- setLoginBusy(false);
823
- return;
1153
+ navigate(nextPath, { replace: true });
824
1154
  }
825
- if (result.workspaceSetupRequired) {
826
- navigate('/setup?step=workspace', { replace: true });
827
- setLoginBusy(false);
828
- return;
829
- }
830
- setLoginEmail('');
831
- setLoginDisplayName('');
832
- setLoginPassword('');
833
- navigate(nextPath, { replace: true });
834
- }
835
- else {
836
- setLoginEmail('');
837
- setLoginDisplayName('');
838
- setLoginPassword('');
839
- navigate(nextPath, { replace: true });
840
- }
841
- setLoginBusy(false);
842
- }, children: loginBusy ? 'Working...' : (authMode === 'login' ? 'Sign In'
843
- : authMode === 'register' ? 'Create Account'
844
- : authMode === 'verify'
845
- ? (verifyCooldownActive ? `Resend in ${verifyCooldownSeconds}s` : 'Resend Verification')
846
- : authMode === 'forgot'
847
- ? (resetCooldownActive ? `Retry in ${resetCooldownSeconds}s` : 'Send Reset Link')
848
- : authMode === 'reset'
849
- ? 'Reset Password'
850
- : (inviteResolution === 'existing_user_signed_out'
851
- ? 'Sign In to Continue'
852
- : (invitePasswordRequired ? 'Create Account and Join' : 'Join Workspace'))) }), authMode === 'register' && (_jsxs("p", { className: authStyles.registerConsent, children: ["By creating an account, you agree to the", ' ', _jsx("a", { className: authStyles.registerConsentLink, href: "https://taskforcehq.com/legal/terms", target: "_blank", rel: "noopener noreferrer", children: "Terms of Service" }), ' ', "and acknowledge the", ' ', _jsx("a", { className: authStyles.registerConsentLink, href: "https://taskforcehq.com/legal/privacy", target: "_blank", rel: "noopener noreferrer", children: "Privacy Policy" }), "."] })), directRegistrationEnabled && (authMode === 'login' || authMode === 'register') && (_jsxs("p", { className: authStyles.authModeSwitch, children: [authMode === 'login' ? 'Need an account?' : 'Already have an account?', ' ', _jsx("button", { className: authStyles.authModeSwitchLink, disabled: loginBusy, onClick: () => {
853
- setLoginError(null);
854
- setLoginErrorCode(null);
855
- setLoginNotice(null);
856
- setLoginMode(authMode === 'login' ? 'register' : 'login');
857
- }, children: authMode === 'login' ? 'Register' : 'Sign In' })] })), _jsxs("div", { className: authStyles.authModeLinks, children: [(authMode === 'login' || authMode === 'register') && (_jsxs(_Fragment, { children: [_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || resetCooldownActive, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('forgot'); }, children: resetCooldownActive ? `Forgot Password (${resetCooldownSeconds}s)` : 'Forgot Password' }), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || verifyCooldownActive, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('verify'); }, children: verifyCooldownActive ? `Resend Verification (${verifyCooldownSeconds}s)` : 'Resend Verification' }), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('invite'); }, children: "Accept Invite" })] })), (authMode !== 'login' && authMode !== 'register') && (_jsxs(_Fragment, { children: [_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('login'); }, children: "Sign In" }), directRegistrationEnabled && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('register'); }, children: "Register" })), authMode === 'reset' && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || resetCooldownActive, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('forgot'); }, children: resetCooldownActive ? `Forgot Password (${resetCooldownSeconds}s)` : 'Forgot Password' }))] }))] })] }) }) }));
1155
+ setLoginBusy(false);
1156
+ }, children: loginBusy ? 'Working...' : (authMode === 'login' ? 'Sign In'
1157
+ : authMode === 'register' ? 'Create Account'
1158
+ : authMode === 'verify' ? 'Verify Email'
1159
+ : authMode === 'forgot'
1160
+ ? (resetCooldownActive ? `Retry in ${resetCooldownSeconds}s` : 'Send Reset Link')
1161
+ : authMode === 'reset'
1162
+ ? 'Reset Password'
1163
+ : (inviteResolution === 'existing_user_signed_out'
1164
+ ? 'Sign In to Continue'
1165
+ : (invitePasswordRequired ? 'Create Account and Join' : 'Join Workspace'))) }) }), authMode === 'register' && (_jsxs("p", { className: authStyles.registerConsent, children: ["By creating an account, you agree to the", ' ', _jsx("a", { className: authStyles.registerConsentLink, href: "https://taskforcehq.com/legal/terms", target: "_blank", rel: "noopener noreferrer", children: "Terms of Service" }), ' ', "and acknowledge the", ' ', _jsx("a", { className: authStyles.registerConsentLink, href: "https://taskforcehq.com/legal/privacy", target: "_blank", rel: "noopener noreferrer", children: "Privacy Policy" }), "."] })), _jsxs("div", { className: authStyles.authSecondaryActions, role: "group", "aria-label": "Authentication navigation", children: [directRegistrationEnabled && (authMode === 'login' || authMode === 'register') && (_jsxs("p", { className: authStyles.authModeSwitch, children: [authMode === 'login' ? 'Need an account?' : 'Already have an account?', ' ', _jsx("button", { className: authStyles.authModeSwitchLink, disabled: loginBusy, "data-testid": "auth-switch-mode-button", onClick: () => {
1166
+ setLoginError(null);
1167
+ setLoginErrorCode(null);
1168
+ setLoginNotice(null);
1169
+ setLoginMode(authMode === 'login' ? 'register' : 'login');
1170
+ }, children: authMode === 'login' ? 'Register' : 'Sign In' })] })), _jsxs("div", { className: authStyles.authModeLinks, children: [(authMode === 'login' || authMode === 'register') && (_jsxs(_Fragment, { children: [_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || resetCooldownActive, "data-testid": "auth-forgot-password-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('forgot'); }, children: resetCooldownActive ? `Forgot Password (${resetCooldownSeconds}s)` : 'Forgot Password' }), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || verifyCooldownActive, "data-testid": "auth-resend-verification-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('verify'); }, children: verifyCooldownActive ? `Resend Verification (${verifyCooldownSeconds}s)` : 'Resend Verification' }), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, "data-testid": "auth-accept-invite-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('invite'); }, children: "Accept Invite" })] })), (authMode !== 'login' && authMode !== 'register') && (_jsxs(_Fragment, { children: [authMode === 'verify' && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || verifyCooldownActive || !loginEmail.trim(), "data-testid": "auth-resend-verification-button", onClick: async () => {
1171
+ setLoginBusy(true);
1172
+ setLoginNotice(null);
1173
+ setLoginError(null);
1174
+ setLoginErrorCode(null);
1175
+ const sent = await requestEmailVerification(loginEmail);
1176
+ if (sent.success) {
1177
+ setVerifyCooldownUntil(Date.now() + 30_000);
1178
+ if (sent.verificationToken)
1179
+ setVerificationToken(sent.verificationToken);
1180
+ setLoginNotice(buildVerificationRequestNotice(sent));
1181
+ }
1182
+ else {
1183
+ setLoginError(formatAuthError(sent));
1184
+ }
1185
+ setLoginBusy(false);
1186
+ }, children: verifyCooldownActive ? `Resend Verification (${verifyCooldownSeconds}s)` : 'Resend Verification' })), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, "data-testid": "auth-return-sign-in-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('login'); }, children: authMode === 'verify' ? 'Back to Sign In' : 'Sign In' }), directRegistrationEnabled && authMode !== 'verify' && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, "data-testid": "auth-return-register-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('register'); }, children: "Register" })), authMode === 'reset' && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || resetCooldownActive, "data-testid": "auth-return-forgot-password-button", onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('forgot'); }, children: resetCooldownActive ? `Forgot Password (${resetCooldownSeconds}s)` : 'Forgot Password' }))] }))] })] })] }) }) }));
858
1187
  }
859
1188
  if (shouldForceLogin && !isLoginRoute) {
860
1189
  return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, 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: "Loading Taskforce" })] }), _jsx("p", { className: authStyles.loginSubtitle, children: "Redirecting to sign in..." })] }) }) }));
@@ -865,6 +1194,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
865
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" })] }) }) }));
866
1195
  }
867
1196
  const showGuidedWorkspaceSetup = isCreateWorkspaceIntent
1197
+ || (isSetupRoute && Boolean(activeCloudWorkspaceImport))
868
1198
  || (isSetupRoute && setupGateActive && (hasSetupReadError || needsGlobalSetup || needsWorkspaceSetup));
869
1199
  if (showGuidedWorkspaceSetup) {
870
1200
  const isError = !isCreateWorkspaceIntent && (hasSetupReadError || setupStep === 'error');
@@ -873,25 +1203,52 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
873
1203
  ? t('setup.headingCheckFailed')
874
1204
  : (isGlobal
875
1205
  ? t('setup.headingGlobalRequired')
876
- : (isCreateWorkspaceIntent ? 'Create Workspace' : t('setup.headingWorkspaceRequired')));
1206
+ : t('setup.headingWorkspaceRequired'));
877
1207
  const subtitle = isError
878
1208
  ? t('setup.subtitleUnreadable')
879
1209
  : isGlobal
880
1210
  ? t('setup.subtitleGlobalRequired')
881
- : (isCreateWorkspaceIntent
882
- ? 'Set up the new workspace before it is created.'
883
- : t('setup.subtitleWorkspaceRequired'));
884
- const workspaceLabel = setupModeChoice === 'operations'
885
- ? t('setup.workspaceTermMission')
886
- : t('setup.workspaceTermProject');
1211
+ : null;
1212
+ const workspaceLabel = 'Workspace';
1213
+ const isLocalWorkspaceSetup = isCloudRuntime || workspaceSetupSource !== 'cloud';
1214
+ const isWorkspaceDetailsPage = isLocalWorkspaceSetup && workspaceSetupPage === 'details';
1215
+ const isWorkspaceLibraryPage = isLocalWorkspaceSetup && workspaceSetupPage === 'library';
1216
+ const showWorkspaceSourceSelector = !isCloudRuntime
1217
+ && canUseCloudWorkspaceSetup
1218
+ && isWorkspaceDetailsPage
1219
+ && !activeCloudWorkspaceImport;
1220
+ const runtimeIcon = runtimeCapabilities?.runtimeMode === 'cloud' ? Cloud : LaptopMinimal;
1221
+ const starterCategoryItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.categories) : '';
1222
+ const starterTypeItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.types) : '';
1223
+ const starterPriorityItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.priorities) : '';
1224
+ const setupPrimaryActionLabel = setupBusy
1225
+ ? t('setup.saving')
1226
+ : wantsCloudWorkspaceSetup
1227
+ ? (activeCloudWorkspaceImport
1228
+ ? (isCloudWorkspaceImportErrored ? 'Retry Import' : 'Importing Workspace...')
1229
+ : 'Connect Workspace')
1230
+ : isWorkspaceDetailsPage
1231
+ ? 'Continue'
1232
+ : isWorkspaceLibraryPage
1233
+ ? 'Create Workspace'
1234
+ : 'Connect Workspace';
887
1235
  return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
888
1236
  backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
889
- }, children: _jsxs("div", { className: authStyles.loginCard, 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" })] })] }), _jsx("p", { className: authStyles.loginSubtitle, children: _jsx("strong", { children: heading }) }), _jsx("p", { className: authStyles.loginSubtitle, children: subtitle }), runtimeCapabilities && (_jsxs("p", { className: authStyles.loginSubtitle, children: [t('setup.runtimeLabel'), ": ", _jsx("strong", { children: runtimeCapabilities.runtimeMode })] })), 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: [!isCloudRuntime && canUseCloudWorkspaceSetup && (_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()] })] })] })), !isCloudRuntime && workspaceSetupSource === 'cloud' && canUseCloudWorkspaceSetup ? (_jsxs("div", { className: authStyles.optionGroup, children: [!isAuthenticated && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.inlineHint, children: "Sign in to choose one of your cloud projects." }), _jsx("button", { className: styles.cancelBtn, disabled: setupBusy, onClick: () => {
890
- navigate(`/login?mode=login&next=${encodeURIComponent('/setup?step=workspace&source=cloud')}`, { replace: true });
891
- }, children: "Sign In" })] })), 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 })] }))] })) : (_jsxs(_Fragment, { children: [_jsx("input", { className: styles.input, type: "text", value: workspaceNameInput, placeholder: t('setup.workspaceNamePlaceholder', { workspaceLabel }), onChange: (event) => setWorkspaceNameInput(event.target.value), disabled: setupBusy }), _jsx("textarea", { className: styles.textarea, value: workspaceDescriptionInput, placeholder: t('setup.workspaceDescriptionPlaceholder', { workspaceLabel }), onChange: (event) => setWorkspaceDescriptionInput(event.target.value), disabled: setupBusy, rows: 4 }), _jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "What are you working on?" }), _jsx("p", { className: authStyles.inlineHint, children: "Choose a starter taxonomy library for this workspace. You can still edit categories, task types, and priorities later." }), setupLibraryPacks.map((pack) => {
892
- const isSelected = selectedSetupPack?.id === pack.id;
893
- return (_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-taxonomy-library", checked: isSelected, onChange: () => setSelectedSetupPackId(pack.id), disabled: setupBusy || setupLibraryLoading }), _jsx("span", { children: pack.label })] }, pack.id));
894
- }), selectedSetupPack ? (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.inlineHint, children: selectedSetupPack.description }), _jsxs("p", { className: authStyles.inlineHint, children: ["Includes ", selectedSetupPack.categories.length, " categories, ", selectedSetupPack.types.length, " task types, and ", selectedSetupPack.priorities.length, " priority levels."] })] })) : 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" })] }), _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" })] }), _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" })] })] }), setupLibraryLoading ? (_jsx("p", { className: authStyles.inlineHint, children: "Loading starter libraries\u2026" })) : null, setupLibraryError ? (_jsx("p", { className: authStyles.loginError, children: setupLibraryError })) : null] }), !isCloudRuntime && canUseCloudWorkspaceSetup && isAuthenticated && (_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: [isGlobal ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: async () => {
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: () => {
1238
+ navigate(`/login?mode=login&next=${encodeURIComponent(cloudWorkspaceSelectionPath)}`, { replace: true });
1239
+ }, children: "Sign In" }), directRegistrationEnabled && (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: () => {
1240
+ navigate(`/login?mode=register&next=${encodeURIComponent(cloudWorkspaceSelectionPath)}`, { replace: true });
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
1242
+ ? 'Finishing setup and opening your workspace.'
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: () => {
1244
+ setSetupError(null);
1245
+ setSetupNotice(null);
1246
+ setWorkspaceSetupPage('details');
1247
+ }, children: "Back" })) : isCreateWorkspaceIntent ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
1248
+ setSetupError(null);
1249
+ setSetupNotice(null);
1250
+ navigate('/', { replace: true });
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 () => {
895
1252
  setSetupBusy(true);
896
1253
  setSetupNotice(null);
897
1254
  setSetupError(null);
@@ -917,11 +1274,32 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
917
1274
  finally {
918
1275
  setSetupBusy(false);
919
1276
  }
920
- }, children: setupBusy ? t('setup.saving') : t('setup.saveGlobalSetup') })) : !isError ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: async () => {
921
- setSetupBusy(true);
922
- setSetupNotice(null);
923
- setSetupError(null);
924
- if (!isCloudRuntime && workspaceSetupSource === 'cloud' && canUseCloudWorkspaceSetup) {
1277
+ }, children: setupBusy ? t('setup.saving') : t('setup.saveGlobalSetup') })) : !isError ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy
1278
+ || (isCloudWorkspaceSetupFlow && !activeCloudWorkspaceImport && !isAuthenticated), onClick: async () => {
1279
+ if (isCloudWorkspaceSetupFlow && activeCloudWorkspaceImport) {
1280
+ if (!isCloudWorkspaceImportErrored)
1281
+ return;
1282
+ setSetupBusy(true);
1283
+ setSetupNotice(`Retrying import for ${activeCloudWorkspaceImport.name}...`);
1284
+ setSetupError(null);
1285
+ try {
1286
+ await retryWorkspaceCloudSync();
1287
+ }
1288
+ catch {
1289
+ setSetupNotice(null);
1290
+ setSetupError(`Failed to retry import for "${activeCloudWorkspaceImport.name}".`);
1291
+ }
1292
+ finally {
1293
+ setSetupBusy(false);
1294
+ }
1295
+ return;
1296
+ }
1297
+ if (wantsCloudWorkspaceSetup) {
1298
+ if (!canUseCloudWorkspaceSetup) {
1299
+ setSetupError('Cloud workspace sync is unavailable right now.');
1300
+ setSetupBusy(false);
1301
+ return;
1302
+ }
925
1303
  if (!isAuthenticated) {
926
1304
  setSetupError('Sign in is required before syncing a cloud project.');
927
1305
  setSetupBusy(false);
@@ -949,44 +1327,65 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
949
1327
  setSetupBusy(false);
950
1328
  return;
951
1329
  }
952
- const stateRes = await fetch('/api/taskforce/ui-state', {
953
- method: 'POST',
954
- headers: { 'Content-Type': 'application/json' },
955
- credentials: 'include',
956
- body: JSON.stringify({
957
- workspaceId: workspaceIdForSync,
958
- stateKey: 'workspace-sync',
959
- patch: {
960
- version: 2,
961
- enabled: true,
962
- phase: 'attach-cloud',
963
- pullCursor: null,
964
- lastPullAt: null,
965
- lastPushAt: null,
966
- lastSyncedAt: null
967
- }
968
- })
969
- });
970
- const statePayload = await stateRes.json().catch(() => ({}));
971
- if (!stateRes.ok || statePayload?.success === false) {
972
- 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.');
973
1345
  setSetupBusy(false);
974
1346
  return;
975
1347
  }
976
1348
  applyWorkspaceSyncStateSnapshot({
977
1349
  enabled: true,
978
1350
  phase: 'attach-cloud',
1351
+ setupIntent: 'attach-cloud-import',
979
1352
  pullCursor: null
980
1353
  });
981
1354
  await refreshSetupContext();
982
- setSetupNotice(`Cloud project synced setup ready: ${selectedWorkspace.name}`);
1355
+ setPendingCloudWorkspaceImport({
1356
+ workspaceId: workspaceIdForSync,
1357
+ name: selectedWorkspace.name || selectedWorkspace.id
1358
+ });
1359
+ setCloudWorkspaceImportFinalizing(false);
1360
+ setSetupNotice(`Importing ${selectedWorkspace.name || selectedWorkspace.id} from cloud...`);
1361
+ setSetupBusy(false);
1362
+ return;
983
1363
  }
984
- else {
1364
+ if (isWorkspaceDetailsPage) {
1365
+ const normalizedWorkspaceName = workspaceNameInput.trim();
1366
+ if (!normalizedWorkspaceName) {
1367
+ setSetupError('Workspace name is required before continuing.');
1368
+ return;
1369
+ }
1370
+ setSetupError(null);
1371
+ setSetupNotice(null);
1372
+ setWorkspaceSetupPage('library');
1373
+ return;
1374
+ }
1375
+ if (isWorkspaceLibraryPage) {
1376
+ // fall through to final create/save behavior below
1377
+ }
1378
+ setSetupBusy(true);
1379
+ setSetupNotice(null);
1380
+ setSetupError(null);
1381
+ {
985
1382
  const creatingNewWorkspace = isCreateWorkspaceIntent || setupState?.workspaceSetupState === 'missing';
986
- const createResult = isCreateWorkspaceIntent
1383
+ const shouldCreateWorkspace = isCreateWorkspaceIntent
1384
+ || (workspaceSwitchingEnabled && creatingNewWorkspace);
1385
+ const createResult = shouldCreateWorkspace
987
1386
  ? await createWorkspace(workspaceNameInput, workspaceDescriptionInput || undefined)
988
1387
  : null;
989
- const saveResult = isCreateWorkspaceIntent
1388
+ const saveResult = shouldCreateWorkspace
990
1389
  ? null
991
1390
  : await saveWorkspaceProfile({
992
1391
  workspaceId: creatingNewWorkspace ? undefined : setupState?.workspaceId,
@@ -1018,7 +1417,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1018
1417
  workspaceIdOverride: workspaceIdForSetup
1019
1418
  });
1020
1419
  if (!applyResult?.success) {
1021
- setSetupError(applyResult?.error || 'Failed to apply starter taxonomy library.');
1420
+ setSetupError(applyResult?.error || 'Failed to apply setup library.');
1022
1421
  setSetupBusy(false);
1023
1422
  return;
1024
1423
  }
@@ -1036,6 +1435,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1036
1435
  version: 2,
1037
1436
  enabled: shouldEnableCloudSync,
1038
1437
  phase: shouldEnableCloudSync ? 'provision-local' : 'idle',
1438
+ setupIntent: null,
1039
1439
  pullCursor: null,
1040
1440
  lastPullAt: null,
1041
1441
  lastPushAt: null,
@@ -1052,6 +1452,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1052
1452
  applyWorkspaceSyncStateSnapshot({
1053
1453
  enabled: shouldEnableCloudSync,
1054
1454
  phase: shouldEnableCloudSync ? 'provision-local' : 'idle',
1455
+ setupIntent: null,
1055
1456
  pullCursor: null
1056
1457
  });
1057
1458
  if (shouldEnableCloudSync) {
@@ -1068,11 +1469,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1068
1469
  }
1069
1470
  }
1070
1471
  setSetupBusy(false);
1071
- }, children: setupBusy ? t('setup.saving') : t('setup.saveWorkspaceSetup', { workspaceLabel }) })) : (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: refreshSetupContext, children: t('setup.retrySetupCheck') })), isCreateWorkspaceIntent ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
1072
- setSetupError(null);
1073
- setSetupNotice(null);
1074
- navigate('/', { replace: true });
1075
- }, children: "Cancel" })) : runtimeMode === 'cloud' ? (_jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: t('setup.signOut') })) : null] })] }) }) }));
1472
+ }, children: setupPrimaryActionLabel })) : (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: refreshSetupContext, children: t('setup.retrySetupCheck') }))] })] }) }) }));
1076
1473
  }
1077
1474
  return (_jsx(StandaloneLayout, { ...taskforceState, onHeaderMouseDown: onHeaderMouseDown, isDragging: isDragging, onClose: onClose }));
1078
1475
  }