@taskforcehq/taskforce 0.3.303 → 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 (165) hide show
  1. package/dist/Taskforce.module.css +102 -5
  2. package/dist/TaskforceCore.js +514 -212
  3. package/dist/TaskforceCore.test.js +1172 -133
  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/TaskStatusActions.d.ts +6 -1
  21. package/dist/components/task/TaskStatusActions.js +8 -3
  22. package/dist/components/task/TaskStatusActions.test.js +9 -0
  23. package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
  24. package/dist/components/task/taskKanbanDropRules.js +187 -0
  25. package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
  26. package/dist/components/task/taskKanbanDropRules.test.js +114 -0
  27. package/dist/components/ui/Modal.d.ts +1 -1
  28. package/dist/components/ui/Modal.js +1 -0
  29. package/dist/components/views/AppShellHeader.js +5 -1
  30. package/dist/components/views/AppShellHeader.test.js +4 -0
  31. package/dist/components/views/PlanComparisonPage.d.ts +3 -2
  32. package/dist/components/views/PlanComparisonPage.js +4 -2
  33. package/dist/components/views/PlanComparisonPage.test.js +4 -0
  34. package/dist/components/views/PlansPage.d.ts +5 -1
  35. package/dist/components/views/PlansPage.js +39 -12
  36. package/dist/components/views/PlansPage.test.js +59 -2
  37. package/dist/components/views/StandaloneLayout.js +113 -38
  38. package/dist/components/views/WidgetView.js +11 -2
  39. package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
  40. package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
  41. package/dist/core/PlanEntitlementService.d.ts +3 -1
  42. package/dist/core/PlanEntitlementService.js +61 -11
  43. package/dist/core/SignupMonetizationSettings.test.js +37 -2
  44. package/dist/core/TaskActivityService.d.ts +4 -1
  45. package/dist/core/TaskActivityService.js +45 -0
  46. package/dist/core/TaskAuditTimeline.test.js +52 -1
  47. package/dist/core/Taskforce.d.ts +15 -11
  48. package/dist/core/Taskforce.js +43 -2
  49. package/dist/core/WorkspacePlanMode.test.js +65 -0
  50. package/dist/documentReviews/service.d.ts +1 -1
  51. package/dist/documentReviews/types.d.ts +1 -1
  52. package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
  53. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
  54. package/dist/hooks/sync/auth.d.ts +37 -0
  55. package/dist/hooks/sync/auth.js +61 -0
  56. package/dist/hooks/sync/bootstrap.d.ts +34 -0
  57. package/dist/hooks/sync/bootstrap.js +52 -0
  58. package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
  59. package/dist/hooks/sync/orchestratorShared.js +209 -0
  60. package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
  61. package/dist/hooks/sync/orchestratorShared.test.js +49 -0
  62. package/dist/hooks/sync/realtime.d.ts +25 -0
  63. package/dist/hooks/sync/realtime.js +60 -0
  64. package/dist/hooks/sync/recovery.d.ts +69 -0
  65. package/dist/hooks/sync/recovery.js +118 -0
  66. package/dist/hooks/sync/transfers.d.ts +122 -0
  67. package/dist/hooks/sync/transfers.js +431 -0
  68. package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
  69. package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
  70. package/dist/hooks/ui/useResourceExport.d.ts +3 -1
  71. package/dist/hooks/ui/useResourceExport.js +5 -2
  72. package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
  73. package/dist/hooks/useRealtimeSync.js +2 -1
  74. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
  75. package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
  76. package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
  77. package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
  78. package/dist/hooks/useSyncOrchestrator.js +314 -873
  79. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +568 -3
  80. package/dist/hooks/useTaskMutations.js +45 -6
  81. package/dist/hooks/useTaskMutations.test.js +147 -0
  82. package/dist/hooks/useTaskforce.d.ts +8 -2
  83. package/dist/hooks/useTaskforce.js +285 -812
  84. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  85. package/dist/hooks/useWorkspaceSyncController.js +22 -10
  86. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
  87. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
  88. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
  89. package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
  90. package/dist/hooks/workspace/workspaceLocalState.js +100 -0
  91. package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
  92. package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
  93. package/dist/localization/locales/en-US.js +2 -2
  94. package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
  95. package/dist/mcp/canonicalAssetHelpers.js +259 -0
  96. package/dist/mcp/clientIntegrations.d.ts +12 -1
  97. package/dist/mcp/clientIntegrations.js +161 -6
  98. package/dist/mcp/clientIntegrations.test.js +76 -4
  99. package/dist/mcp/documentHelpers.d.ts +100 -0
  100. package/dist/mcp/documentHelpers.js +161 -0
  101. package/dist/mcp/runtime.js +174 -476
  102. package/dist/mcp/runtime.test.js +81 -8
  103. package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
  104. package/dist/mcp/taskAttachmentHelpers.js +60 -0
  105. package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
  106. package/dist/mcp/taskAttachmentTypes.js +1 -0
  107. package/dist/plugins/vite.js +1 -1
  108. package/dist/runtime/appGateDefinitions.d.ts +1 -1
  109. package/dist/runtime/appGateDefinitions.js +1 -1
  110. package/dist/server/index.d.ts +1 -0
  111. package/dist/server/index.js +46 -3
  112. package/dist/server/index.test.js +12 -1
  113. package/dist/server/routes/admin.js +13 -19
  114. package/dist/server/routes/auth.js +37 -2
  115. package/dist/server/routes/billing.js +18 -67
  116. package/dist/server/routes/billing.test.js +43 -0
  117. package/dist/server/routes/shared.d.ts +30 -0
  118. package/dist/server/routes/shared.js +26 -0
  119. package/dist/server/routes/sync.integration.test.js +20 -0
  120. package/dist/server/routes/sync.js +110 -237
  121. package/dist/server/routes/tasks.js +49 -7
  122. package/dist/server/routes.js +8 -19
  123. package/dist/server/routes.test.js +243 -5
  124. package/dist/shared/taskActor.d.ts +10 -0
  125. package/dist/shared/taskActor.js +1 -0
  126. package/dist/sync/collaborationSyncPayload.js +4 -0
  127. package/dist/sync/collaborationSyncPayload.test.js +8 -0
  128. package/dist/sync/collaborationSyncState.d.ts +3 -1
  129. package/dist/sync/collaborationSyncState.js +19 -0
  130. package/dist/sync/contentSyncState.js +12 -10
  131. package/dist/sync/syncApplyHandlers.d.ts +87 -1
  132. package/dist/sync/syncApplyHandlers.js +370 -8
  133. package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
  134. package/dist/sync/syncApplyHandlers.test.js +377 -0
  135. package/dist/sync/syncService.d.ts +8 -1
  136. package/dist/sync/syncService.js +36 -2
  137. package/dist/sync/syncService.test.js +48 -0
  138. package/dist/sync/workspacePullFeed.d.ts +1 -0
  139. package/dist/sync/workspacePullFeed.js +66 -59
  140. package/dist/sync/workspacePullFeed.test.js +72 -4
  141. package/dist/sync/workspaceSyncModel.d.ts +22 -1
  142. package/dist/sync/workspaceSyncModel.js +65 -0
  143. package/dist/sync/workspaceSyncModel.test.js +119 -0
  144. package/dist/types.d.ts +2 -10
  145. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
  146. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-C_TmXZwA.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
  147. package/dist/ui/assets/{DocumentWorkspace-C3GyzrWm.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
  148. package/dist/ui/assets/{InitiativesModule-4-Rh2K2n.js → InitiativesModule-BjR6iBf9.js} +1 -1
  149. package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
  150. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
  151. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
  152. package/dist/ui/assets/{WorkflowsModule-CGk9s3NJ.js → WorkflowsModule-DnFqdUME.js} +1 -1
  153. package/dist/ui/assets/index-DXUdtH1B.js +6 -0
  154. package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
  155. package/dist/ui/assets/{vendor-icons-pWocEipT.js → vendor-icons-I7ZwG1z_.js} +1 -1
  156. package/dist/ui/index.html +4 -4
  157. package/dist/utils/taskActivity.js +4 -0
  158. package/dist/utils/taskActivity.test.js +25 -1
  159. package/package.json +3 -1
  160. package/scripts/playwright-auth.sh +4 -0
  161. package/dist/ui/assets/AgentsModule-CpsTmrIW.js +0 -1
  162. package/dist/ui/assets/PlansPage-BP7AYOqr.js +0 -1
  163. package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +0 -8
  164. package/dist/ui/assets/index-CLIMgR6g.js +0 -6
  165. package/dist/ui/assets/index-DneETxcu.css +0 -1
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import { MemoryRouter, Navigate, useInRouterContext, useLocation, useNavigate } from 'react-router-dom';
4
4
  import { useTaskforce } from './hooks/useTaskforce';
5
5
  import { WidgetView } from './components/views/WidgetView';
@@ -14,6 +14,7 @@ import logoLight from './assets/branding/logos/logo_light.svg';
14
14
  import logoDark from './assets/branding/logos/logo_dark.svg';
15
15
  import taskforceBg from './assets/images/taskforce.png';
16
16
  import { isLightThemeFamily } from './utils/theme';
17
+ import { Cloud, LaptopMinimal } from 'lucide-react';
17
18
  function isDebugModeEnabled() {
18
19
  const runtimeOverride = globalThis.__DEBUG_MODE__;
19
20
  if (typeof runtimeOverride === 'boolean')
@@ -27,6 +28,12 @@ function defaultSetupTaxonomySections(pack) {
27
28
  priorities: Boolean(pack?.priorities?.length),
28
29
  };
29
30
  }
31
+ function formatStarterSectionItems(values) {
32
+ return (Array.isArray(values) ? values : [])
33
+ .map((entry) => String(entry.label || entry.value || '').trim())
34
+ .filter(Boolean)
35
+ .join(', ');
36
+ }
30
37
  function buildPlansPath(params = {}) {
31
38
  const query = new URLSearchParams();
32
39
  query.set('screen', 'plans');
@@ -54,7 +61,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
54
61
  const [authMode, setAuthMode] = useState('login');
55
62
  const [verificationToken, setVerificationToken] = useState('');
56
63
  const [pendingVerificationWorkspaceSetupRequired, setPendingVerificationWorkspaceSetupRequired] = useState(false);
64
+ const [pendingVerificationCommercialState, setPendingVerificationCommercialState] = useState(null);
57
65
  const [pendingVerificationRedirectPath, setPendingVerificationRedirectPath] = useState(null);
66
+ const [pendingPostAuthRedirect, setPendingPostAuthRedirect] = useState(null);
58
67
  const [resetToken, setResetToken] = useState('');
59
68
  const [inviteToken, setInviteToken] = useState('');
60
69
  const [inviteEmail, setInviteEmail] = useState(null);
@@ -75,11 +84,14 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
75
84
  const [workspaceNameInput, setWorkspaceNameInput] = useState('');
76
85
  const [workspaceDescriptionInput, setWorkspaceDescriptionInput] = useState('');
77
86
  const [workspaceSetupSource, setWorkspaceSetupSource] = useState('local');
87
+ const [workspaceSetupPage, setWorkspaceSetupPage] = useState('details');
78
88
  const [cloudWorkspaceOptions, setCloudWorkspaceOptions] = useState([]);
79
89
  const [cloudWorkspaceSelection, setCloudWorkspaceSelection] = useState('');
80
90
  const [cloudWorkspaceLoading, setCloudWorkspaceLoading] = useState(false);
81
91
  const [cloudWorkspaceError, setCloudWorkspaceError] = useState(null);
82
92
  const [syncNewLocalWorkspaceToCloud, setSyncNewLocalWorkspaceToCloud] = useState(false);
93
+ const [pendingCloudWorkspaceImport, setPendingCloudWorkspaceImport] = useState(null);
94
+ const [cloudWorkspaceImportFinalizing, setCloudWorkspaceImportFinalizing] = useState(false);
83
95
  const fallbackSetupLibraryPacks = useMemo(() => listBuiltInTaxonomyLibraryPacks(), []);
84
96
  const [setupLibraryPacks, setSetupLibraryPacks] = useState(fallbackSetupLibraryPacks);
85
97
  const [selectedSetupPackId, setSelectedSetupPackId] = useState(() => (fallbackSetupLibraryPacks.find((pack) => pack.isDefaultStarter)?.id || fallbackSetupLibraryPacks[0]?.id || ''));
@@ -88,6 +100,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
88
100
  const [setupLibraryError, setSetupLibraryError] = useState(null);
89
101
  const [bootstrapRetryBusy, setBootstrapRetryBusy] = useState(false);
90
102
  const [accountAccessGate, setAccountAccessGate] = useState(null);
103
+ const [accountAccessGateResolved, setAccountAccessGateResolved] = useState(false);
91
104
  // Initialize the hook
92
105
  const taskforceState = useTaskforce({
93
106
  config,
@@ -95,7 +108,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
95
108
  onTaskCountChange,
96
109
  onClose
97
110
  });
98
- const { runtimeMode, workspaceSwitchingEnabled, currentWorkspaceId, configLoaded, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, hasBetaAccess, authSessionResolved, workspaceBootstrapPending, bootstrapPhase, bootstrapError, setupState, runtimeCapabilities, refreshSetupContext, retryBootstrapChecks, createWorkspace, saveWorkspaceProfile, fetchWorkspaces, applyWorkspaceSyncStateSnapshot, settingsModel, fetchTasks, loginWithCredentials, registerWithCredentials, requestEmailVerification, confirmEmailVerification, requestPasswordReset, confirmPasswordReset, inspectInviteAcceptance, acceptInviteWithToken, joinInviteWithToken, authUserEmail, currentTheme, logout } = taskforceState;
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;
99
112
  const selectedSetupPack = useMemo(() => {
100
113
  return setupLibraryPacks.find((pack) => pack.id === selectedSetupPackId)
101
114
  || setupLibraryPacks.find((pack) => pack.isDefaultStarter)
@@ -118,6 +131,46 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
118
131
  const isLoginRoute = location.pathname === '/login';
119
132
  const isSetupRoute = location.pathname === '/setup';
120
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
+ ]);
121
174
  const shouldForceLogin = isLikelyCloudAppHost
122
175
  ? ((!isAuthenticated || authBlocked) && !isPricingRoute && !isPlansRoute && !isPlansScreen)
123
176
  : (((isCloudAuthRequired && !isAuthenticated) || shouldForceForAuthBlocked) && !isPricingRoute && !isPlansRoute && !isPlansScreen);
@@ -131,6 +184,23 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
131
184
  && !isPlansRoute
132
185
  && !isPlansScreen;
133
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]);
134
204
  const setupStep = useMemo(() => String(new URLSearchParams(location.search).get('step') || '').trim().toLowerCase(), [location.search]);
135
205
  const setupIntent = useMemo(() => String(new URLSearchParams(location.search).get('intent') || '').trim().toLowerCase(), [location.search]);
136
206
  const isCreateWorkspaceIntent = isSetupRoute && setupStep === 'workspace' && setupIntent === 'create-workspace';
@@ -198,9 +268,11 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
198
268
  useEffect(() => {
199
269
  if (runtimeMode !== 'cloud' || !isAuthenticated || !authSessionResolved) {
200
270
  setAccountAccessGate(null);
271
+ setAccountAccessGateResolved(false);
201
272
  return;
202
273
  }
203
274
  setAccountAccessGate(null);
275
+ setAccountAccessGateResolved(false);
204
276
  let cancelled = false;
205
277
  (async () => {
206
278
  try {
@@ -209,11 +281,17 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
209
281
  credentials: 'include'
210
282
  });
211
283
  const payload = await res.json().catch(() => ({}));
212
- if (cancelled || !res.ok)
284
+ if (cancelled)
285
+ return;
286
+ if (!res.ok) {
287
+ setAccountAccessGate(null);
288
+ setAccountAccessGateResolved(true);
213
289
  return;
290
+ }
214
291
  const gate = String(payload?.gate || '').trim().toLowerCase();
215
292
  if (gate !== 'ok' && gate !== 'plan_selection_required' && gate !== 'checkout_pending' && gate !== 'misconfigured' && gate !== 'missing_entitlement') {
216
293
  setAccountAccessGate(null);
294
+ setAccountAccessGateResolved(true);
217
295
  return;
218
296
  }
219
297
  setAccountAccessGate({
@@ -222,10 +300,13 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
222
300
  canAccessPlans: payload?.canAccessPlans !== false,
223
301
  message: typeof payload?.message === 'string' ? payload.message : null
224
302
  });
303
+ setAccountAccessGateResolved(true);
225
304
  }
226
305
  catch {
227
- if (!cancelled)
306
+ if (!cancelled) {
228
307
  setAccountAccessGate(null);
308
+ setAccountAccessGateResolved(true);
309
+ }
229
310
  }
230
311
  })();
231
312
  return () => {
@@ -258,6 +339,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
258
339
  useEffect(() => {
259
340
  if (!isSetupRoute || setupStep !== 'workspace')
260
341
  return;
342
+ setWorkspaceSetupPage('details');
261
343
  const source = String(new URLSearchParams(location.search).get('source') || '').trim().toLowerCase();
262
344
  if (source === 'cloud' && canUseCloudWorkspaceSetup) {
263
345
  setWorkspaceSetupSource('cloud');
@@ -355,6 +437,63 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
355
437
  useEffect(() => {
356
438
  setSetupTaxonomySections(defaultSetupTaxonomySections(selectedSetupPack));
357
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
+ ]);
358
497
  const nextPath = useMemo(() => {
359
498
  const raw = new URLSearchParams(location.search).get('next') || '/';
360
499
  if (!raw.startsWith('/'))
@@ -363,7 +502,29 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
363
502
  return '/';
364
503
  return raw;
365
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]);
366
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
+ }, []);
367
528
  const resolvePostRegistrationPath = useCallback((input) => {
368
529
  if (input?.planSelectionRequired || input?.commercialState === 'pending_plan_selection') {
369
530
  return planSelectionPath;
@@ -408,26 +569,6 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
408
569
  const query = params.toString();
409
570
  navigate(`/login${query ? `?${query}` : ''}`, { replace: true });
410
571
  }, [directRegistrationEnabled, location.search, navigate]);
411
- const routeToWorkspaceSetupIfNeeded = useCallback(async (hintWorkspaceSetupRequired) => {
412
- if (!workspaceSwitchingEnabled)
413
- return false;
414
- const workspacesResult = await fetchWorkspaces();
415
- if (workspacesResult.success) {
416
- const workspaceCount = Array.isArray(workspacesResult.workspaces)
417
- ? workspacesResult.workspaces.length
418
- : 0;
419
- if (workspaceCount === 0) {
420
- navigate('/setup?step=workspace', { replace: true });
421
- return true;
422
- }
423
- return false;
424
- }
425
- if (hintWorkspaceSetupRequired) {
426
- navigate('/setup?step=workspace', { replace: true });
427
- return true;
428
- }
429
- return false;
430
- }, [fetchWorkspaces, navigate, workspaceSwitchingEnabled]);
431
572
  useEffect(() => {
432
573
  if (!isLoginRoute)
433
574
  return;
@@ -445,8 +586,40 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
445
586
  if (isLoginRoute && loginRouteMode === 'verify')
446
587
  return;
447
588
  setPendingVerificationWorkspaceSetupRequired(false);
589
+ setPendingVerificationCommercialState(null);
448
590
  setPendingVerificationRedirectPath(null);
449
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]);
450
623
  useEffect(() => {
451
624
  if (!isLoginRoute || authMode !== 'invite' || !inviteToken.trim())
452
625
  return;
@@ -533,6 +706,17 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
533
706
  const shouldStayOnInviteRoute = isLoginRoute
534
707
  && authMode === 'invite'
535
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
+ }
536
720
  if (isLoginRoute && isAuthenticated && !shouldStayOnInviteRoute)
537
721
  navigate(nextPath, { replace: true });
538
722
  }
@@ -544,6 +728,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
544
728
  }
545
729
  return;
546
730
  }
731
+ if (isLoginRoute && !isAuthenticated) {
732
+ return;
733
+ }
547
734
  if (shouldForceAccountAccessGate) {
548
735
  if (!isPlansScreen) {
549
736
  const gate = encodeURIComponent(String(accountAccessGate?.gate || 'plan_selection_required'));
@@ -551,6 +738,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
551
738
  }
552
739
  return;
553
740
  }
741
+ if (isSetupRoute && pendingCloudWorkspaceImport) {
742
+ return;
743
+ }
554
744
  if (setupGateActive) {
555
745
  if (hasSetupReadError) {
556
746
  if (!isSetupRoute || setupStep !== 'error') {
@@ -566,7 +756,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
566
756
  }
567
757
  if (needsWorkspaceSetup) {
568
758
  if (!isSetupRoute || setupStep !== 'workspace') {
569
- navigate('/setup?step=workspace', { replace: true });
759
+ navigate(resolveWorkspaceSetupPath(), { replace: true });
570
760
  }
571
761
  return;
572
762
  }
@@ -589,19 +779,26 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
589
779
  isPlansRoute,
590
780
  isPlansScreen,
591
781
  accountAccessGate?.gate,
782
+ accountAccessGateResolved,
783
+ authMode,
592
784
  setupGateActive,
593
785
  setupState,
594
786
  hasSetupReadError,
595
787
  isCreateWorkspaceIntent,
788
+ pendingCloudWorkspaceImport,
596
789
  needsGlobalSetup,
597
790
  needsWorkspaceSetup,
598
791
  setupStep,
599
792
  workspaceBootstrapPending,
793
+ configLoaded,
794
+ runtimeMode,
795
+ inviteToken,
600
796
  location.pathname,
601
797
  location.search,
602
798
  location.hash,
603
799
  navigate,
604
- nextPath
800
+ nextPath,
801
+ pendingPostAuthRedirect
605
802
  ]);
606
803
  useEffect(() => {
607
804
  if (mode === 'widget')
@@ -653,18 +850,26 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
653
850
  if (isLoginRoute) {
654
851
  return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
655
852
  backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
656
- }, children: _jsxs("div", { className: authStyles.loginCard, children: [!isCloudRuntime && !shouldForceLogin && (_jsx("button", { className: authStyles.loginCloseBtn, "aria-label": "Close sign in", onClick: () => navigate(nextPath, { replace: true }), children: "\u00D7" })), _jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsxs("h1", { className: authStyles.loginTitle, children: ["TASKFORCE ", _jsx("span", { className: authStyles.loginTitleAccent, children: "HQ" })] })] }), _jsxs("p", { className: authStyles.loginSubtitle, children: [authMode === 'login' && 'Sign in with your account credentials.', authMode === 'register' && 'Create your Taskforce account.', authMode === 'verify' && 'Enter your verification token.', authMode === 'forgot' && 'Request a password reset link.', authMode === 'reset' && 'Set a new password using your reset token.', authMode === 'invite' && (inviteResolution === 'existing_user_ready'
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'
657
854
  ? `You've been invited to join ${inviteWorkspaceId || 'this workspace'}.`
658
855
  : inviteResolution === 'existing_user_signed_out'
659
856
  ? 'Sign in to join this workspace.'
660
- : 'Create your account to join this workspace.')] }), (authMode === 'login' || authMode === 'register' || authMode === 'forgot' || (authMode === 'invite' && inviteResolution === 'existing_user_signed_out')) && (_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "email", value: loginEmail, placeholder: "Email", onChange: (event) => setLoginEmail(event.target.value) })), authMode === 'register' && (_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: loginDisplayName, placeholder: "Display name", onChange: (event) => setLoginDisplayName(event.target.value) })), (authMode === 'login'
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'
661
862
  || authMode === 'register'
662
863
  || authMode === 'reset'
663
- || (authMode === 'invite' && (invitePasswordRequired || inviteResolution === 'existing_user_signed_out'))) && (_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "password", value: loginPassword, placeholder: authMode === 'reset'
664
- ? 'New password'
665
- : (authMode === 'invite'
666
- ? (invitePasswordRequired ? 'Create password' : 'Password')
667
- : 'Password'), onChange: (event) => setLoginPassword(event.target.value) })), authMode === 'verify' && (_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: verificationToken, placeholder: "Verification token", onChange: (event) => setVerificationToken(event.target.value) })), authMode === 'reset' && (_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: resetToken, placeholder: "Reset token", onChange: (event) => setResetToken(event.target.value) })), authMode === 'invite' && (_jsxs(_Fragment, { children: [_jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "text", value: inviteToken, placeholder: "Invite token", onChange: (event) => setInviteToken(event.target.value) }), inviteEmail && (_jsxs("p", { className: authStyles.loginSubtitle, children: ["Invite for: ", _jsx("strong", { children: inviteEmail }), inviteWorkspaceId ? ` · Workspace: ${inviteWorkspaceId}` : ''] }))] })), loginNotice && _jsx("p", { className: authStyles.loginSubtitle, children: loginNotice }), loginError && _jsx("p", { className: authStyles.loginError, children: loginError }), authMode === 'login' && loginErrorCode === 'EMAIL_NOT_VERIFIED' && loginEmail.trim() && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || verifyCooldownActive, onClick: async () => {
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 () => {
668
873
  setLoginBusy(true);
669
874
  setLoginNotice(null);
670
875
  const sent = await requestEmailVerification(loginEmail);
@@ -681,180 +886,206 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
681
886
  setLoginError(formatAuthError(sent));
682
887
  }
683
888
  setLoginBusy(false);
684
- }, children: verifyCooldownActive ? `Resend in ${verifyCooldownSeconds}s` : 'Resend Verification Email' })), _jsx("button", { className: `${styles.submitBtn} ${authStyles.loginPrimaryBtn}`, disabled: loginBusy || (authMode === 'verify' && verifyCooldownActive) || (authMode === 'forgot' && resetCooldownActive), onClick: async () => {
685
- setLoginBusy(true);
686
- setLoginNotice(null);
687
- setLoginError(null);
688
- setLoginErrorCode(null);
689
- if (authMode === 'register' && !loginDisplayName.trim()) {
690
- setLoginError('Display name is required.');
691
- setLoginBusy(false);
692
- return;
693
- }
694
- let result = { success: false };
695
- if (authMode === 'login') {
696
- result = await loginWithCredentials(loginEmail, loginPassword);
697
- }
698
- else if (authMode === 'register') {
699
- result = await registerWithCredentials(loginEmail, loginPassword, {
700
- displayName: loginDisplayName,
701
- planId: selectedSignupPlanId || undefined,
702
- planVersionId: selectedSignupPlanVersionId || undefined,
703
- interval: selectedSignupInterval || undefined
704
- });
705
- }
706
- else if (authMode === 'verify') {
707
- result = await confirmEmailVerification(verificationToken);
708
- }
709
- else if (authMode === 'forgot') {
710
- result = await requestPasswordReset(loginEmail);
711
- }
712
- else if (authMode === 'reset') {
713
- result = await confirmPasswordReset(resetToken, loginPassword);
714
- }
715
- else if (authMode === 'invite') {
716
- if (inviteResolution === 'existing_user_signed_out') {
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') {
717
901
  result = await loginWithCredentials(loginEmail, loginPassword);
718
902
  }
719
- else {
720
- result = invitePasswordRequired
721
- ? await acceptInviteWithToken(inviteToken, loginPassword)
722
- : 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
+ });
723
910
  }
724
- }
725
- if (!result.success) {
726
- if (authMode === 'register'
727
- && (result.code === 'SIGNUP_PLAN_NOT_ENABLED'
728
- || result.code === 'SIGNUP_PLAN_VERSION_NOT_FOUND'
729
- || result.code === 'SIGNUP_PLAN_VERSION_REQUIRED')) {
730
- navigate(planSelectionPath, { replace: true });
731
- setLoginBusy(false);
732
- return;
911
+ else if (authMode === 'verify') {
912
+ result = await confirmEmailVerification(verificationToken);
733
913
  }
734
- setLoginError(formatAuthError(result));
735
- setLoginErrorCode(result.code || null);
736
- if (authMode === 'login' && (result.code === 'WORKSPACE_NOT_FOUND' || result.code === 'WORKSPACE_ID_REQUIRED')) {
737
- navigate('/setup?step=workspace', { replace: true });
738
- setLoginBusy(false);
739
- return;
914
+ else if (authMode === 'forgot') {
915
+ result = await requestPasswordReset(loginEmail);
916
+ }
917
+ else if (authMode === 'reset') {
918
+ result = await confirmPasswordReset(resetToken, loginPassword);
740
919
  }
741
- if (authMode === 'login' && result.code === 'EMAIL_NOT_VERIFIED' && loginEmail.trim()) {
742
- const sent = await requestEmailVerification(loginEmail);
743
- if (sent.success) {
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()) {
744
959
  setVerifyCooldownUntil(Date.now() + 30_000);
745
960
  setLoginMode('verify');
746
- if (sent.verificationToken)
747
- setVerificationToken(sent.verificationToken);
748
- 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.');
749
966
  setLoginError(null);
750
967
  setLoginErrorCode(null);
751
968
  }
752
969
  }
753
- }
754
- else if (authMode === 'register') {
755
- const postRegistrationPath = resolvePostRegistrationPath(result);
756
- if (result.verificationRequired) {
757
- setPendingVerificationWorkspaceSetupRequired(Boolean(result.workspaceSetupRequired));
758
- setPendingVerificationRedirectPath(postRegistrationPath);
759
- if (result.verificationToken)
760
- setVerificationToken(result.verificationToken);
761
- setVerifyCooldownUntil(Date.now() + 30_000);
762
- setLoginMode('verify');
763
- setLoginNotice(result.emailSent === false
764
- ? `Account created, but verification email failed to send. ${result.emailError || 'Try Resend Verification again.'}`
765
- : 'Account created. Check your email for verification instructions.');
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
+ }
766
994
  }
767
- else {
995
+ else if (authMode === 'verify') {
768
996
  await retryBootstrapChecks();
769
- if (await routeToWorkspaceSetupIfNeeded(result.workspaceSetupRequired)) {
997
+ const commercialOnboardingGate = pendingVerificationCommercialState === 'pending_plan_selection'
998
+ || pendingVerificationCommercialState === 'checkout_pending';
999
+ setPendingPostAuthRedirect({
1000
+ path: pendingVerificationRedirectPath || nextPath,
1001
+ workspaceSetupRequired: pendingVerificationWorkspaceSetupRequired,
1002
+ commercialOnboardingGate
1003
+ });
1004
+ }
1005
+ else if (authMode === 'forgot') {
1006
+ if (result.resetToken)
1007
+ setResetToken(result.resetToken);
1008
+ setResetCooldownUntil(Date.now() + 30_000);
1009
+ setLoginMode('reset');
1010
+ setLoginNotice(result.emailSent === false
1011
+ ? `Password reset email failed to send. ${result.emailError || 'Try again in a minute.'}`
1012
+ : 'Password reset instructions sent.');
1013
+ }
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 });
770
1043
  setLoginBusy(false);
771
1044
  return;
772
1045
  }
773
1046
  setLoginEmail('');
774
1047
  setLoginDisplayName('');
775
1048
  setLoginPassword('');
776
- navigate(postRegistrationPath, { replace: true });
1049
+ navigate(nextPath, { replace: true });
777
1050
  }
778
- }
779
- else if (authMode === 'verify') {
780
- await retryBootstrapChecks();
781
- if (await routeToWorkspaceSetupIfNeeded(pendingVerificationWorkspaceSetupRequired)) {
782
- setLoginBusy(false);
783
- return;
784
- }
785
- setLoginEmail('');
786
- setLoginPassword('');
787
- navigate(pendingVerificationRedirectPath || nextPath, { replace: true });
788
- }
789
- else if (authMode === 'forgot') {
790
- if (result.resetToken)
791
- setResetToken(result.resetToken);
792
- setResetCooldownUntil(Date.now() + 30_000);
793
- setLoginMode('reset');
794
- setLoginNotice(result.emailSent === false
795
- ? `Password reset email failed to send. ${result.emailError || 'Try again in a minute.'}`
796
- : 'Password reset instructions sent.');
797
- }
798
- else if (authMode === 'reset') {
799
- setLoginMode('login');
800
- setLoginNotice('Password updated. Sign in with your new password.');
801
- }
802
- else if (authMode === 'invite') {
803
- if (inviteResolution === 'existing_user_signed_out') {
804
- setAuthMode('invite');
805
- setLoginPassword('');
806
- setLoginNotice('Signed in. Review the invite details to continue.');
807
- setLoginBusy(false);
808
- return;
809
- }
810
- if (result.workspaceSetupRequired) {
811
- navigate('/setup?step=workspace', { replace: true });
812
- setLoginBusy(false);
813
- return;
814
- }
815
- navigate(nextPath, { replace: true });
816
- }
817
- else if (authMode === 'login') {
818
- if (inviteToken.trim()) {
819
- setAuthMode('invite');
1051
+ else {
1052
+ setLoginEmail('');
1053
+ setLoginDisplayName('');
820
1054
  setLoginPassword('');
821
- setLoginNotice('Signed in. Review the invite details to continue.');
822
- setLoginBusy(false);
823
- return;
824
- }
825
- if (result.workspaceSetupRequired) {
826
- navigate('/setup?step=workspace', { replace: true });
827
- setLoginBusy(false);
828
- return;
1055
+ navigate(nextPath, { replace: true });
829
1056
  }
830
- setLoginEmail('');
831
- setLoginDisplayName('');
832
- setLoginPassword('');
833
- navigate(nextPath, { replace: true });
834
- }
835
- else {
836
- setLoginEmail('');
837
- setLoginDisplayName('');
838
- setLoginPassword('');
839
- navigate(nextPath, { replace: true });
840
- }
841
- setLoginBusy(false);
842
- }, children: loginBusy ? 'Working...' : (authMode === 'login' ? 'Sign In'
843
- : authMode === 'register' ? 'Create Account'
844
- : authMode === 'verify'
845
- ? (verifyCooldownActive ? `Resend in ${verifyCooldownSeconds}s` : 'Resend Verification')
846
- : authMode === 'forgot'
847
- ? (resetCooldownActive ? `Retry in ${resetCooldownSeconds}s` : 'Send Reset Link')
848
- : authMode === 'reset'
849
- ? 'Reset Password'
850
- : (inviteResolution === 'existing_user_signed_out'
851
- ? 'Sign In to Continue'
852
- : (invitePasswordRequired ? 'Create Account and Join' : 'Join Workspace'))) }), authMode === 'register' && (_jsxs("p", { className: authStyles.registerConsent, children: ["By creating an account, you agree to the", ' ', _jsx("a", { className: authStyles.registerConsentLink, href: "https://taskforcehq.com/legal/terms", target: "_blank", rel: "noopener noreferrer", children: "Terms of Service" }), ' ', "and acknowledge the", ' ', _jsx("a", { className: authStyles.registerConsentLink, href: "https://taskforcehq.com/legal/privacy", target: "_blank", rel: "noopener noreferrer", children: "Privacy Policy" }), "."] })), directRegistrationEnabled && (authMode === 'login' || authMode === 'register') && (_jsxs("p", { className: authStyles.authModeSwitch, children: [authMode === 'login' ? 'Need an account?' : 'Already have an account?', ' ', _jsx("button", { className: authStyles.authModeSwitchLink, disabled: loginBusy, onClick: () => {
853
- setLoginError(null);
854
- setLoginErrorCode(null);
855
- setLoginNotice(null);
856
- setLoginMode(authMode === 'login' ? 'register' : 'login');
857
- }, children: authMode === 'login' ? 'Register' : 'Sign In' })] })), _jsxs("div", { className: authStyles.authModeLinks, children: [(authMode === 'login' || authMode === 'register') && (_jsxs(_Fragment, { children: [_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || resetCooldownActive, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('forgot'); }, children: resetCooldownActive ? `Forgot Password (${resetCooldownSeconds}s)` : 'Forgot Password' }), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || verifyCooldownActive, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('verify'); }, children: verifyCooldownActive ? `Resend Verification (${verifyCooldownSeconds}s)` : 'Resend Verification' }), _jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('invite'); }, children: "Accept Invite" })] })), (authMode !== 'login' && authMode !== 'register') && (_jsxs(_Fragment, { children: [_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('login'); }, children: "Sign In" }), directRegistrationEnabled && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('register'); }, children: "Register" })), authMode === 'reset' && (_jsx("button", { className: authStyles.authModeLink, disabled: loginBusy || resetCooldownActive, onClick: () => { setLoginError(null); setLoginErrorCode(null); setLoginNotice(null); setLoginMode('forgot'); }, children: resetCooldownActive ? `Forgot Password (${resetCooldownSeconds}s)` : 'Forgot Password' }))] }))] })] }) }) }));
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' }))] }))] })] })] }) }) }));
858
1089
  }
859
1090
  if (shouldForceLogin && !isLoginRoute) {
860
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..." })] }) }) }));
@@ -865,6 +1096,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
865
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" })] }) }) }));
866
1097
  }
867
1098
  const showGuidedWorkspaceSetup = isCreateWorkspaceIntent
1099
+ || (isSetupRoute && Boolean(pendingCloudWorkspaceImport))
868
1100
  || (isSetupRoute && setupGateActive && (hasSetupReadError || needsGlobalSetup || needsWorkspaceSetup));
869
1101
  if (showGuidedWorkspaceSetup) {
870
1102
  const isError = !isCreateWorkspaceIntent && (hasSetupReadError || setupStep === 'error');
@@ -873,25 +1105,52 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
873
1105
  ? t('setup.headingCheckFailed')
874
1106
  : (isGlobal
875
1107
  ? t('setup.headingGlobalRequired')
876
- : (isCreateWorkspaceIntent ? 'Create Workspace' : t('setup.headingWorkspaceRequired')));
1108
+ : t('setup.headingWorkspaceRequired'));
877
1109
  const subtitle = isError
878
1110
  ? t('setup.subtitleUnreadable')
879
1111
  : isGlobal
880
1112
  ? t('setup.subtitleGlobalRequired')
881
- : (isCreateWorkspaceIntent
882
- ? 'Set up the new workspace before it is created.'
883
- : t('setup.subtitleWorkspaceRequired'));
884
- const workspaceLabel = setupModeChoice === 'operations'
885
- ? t('setup.workspaceTermMission')
886
- : t('setup.workspaceTermProject');
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';
887
1137
  return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
888
1138
  backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
889
- }, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsxs("h1", { className: authStyles.loginTitle, children: ["TASKFORCE ", _jsx("span", { className: authStyles.loginTitleAccent, children: "HQ" })] })] }), _jsx("p", { className: authStyles.loginSubtitle, children: _jsx("strong", { children: heading }) }), _jsx("p", { className: authStyles.loginSubtitle, children: subtitle }), runtimeCapabilities && (_jsxs("p", { className: authStyles.loginSubtitle, children: [t('setup.runtimeLabel'), ": ", _jsx("strong", { children: runtimeCapabilities.runtimeMode })] })), setupNotice && _jsx("p", { className: authStyles.loginSubtitle, children: setupNotice }), setupError && _jsx("p", { className: authStyles.loginError, children: setupError }), isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "core", checked: setupModeChoice === 'core', onChange: () => setSetupModeChoice('core'), disabled: setupBusy }), _jsx("span", { children: t('setup.coreModeOption', { workspaceLabel }) })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "operations", checked: setupModeChoice === 'operations', onChange: () => setSetupModeChoice('operations'), disabled: setupBusy }), _jsx("span", { children: t('setup.operationsModeOption') })] })] })), !isError && !isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [!isCloudRuntime && canUseCloudWorkspaceSetup && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "local", checked: workspaceSetupSource === 'local', onChange: () => setWorkspaceSetupSource('local'), disabled: setupBusy }), _jsxs("span", { children: ["Create new local ", workspaceLabel.toLowerCase()] })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "cloud", checked: workspaceSetupSource === 'cloud', onChange: () => setWorkspaceSetupSource('cloud'), disabled: setupBusy }), _jsxs("span", { children: ["Sync existing cloud ", workspaceLabel.toLowerCase()] })] })] })), !isCloudRuntime && workspaceSetupSource === 'cloud' && canUseCloudWorkspaceSetup ? (_jsxs("div", { className: authStyles.optionGroup, children: [!isAuthenticated && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.inlineHint, children: "Sign in to choose one of your cloud projects." }), _jsx("button", { className: styles.cancelBtn, disabled: setupBusy, onClick: () => {
890
- navigate(`/login?mode=login&next=${encodeURIComponent('/setup?step=workspace&source=cloud')}`, { replace: true });
891
- }, children: "Sign In" })] })), isAuthenticated && (_jsxs(_Fragment, { children: [_jsxs("select", { className: styles.input, value: cloudWorkspaceSelection, onChange: (event) => setCloudWorkspaceSelection(event.target.value), disabled: setupBusy || cloudWorkspaceLoading || cloudWorkspaceOptions.length === 0, children: [cloudWorkspaceOptions.length === 0 && (_jsx("option", { value: "", children: cloudWorkspaceLoading ? 'Loading cloud projects...' : 'No cloud projects found' })), cloudWorkspaceOptions.map((workspace) => (_jsxs("option", { value: workspace.id, children: [workspace.name, " (", workspace.id, ")"] }, workspace.id)))] }), _jsx("div", { className: authStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, disabled: setupBusy || cloudWorkspaceLoading, onClick: () => void fetchCloudWorkspaceOptions(), children: "Refresh Cloud Projects" }) }), cloudWorkspaceError && _jsx("p", { className: authStyles.loginError, children: cloudWorkspaceError })] }))] })) : (_jsxs(_Fragment, { children: [_jsx("input", { className: styles.input, type: "text", value: workspaceNameInput, placeholder: t('setup.workspaceNamePlaceholder', { workspaceLabel }), onChange: (event) => setWorkspaceNameInput(event.target.value), disabled: setupBusy }), _jsx("textarea", { className: styles.textarea, value: workspaceDescriptionInput, placeholder: t('setup.workspaceDescriptionPlaceholder', { workspaceLabel }), onChange: (event) => setWorkspaceDescriptionInput(event.target.value), disabled: setupBusy, rows: 4 }), _jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "What are you working on?" }), _jsx("p", { className: authStyles.inlineHint, children: "Choose a starter taxonomy library for this workspace. You can still edit categories, task types, and priorities later." }), setupLibraryPacks.map((pack) => {
892
- const isSelected = selectedSetupPack?.id === pack.id;
893
- return (_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-taxonomy-library", checked: isSelected, onChange: () => setSelectedSetupPackId(pack.id), disabled: setupBusy || setupLibraryLoading }), _jsx("span", { children: pack.label })] }, pack.id));
894
- }), selectedSetupPack ? (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.inlineHint, children: selectedSetupPack.description }), _jsxs("p", { className: authStyles.inlineHint, children: ["Includes ", selectedSetupPack.categories.length, " categories, ", selectedSetupPack.types.length, " task types, and ", selectedSetupPack.priorities.length, " priority levels."] })] })) : null, _jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "Apply Starter Sections" }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.categories, onChange: () => setSetupTaxonomySections((current) => ({ ...current, categories: !current.categories })), disabled: setupBusy || !selectedSetupPack?.categories.length }), _jsx("span", { children: "Categories" })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.types, onChange: () => setSetupTaxonomySections((current) => ({ ...current, types: !current.types })), disabled: setupBusy || !selectedSetupPack?.types.length }), _jsx("span", { children: "Task Types" })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.priorities, onChange: () => setSetupTaxonomySections((current) => ({ ...current, priorities: !current.priorities })), disabled: setupBusy || !selectedSetupPack?.priorities.length }), _jsx("span", { children: "Priorities" })] })] }), setupLibraryLoading ? (_jsx("p", { className: authStyles.inlineHint, children: "Loading starter libraries\u2026" })) : null, setupLibraryError ? (_jsx("p", { className: authStyles.loginError, children: setupLibraryError })) : null] }), !isCloudRuntime && canUseCloudWorkspaceSetup && isAuthenticated && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: syncNewLocalWorkspaceToCloud, onChange: (event) => setSyncNewLocalWorkspaceToCloud(event.target.checked), disabled: setupBusy }), _jsx("span", { children: "Sync to cloud after setup" })] }), _jsx("p", { className: authStyles.inlineHint, children: "When enabled, this project will connect to cloud sync as soon as setup finishes." })] }))] }))] })), _jsxs("div", { className: authStyles.authModeLinks, children: [isGlobal ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: async () => {
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 () => {
895
1154
  setSetupBusy(true);
896
1155
  setSetupNotice(null);
897
1156
  setSetupError(null);
@@ -917,11 +1176,32 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
917
1176
  finally {
918
1177
  setSetupBusy(false);
919
1178
  }
920
- }, children: setupBusy ? t('setup.saving') : t('setup.saveGlobalSetup') })) : !isError ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: async () => {
921
- setSetupBusy(true);
922
- setSetupNotice(null);
923
- setSetupError(null);
924
- if (!isCloudRuntime && workspaceSetupSource === 'cloud' && canUseCloudWorkspaceSetup) {
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
+ }
925
1205
  if (!isAuthenticated) {
926
1206
  setSetupError('Sign in is required before syncing a cloud project.');
927
1207
  setSetupBusy(false);
@@ -979,14 +1259,40 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
979
1259
  pullCursor: null
980
1260
  });
981
1261
  await refreshSetupContext();
982
- 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;
983
1270
  }
984
- 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
+ {
985
1289
  const creatingNewWorkspace = isCreateWorkspaceIntent || setupState?.workspaceSetupState === 'missing';
986
- const createResult = isCreateWorkspaceIntent
1290
+ const shouldCreateWorkspace = isCreateWorkspaceIntent
1291
+ || (workspaceSwitchingEnabled && creatingNewWorkspace);
1292
+ const createResult = shouldCreateWorkspace
987
1293
  ? await createWorkspace(workspaceNameInput, workspaceDescriptionInput || undefined)
988
1294
  : null;
989
- const saveResult = isCreateWorkspaceIntent
1295
+ const saveResult = shouldCreateWorkspace
990
1296
  ? null
991
1297
  : await saveWorkspaceProfile({
992
1298
  workspaceId: creatingNewWorkspace ? undefined : setupState?.workspaceId,
@@ -1068,11 +1374,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1068
1374
  }
1069
1375
  }
1070
1376
  setSetupBusy(false);
1071
- }, children: setupBusy ? t('setup.saving') : t('setup.saveWorkspaceSetup', { workspaceLabel }) })) : (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: refreshSetupContext, children: t('setup.retrySetupCheck') })), isCreateWorkspaceIntent ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
1072
- setSetupError(null);
1073
- setSetupNotice(null);
1074
- navigate('/', { replace: true });
1075
- }, children: "Cancel" })) : runtimeMode === 'cloud' ? (_jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: t('setup.signOut') })) : null] })] }) }) }));
1377
+ }, children: setupPrimaryActionLabel })) : (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: refreshSetupContext, children: t('setup.retrySetupCheck') }))] })] }) }) }));
1076
1378
  }
1077
1379
  return (_jsx(StandaloneLayout, { ...taskforceState, onHeaderMouseDown: onHeaderMouseDown, isDragging: isDragging, onClose: onClose }));
1078
1380
  }