@taskforcehq/taskforce 0.3.302 → 0.3.304

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/dist/Taskforce.module.css +113 -5
  2. package/dist/TaskforceCore.js +560 -208
  3. package/dist/TaskforceCore.test.js +1833 -91
  4. package/dist/annotatedAttachments/service.d.ts +1 -1
  5. package/dist/annotatedAttachments/types.d.ts +1 -1
  6. package/dist/components/features/AgentsModule.js +1 -1
  7. package/dist/components/features/AgentsModule.test.js +1 -1
  8. package/dist/components/features/TaskSettings.js +58 -38
  9. package/dist/components/features/TaskSettings.test.js +139 -40
  10. package/dist/components/task/TaskActionHeader.d.ts +2 -1
  11. package/dist/components/task/TaskActionHeader.js +3 -3
  12. package/dist/components/task/TaskActionHeader.test.js +8 -0
  13. package/dist/components/task/TaskCard.js +16 -1
  14. package/dist/components/task/TaskCard.test.js +31 -0
  15. package/dist/components/task/TaskForm.d.ts +2 -1
  16. package/dist/components/task/TaskForm.js +22 -4
  17. package/dist/components/task/TaskForm.test.js +46 -0
  18. package/dist/components/task/TaskKanban.d.ts +1 -1
  19. package/dist/components/task/TaskKanban.js +31 -160
  20. package/dist/components/task/TaskKanban.test.js +17 -0
  21. package/dist/components/task/TaskStatusActions.d.ts +6 -1
  22. package/dist/components/task/TaskStatusActions.js +8 -3
  23. package/dist/components/task/TaskStatusActions.test.js +9 -0
  24. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  25. package/dist/components/task/taskKanbanDropRules.js +187 -0
  26. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  27. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  28. package/dist/components/ui/Modal.d.ts +1 -1
  29. package/dist/components/ui/Modal.js +1 -0
  30. package/dist/components/views/AppShellHeader.js +5 -1
  31. package/dist/components/views/AppShellHeader.test.js +4 -0
  32. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  33. package/dist/components/views/PlanComparisonPage.js +4 -2
  34. package/dist/components/views/PlanComparisonPage.test.js +4 -0
  35. package/dist/components/views/PlansPage.d.ts +7 -1
  36. package/dist/components/views/PlansPage.js +139 -53
  37. package/dist/components/views/PlansPage.test.d.ts +1 -0
  38. package/dist/components/views/PlansPage.test.js +157 -0
  39. package/dist/components/views/StandaloneLayout.d.ts +1 -0
  40. package/dist/components/views/StandaloneLayout.js +216 -186
  41. package/dist/components/views/WidgetView.js +11 -2
  42. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
  43. package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
  44. package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
  45. package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
  46. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  47. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  48. package/dist/core/EntitlementsPolicy.test.js +2 -2
  49. package/dist/core/GlobalSettingsService.js +78 -18
  50. package/dist/core/PlanEntitlementService.d.ts +31 -5
  51. package/dist/core/PlanEntitlementService.js +350 -222
  52. package/dist/core/SignupMonetizationSettings.test.js +208 -49
  53. package/dist/core/SystemAdmin.test.js +186 -96
  54. package/dist/core/TaskActivityService.d.ts +4 -1
  55. package/dist/core/TaskActivityService.js +45 -0
  56. package/dist/core/TaskAuditTimeline.test.js +52 -1
  57. package/dist/core/Taskforce.d.ts +65 -20
  58. package/dist/core/Taskforce.js +261 -51
  59. package/dist/core/Taskforce.mcpAuth.test.js +0 -2
  60. package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
  61. package/dist/core/WorkspacePlanMode.test.js +65 -0
  62. package/dist/core/types.d.ts +11 -12
  63. package/dist/documentReviews/service.d.ts +1 -1
  64. package/dist/documentReviews/types.d.ts +1 -1
  65. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  66. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  67. package/dist/hooks/sync/auth.d.ts +37 -0
  68. package/dist/hooks/sync/auth.js +61 -0
  69. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  70. package/dist/hooks/sync/bootstrap.js +52 -0
  71. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  72. package/dist/hooks/sync/orchestratorShared.js +209 -0
  73. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  74. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  75. package/dist/hooks/sync/realtime.d.ts +25 -0
  76. package/dist/hooks/sync/realtime.js +60 -0
  77. package/dist/hooks/sync/recovery.d.ts +69 -0
  78. package/dist/hooks/sync/recovery.js +118 -0
  79. package/dist/hooks/sync/transfers.d.ts +122 -0
  80. package/dist/hooks/sync/transfers.js +431 -0
  81. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  82. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  83. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  84. package/dist/hooks/ui/useResourceExport.js +5 -2
  85. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  86. package/dist/hooks/useRealtimeSync.js +2 -1
  87. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  88. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  89. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  90. package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
  91. package/dist/hooks/useSyncOrchestrator.js +315 -860
  92. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
  93. package/dist/hooks/useTaskMutations.js +45 -6
  94. package/dist/hooks/useTaskMutations.test.js +147 -0
  95. package/dist/hooks/useTaskforce.d.ts +11 -2
  96. package/dist/hooks/useTaskforce.js +290 -812
  97. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  98. package/dist/hooks/useWorkspaceSyncController.js +22 -10
  99. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  100. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  101. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  102. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  103. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  104. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  105. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  106. package/dist/localization/locales/en-US.js +2 -2
  107. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  108. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  109. package/dist/mcp/clientIntegrations.d.ts +12 -1
  110. package/dist/mcp/clientIntegrations.js +161 -6
  111. package/dist/mcp/clientIntegrations.test.js +76 -4
  112. package/dist/mcp/documentHelpers.d.ts +100 -0
  113. package/dist/mcp/documentHelpers.js +161 -0
  114. package/dist/mcp/runtime.js +264 -505
  115. package/dist/mcp/runtime.test.js +353 -89
  116. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  117. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  118. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  119. package/dist/mcp/taskAttachmentTypes.js +1 -0
  120. package/dist/migrations/billingSchemaParity.test.js +3 -0
  121. package/dist/migrations/taskSchemaMigrations.js +15 -0
  122. package/dist/plugins/vite.js +1 -1
  123. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  124. package/dist/runtime/appGateDefinitions.js +1 -1
  125. package/dist/server/index.cookieProxy.test.js +1 -0
  126. package/dist/server/index.d.ts +1 -0
  127. package/dist/server/index.js +48 -3
  128. package/dist/server/index.test.js +14 -2
  129. package/dist/server/mockStripe.d.ts +1 -0
  130. package/dist/server/mockStripe.js +2 -0
  131. package/dist/server/routes/admin.js +91 -40
  132. package/dist/server/routes/auth.d.ts +1 -0
  133. package/dist/server/routes/auth.js +231 -55
  134. package/dist/server/routes/billing.js +473 -39
  135. package/dist/server/routes/billing.test.js +972 -37
  136. package/dist/server/routes/shared.d.ts +30 -0
  137. package/dist/server/routes/shared.js +31 -3
  138. package/dist/server/routes/sync.integration.test.js +180 -0
  139. package/dist/server/routes/sync.js +125 -242
  140. package/dist/server/routes/tasks.js +49 -7
  141. package/dist/server/routes.js +11 -22
  142. package/dist/server/routes.test.js +530 -81
  143. package/dist/shared/taskActor.d.ts +10 -0
  144. package/dist/shared/taskActor.js +1 -0
  145. package/dist/sync/cloudSyncApi.d.ts +1 -0
  146. package/dist/sync/collaborationSyncPayload.js +4 -0
  147. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  148. package/dist/sync/collaborationSyncState.d.ts +3 -1
  149. package/dist/sync/collaborationSyncState.js +19 -0
  150. package/dist/sync/contentSyncState.d.ts +10 -0
  151. package/dist/sync/contentSyncState.js +277 -14
  152. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  153. package/dist/sync/syncApplyHandlers.js +370 -8
  154. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  155. package/dist/sync/syncApplyHandlers.test.js +377 -0
  156. package/dist/sync/syncService.d.ts +9 -1
  157. package/dist/sync/syncService.js +57 -10
  158. package/dist/sync/syncService.test.js +48 -0
  159. package/dist/sync/workspacePullFeed.d.ts +11 -0
  160. package/dist/sync/workspacePullFeed.js +123 -68
  161. package/dist/sync/workspacePullFeed.test.js +180 -0
  162. package/dist/sync/workspaceSyncModel.d.ts +22 -1
  163. package/dist/sync/workspaceSyncModel.js +65 -0
  164. package/dist/sync/workspaceSyncModel.test.js +119 -0
  165. package/dist/types.d.ts +2 -10
  166. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
  167. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
  168. package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
  169. package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
  170. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  171. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
  172. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
  173. package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
  174. package/dist/ui/assets/index-DXUdtH1B.js +6 -0
  175. package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
  176. package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
  177. package/dist/ui/index.html +4 -4
  178. package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
  179. package/dist/utils/accountProfileSummaryCache.js +97 -0
  180. package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
  181. package/dist/utils/accountProfileSummaryCache.test.js +63 -0
  182. package/dist/utils/taskActivity.js +4 -0
  183. package/dist/utils/taskActivity.test.js +25 -1
  184. package/package.json +5 -3
  185. package/scripts/playwright-auth.sh +4 -0
  186. package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
  187. package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
  188. package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
  189. package/dist/ui/assets/index-MXeWoebC.css +0 -1
  190. package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
  191. package/dist/utils/billingStatusCache.d.ts +0 -21
  192. package/dist/utils/billingStatusCache.js +0 -70
@@ -1,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, 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,22 @@ 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
+ }
37
+ function buildPlansPath(params = {}) {
38
+ const query = new URLSearchParams();
39
+ query.set('screen', 'plans');
40
+ for (const [key, value] of Object.entries(params)) {
41
+ const normalized = String(value || '').trim();
42
+ if (normalized)
43
+ query.set(key, normalized);
44
+ }
45
+ return `/?${query.toString()}`;
46
+ }
30
47
  function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange, onHeaderMouseDown, isDragging, onClose, mode = 'standalone' }) {
31
48
  const location = useLocation();
32
49
  const routeQuery = useMemo(() => new URLSearchParams(location.search), [location.search]);
@@ -36,13 +53,17 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
36
53
  const isPricingRoute = location.pathname === '/pricing';
37
54
  const isPlansRoute = location.pathname === '/plans';
38
55
  const isPlansScreen = location.pathname === '/' && String(routeQuery.get('screen') || '').trim().toLowerCase() === 'plans';
39
- const planSelectionPath = '/?screen=plans&gate=plan_selection_required';
56
+ const planSelectionPath = buildPlansPath({ gate: 'plan_selection_required' });
40
57
  const navigate = useNavigate();
41
58
  const [loginEmail, setLoginEmail] = useState('');
42
59
  const [loginDisplayName, setLoginDisplayName] = useState('');
43
60
  const [loginPassword, setLoginPassword] = useState('');
44
61
  const [authMode, setAuthMode] = useState('login');
45
62
  const [verificationToken, setVerificationToken] = useState('');
63
+ const [pendingVerificationWorkspaceSetupRequired, setPendingVerificationWorkspaceSetupRequired] = useState(false);
64
+ const [pendingVerificationCommercialState, setPendingVerificationCommercialState] = useState(null);
65
+ const [pendingVerificationRedirectPath, setPendingVerificationRedirectPath] = useState(null);
66
+ const [pendingPostAuthRedirect, setPendingPostAuthRedirect] = useState(null);
46
67
  const [resetToken, setResetToken] = useState('');
47
68
  const [inviteToken, setInviteToken] = useState('');
48
69
  const [inviteEmail, setInviteEmail] = useState(null);
@@ -63,11 +84,14 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
63
84
  const [workspaceNameInput, setWorkspaceNameInput] = useState('');
64
85
  const [workspaceDescriptionInput, setWorkspaceDescriptionInput] = useState('');
65
86
  const [workspaceSetupSource, setWorkspaceSetupSource] = useState('local');
87
+ const [workspaceSetupPage, setWorkspaceSetupPage] = useState('details');
66
88
  const [cloudWorkspaceOptions, setCloudWorkspaceOptions] = useState([]);
67
89
  const [cloudWorkspaceSelection, setCloudWorkspaceSelection] = useState('');
68
90
  const [cloudWorkspaceLoading, setCloudWorkspaceLoading] = useState(false);
69
91
  const [cloudWorkspaceError, setCloudWorkspaceError] = useState(null);
70
92
  const [syncNewLocalWorkspaceToCloud, setSyncNewLocalWorkspaceToCloud] = useState(false);
93
+ const [pendingCloudWorkspaceImport, setPendingCloudWorkspaceImport] = useState(null);
94
+ const [cloudWorkspaceImportFinalizing, setCloudWorkspaceImportFinalizing] = useState(false);
71
95
  const fallbackSetupLibraryPacks = useMemo(() => listBuiltInTaxonomyLibraryPacks(), []);
72
96
  const [setupLibraryPacks, setSetupLibraryPacks] = useState(fallbackSetupLibraryPacks);
73
97
  const [selectedSetupPackId, setSelectedSetupPackId] = useState(() => (fallbackSetupLibraryPacks.find((pack) => pack.isDefaultStarter)?.id || fallbackSetupLibraryPacks[0]?.id || ''));
@@ -76,6 +100,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
76
100
  const [setupLibraryError, setSetupLibraryError] = useState(null);
77
101
  const [bootstrapRetryBusy, setBootstrapRetryBusy] = useState(false);
78
102
  const [accountAccessGate, setAccountAccessGate] = useState(null);
103
+ const [accountAccessGateResolved, setAccountAccessGateResolved] = useState(false);
79
104
  // Initialize the hook
80
105
  const taskforceState = useTaskforce({
81
106
  config,
@@ -83,7 +108,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
83
108
  onTaskCountChange,
84
109
  onClose
85
110
  });
86
- 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;
111
+ const { runtimeMode, workspaceSwitchingEnabled, currentWorkspaceId, configLoaded, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, hasBetaAccess, authSessionResolved, workspaceBootstrapPending, bootstrapPhase, bootstrapError, setupState, runtimeCapabilities, refreshSetupContext, retryBootstrapChecks, createWorkspace, saveWorkspaceProfile, fetchWorkspaces, applyWorkspaceSyncStateSnapshot, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceLastErrorMessage, retryWorkspaceCloudSync, settingsModel, fetchTasks, loginWithCredentials, registerWithCredentials, requestEmailVerification, confirmEmailVerification, requestPasswordReset, confirmPasswordReset, inspectInviteAcceptance, acceptInviteWithToken, joinInviteWithToken, authUserEmail, currentTheme, logout } = taskforceState;
87
112
  const selectedSetupPack = useMemo(() => {
88
113
  return setupLibraryPacks.find((pack) => pack.id === selectedSetupPackId)
89
114
  || setupLibraryPacks.find((pack) => pack.isDefaultStarter)
@@ -106,6 +131,46 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
106
131
  const isLoginRoute = location.pathname === '/login';
107
132
  const isSetupRoute = location.pathname === '/setup';
108
133
  const isComingSoonRoute = location.pathname === '/coming-soon';
134
+ const wantsCloudWorkspaceSetup = !isCloudRuntime && workspaceSetupSource === 'cloud';
135
+ const isCloudWorkspaceSetupFlow = wantsCloudWorkspaceSetup && canUseCloudWorkspaceSetup;
136
+ const cloudWorkspaceImportMatchesCurrentWorkspace = Boolean(pendingCloudWorkspaceImport
137
+ && currentWorkspaceId === pendingCloudWorkspaceImport.workspaceId);
138
+ const isCloudWorkspaceImportErrored = Boolean(pendingCloudWorkspaceImport
139
+ && cloudWorkspaceImportMatchesCurrentWorkspace
140
+ && workspaceSyncPhase === 'error');
141
+ const isCloudWorkspaceImportReady = Boolean(pendingCloudWorkspaceImport
142
+ && cloudWorkspaceImportMatchesCurrentWorkspace
143
+ && workspaceCloudSyncEnabled
144
+ && workspaceSyncPhase === 'active'
145
+ && !workspaceSyncBusy);
146
+ const cloudWorkspaceImportProgressLines = useMemo(() => {
147
+ if (!pendingCloudWorkspaceImport)
148
+ return [];
149
+ const connectingState = setupBusy ? 'In progress' : 'Complete';
150
+ const importingState = isCloudWorkspaceImportErrored
151
+ ? 'Needs attention'
152
+ : (workspaceSyncBusy || workspaceSyncPhase === 'attach-cloud')
153
+ ? 'In progress'
154
+ : (workspaceSyncPhase === 'active' ? 'Complete' : 'Waiting');
155
+ const finishingState = cloudWorkspaceImportFinalizing
156
+ ? 'In progress'
157
+ : isCloudWorkspaceImportReady
158
+ ? 'Ready'
159
+ : 'Waiting';
160
+ return [
161
+ `Connect workspace: ${connectingState}`,
162
+ `Import cloud data: ${importingState}`,
163
+ `Open local workspace: ${finishingState}`
164
+ ];
165
+ }, [
166
+ pendingCloudWorkspaceImport,
167
+ setupBusy,
168
+ isCloudWorkspaceImportErrored,
169
+ workspaceSyncBusy,
170
+ workspaceSyncPhase,
171
+ cloudWorkspaceImportFinalizing,
172
+ isCloudWorkspaceImportReady
173
+ ]);
109
174
  const shouldForceLogin = isLikelyCloudAppHost
110
175
  ? ((!isAuthenticated || authBlocked) && !isPricingRoute && !isPlansRoute && !isPlansScreen)
111
176
  : (((isCloudAuthRequired && !isAuthenticated) || shouldForceForAuthBlocked) && !isPricingRoute && !isPlansRoute && !isPlansScreen);
@@ -119,6 +184,23 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
119
184
  && !isPlansRoute
120
185
  && !isPlansScreen;
121
186
  const directRegistrationEnabled = cloudAuthConfigured;
187
+ const authPageTitle = useMemo(() => {
188
+ switch (authMode) {
189
+ case 'register':
190
+ return 'Create Account | Taskforce';
191
+ case 'verify':
192
+ return 'Verify Email | Taskforce';
193
+ case 'forgot':
194
+ return 'Reset Password | Taskforce';
195
+ case 'reset':
196
+ return 'Set New Password | Taskforce';
197
+ case 'invite':
198
+ return 'Accept Invite | Taskforce';
199
+ case 'login':
200
+ default:
201
+ return 'Sign In | Taskforce';
202
+ }
203
+ }, [authMode]);
122
204
  const setupStep = useMemo(() => String(new URLSearchParams(location.search).get('step') || '').trim().toLowerCase(), [location.search]);
123
205
  const setupIntent = useMemo(() => String(new URLSearchParams(location.search).get('intent') || '').trim().toLowerCase(), [location.search]);
124
206
  const isCreateWorkspaceIntent = isSetupRoute && setupStep === 'workspace' && setupIntent === 'create-workspace';
@@ -186,9 +268,11 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
186
268
  useEffect(() => {
187
269
  if (runtimeMode !== 'cloud' || !isAuthenticated || !authSessionResolved) {
188
270
  setAccountAccessGate(null);
271
+ setAccountAccessGateResolved(false);
189
272
  return;
190
273
  }
191
274
  setAccountAccessGate(null);
275
+ setAccountAccessGateResolved(false);
192
276
  let cancelled = false;
193
277
  (async () => {
194
278
  try {
@@ -197,23 +281,32 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
197
281
  credentials: 'include'
198
282
  });
199
283
  const payload = await res.json().catch(() => ({}));
200
- if (cancelled || !res.ok)
284
+ if (cancelled)
201
285
  return;
286
+ if (!res.ok) {
287
+ setAccountAccessGate(null);
288
+ setAccountAccessGateResolved(true);
289
+ return;
290
+ }
202
291
  const gate = String(payload?.gate || '').trim().toLowerCase();
203
- if (gate !== 'ok' && gate !== 'plan_selection_required' && gate !== 'misconfigured' && gate !== 'missing_entitlement') {
292
+ if (gate !== 'ok' && gate !== 'plan_selection_required' && gate !== 'checkout_pending' && gate !== 'misconfigured' && gate !== 'missing_entitlement') {
204
293
  setAccountAccessGate(null);
294
+ setAccountAccessGateResolved(true);
205
295
  return;
206
296
  }
207
297
  setAccountAccessGate({
208
- gate,
298
+ gate: gate,
209
299
  canAccessApp: payload?.canAccessApp === true,
210
300
  canAccessPlans: payload?.canAccessPlans !== false,
211
301
  message: typeof payload?.message === 'string' ? payload.message : null
212
302
  });
303
+ setAccountAccessGateResolved(true);
213
304
  }
214
305
  catch {
215
- if (!cancelled)
306
+ if (!cancelled) {
216
307
  setAccountAccessGate(null);
308
+ setAccountAccessGateResolved(true);
309
+ }
217
310
  }
218
311
  })();
219
312
  return () => {
@@ -246,6 +339,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
246
339
  useEffect(() => {
247
340
  if (!isSetupRoute || setupStep !== 'workspace')
248
341
  return;
342
+ setWorkspaceSetupPage('details');
249
343
  const source = String(new URLSearchParams(location.search).get('source') || '').trim().toLowerCase();
250
344
  if (source === 'cloud' && canUseCloudWorkspaceSetup) {
251
345
  setWorkspaceSetupSource('cloud');
@@ -343,6 +437,63 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
343
437
  useEffect(() => {
344
438
  setSetupTaxonomySections(defaultSetupTaxonomySections(selectedSetupPack));
345
439
  }, [selectedSetupPackId, selectedSetupPack]);
440
+ useEffect(() => {
441
+ if (workspaceSetupSource === 'cloud') {
442
+ setWorkspaceSetupPage('details');
443
+ }
444
+ }, [workspaceSetupSource]);
445
+ useEffect(() => {
446
+ if (!isCloudWorkspaceSetupFlow) {
447
+ setPendingCloudWorkspaceImport(null);
448
+ setCloudWorkspaceImportFinalizing(false);
449
+ }
450
+ }, [isCloudWorkspaceSetupFlow]);
451
+ useEffect(() => {
452
+ if (!isCloudWorkspaceImportErrored || !pendingCloudWorkspaceImport)
453
+ return;
454
+ setSetupNotice(null);
455
+ setSetupError(workspaceLastErrorMessage
456
+ || `Failed to import "${pendingCloudWorkspaceImport.name}" from cloud. Retry import to continue.`);
457
+ }, [isCloudWorkspaceImportErrored, pendingCloudWorkspaceImport, workspaceLastErrorMessage]);
458
+ useEffect(() => {
459
+ if (!isCloudWorkspaceImportReady || !pendingCloudWorkspaceImport || cloudWorkspaceImportFinalizing)
460
+ return;
461
+ let cancelled = false;
462
+ setCloudWorkspaceImportFinalizing(true);
463
+ setSetupError(null);
464
+ setSetupNotice(`Workspace import complete. Opening ${pendingCloudWorkspaceImport.name}...`);
465
+ void (async () => {
466
+ try {
467
+ await refreshSetupContext();
468
+ await Promise.all([
469
+ fetchWorkspaces(),
470
+ fetchTasks(true)
471
+ ]);
472
+ if (cancelled)
473
+ return;
474
+ setPendingCloudWorkspaceImport(null);
475
+ navigate('/', { replace: true });
476
+ }
477
+ catch {
478
+ if (cancelled)
479
+ return;
480
+ setSetupNotice(null);
481
+ setSetupError(`Imported "${pendingCloudWorkspaceImport.name}" but failed to finish opening it.`);
482
+ setCloudWorkspaceImportFinalizing(false);
483
+ }
484
+ })();
485
+ return () => {
486
+ cancelled = true;
487
+ };
488
+ }, [
489
+ cloudWorkspaceImportFinalizing,
490
+ fetchTasks,
491
+ fetchWorkspaces,
492
+ isCloudWorkspaceImportReady,
493
+ navigate,
494
+ pendingCloudWorkspaceImport,
495
+ refreshSetupContext
496
+ ]);
346
497
  const nextPath = useMemo(() => {
347
498
  const raw = new URLSearchParams(location.search).get('next') || '/';
348
499
  if (!raw.startsWith('/'))
@@ -351,6 +502,50 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
351
502
  return '/';
352
503
  return raw;
353
504
  }, [location.search]);
505
+ const cloudWorkspaceSelectionPath = '/setup?step=workspace&source=cloud&postAuth=select-cloud-workspace';
506
+ const shouldReturnToCloudWorkspaceSelection = useMemo(() => {
507
+ try {
508
+ const parsed = new URL(nextPath, 'https://taskforce.local');
509
+ return parsed.pathname === '/setup'
510
+ && parsed.searchParams.get('step') === 'workspace'
511
+ && parsed.searchParams.get('source') === 'cloud'
512
+ && parsed.searchParams.get('postAuth') === 'select-cloud-workspace';
513
+ }
514
+ catch {
515
+ return false;
516
+ }
517
+ }, [nextPath]);
518
+ const resolveWorkspaceSetupPath = useCallback(() => (shouldReturnToCloudWorkspaceSelection
519
+ ? cloudWorkspaceSelectionPath
520
+ : '/setup?step=workspace'), [shouldReturnToCloudWorkspaceSelection]);
521
+ const resolvedSignupInterval = selectedSignupInterval === 'year' ? 'year' : 'month';
522
+ const hasCommercialOnboardingGate = useCallback((input) => {
523
+ return Boolean(input?.planSelectionRequired
524
+ || input?.checkoutPending
525
+ || input?.commercialState === 'pending_plan_selection'
526
+ || input?.commercialState === 'checkout_pending');
527
+ }, []);
528
+ const resolvePostRegistrationPath = useCallback((input) => {
529
+ if (input?.planSelectionRequired || input?.commercialState === 'pending_plan_selection') {
530
+ return planSelectionPath;
531
+ }
532
+ if (input?.checkoutPending || input?.commercialState === 'checkout_pending') {
533
+ return buildPlansPath({
534
+ gate: 'checkout_pending',
535
+ checkout: selectedSignupPlanVersionId ? 'start' : null,
536
+ planId: selectedSignupPlanId || null,
537
+ planVersionId: selectedSignupPlanVersionId || null,
538
+ interval: selectedSignupPlanVersionId ? resolvedSignupInterval : null
539
+ });
540
+ }
541
+ return nextPath;
542
+ }, [
543
+ nextPath,
544
+ planSelectionPath,
545
+ resolvedSignupInterval,
546
+ selectedSignupPlanId,
547
+ selectedSignupPlanVersionId
548
+ ]);
354
549
  const loginRouteMode = useMemo(() => {
355
550
  const modeRaw = String(new URLSearchParams(location.search).get('mode') || '').trim().toLowerCase();
356
551
  if (modeRaw === 'register') {
@@ -374,26 +569,6 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
374
569
  const query = params.toString();
375
570
  navigate(`/login${query ? `?${query}` : ''}`, { replace: true });
376
571
  }, [directRegistrationEnabled, location.search, navigate]);
377
- const routeToWorkspaceSetupIfNeeded = useCallback(async (hintWorkspaceSetupRequired) => {
378
- if (!workspaceSwitchingEnabled)
379
- return false;
380
- const workspacesResult = await fetchWorkspaces();
381
- if (workspacesResult.success) {
382
- const workspaceCount = Array.isArray(workspacesResult.workspaces)
383
- ? workspacesResult.workspaces.length
384
- : 0;
385
- if (workspaceCount === 0) {
386
- navigate('/setup?step=workspace', { replace: true });
387
- return true;
388
- }
389
- return false;
390
- }
391
- if (hintWorkspaceSetupRequired) {
392
- navigate('/setup?step=workspace', { replace: true });
393
- return true;
394
- }
395
- return false;
396
- }, [fetchWorkspaces, navigate, workspaceSwitchingEnabled]);
397
572
  useEffect(() => {
398
573
  if (!isLoginRoute)
399
574
  return;
@@ -405,6 +580,46 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
405
580
  if (loginRouteMode === 'invite' && loginRouteToken)
406
581
  setInviteToken(loginRouteToken);
407
582
  }, [isLoginRoute, loginRouteMode, loginRouteToken]);
583
+ useEffect(() => {
584
+ if (authMode === 'verify')
585
+ return;
586
+ if (isLoginRoute && loginRouteMode === 'verify')
587
+ return;
588
+ setPendingVerificationWorkspaceSetupRequired(false);
589
+ setPendingVerificationCommercialState(null);
590
+ setPendingVerificationRedirectPath(null);
591
+ }, [authMode, isLoginRoute, loginRouteMode]);
592
+ useEffect(() => {
593
+ if (!isLoginRoute || !pendingPostAuthRedirect || !authSessionResolved || !isAuthenticated)
594
+ return;
595
+ if (runtimeMode === 'cloud' && !pendingPostAuthRedirect.commercialOnboardingGate && !accountAccessGateResolved) {
596
+ return;
597
+ }
598
+ if (!pendingPostAuthRedirect.commercialOnboardingGate && pendingPostAuthRedirect.workspaceSetupRequired) {
599
+ navigate(resolveWorkspaceSetupPath(), { replace: true });
600
+ }
601
+ else {
602
+ navigate(pendingPostAuthRedirect.path, { replace: true });
603
+ }
604
+ setLoginEmail('');
605
+ setLoginDisplayName('');
606
+ setLoginPassword('');
607
+ setPendingPostAuthRedirect(null);
608
+ }, [
609
+ accountAccessGateResolved,
610
+ authSessionResolved,
611
+ isAuthenticated,
612
+ isLoginRoute,
613
+ navigate,
614
+ pendingPostAuthRedirect,
615
+ resolveWorkspaceSetupPath,
616
+ runtimeMode
617
+ ]);
618
+ useEffect(() => {
619
+ if (!isLoginRoute)
620
+ return;
621
+ document.title = authPageTitle;
622
+ }, [authPageTitle, isLoginRoute]);
408
623
  useEffect(() => {
409
624
  if (!isLoginRoute || authMode !== 'invite' || !inviteToken.trim())
410
625
  return;
@@ -491,6 +706,17 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
491
706
  const shouldStayOnInviteRoute = isLoginRoute
492
707
  && authMode === 'invite'
493
708
  && inviteToken.trim().length > 0;
709
+ const shouldDelayAuthenticatedLoginRedirect = isLoginRoute
710
+ && isAuthenticated
711
+ && !shouldStayOnInviteRoute
712
+ && (Boolean(pendingPostAuthRedirect)
713
+ || workspaceBootstrapPending
714
+ || authMode === 'register'
715
+ || authMode === 'verify'
716
+ || (runtimeMode === 'cloud' && (!configLoaded || !accountAccessGateResolved)));
717
+ if (shouldDelayAuthenticatedLoginRedirect) {
718
+ return;
719
+ }
494
720
  if (isLoginRoute && isAuthenticated && !shouldStayOnInviteRoute)
495
721
  navigate(nextPath, { replace: true });
496
722
  }
@@ -502,6 +728,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
502
728
  }
503
729
  return;
504
730
  }
731
+ if (isLoginRoute && !isAuthenticated) {
732
+ return;
733
+ }
505
734
  if (shouldForceAccountAccessGate) {
506
735
  if (!isPlansScreen) {
507
736
  const gate = encodeURIComponent(String(accountAccessGate?.gate || 'plan_selection_required'));
@@ -509,6 +738,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
509
738
  }
510
739
  return;
511
740
  }
741
+ if (isSetupRoute && pendingCloudWorkspaceImport) {
742
+ return;
743
+ }
512
744
  if (setupGateActive) {
513
745
  if (hasSetupReadError) {
514
746
  if (!isSetupRoute || setupStep !== 'error') {
@@ -524,7 +756,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
524
756
  }
525
757
  if (needsWorkspaceSetup) {
526
758
  if (!isSetupRoute || setupStep !== 'workspace') {
527
- navigate('/setup?step=workspace', { replace: true });
759
+ navigate(resolveWorkspaceSetupPath(), { replace: true });
528
760
  }
529
761
  return;
530
762
  }
@@ -547,19 +779,26 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
547
779
  isPlansRoute,
548
780
  isPlansScreen,
549
781
  accountAccessGate?.gate,
782
+ accountAccessGateResolved,
783
+ authMode,
550
784
  setupGateActive,
551
785
  setupState,
552
786
  hasSetupReadError,
553
787
  isCreateWorkspaceIntent,
788
+ pendingCloudWorkspaceImport,
554
789
  needsGlobalSetup,
555
790
  needsWorkspaceSetup,
556
791
  setupStep,
557
792
  workspaceBootstrapPending,
793
+ configLoaded,
794
+ runtimeMode,
795
+ inviteToken,
558
796
  location.pathname,
559
797
  location.search,
560
798
  location.hash,
561
799
  navigate,
562
- nextPath
800
+ nextPath,
801
+ pendingPostAuthRedirect
563
802
  ]);
564
803
  useEffect(() => {
565
804
  if (mode === 'widget')
@@ -611,18 +850,26 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
611
850
  if (isLoginRoute) {
612
851
  return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
613
852
  backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
614
- }, 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'
853
+ }, 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'
615
854
  ? `You've been invited to join ${inviteWorkspaceId || 'this workspace'}.`
616
855
  : inviteResolution === 'existing_user_signed_out'
617
856
  ? 'Sign in to join this workspace.'
618
- : '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'
857
+ : 'Create your account to join this workspace.')] }), (authMode === 'login'
858
+ || authMode === 'register'
859
+ || authMode === 'verify'
860
+ || authMode === 'forgot'
861
+ || (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'
619
862
  || authMode === 'register'
620
863
  || authMode === 'reset'
621
- || (authMode === 'invite' && (invitePasswordRequired || inviteResolution === 'existing_user_signed_out'))) && (_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "password", value: loginPassword, placeholder: authMode === 'reset'
622
- ? 'New password'
623
- : (authMode === 'invite'
624
- ? (invitePasswordRequired ? 'Create password' : 'Password')
625
- : '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 () => {
864
+ || (authMode === 'invite' && (invitePasswordRequired || inviteResolution === 'existing_user_signed_out'))) && (_jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: authMode === 'reset'
865
+ ? 'New password'
866
+ : (authMode === 'invite' && invitePasswordRequired ? 'Create password' : 'Password') }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "password", value: loginPassword, placeholder: authMode === 'reset'
867
+ ? 'New password'
868
+ : (authMode === 'invite'
869
+ ? (invitePasswordRequired ? 'Create password' : 'Password')
870
+ : 'Password'), autoComplete: authMode === 'register' || authMode === 'reset' || (authMode === 'invite' && invitePasswordRequired)
871
+ ? 'new-password'
872
+ : '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 () => {
626
873
  setLoginBusy(true);
627
874
  setLoginNotice(null);
628
875
  const sent = await requestEmailVerification(loginEmail);
@@ -639,172 +886,206 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
639
886
  setLoginError(formatAuthError(sent));
640
887
  }
641
888
  setLoginBusy(false);
642
- }, 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 () => {
643
- setLoginBusy(true);
644
- setLoginNotice(null);
645
- setLoginError(null);
646
- setLoginErrorCode(null);
647
- if (authMode === 'register' && !loginDisplayName.trim()) {
648
- setLoginError('Display name is required.');
649
- setLoginBusy(false);
650
- return;
651
- }
652
- let result = { success: false };
653
- if (authMode === 'login') {
654
- result = await loginWithCredentials(loginEmail, loginPassword);
655
- }
656
- else if (authMode === 'register') {
657
- result = await registerWithCredentials(loginEmail, loginPassword, {
658
- displayName: loginDisplayName,
659
- planId: selectedSignupPlanId || undefined,
660
- planVersionId: selectedSignupPlanVersionId || undefined,
661
- interval: selectedSignupInterval || undefined
662
- });
663
- }
664
- else if (authMode === 'verify') {
665
- result = await confirmEmailVerification(verificationToken);
666
- }
667
- else if (authMode === 'forgot') {
668
- result = await requestPasswordReset(loginEmail);
669
- }
670
- else if (authMode === 'reset') {
671
- result = await confirmPasswordReset(resetToken, loginPassword);
672
- }
673
- else if (authMode === 'invite') {
674
- if (inviteResolution === 'existing_user_signed_out') {
889
+ }, 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 () => {
890
+ setLoginBusy(true);
891
+ setLoginNotice(null);
892
+ setLoginError(null);
893
+ setLoginErrorCode(null);
894
+ if (authMode === 'register' && !loginDisplayName.trim()) {
895
+ setLoginError('Display name is required.');
896
+ setLoginBusy(false);
897
+ return;
898
+ }
899
+ let result = { success: false };
900
+ if (authMode === 'login') {
675
901
  result = await loginWithCredentials(loginEmail, loginPassword);
676
902
  }
677
- else {
678
- result = invitePasswordRequired
679
- ? await acceptInviteWithToken(inviteToken, loginPassword)
680
- : await joinInviteWithToken(inviteToken);
903
+ else if (authMode === 'register') {
904
+ result = await registerWithCredentials(loginEmail, loginPassword, {
905
+ displayName: loginDisplayName,
906
+ planId: selectedSignupPlanId || undefined,
907
+ planVersionId: selectedSignupPlanVersionId || undefined,
908
+ interval: selectedSignupInterval || undefined
909
+ });
681
910
  }
682
- }
683
- if (!result.success) {
684
- if (authMode === 'register'
685
- && (result.code === 'SIGNUP_PLAN_NOT_ENABLED'
686
- || result.code === 'SIGNUP_PLAN_VERSION_NOT_FOUND'
687
- || result.code === 'SIGNUP_PLAN_VERSION_REQUIRED')) {
688
- navigate(planSelectionPath, { replace: true });
689
- setLoginBusy(false);
690
- return;
911
+ else if (authMode === 'verify') {
912
+ result = await confirmEmailVerification(verificationToken);
691
913
  }
692
- setLoginError(formatAuthError(result));
693
- setLoginErrorCode(result.code || null);
694
- if (authMode === 'login' && (result.code === 'WORKSPACE_NOT_FOUND' || result.code === 'WORKSPACE_ID_REQUIRED')) {
695
- navigate('/setup?step=workspace', { replace: true });
696
- setLoginBusy(false);
697
- return;
914
+ else if (authMode === 'forgot') {
915
+ result = await requestPasswordReset(loginEmail);
698
916
  }
699
- if (authMode === 'login' && result.code === 'EMAIL_NOT_VERIFIED' && loginEmail.trim()) {
700
- const sent = await requestEmailVerification(loginEmail);
701
- if (sent.success) {
917
+ else if (authMode === 'reset') {
918
+ result = await confirmPasswordReset(resetToken, loginPassword);
919
+ }
920
+ else if (authMode === 'invite') {
921
+ if (inviteResolution === 'existing_user_signed_out') {
922
+ result = await loginWithCredentials(loginEmail, loginPassword);
923
+ }
924
+ else {
925
+ result = invitePasswordRequired
926
+ ? await acceptInviteWithToken(inviteToken, loginPassword)
927
+ : await joinInviteWithToken(inviteToken);
928
+ }
929
+ }
930
+ if (!result.success) {
931
+ if (authMode === 'register'
932
+ && (result.code === 'SIGNUP_PLAN_NOT_ENABLED'
933
+ || result.code === 'SIGNUP_PLAN_VERSION_NOT_FOUND'
934
+ || result.code === 'SIGNUP_PLAN_VERSION_REQUIRED')) {
935
+ navigate(planSelectionPath, { replace: true });
936
+ setLoginBusy(false);
937
+ return;
938
+ }
939
+ setLoginError(formatAuthError(result));
940
+ setLoginErrorCode(result.code || null);
941
+ if (authMode === 'login' && (result.code === 'WORKSPACE_NOT_FOUND' || result.code === 'WORKSPACE_ID_REQUIRED')) {
942
+ navigate('/setup?step=workspace', { replace: true });
943
+ setLoginBusy(false);
944
+ return;
945
+ }
946
+ if (authMode === 'login' && result.code === 'EMAIL_NOT_VERIFIED' && loginEmail.trim()) {
947
+ const sent = await requestEmailVerification(loginEmail);
948
+ if (sent.success) {
949
+ setVerifyCooldownUntil(Date.now() + 30_000);
950
+ setLoginMode('verify');
951
+ if (sent.verificationToken)
952
+ setVerificationToken(sent.verificationToken);
953
+ setLoginNotice('Email not verified. A verification token was sent.');
954
+ setLoginError(null);
955
+ setLoginErrorCode(null);
956
+ }
957
+ }
958
+ if (authMode === 'register' && result.code === 'EMAIL_ALREADY_EXISTS_UNVERIFIED' && loginEmail.trim()) {
702
959
  setVerifyCooldownUntil(Date.now() + 30_000);
703
960
  setLoginMode('verify');
704
- if (sent.verificationToken)
705
- setVerificationToken(sent.verificationToken);
706
- setLoginNotice('Email not verified. A verification token was sent.');
961
+ if (result.verificationToken)
962
+ setVerificationToken(result.verificationToken);
963
+ setLoginNotice(result.emailSent === false
964
+ ? `This email already has an unverified account, but verification email delivery failed. ${result.emailError || 'Try Resend Verification again.'}`
965
+ : 'This email already has an unverified account. Check your email for verification instructions.');
707
966
  setLoginError(null);
708
967
  setLoginErrorCode(null);
709
968
  }
710
969
  }
711
- }
712
- else if (authMode === 'register') {
713
- if (await routeToWorkspaceSetupIfNeeded(result.workspaceSetupRequired)) {
714
- setLoginBusy(false);
715
- return;
970
+ else if (authMode === 'register') {
971
+ const postRegistrationPath = resolvePostRegistrationPath(result);
972
+ const commercialOnboardingGate = hasCommercialOnboardingGate(result);
973
+ if (result.verificationRequired) {
974
+ setPendingVerificationWorkspaceSetupRequired(Boolean(result.workspaceSetupRequired));
975
+ setPendingVerificationCommercialState(commercialOnboardingGate
976
+ ? String(result.commercialState || '').trim() || (result.planSelectionRequired ? 'pending_plan_selection' : 'checkout_pending')
977
+ : null);
978
+ setPendingVerificationRedirectPath(postRegistrationPath);
979
+ if (result.verificationToken)
980
+ setVerificationToken(result.verificationToken);
981
+ setVerifyCooldownUntil(Date.now() + 30_000);
982
+ setLoginMode('verify');
983
+ setLoginNotice(result.emailSent === false
984
+ ? `Account created, but verification email failed to send. ${result.emailError || 'Try Resend Verification again.'}`
985
+ : 'Account created. Check your email for verification instructions.');
986
+ }
987
+ else {
988
+ setPendingPostAuthRedirect({
989
+ path: postRegistrationPath,
990
+ workspaceSetupRequired: Boolean(result.workspaceSetupRequired),
991
+ commercialOnboardingGate
992
+ });
993
+ }
994
+ }
995
+ else if (authMode === 'verify') {
996
+ await retryBootstrapChecks();
997
+ const commercialOnboardingGate = pendingVerificationCommercialState === 'pending_plan_selection'
998
+ || pendingVerificationCommercialState === 'checkout_pending';
999
+ setPendingPostAuthRedirect({
1000
+ path: pendingVerificationRedirectPath || nextPath,
1001
+ workspaceSetupRequired: pendingVerificationWorkspaceSetupRequired,
1002
+ commercialOnboardingGate
1003
+ });
716
1004
  }
717
- if (result.verificationRequired) {
718
- if (result.verificationToken)
719
- setVerificationToken(result.verificationToken);
720
- setVerifyCooldownUntil(Date.now() + 30_000);
721
- setLoginMode('verify');
1005
+ else if (authMode === 'forgot') {
1006
+ if (result.resetToken)
1007
+ setResetToken(result.resetToken);
1008
+ setResetCooldownUntil(Date.now() + 30_000);
1009
+ setLoginMode('reset');
722
1010
  setLoginNotice(result.emailSent === false
723
- ? `Account created, but verification email failed to send. ${result.emailError || 'Try Resend Verification again.'}`
724
- : 'Account created. Check your email for verification instructions.');
1011
+ ? `Password reset email failed to send. ${result.emailError || 'Try again in a minute.'}`
1012
+ : 'Password reset instructions sent.');
725
1013
  }
726
- else {
1014
+ else if (authMode === 'reset') {
1015
+ setLoginMode('login');
1016
+ setLoginNotice('Password updated. Sign in with your new password.');
1017
+ }
1018
+ else if (authMode === 'invite') {
1019
+ if (inviteResolution === 'existing_user_signed_out') {
1020
+ setAuthMode('invite');
1021
+ setLoginPassword('');
1022
+ setLoginNotice('Signed in. Review the invite details to continue.');
1023
+ setLoginBusy(false);
1024
+ return;
1025
+ }
1026
+ if (result.workspaceSetupRequired) {
1027
+ navigate(resolveWorkspaceSetupPath(), { replace: true });
1028
+ setLoginBusy(false);
1029
+ return;
1030
+ }
1031
+ navigate(nextPath, { replace: true });
1032
+ }
1033
+ else if (authMode === 'login') {
1034
+ if (inviteToken.trim()) {
1035
+ setAuthMode('invite');
1036
+ setLoginPassword('');
1037
+ setLoginNotice('Signed in. Review the invite details to continue.');
1038
+ setLoginBusy(false);
1039
+ return;
1040
+ }
1041
+ if (result.workspaceSetupRequired) {
1042
+ navigate(resolveWorkspaceSetupPath(), { replace: true });
1043
+ setLoginBusy(false);
1044
+ return;
1045
+ }
727
1046
  setLoginEmail('');
728
1047
  setLoginDisplayName('');
729
1048
  setLoginPassword('');
730
1049
  navigate(nextPath, { replace: true });
731
1050
  }
732
- }
733
- else if (authMode === 'verify') {
734
- await retryBootstrapChecks();
735
- setLoginEmail('');
736
- setLoginPassword('');
737
- navigate(nextPath, { replace: true });
738
- }
739
- else if (authMode === 'forgot') {
740
- if (result.resetToken)
741
- setResetToken(result.resetToken);
742
- setResetCooldownUntil(Date.now() + 30_000);
743
- setLoginMode('reset');
744
- setLoginNotice(result.emailSent === false
745
- ? `Password reset email failed to send. ${result.emailError || 'Try again in a minute.'}`
746
- : 'Password reset instructions sent.');
747
- }
748
- else if (authMode === 'reset') {
749
- setLoginMode('login');
750
- setLoginNotice('Password updated. Sign in with your new password.');
751
- }
752
- else if (authMode === 'invite') {
753
- if (inviteResolution === 'existing_user_signed_out') {
754
- setAuthMode('invite');
755
- setLoginPassword('');
756
- setLoginNotice('Signed in. Review the invite details to continue.');
757
- setLoginBusy(false);
758
- return;
759
- }
760
- if (result.workspaceSetupRequired) {
761
- navigate('/setup?step=workspace', { replace: true });
762
- setLoginBusy(false);
763
- return;
764
- }
765
- navigate(nextPath, { replace: true });
766
- }
767
- else if (authMode === 'login') {
768
- if (inviteToken.trim()) {
769
- setAuthMode('invite');
1051
+ else {
1052
+ setLoginEmail('');
1053
+ setLoginDisplayName('');
770
1054
  setLoginPassword('');
771
- setLoginNotice('Signed in. Review the invite details to continue.');
772
- setLoginBusy(false);
773
- return;
774
- }
775
- if (result.workspaceSetupRequired) {
776
- navigate('/setup?step=workspace', { replace: true });
777
- setLoginBusy(false);
778
- return;
1055
+ navigate(nextPath, { replace: true });
779
1056
  }
780
- setLoginEmail('');
781
- setLoginDisplayName('');
782
- setLoginPassword('');
783
- navigate(nextPath, { replace: true });
784
- }
785
- else {
786
- setLoginEmail('');
787
- setLoginDisplayName('');
788
- setLoginPassword('');
789
- navigate(nextPath, { replace: true });
790
- }
791
- setLoginBusy(false);
792
- }, children: loginBusy ? 'Working...' : (authMode === 'login' ? 'Sign In'
793
- : authMode === 'register' ? 'Create Account'
794
- : authMode === 'verify'
795
- ? (verifyCooldownActive ? `Resend in ${verifyCooldownSeconds}s` : 'Resend Verification')
796
- : authMode === 'forgot'
797
- ? (resetCooldownActive ? `Retry in ${resetCooldownSeconds}s` : 'Send Reset Link')
798
- : authMode === 'reset'
799
- ? 'Reset Password'
800
- : (inviteResolution === 'existing_user_signed_out'
801
- ? 'Sign In to Continue'
802
- : (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: () => {
803
- setLoginError(null);
804
- setLoginErrorCode(null);
805
- setLoginNotice(null);
806
- setLoginMode(authMode === 'login' ? 'register' : 'login');
807
- }, 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' }))] }))] })] }) }) }));
1057
+ setLoginBusy(false);
1058
+ }, children: loginBusy ? 'Working...' : (authMode === 'login' ? 'Sign In'
1059
+ : authMode === 'register' ? 'Create Account'
1060
+ : authMode === 'verify' ? 'Verify Email'
1061
+ : authMode === 'forgot'
1062
+ ? (resetCooldownActive ? `Retry in ${resetCooldownSeconds}s` : 'Send Reset Link')
1063
+ : authMode === 'reset'
1064
+ ? 'Reset Password'
1065
+ : (inviteResolution === 'existing_user_signed_out'
1066
+ ? 'Sign In to Continue'
1067
+ : (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: () => {
1068
+ setLoginError(null);
1069
+ setLoginErrorCode(null);
1070
+ setLoginNotice(null);
1071
+ setLoginMode(authMode === 'login' ? 'register' : 'login');
1072
+ }, 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 () => {
1073
+ setLoginBusy(true);
1074
+ setLoginNotice(null);
1075
+ setLoginError(null);
1076
+ setLoginErrorCode(null);
1077
+ const sent = await requestEmailVerification(loginEmail);
1078
+ if (sent.success) {
1079
+ setVerifyCooldownUntil(Date.now() + 30_000);
1080
+ if (sent.verificationToken)
1081
+ setVerificationToken(sent.verificationToken);
1082
+ setLoginNotice('Verification email resent.');
1083
+ }
1084
+ else {
1085
+ setLoginError(formatAuthError(sent));
1086
+ }
1087
+ setLoginBusy(false);
1088
+ }, 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' }))] }))] })] })] }) }) }));
808
1089
  }
809
1090
  if (shouldForceLogin && !isLoginRoute) {
810
1091
  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..." })] }) }) }));
@@ -815,6 +1096,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
815
1096
  }, 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" })] }) }) }));
816
1097
  }
817
1098
  const showGuidedWorkspaceSetup = isCreateWorkspaceIntent
1099
+ || (isSetupRoute && Boolean(pendingCloudWorkspaceImport))
818
1100
  || (isSetupRoute && setupGateActive && (hasSetupReadError || needsGlobalSetup || needsWorkspaceSetup));
819
1101
  if (showGuidedWorkspaceSetup) {
820
1102
  const isError = !isCreateWorkspaceIntent && (hasSetupReadError || setupStep === 'error');
@@ -823,25 +1105,52 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
823
1105
  ? t('setup.headingCheckFailed')
824
1106
  : (isGlobal
825
1107
  ? t('setup.headingGlobalRequired')
826
- : (isCreateWorkspaceIntent ? 'Create Workspace' : t('setup.headingWorkspaceRequired')));
1108
+ : t('setup.headingWorkspaceRequired'));
827
1109
  const subtitle = isError
828
1110
  ? t('setup.subtitleUnreadable')
829
1111
  : isGlobal
830
1112
  ? t('setup.subtitleGlobalRequired')
831
- : (isCreateWorkspaceIntent
832
- ? 'Set up the new workspace before it is created.'
833
- : t('setup.subtitleWorkspaceRequired'));
834
- const workspaceLabel = setupModeChoice === 'operations'
835
- ? t('setup.workspaceTermMission')
836
- : t('setup.workspaceTermProject');
1113
+ : null;
1114
+ const workspaceLabel = 'Workspace';
1115
+ const isLocalWorkspaceSetup = isCloudRuntime || workspaceSetupSource !== 'cloud';
1116
+ const isWorkspaceDetailsPage = isLocalWorkspaceSetup && workspaceSetupPage === 'details';
1117
+ const isWorkspaceStarterPage = isLocalWorkspaceSetup && workspaceSetupPage === 'starter';
1118
+ const showWorkspaceSourceSelector = !isCloudRuntime
1119
+ && canUseCloudWorkspaceSetup
1120
+ && isWorkspaceDetailsPage
1121
+ && !pendingCloudWorkspaceImport;
1122
+ const runtimeIcon = runtimeCapabilities?.runtimeMode === 'cloud' ? Cloud : LaptopMinimal;
1123
+ const starterCategoryItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.categories) : '';
1124
+ const starterTypeItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.types) : '';
1125
+ const starterPriorityItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.priorities) : '';
1126
+ const setupPrimaryActionLabel = setupBusy
1127
+ ? t('setup.saving')
1128
+ : wantsCloudWorkspaceSetup
1129
+ ? (pendingCloudWorkspaceImport
1130
+ ? (isCloudWorkspaceImportErrored ? 'Retry Import' : 'Importing Workspace...')
1131
+ : 'Connect Workspace')
1132
+ : isWorkspaceDetailsPage
1133
+ ? 'Continue'
1134
+ : isWorkspaceStarterPage
1135
+ ? 'Create Workspace'
1136
+ : 'Connect Workspace';
837
1137
  return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
838
1138
  backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
839
- }, 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: () => {
840
- navigate(`/login?mode=login&next=${encodeURIComponent('/setup?step=workspace&source=cloud')}`, { replace: true });
841
- }, 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) => {
842
- const isSelected = selectedSetupPack?.id === pack.id;
843
- 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));
844
- }), 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 () => {
1139
+ }, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.setupHeaderRow, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsxs("h1", { className: authStyles.loginTitle, children: ["TASKFORCE ", _jsx("span", { className: authStyles.loginTitleAccent, children: "HQ" })] })] }), runtimeCapabilities && (_jsx("span", { className: authStyles.runtimeIconBadge, title: runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', "aria-label": runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', children: React.createElement(runtimeIcon, { size: 16, 'aria-hidden': true }) }))] }), _jsx("p", { className: authStyles.loginSubtitle, children: _jsx("strong", { children: heading }) }), subtitle ? _jsx("p", { className: authStyles.loginSubtitle, children: subtitle }) : null, setupNotice && _jsx("p", { className: authStyles.loginSubtitle, children: setupNotice }), setupError && _jsx("p", { className: authStyles.loginError, children: setupError }), isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "core", checked: setupModeChoice === 'core', onChange: () => setSetupModeChoice('core'), disabled: setupBusy }), _jsx("span", { children: t('setup.coreModeOption', { workspaceLabel }) })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "operations", checked: setupModeChoice === 'operations', onChange: () => setSetupModeChoice('operations'), disabled: setupBusy }), _jsx("span", { children: t('setup.operationsModeOption') })] })] })), !isError && !isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [showWorkspaceSourceSelector && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "local", checked: workspaceSetupSource === 'local', onChange: () => setWorkspaceSetupSource('local'), disabled: setupBusy }), _jsxs("span", { children: ["Create new local ", workspaceLabel.toLowerCase()] })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "cloud", checked: workspaceSetupSource === 'cloud', onChange: () => setWorkspaceSetupSource('cloud'), disabled: setupBusy }), _jsxs("span", { children: ["Sync existing cloud ", workspaceLabel.toLowerCase()] })] })] })), wantsCloudWorkspaceSetup ? (_jsxs("div", { className: authStyles.optionGroup, children: [!pendingCloudWorkspaceImport && !canUseCloudWorkspaceSetup && (_jsx("p", { className: authStyles.loginError, children: "Cloud workspace sync is unavailable right now. Switch back to local setup or refresh runtime configuration." })), !pendingCloudWorkspaceImport && canUseCloudWorkspaceSetup && !isAuthenticated && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.inlineHint, children: "Sign in to choose one of your cloud projects." }), _jsxs("div", { className: authStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, disabled: setupBusy, onClick: () => {
1140
+ navigate(`/login?mode=login&next=${encodeURIComponent(cloudWorkspaceSelectionPath)}`, { replace: true });
1141
+ }, children: "Sign In" }), directRegistrationEnabled && (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: () => {
1142
+ navigate(`/login?mode=register&next=${encodeURIComponent(cloudWorkspaceSelectionPath)}`, { replace: true });
1143
+ }, children: "Register" }))] })] })), !pendingCloudWorkspaceImport && canUseCloudWorkspaceSetup && isAuthenticated && (_jsxs(_Fragment, { children: [_jsxs("select", { className: styles.input, value: cloudWorkspaceSelection, onChange: (event) => setCloudWorkspaceSelection(event.target.value), disabled: setupBusy || cloudWorkspaceLoading || cloudWorkspaceOptions.length === 0, children: [cloudWorkspaceOptions.length === 0 && (_jsx("option", { value: "", children: cloudWorkspaceLoading ? 'Loading cloud projects...' : 'No cloud projects found' })), cloudWorkspaceOptions.map((workspace) => (_jsxs("option", { value: workspace.id, children: [workspace.name, " (", workspace.id, ")"] }, workspace.id)))] }), _jsx("div", { className: authStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, disabled: setupBusy || cloudWorkspaceLoading, onClick: () => void fetchCloudWorkspaceOptions(), children: "Refresh Cloud Projects" }) }), cloudWorkspaceError && _jsx("p", { className: authStyles.loginError, children: cloudWorkspaceError })] })), pendingCloudWorkspaceImport && (_jsxs(_Fragment, { children: [_jsxs("p", { className: authStyles.inlineHint, children: ["Importing \"", pendingCloudWorkspaceImport.name, "\" from cloud into local."] }), _jsx("div", { className: authStyles.optionGroup, children: cloudWorkspaceImportProgressLines.map((line) => (_jsx("p", { className: authStyles.inlineHint, children: line }, line))) }), _jsx("p", { className: authStyles.inlineHint, children: cloudWorkspaceImportFinalizing
1144
+ ? 'Finishing setup and opening your workspace.'
1145
+ : (workspaceSyncSummary || 'Pulling tasks, taxonomies, and settings into local.') }), !cloudWorkspaceImportFinalizing && workspaceSyncRecommendedAction && (_jsx("p", { className: authStyles.inlineHint, children: workspaceSyncRecommendedAction }))] }))] })) : (_jsxs(_Fragment, { children: [isWorkspaceDetailsPage && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.loginSubtitle, children: "Workspace name" }), _jsx("input", { className: styles.input, type: "text", value: workspaceNameInput, placeholder: "Workspace name", onChange: (event) => setWorkspaceNameInput(event.target.value), disabled: setupBusy }), _jsx("p", { className: authStyles.loginSubtitle, children: "Workspace description" }), _jsx("textarea", { className: styles.textarea, value: workspaceDescriptionInput, placeholder: "Workspace description (optional)", onChange: (event) => setWorkspaceDescriptionInput(event.target.value), disabled: setupBusy, rows: 4 })] })), isWorkspaceStarterPage && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "What are you working on?" }), _jsxs("select", { className: styles.input, value: selectedSetupPackId, onChange: (event) => setSelectedSetupPackId(event.target.value), disabled: setupBusy || setupLibraryLoading || setupLibraryPacks.length === 0, children: [setupLibraryPacks.length === 0 && (_jsx("option", { value: "", children: setupLibraryLoading ? 'Loading starter options...' : 'No starter options available' })), setupLibraryPacks.map((pack) => (_jsx("option", { value: pack.id, children: pack.label }, pack.id)))] }), selectedSetupPack ? (_jsx(_Fragment, { children: selectedSetupPack.description ? (_jsx("p", { className: authStyles.inlineHint, children: selectedSetupPack.description })) : null })) : null, _jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "Apply Starter Sections" }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.categories, onChange: () => setSetupTaxonomySections((current) => ({ ...current, categories: !current.categories })), disabled: setupBusy || !selectedSetupPack?.categories.length }), _jsx("span", { children: "Categories" })] }), starterCategoryItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterCategoryItems })) : null, _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.types, onChange: () => setSetupTaxonomySections((current) => ({ ...current, types: !current.types })), disabled: setupBusy || !selectedSetupPack?.types.length }), _jsx("span", { children: "Task Types" })] }), starterTypeItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterTypeItems })) : null, _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.priorities, onChange: () => setSetupTaxonomySections((current) => ({ ...current, priorities: !current.priorities })), disabled: setupBusy || !selectedSetupPack?.priorities.length }), _jsx("span", { children: "Priorities" })] }), starterPriorityItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterPriorityItems })) : null] }), setupLibraryLoading ? (_jsx("p", { className: authStyles.inlineHint, children: "Loading starter options\u2026" })) : null, setupLibraryError ? (_jsx("p", { className: authStyles.loginError, children: setupLibraryError })) : null] })), !isCloudRuntime && canUseCloudWorkspaceSetup && isAuthenticated && isWorkspaceDetailsPage && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: syncNewLocalWorkspaceToCloud, onChange: (event) => setSyncNewLocalWorkspaceToCloud(event.target.checked), disabled: setupBusy }), _jsx("span", { children: "Sync to cloud after setup" })] }), _jsx("p", { className: authStyles.inlineHint, children: "When enabled, this project will connect to cloud sync as soon as setup finishes." })] }))] }))] })), _jsxs("div", { className: authStyles.authModeLinks, children: [isWorkspaceStarterPage ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
1146
+ setSetupError(null);
1147
+ setSetupNotice(null);
1148
+ setWorkspaceSetupPage('details');
1149
+ }, children: "Back" })) : isCreateWorkspaceIntent ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
1150
+ setSetupError(null);
1151
+ setSetupNotice(null);
1152
+ navigate('/', { replace: true });
1153
+ }, children: "Cancel" })) : runtimeMode === 'cloud' ? (_jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: t('setup.signOut') })) : null, isGlobal ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy || (isCloudWorkspaceSetupFlow && Boolean(pendingCloudWorkspaceImport) && !isCloudWorkspaceImportErrored), onClick: async () => {
845
1154
  setSetupBusy(true);
846
1155
  setSetupNotice(null);
847
1156
  setSetupError(null);
@@ -867,11 +1176,32 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
867
1176
  finally {
868
1177
  setSetupBusy(false);
869
1178
  }
870
- }, children: setupBusy ? t('setup.saving') : t('setup.saveGlobalSetup') })) : !isError ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: async () => {
871
- setSetupBusy(true);
872
- setSetupNotice(null);
873
- setSetupError(null);
874
- if (!isCloudRuntime && workspaceSetupSource === 'cloud' && canUseCloudWorkspaceSetup) {
1179
+ }, children: setupBusy ? t('setup.saving') : t('setup.saveGlobalSetup') })) : !isError ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy
1180
+ || (isCloudWorkspaceSetupFlow && !pendingCloudWorkspaceImport && !isAuthenticated), onClick: async () => {
1181
+ if (isCloudWorkspaceSetupFlow && pendingCloudWorkspaceImport) {
1182
+ if (!isCloudWorkspaceImportErrored)
1183
+ return;
1184
+ setSetupBusy(true);
1185
+ setSetupNotice(`Retrying import for ${pendingCloudWorkspaceImport.name}...`);
1186
+ setSetupError(null);
1187
+ try {
1188
+ await retryWorkspaceCloudSync();
1189
+ }
1190
+ catch {
1191
+ setSetupNotice(null);
1192
+ setSetupError(`Failed to retry import for "${pendingCloudWorkspaceImport.name}".`);
1193
+ }
1194
+ finally {
1195
+ setSetupBusy(false);
1196
+ }
1197
+ return;
1198
+ }
1199
+ if (wantsCloudWorkspaceSetup) {
1200
+ if (!canUseCloudWorkspaceSetup) {
1201
+ setSetupError('Cloud workspace sync is unavailable right now.');
1202
+ setSetupBusy(false);
1203
+ return;
1204
+ }
875
1205
  if (!isAuthenticated) {
876
1206
  setSetupError('Sign in is required before syncing a cloud project.');
877
1207
  setSetupBusy(false);
@@ -929,14 +1259,40 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
929
1259
  pullCursor: null
930
1260
  });
931
1261
  await refreshSetupContext();
932
- setSetupNotice(`Cloud project synced setup ready: ${selectedWorkspace.name}`);
1262
+ setPendingCloudWorkspaceImport({
1263
+ workspaceId: workspaceIdForSync,
1264
+ name: selectedWorkspace.name || selectedWorkspace.id
1265
+ });
1266
+ setCloudWorkspaceImportFinalizing(false);
1267
+ setSetupNotice(`Importing ${selectedWorkspace.name || selectedWorkspace.id} from cloud...`);
1268
+ setSetupBusy(false);
1269
+ return;
933
1270
  }
934
- else {
1271
+ if (isWorkspaceDetailsPage) {
1272
+ const normalizedWorkspaceName = workspaceNameInput.trim();
1273
+ if (!normalizedWorkspaceName) {
1274
+ setSetupError('Workspace name is required before continuing.');
1275
+ return;
1276
+ }
1277
+ setSetupError(null);
1278
+ setSetupNotice(null);
1279
+ setWorkspaceSetupPage('starter');
1280
+ return;
1281
+ }
1282
+ if (isWorkspaceStarterPage) {
1283
+ // fall through to final create/save behavior below
1284
+ }
1285
+ setSetupBusy(true);
1286
+ setSetupNotice(null);
1287
+ setSetupError(null);
1288
+ {
935
1289
  const creatingNewWorkspace = isCreateWorkspaceIntent || setupState?.workspaceSetupState === 'missing';
936
- const createResult = isCreateWorkspaceIntent
1290
+ const shouldCreateWorkspace = isCreateWorkspaceIntent
1291
+ || (workspaceSwitchingEnabled && creatingNewWorkspace);
1292
+ const createResult = shouldCreateWorkspace
937
1293
  ? await createWorkspace(workspaceNameInput, workspaceDescriptionInput || undefined)
938
1294
  : null;
939
- const saveResult = isCreateWorkspaceIntent
1295
+ const saveResult = shouldCreateWorkspace
940
1296
  ? null
941
1297
  : await saveWorkspaceProfile({
942
1298
  workspaceId: creatingNewWorkspace ? undefined : setupState?.workspaceId,
@@ -1018,11 +1374,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1018
1374
  }
1019
1375
  }
1020
1376
  setSetupBusy(false);
1021
- }, 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: () => {
1022
- setSetupError(null);
1023
- setSetupNotice(null);
1024
- navigate('/', { replace: true });
1025
- }, children: "Cancel" })) : runtimeMode === 'cloud' ? (_jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: t('setup.signOut') })) : null] })] }) }) }));
1377
+ }, children: setupPrimaryActionLabel })) : (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: refreshSetupContext, children: t('setup.retrySetupCheck') }))] })] }) }) }));
1026
1378
  }
1027
1379
  return (_jsx(StandaloneLayout, { ...taskforceState, onHeaderMouseDown: onHeaderMouseDown, isDragging: isDragging, onClose: onClose }));
1028
1380
  }