@taskforcehq/taskforce 0.3.304 → 0.3.306

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 (383) hide show
  1. package/README.md +140 -247
  2. package/dist/Taskforce.module.css +295 -89
  3. package/dist/TaskforceCore.js +378 -111
  4. package/dist/TaskforceCore.test.js +1221 -107
  5. package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
  6. package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  7. package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
  8. package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
  9. package/dist/assets/fonts/Noir_medium.woff2 +0 -0
  10. package/dist/assets/fonts/Noir_regular.woff2 +0 -0
  11. package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
  12. package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
  13. package/dist/compat/workspaceSyncCompat.js +52 -2
  14. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  15. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  16. package/dist/components/context/ContextAttachmentManager.js +549 -0
  17. package/dist/components/features/AgentsModule.d.ts +10 -1
  18. package/dist/components/features/AgentsModule.js +260 -15
  19. package/dist/components/features/AgentsModule.test.js +255 -5
  20. package/dist/components/features/DocumentIndex.d.ts +1 -1
  21. package/dist/components/features/DocumentIndex.js +1 -1
  22. package/dist/components/features/DocumentViewer.d.ts +6 -2
  23. package/dist/components/features/DocumentViewer.js +90 -10
  24. package/dist/components/features/DocumentWorkspace.d.ts +5 -22
  25. package/dist/components/features/DocumentWorkspace.js +12 -155
  26. package/dist/components/features/DocumentWorkspace.test.js +226 -6
  27. package/dist/components/features/TaskSettings.js +29 -3
  28. package/dist/components/features/TaskSettings.test.js +42 -1
  29. package/dist/components/features/documentWorkspaceModel.d.ts +24 -0
  30. package/dist/components/features/documentWorkspaceModel.js +57 -0
  31. package/dist/components/features/useDocumentWorkspaceController.d.ts +34 -0
  32. package/dist/components/features/useDocumentWorkspaceController.js +113 -0
  33. package/dist/components/task/TaskCard.js +19 -28
  34. package/dist/components/task/TaskCard.test.js +38 -3
  35. package/dist/components/task/TaskContextUpload.js +4 -526
  36. package/dist/components/task/TaskForm.d.ts +8 -8
  37. package/dist/components/task/TaskForm.js +362 -230
  38. package/dist/components/task/TaskForm.test.js +558 -381
  39. package/dist/components/task/TaskKanban.d.ts +2 -1
  40. package/dist/components/task/TaskKanban.js +96 -14
  41. package/dist/components/task/TaskKanban.test.js +36 -0
  42. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  43. package/dist/components/ui/EditableAvatarButton.js +18 -0
  44. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  45. package/dist/components/views/PlanComparisonPage.js +70 -17
  46. package/dist/components/views/PlanComparisonPage.test.js +289 -41
  47. package/dist/components/views/PlansPage.js +239 -106
  48. package/dist/components/views/PlansPage.test.js +593 -42
  49. package/dist/components/views/StandaloneLayout.js +310 -554
  50. package/dist/components/views/panels/FilterToolbar.test.js +6 -4
  51. package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
  52. package/dist/components/views/panels/PlanningDrawer.js +18 -11
  53. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  54. package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
  55. package/dist/components/views/planningScope.d.ts +20 -0
  56. package/dist/components/views/planningScope.js +25 -0
  57. package/dist/components/views/planningScope.test.d.ts +1 -0
  58. package/dist/components/views/planningScope.test.js +60 -0
  59. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +27 -14
  60. package/dist/components/views/standalone/modals/AccountHubModal.js +87 -15
  61. package/dist/components/views/standalone/modals/AccountHubModal.test.js +42 -2
  62. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  63. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  64. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  65. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  66. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  67. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  68. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  69. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  70. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  71. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  72. package/dist/config/envSchema.js +1 -1
  73. package/dist/core/AiProfileService.d.ts +45 -3
  74. package/dist/core/AiProfileService.js +774 -63
  75. package/dist/core/AiProfileService.test.js +22 -2
  76. package/dist/core/AiProfiles.test.js +761 -2
  77. package/dist/core/AttachmentLinkService.js +57 -15
  78. package/dist/core/AuthIdentityService.d.ts +107 -0
  79. package/dist/core/AuthIdentityService.js +284 -0
  80. package/dist/core/AuthTokenService.d.ts +4 -0
  81. package/dist/core/AuthTokenService.js +34 -6
  82. package/dist/core/AuthTokenService.test.d.ts +1 -0
  83. package/dist/core/AuthTokenService.test.js +78 -0
  84. package/dist/core/EntitlementsPolicy.test.js +77 -15
  85. package/dist/core/GlobalSettingsService.js +115 -6
  86. package/dist/core/PlanEntitlementService.d.ts +30 -3
  87. package/dist/core/PlanEntitlementService.js +454 -93
  88. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  89. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  90. package/dist/core/PlanVersionPolicy.test.js +54 -28
  91. package/dist/core/PlanningEntities.test.js +52 -0
  92. package/dist/core/SignupMonetizationSettings.test.js +241 -44
  93. package/dist/core/SyncReconciliationService.js +2 -65
  94. package/dist/core/SystemAdmin.test.js +244 -88
  95. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  96. package/dist/core/TaskTaxonomyValidation.test.js +53 -0
  97. package/dist/core/Taskforce.d.ts +228 -14
  98. package/dist/core/Taskforce.js +814 -201
  99. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  100. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  101. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  102. package/dist/core/WorkspaceLifecycleService.js +0 -5
  103. package/dist/core/WorkspacePermissions.test.js +25 -1
  104. package/dist/core/WorkspacePlanMode.test.js +128 -27
  105. package/dist/core/shared.d.ts +3 -1
  106. package/dist/core/shared.js +16 -1
  107. package/dist/core/types.d.ts +68 -2
  108. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
  109. package/dist/hooks/sync/auth.js +3 -1
  110. package/dist/hooks/sync/bootstrap.js +2 -0
  111. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  112. package/dist/hooks/sync/controlPlane.js +78 -0
  113. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  114. package/dist/hooks/sync/controlPlane.test.js +121 -0
  115. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  116. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  117. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  118. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  119. package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
  120. package/dist/hooks/sync/orchestratorShared.js +68 -47
  121. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  122. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  123. package/dist/hooks/sync/pullLifecycle.js +24 -0
  124. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  125. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  126. package/dist/hooks/sync/recovery.d.ts +16 -2
  127. package/dist/hooks/sync/recovery.js +171 -53
  128. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  129. package/dist/hooks/sync/recovery.test.js +292 -0
  130. package/dist/hooks/sync/transfers.d.ts +16 -3
  131. package/dist/hooks/sync/transfers.js +186 -65
  132. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  133. package/dist/hooks/sync/transfers.test.js +396 -0
  134. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  135. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  136. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  137. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  138. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  139. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  140. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  141. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  142. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  143. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  144. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  145. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  146. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  147. package/dist/hooks/useSyncOrchestrator.d.ts +16 -6
  148. package/dist/hooks/useSyncOrchestrator.js +424 -175
  149. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +487 -10
  150. package/dist/hooks/useTaskData.js +8 -3
  151. package/dist/hooks/useTaskData.test.js +45 -0
  152. package/dist/hooks/useTaskMutations.d.ts +2 -1
  153. package/dist/hooks/useTaskMutations.js +12 -1
  154. package/dist/hooks/useTaskMutations.test.js +30 -1
  155. package/dist/hooks/useTaskforce.d.ts +65 -4
  156. package/dist/hooks/useTaskforce.js +564 -243
  157. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  158. package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
  159. package/dist/hooks/useTaskforce.sync-behavior.test.js +2454 -208
  160. package/dist/hooks/useWorkspaceSyncController.d.ts +4 -1
  161. package/dist/hooks/useWorkspaceSyncController.js +17 -2
  162. package/dist/hooks/useWorkspaceSyncController.test.js +1 -0
  163. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  164. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
  165. package/dist/localization/locales/en-US.d.ts +0 -1
  166. package/dist/localization/locales/en-US.js +0 -1
  167. package/dist/localization/locales/es-419.js +0 -1
  168. package/dist/localization/locales/pt-BR.js +0 -1
  169. package/dist/mcp/adminWorkspaceRegistrar.d.ts +2 -0
  170. package/dist/mcp/adminWorkspaceRegistrar.js +154 -0
  171. package/dist/mcp/collaborationRegistrar.d.ts +2 -0
  172. package/dist/mcp/collaborationRegistrar.js +71 -0
  173. package/dist/mcp/documentAssetRegistrar.d.ts +2 -0
  174. package/dist/mcp/documentAssetRegistrar.js +346 -0
  175. package/dist/mcp/runtime.d.ts +2 -0
  176. package/dist/mcp/runtime.js +2433 -3534
  177. package/dist/mcp/runtime.test.js +440 -2
  178. package/dist/mcp/taskPlanningRegistrar.d.ts +2 -0
  179. package/dist/mcp/taskPlanningRegistrar.js +418 -0
  180. package/dist/mcp/toolCatalog.d.ts +35 -0
  181. package/dist/mcp/toolCatalog.js +3 -0
  182. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  183. package/dist/migrations/taskSchemaMigrations.js +171 -61
  184. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  185. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  186. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  187. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  188. package/dist/resources/templates/workflows/plan.yaml +1 -1
  189. package/dist/resources/templates/workflows/review.yaml +2 -2
  190. package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
  191. package/dist/server/auth/providers/apple.d.ts +33 -0
  192. package/dist/server/auth/providers/apple.js +82 -0
  193. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  194. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  195. package/dist/server/auth/providers/github.d.ts +26 -0
  196. package/dist/server/auth/providers/github.js +86 -0
  197. package/dist/server/auth/providers/google.d.ts +21 -0
  198. package/dist/server/auth/providers/google.js +56 -0
  199. package/dist/server/auth/providers/types.d.ts +21 -0
  200. package/dist/server/auth/providers/types.js +1 -0
  201. package/dist/server/auth.d.ts +2 -0
  202. package/dist/server/auth.js +6 -3
  203. package/dist/server/documentReviewRoutes.test.js +36 -3
  204. package/dist/server/index.cookieProxy.test.js +1 -0
  205. package/dist/server/index.d.ts +12 -0
  206. package/dist/server/index.js +120 -9
  207. package/dist/server/index.rateLimit.test.js +3 -0
  208. package/dist/server/index.test.js +106 -3
  209. package/dist/server/routes/admin.d.ts +4 -5
  210. package/dist/server/routes/admin.js +410 -80
  211. package/dist/server/routes/annotatedAttachments.d.ts +1 -1
  212. package/dist/server/routes/annotatedAttachments.js +1 -1
  213. package/dist/server/routes/auth.d.ts +16 -6
  214. package/dist/server/routes/auth.js +1015 -118
  215. package/dist/server/routes/authSupport.d.ts +30 -0
  216. package/dist/server/routes/authSupport.js +81 -0
  217. package/dist/server/routes/billing.d.ts +1 -1
  218. package/dist/server/routes/billing.js +276 -34
  219. package/dist/server/routes/billing.test.js +760 -52
  220. package/dist/server/routes/documentReviews.d.ts +1 -1
  221. package/dist/server/routes/documentReviews.js +1 -1
  222. package/dist/server/routes/documents.d.ts +5 -2
  223. package/dist/server/routes/documents.js +242 -74
  224. package/dist/server/routes/primitives.d.ts +11 -0
  225. package/dist/server/routes/primitives.js +73 -0
  226. package/dist/server/routes/resources.d.ts +1 -1
  227. package/dist/server/routes/resources.js +1 -1
  228. package/dist/server/routes/shared.d.ts +29 -3
  229. package/dist/server/routes/shared.js +69 -4
  230. package/dist/server/routes/sync.d.ts +1 -1
  231. package/dist/server/routes/sync.integration.test.js +437 -39
  232. package/dist/server/routes/sync.js +62 -606
  233. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  234. package/dist/server/routes/syncAuxRoutes.js +91 -0
  235. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  236. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  237. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  238. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  239. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  240. package/dist/server/routes/syncPushRoutes.js +212 -0
  241. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  242. package/dist/server/routes/syncRouteGuards.js +67 -0
  243. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  244. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  245. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  246. package/dist/server/routes/syncRouteTypes.js +1 -0
  247. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  248. package/dist/server/routes/syncSnapshotRoutes.js +180 -0
  249. package/dist/server/routes/tasks.d.ts +1 -1
  250. package/dist/server/routes/tasks.js +44 -2
  251. package/dist/server/routes/workspaces.d.ts +1 -1
  252. package/dist/server/routes/workspaces.js +37 -6
  253. package/dist/server/routes.d.ts +2 -9
  254. package/dist/server/routes.js +136 -165
  255. package/dist/server/routes.test.js +2457 -162
  256. package/dist/services/workflowExportSnapshots.test.js +2 -0
  257. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  258. package/dist/shared/aiProfileSeatScope.js +21 -0
  259. package/dist/shared/passwordPolicy.d.ts +13 -0
  260. package/dist/shared/passwordPolicy.js +84 -0
  261. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  262. package/dist/shared/passwordPolicy.test.js +34 -0
  263. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  264. package/dist/storage/workspaceAssetStore.js +132 -44
  265. package/dist/storage/workspaceAssetStore.test.js +93 -6
  266. package/dist/styles/fonts.css +70 -0
  267. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  268. package/dist/sync/collaborationSyncPayload.js +17 -11
  269. package/dist/sync/collaborationSyncPayload.test.js +13 -1
  270. package/dist/sync/contentSyncState.d.ts +4 -8
  271. package/dist/sync/contentSyncState.js +33 -77
  272. package/dist/sync/syncApplyHandlers.d.ts +7 -0
  273. package/dist/sync/syncApplyHandlers.js +28 -13
  274. package/dist/sync/syncApplyHandlers.test.js +72 -3
  275. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  276. package/dist/sync/syncFieldSemantics.js +55 -0
  277. package/dist/sync/syncResourceAdapters.d.ts +44 -0
  278. package/dist/sync/syncResourceAdapters.js +77 -0
  279. package/dist/sync/syncService.d.ts +6 -0
  280. package/dist/sync/syncService.js +60 -0
  281. package/dist/sync/syncService.test.js +71 -3
  282. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  283. package/dist/sync/taskSyncChangeKey.js +143 -0
  284. package/dist/sync/taskSyncPayload.js +3 -8
  285. package/dist/sync/workspacePullFeed.js +4 -3
  286. package/dist/sync/workspacePullFeed.test.js +43 -0
  287. package/dist/sync/workspaceRepair.js +5 -2
  288. package/dist/sync/workspaceSyncModel.d.ts +48 -0
  289. package/dist/sync/workspaceSyncModel.js +195 -31
  290. package/dist/sync/workspaceSyncModel.test.js +325 -12
  291. package/dist/sync/workspaceSyncState.d.ts +19 -0
  292. package/dist/sync/workspaceSyncState.js +3 -1
  293. package/dist/sync/workspaceSyncSurface.js +3 -8
  294. package/dist/types.d.ts +17 -0
  295. package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
  296. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BlS-cqnl.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
  297. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  298. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  299. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  300. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  301. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
  302. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
  303. package/dist/ui/assets/{InitiativesModule-BjR6iBf9.js → InitiativesModule-9E6ParrY.js} +1 -1
  304. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  305. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  306. package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
  307. package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
  308. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  309. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  310. package/dist/ui/assets/TaskSettings-DZX4jk7e.js +9 -0
  311. package/dist/ui/assets/{WorkflowsModule-DnFqdUME.js → WorkflowsModule-BA7jcEpH.js} +1 -1
  312. package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
  313. package/dist/ui/assets/index-C6k75jr8.js +6 -0
  314. package/dist/ui/assets/{vendor-icons-I7ZwG1z_.js → vendor-icons-DuEd_65c.js} +1 -1
  315. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  316. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  317. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  318. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  319. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  320. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  321. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  322. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  323. package/dist/ui/fonts/Noir_medium.otf +0 -0
  324. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  325. package/dist/ui/fonts/Noir_regular.otf +0 -0
  326. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  327. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  328. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  329. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  330. package/dist/ui/index.html +3 -3
  331. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  332. package/dist/utils/assignees.d.ts +2 -0
  333. package/dist/utils/assignees.js +2 -0
  334. package/dist/utils/avatarUpload.d.ts +27 -0
  335. package/dist/utils/avatarUpload.js +132 -0
  336. package/dist/utils/avatarUpload.test.d.ts +1 -0
  337. package/dist/utils/avatarUpload.test.js +40 -0
  338. package/dist/utils/bootstrapTrace.d.ts +7 -0
  339. package/dist/utils/bootstrapTrace.js +29 -0
  340. package/dist/utils/commercialLifecyclePresentation.d.ts +19 -0
  341. package/dist/utils/commercialLifecyclePresentation.js +199 -0
  342. package/dist/utils/contextAssetEvents.d.ts +2 -0
  343. package/dist/utils/syncEventDetails.d.ts +3 -0
  344. package/dist/utils/syncEventDetails.js +64 -0
  345. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  346. package/dist/utils/syncEventDetails.test.js +23 -0
  347. package/dist/utils/syncEventPresentation.d.ts +15 -0
  348. package/dist/utils/syncEventPresentation.js +129 -0
  349. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  350. package/dist/utils/syncEventPresentation.test.js +64 -0
  351. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  352. package/dist/utils/syncStatusPresentation.js +217 -0
  353. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  354. package/dist/utils/syncStatusPresentation.test.js +164 -0
  355. package/dist/utils/taskActivity.d.ts +2 -0
  356. package/dist/utils/taskActivity.js +228 -22
  357. package/dist/utils/taskActivity.test.js +119 -8
  358. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  359. package/dist/utils/workspaceSyncPresentation.js +68 -2
  360. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  361. package/package.json +10 -2
  362. package/scripts/check-cloud-mcp.mjs +83 -0
  363. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  364. package/scripts/playwright-auth.sh +5 -1
  365. package/scripts/run-billing-performance-smoke.sh +24 -0
  366. package/scripts/run-e2e-realtime.sh +17 -3
  367. package/scripts/run-smoke.sh +17 -5
  368. package/scripts/run-soak.sh +17 -5
  369. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  370. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  371. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  372. package/src/resources/templates/workflows/plan.yaml +1 -1
  373. package/src/resources/templates/workflows/review.yaml +2 -2
  374. package/dist/ui/assets/AgentsModule-BLWVbuKP.js +0 -1
  375. package/dist/ui/assets/DocumentWorkspace-BH_6DF6x.js +0 -250
  376. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +0 -1
  377. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  378. package/dist/ui/assets/PlansPage-CSDQ4sLa.js +0 -1
  379. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  380. package/dist/ui/assets/TaskSettings-CEDy34Jo.js +0 -9
  381. package/dist/ui/assets/index-DXUdtH1B.js +0 -6
  382. package/dist/ui/assets/index-Ii0B0rTO.css +0 -1
  383. package/scripts/migrate-planning-model.ts +0 -233
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { MemoryRouter, Navigate, useInRouterContext, useLocation, useNavigate } from 'react-router-dom';
4
4
  import { useTaskforce } from './hooks/useTaskforce';
5
5
  import { WidgetView } from './components/views/WidgetView';
@@ -15,6 +15,7 @@ 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
17
  import { Cloud, LaptopMinimal } from 'lucide-react';
18
+ import { getPasswordPolicyGuidance, validatePasswordPolicy } from './shared/passwordPolicy';
18
19
  function isDebugModeEnabled() {
19
20
  const runtimeOverride = globalThis.__DEBUG_MODE__;
20
21
  if (typeof runtimeOverride === 'boolean')
@@ -58,18 +59,21 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
58
59
  const [loginEmail, setLoginEmail] = useState('');
59
60
  const [loginDisplayName, setLoginDisplayName] = useState('');
60
61
  const [loginPassword, setLoginPassword] = useState('');
62
+ const [loginPasswordConfirm, setLoginPasswordConfirm] = useState('');
61
63
  const [authMode, setAuthMode] = useState('login');
62
64
  const [verificationToken, setVerificationToken] = useState('');
63
65
  const [pendingVerificationWorkspaceSetupRequired, setPendingVerificationWorkspaceSetupRequired] = useState(false);
64
66
  const [pendingVerificationCommercialState, setPendingVerificationCommercialState] = useState(null);
65
67
  const [pendingVerificationRedirectPath, setPendingVerificationRedirectPath] = useState(null);
66
68
  const [pendingPostAuthRedirect, setPendingPostAuthRedirect] = useState(null);
69
+ const [commercialOnboardingRedirectActive, setCommercialOnboardingRedirectActive] = useState(false);
67
70
  const [resetToken, setResetToken] = useState('');
68
71
  const [inviteToken, setInviteToken] = useState('');
69
72
  const [inviteEmail, setInviteEmail] = useState(null);
70
73
  const [inviteWorkspaceId, setInviteWorkspaceId] = useState(null);
71
74
  const [inviteResolution, setInviteResolution] = useState('unknown');
72
75
  const [invitePasswordRequired, setInvitePasswordRequired] = useState(false);
76
+ const [inviteOAuthProviders, setInviteOAuthProviders] = useState([]);
73
77
  const [loginNotice, setLoginNotice] = useState(null);
74
78
  const [loginError, setLoginError] = useState(null);
75
79
  const [loginErrorCode, setLoginErrorCode] = useState(null);
@@ -92,6 +96,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
92
96
  const [syncNewLocalWorkspaceToCloud, setSyncNewLocalWorkspaceToCloud] = useState(false);
93
97
  const [pendingCloudWorkspaceImport, setPendingCloudWorkspaceImport] = useState(null);
94
98
  const [cloudWorkspaceImportFinalizing, setCloudWorkspaceImportFinalizing] = useState(false);
99
+ const cloudWorkspaceImportFinalizingRef = useRef(false);
95
100
  const fallbackSetupLibraryPacks = useMemo(() => listBuiltInTaxonomyLibraryPacks(), []);
96
101
  const [setupLibraryPacks, setSetupLibraryPacks] = useState(fallbackSetupLibraryPacks);
97
102
  const [selectedSetupPackId, setSelectedSetupPackId] = useState(() => (fallbackSetupLibraryPacks.find((pack) => pack.isDefaultStarter)?.id || fallbackSetupLibraryPacks[0]?.id || ''));
@@ -108,7 +113,24 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
108
113
  onTaskCountChange,
109
114
  onClose
110
115
  });
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;
116
+ const { runtimeMode, workspaceSwitchingEnabled, currentWorkspaceId, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, hasBetaAccess, authSessionResolved, setupState, runtimeCapabilities, refreshSetupContext, retryBootstrapChecks, createWorkspace, saveWorkspaceProfile, fetchWorkspaces, applyWorkspaceSyncStateSnapshot, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncSetupIntent, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceLastErrorMessage, retryWorkspaceCloudSync, settingsModel, fetchTasks, bootstrapState, loginWithCredentials, beginOAuthLogin, availableAuthProviders, registerWithCredentials, requestEmailVerification, confirmEmailVerification, requestPasswordReset, confirmPasswordReset, inspectInviteAcceptance, acceptInviteWithToken, joinInviteWithToken, authUserEmail, currentTheme, logout } = taskforceState;
117
+ const persistWorkspaceSyncStatePatch = useCallback(async (workspaceId, patch) => {
118
+ const response = await fetch('/api/taskforce/ui-state', {
119
+ method: 'POST',
120
+ headers: { 'Content-Type': 'application/json' },
121
+ credentials: 'include',
122
+ body: JSON.stringify({
123
+ workspaceId,
124
+ stateKey: 'workspace-sync',
125
+ patch
126
+ })
127
+ });
128
+ const payload = await response.json().catch(() => ({}));
129
+ if (!response.ok || payload?.success === false) {
130
+ throw new Error(payload?.error || `Failed to configure workspace sync (${response.status})`);
131
+ }
132
+ return payload;
133
+ }, []);
112
134
  const selectedSetupPack = useMemo(() => {
113
135
  return setupLibraryPacks.find((pack) => pack.id === selectedSetupPackId)
114
136
  || setupLibraryPacks.find((pack) => pack.isDefaultStarter)
@@ -131,20 +153,35 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
131
153
  const isLoginRoute = location.pathname === '/login';
132
154
  const isSetupRoute = location.pathname === '/setup';
133
155
  const isComingSoonRoute = location.pathname === '/coming-soon';
156
+ const cloudWorkspaceSelectionPath = '/setup?step=workspace&source=cloud&postAuth=select-cloud-workspace';
134
157
  const wantsCloudWorkspaceSetup = !isCloudRuntime && workspaceSetupSource === 'cloud';
135
158
  const isCloudWorkspaceSetupFlow = wantsCloudWorkspaceSetup && canUseCloudWorkspaceSetup;
136
- const cloudWorkspaceImportMatchesCurrentWorkspace = Boolean(pendingCloudWorkspaceImport
137
- && currentWorkspaceId === pendingCloudWorkspaceImport.workspaceId);
138
- const isCloudWorkspaceImportErrored = Boolean(pendingCloudWorkspaceImport
159
+ const activeCloudWorkspaceImport = useMemo(() => {
160
+ if (pendingCloudWorkspaceImport)
161
+ return pendingCloudWorkspaceImport;
162
+ if (workspaceSyncSetupIntent !== 'attach-cloud-import')
163
+ return null;
164
+ const workspaceId = String(currentWorkspaceId || '').trim();
165
+ if (!workspaceId || workspaceId.toLowerCase() === 'default')
166
+ return null;
167
+ const matchingWorkspace = cloudWorkspaceOptions.find((workspace) => workspace.id === workspaceId);
168
+ return {
169
+ workspaceId,
170
+ name: matchingWorkspace?.name || workspaceId
171
+ };
172
+ }, [cloudWorkspaceOptions, currentWorkspaceId, pendingCloudWorkspaceImport, workspaceSyncSetupIntent]);
173
+ const cloudWorkspaceImportMatchesCurrentWorkspace = Boolean(activeCloudWorkspaceImport
174
+ && currentWorkspaceId === activeCloudWorkspaceImport.workspaceId);
175
+ const isCloudWorkspaceImportErrored = Boolean(activeCloudWorkspaceImport
139
176
  && cloudWorkspaceImportMatchesCurrentWorkspace
140
177
  && workspaceSyncPhase === 'error');
141
- const isCloudWorkspaceImportReady = Boolean(pendingCloudWorkspaceImport
178
+ const isCloudWorkspaceImportReady = Boolean(activeCloudWorkspaceImport
142
179
  && cloudWorkspaceImportMatchesCurrentWorkspace
143
180
  && workspaceCloudSyncEnabled
144
181
  && workspaceSyncPhase === 'active'
145
182
  && !workspaceSyncBusy);
146
183
  const cloudWorkspaceImportProgressLines = useMemo(() => {
147
- if (!pendingCloudWorkspaceImport)
184
+ if (!activeCloudWorkspaceImport)
148
185
  return [];
149
186
  const connectingState = setupBusy ? 'In progress' : 'Complete';
150
187
  const importingState = isCloudWorkspaceImportErrored
@@ -163,7 +200,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
163
200
  `Open local workspace: ${finishingState}`
164
201
  ];
165
202
  }, [
166
- pendingCloudWorkspaceImport,
203
+ activeCloudWorkspaceImport,
167
204
  setupBusy,
168
205
  isCloudWorkspaceImportErrored,
169
206
  workspaceSyncBusy,
@@ -183,6 +220,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
183
220
  && !isPricingRoute
184
221
  && !isPlansRoute
185
222
  && !isPlansScreen;
223
+ const cloudAccountAccessPending = runtimeMode === 'cloud'
224
+ && isAuthenticated
225
+ && (!authSessionResolved || !accountAccessGateResolved);
186
226
  const directRegistrationEnabled = cloudAuthConfigured;
187
227
  const authPageTitle = useMemo(() => {
188
228
  switch (authMode) {
@@ -205,7 +245,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
205
245
  const setupIntent = useMemo(() => String(new URLSearchParams(location.search).get('intent') || '').trim().toLowerCase(), [location.search]);
206
246
  const isCreateWorkspaceIntent = isSetupRoute && setupStep === 'workspace' && setupIntent === 'create-workspace';
207
247
  const shouldHoldForAuthBootstrap = mode !== 'widget'
208
- && !authSessionResolved
248
+ && bootstrapState.authPending
209
249
  && !isLoginRoute
210
250
  && !isSetupRoute
211
251
  && !isPricingRoute
@@ -233,23 +273,47 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
233
273
  const needsWorkspaceSetup = workspaceLifecycle.needsWorkspaceSetup;
234
274
  const hasSetupReadError = workspaceLifecycle.hasSetupReadError;
235
275
  const shouldHoldForSetupBootstrap = mode !== 'widget'
236
- && authSessionResolved
276
+ && bootstrapState.auth.resolved
237
277
  && !shouldForceLogin
238
278
  && !shouldForceComingSoon
239
279
  && !isPricingRoute
240
280
  && !isPlansRoute
241
281
  && !isPlansScreen
242
- && (!configLoaded || workspaceBootstrapPending || bootstrapPhase === 'stalled')
282
+ && bootstrapState.appPending
243
283
  && (isSetupRoute || runtimeMode === 'cloud' || isLikelyCloudAppHost);
244
- const bootstrapSubtitle = useMemo(() => {
245
- if (bootstrapPhase === 'auth')
246
- return 'Verifying session...';
247
- if (bootstrapPhase === 'workspace')
248
- return 'Resolving workspace access...';
249
- if (bootstrapPhase === 'config')
250
- return 'Loading configuration...';
251
- return 'Checking account, workspace access, and setup status...';
252
- }, [bootstrapPhase]);
284
+ const shouldHoldForCommercialOnboardingRedirect = runtimeMode === 'cloud'
285
+ && commercialOnboardingRedirectActive
286
+ && !isPlansScreen;
287
+ const bootstrapSubtitle = bootstrapState.subtitle;
288
+ const showGuidedWorkspaceSetup = isCreateWorkspaceIntent
289
+ || (isSetupRoute && Boolean(activeCloudWorkspaceImport))
290
+ || (isSetupRoute && setupGateActive && (hasSetupReadError || needsGlobalSetup || needsWorkspaceSetup));
291
+ const shellRenderState = useMemo(() => {
292
+ if (shouldHoldForAuthBootstrap)
293
+ return 'auth-bootstrap';
294
+ if (shouldHoldForCommercialOnboardingRedirect)
295
+ return 'app-bootstrap';
296
+ if (shouldHoldForSetupBootstrap)
297
+ return 'app-bootstrap';
298
+ if (isLoginRoute)
299
+ return 'login';
300
+ if (shouldForceLogin && !isLoginRoute)
301
+ return 'login-redirect';
302
+ if (isComingSoonRoute && shouldForceComingSoon)
303
+ return 'coming-soon';
304
+ if (showGuidedWorkspaceSetup)
305
+ return 'guided-setup';
306
+ return 'app';
307
+ }, [
308
+ isComingSoonRoute,
309
+ isLoginRoute,
310
+ shouldForceComingSoon,
311
+ shouldForceLogin,
312
+ shouldHoldForCommercialOnboardingRedirect,
313
+ shouldHoldForAuthBootstrap,
314
+ shouldHoldForSetupBootstrap,
315
+ showGuidedWorkspaceSetup
316
+ ]);
253
317
  const isPlaceholderWorkspaceName = useCallback((name, workspaceId, _description) => {
254
318
  const normalizedName = String(name || '').trim().toLowerCase();
255
319
  const normalizedWorkspaceId = String(workspaceId || '').trim().toLowerCase();
@@ -266,7 +330,12 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
266
330
  setSetupModeChoice(modeValue);
267
331
  }, [setupState?.mode]);
268
332
  useEffect(() => {
269
- if (runtimeMode !== 'cloud' || !isAuthenticated || !authSessionResolved) {
333
+ if (runtimeMode !== 'cloud' || !isAuthenticated) {
334
+ setAccountAccessGate(null);
335
+ setAccountAccessGateResolved(false);
336
+ return;
337
+ }
338
+ if (!authSessionResolved) {
270
339
  setAccountAccessGate(null);
271
340
  setAccountAccessGateResolved(false);
272
341
  return;
@@ -340,6 +409,11 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
340
409
  if (!isSetupRoute || setupStep !== 'workspace')
341
410
  return;
342
411
  setWorkspaceSetupPage('details');
412
+ if (activeCloudWorkspaceImport && canUseCloudWorkspaceSetup) {
413
+ setWorkspaceSetupSource('cloud');
414
+ setSyncNewLocalWorkspaceToCloud(false);
415
+ return;
416
+ }
343
417
  const source = String(new URLSearchParams(location.search).get('source') || '').trim().toLowerCase();
344
418
  if (source === 'cloud' && canUseCloudWorkspaceSetup) {
345
419
  setWorkspaceSetupSource('cloud');
@@ -347,7 +421,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
347
421
  return;
348
422
  }
349
423
  setWorkspaceSetupSource('local');
350
- }, [canUseCloudWorkspaceSetup, isSetupRoute, location.search, setupStep]);
424
+ }, [activeCloudWorkspaceImport, canUseCloudWorkspaceSetup, isSetupRoute, location.search, setupStep]);
351
425
  const fetchCloudWorkspaceOptions = useCallback(async () => {
352
426
  if (!canUseCloudWorkspaceSetup || !isAuthenticated) {
353
427
  setCloudWorkspaceOptions([]);
@@ -410,7 +484,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
410
484
  })
411
485
  .then(async (res) => {
412
486
  if (!res.ok)
413
- throw new Error(`Failed to load starter libraries (${res.status})`);
487
+ throw new Error(`Failed to load setup libraries (${res.status})`);
414
488
  const payload = await res.json().catch(() => ({}));
415
489
  const nextPacks = Array.isArray(payload?.packs) ? payload.packs : [];
416
490
  if (cancelled || nextPacks.length === 0)
@@ -424,7 +498,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
424
498
  if (cancelled)
425
499
  return;
426
500
  setSetupLibraryPacks(fallbackSetupLibraryPacks);
427
- setSetupLibraryError(error instanceof Error ? error.message : 'Failed to load starter libraries.');
501
+ setSetupLibraryError(error instanceof Error ? error.message : 'Failed to load setup libraries.');
428
502
  })
429
503
  .finally(() => {
430
504
  if (!cancelled)
@@ -446,22 +520,24 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
446
520
  if (!isCloudWorkspaceSetupFlow) {
447
521
  setPendingCloudWorkspaceImport(null);
448
522
  setCloudWorkspaceImportFinalizing(false);
523
+ cloudWorkspaceImportFinalizingRef.current = false;
449
524
  }
450
525
  }, [isCloudWorkspaceSetupFlow]);
451
526
  useEffect(() => {
452
- if (!isCloudWorkspaceImportErrored || !pendingCloudWorkspaceImport)
527
+ if (!isCloudWorkspaceImportErrored || !activeCloudWorkspaceImport)
453
528
  return;
454
529
  setSetupNotice(null);
455
530
  setSetupError(workspaceLastErrorMessage
456
- || `Failed to import "${pendingCloudWorkspaceImport.name}" from cloud. Retry import to continue.`);
457
- }, [isCloudWorkspaceImportErrored, pendingCloudWorkspaceImport, workspaceLastErrorMessage]);
531
+ || `Failed to import "${activeCloudWorkspaceImport.name}" from cloud. Retry import to continue.`);
532
+ }, [activeCloudWorkspaceImport, isCloudWorkspaceImportErrored, workspaceLastErrorMessage]);
458
533
  useEffect(() => {
459
- if (!isCloudWorkspaceImportReady || !pendingCloudWorkspaceImport || cloudWorkspaceImportFinalizing)
534
+ if (!isCloudWorkspaceImportReady || !activeCloudWorkspaceImport || cloudWorkspaceImportFinalizingRef.current)
460
535
  return;
461
536
  let cancelled = false;
537
+ cloudWorkspaceImportFinalizingRef.current = true;
462
538
  setCloudWorkspaceImportFinalizing(true);
463
539
  setSetupError(null);
464
- setSetupNotice(`Workspace import complete. Opening ${pendingCloudWorkspaceImport.name}...`);
540
+ setSetupNotice(`Workspace import complete. Opening ${activeCloudWorkspaceImport.name}...`);
465
541
  void (async () => {
466
542
  try {
467
543
  await refreshSetupContext();
@@ -471,6 +547,12 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
471
547
  ]);
472
548
  if (cancelled)
473
549
  return;
550
+ await persistWorkspaceSyncStatePatch(activeCloudWorkspaceImport.workspaceId, { setupIntent: null });
551
+ applyWorkspaceSyncStateSnapshot({
552
+ enabled: workspaceCloudSyncEnabled,
553
+ phase: workspaceSyncPhase,
554
+ setupIntent: null
555
+ });
474
556
  setPendingCloudWorkspaceImport(null);
475
557
  navigate('/', { replace: true });
476
558
  }
@@ -478,20 +560,22 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
478
560
  if (cancelled)
479
561
  return;
480
562
  setSetupNotice(null);
481
- setSetupError(`Imported "${pendingCloudWorkspaceImport.name}" but failed to finish opening it.`);
563
+ setSetupError(`Imported "${activeCloudWorkspaceImport.name}" but failed to finish opening it.`);
482
564
  setCloudWorkspaceImportFinalizing(false);
565
+ cloudWorkspaceImportFinalizingRef.current = false;
483
566
  }
484
567
  })();
485
568
  return () => {
486
569
  cancelled = true;
487
570
  };
488
571
  }, [
489
- cloudWorkspaceImportFinalizing,
572
+ activeCloudWorkspaceImport,
573
+ applyWorkspaceSyncStateSnapshot,
490
574
  fetchTasks,
491
575
  fetchWorkspaces,
492
576
  isCloudWorkspaceImportReady,
493
577
  navigate,
494
- pendingCloudWorkspaceImport,
578
+ persistWorkspaceSyncStatePatch,
495
579
  refreshSetupContext
496
580
  ]);
497
581
  const nextPath = useMemo(() => {
@@ -502,7 +586,6 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
502
586
  return '/';
503
587
  return raw;
504
588
  }, [location.search]);
505
- const cloudWorkspaceSelectionPath = '/setup?step=workspace&source=cloud&postAuth=select-cloud-workspace';
506
589
  const shouldReturnToCloudWorkspaceSelection = useMemo(() => {
507
590
  try {
508
591
  const parsed = new URL(nextPath, 'https://taskforce.local');
@@ -590,27 +673,95 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
590
673
  setPendingVerificationRedirectPath(null);
591
674
  }, [authMode, isLoginRoute, loginRouteMode]);
592
675
  useEffect(() => {
593
- if (!isLoginRoute || !pendingPostAuthRedirect || !authSessionResolved || !isAuthenticated)
676
+ if (authMode === 'register')
677
+ return;
678
+ setLoginPasswordConfirm('');
679
+ }, [authMode]);
680
+ useEffect(() => {
681
+ if (!pendingPostAuthRedirect || !isAuthenticated)
594
682
  return;
595
- if (runtimeMode === 'cloud' && !pendingPostAuthRedirect.commercialOnboardingGate && !accountAccessGateResolved) {
683
+ if (!pendingPostAuthRedirect.commercialOnboardingGate && (!authSessionResolved || cloudAccountAccessPending)) {
596
684
  return;
597
685
  }
598
- if (!pendingPostAuthRedirect.commercialOnboardingGate && pendingPostAuthRedirect.workspaceSetupRequired) {
599
- navigate(resolveWorkspaceSetupPath(), { replace: true });
686
+ const destination = !pendingPostAuthRedirect.commercialOnboardingGate && pendingPostAuthRedirect.workspaceSetupRequired
687
+ ? resolveWorkspaceSetupPath()
688
+ : pendingPostAuthRedirect.path;
689
+ const currentRoute = `${location.pathname}${location.search}${location.hash || ''}`;
690
+ if (currentRoute !== destination) {
691
+ navigate(destination, { replace: true });
600
692
  }
601
- else {
602
- navigate(pendingPostAuthRedirect.path, { replace: true });
693
+ }, [
694
+ authSessionResolved,
695
+ cloudAccountAccessPending,
696
+ isAuthenticated,
697
+ location.hash,
698
+ location.pathname,
699
+ location.search,
700
+ navigate,
701
+ pendingPostAuthRedirect,
702
+ resolveWorkspaceSetupPath
703
+ ]);
704
+ useEffect(() => {
705
+ if (!commercialOnboardingRedirectActive)
706
+ return;
707
+ if (!authSessionResolved) {
708
+ return;
709
+ }
710
+ if (!isAuthenticated) {
711
+ setCommercialOnboardingRedirectActive(false);
712
+ setPendingPostAuthRedirect(null);
713
+ return;
603
714
  }
715
+ if (accountAccessGateResolved && accountAccessGate?.canAccessApp === true) {
716
+ setLoginEmail('');
717
+ setLoginDisplayName('');
718
+ setLoginPassword('');
719
+ setLoginPasswordConfirm('');
720
+ setPendingPostAuthRedirect(null);
721
+ setCommercialOnboardingRedirectActive(false);
722
+ return;
723
+ }
724
+ const destination = pendingPostAuthRedirect?.commercialOnboardingGate
725
+ ? pendingPostAuthRedirect.path
726
+ : planSelectionPath;
727
+ const currentRoute = `${location.pathname}${location.search}${location.hash || ''}`;
728
+ if (currentRoute !== destination) {
729
+ navigate(destination, { replace: true });
730
+ }
731
+ }, [
732
+ accountAccessGate?.canAccessApp,
733
+ accountAccessGateResolved,
734
+ authSessionResolved,
735
+ commercialOnboardingRedirectActive,
736
+ isAuthenticated,
737
+ location.hash,
738
+ location.pathname,
739
+ location.search,
740
+ navigate,
741
+ pendingPostAuthRedirect,
742
+ planSelectionPath
743
+ ]);
744
+ useEffect(() => {
745
+ if (!pendingPostAuthRedirect || !isAuthenticated)
746
+ return;
747
+ const destination = !pendingPostAuthRedirect.commercialOnboardingGate && pendingPostAuthRedirect.workspaceSetupRequired
748
+ ? resolveWorkspaceSetupPath()
749
+ : pendingPostAuthRedirect.path;
750
+ const currentRoute = `${location.pathname}${location.search}${location.hash || ''}`;
751
+ if (currentRoute !== destination)
752
+ return;
753
+ if (pendingPostAuthRedirect.commercialOnboardingGate)
754
+ return;
604
755
  setLoginEmail('');
605
756
  setLoginDisplayName('');
606
757
  setLoginPassword('');
758
+ setLoginPasswordConfirm('');
607
759
  setPendingPostAuthRedirect(null);
608
760
  }, [
609
- accountAccessGateResolved,
610
- authSessionResolved,
761
+ location.hash,
762
+ location.pathname,
763
+ location.search,
611
764
  isAuthenticated,
612
- isLoginRoute,
613
- navigate,
614
765
  pendingPostAuthRedirect,
615
766
  resolveWorkspaceSetupPath,
616
767
  runtimeMode
@@ -632,6 +783,9 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
632
783
  setInviteEmail(inspected.email || null);
633
784
  setInviteWorkspaceId(inspected.workspaceId || null);
634
785
  setInvitePasswordRequired(inspected.passwordRequired === true);
786
+ // Capture OAuth providers available for invite-claim (pending_setup only)
787
+ const oauthMethods = (inspected.availableMethods || []).filter(m => m === 'google' || m === 'github' || m === 'apple');
788
+ setInviteOAuthProviders(inspected.inviteeState === 'pending_setup' ? oauthMethods : []);
635
789
  const inviteMatchesAuthenticatedUser = Boolean(isAuthenticated
636
790
  && authUserEmail
637
791
  && inspected.email
@@ -657,6 +811,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
657
811
  setInviteWorkspaceId(null);
658
812
  setInviteResolution('invalid');
659
813
  setInvitePasswordRequired(false);
814
+ setInviteOAuthProviders([]);
660
815
  setLoginError(formatAuthError(inspected));
661
816
  setLoginErrorCode(inspected.code || inspected.state || null);
662
817
  }
@@ -683,15 +838,42 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
683
838
  const resetCooldownSeconds = Math.max(0, Math.ceil((resetCooldownUntil - cooldownNow) / 1000));
684
839
  const verifyCooldownActive = verifyCooldownSeconds > 0;
685
840
  const resetCooldownActive = resetCooldownSeconds > 0;
841
+ const registerPasswordGuidance = useMemo(() => getPasswordPolicyGuidance(), []);
686
842
  const formatAuthError = (result) => {
687
843
  const message = result.error || 'Request failed.';
688
844
  const code = result.code || result.state;
689
845
  return code ? `[${code}] ${message}` : message;
690
846
  };
847
+ const buildVerificationRequestNotice = (input) => {
848
+ if (input?.deliveryAttempted === false) {
849
+ return input?.intro || 'If that account exists and still needs verification, we sent an email.';
850
+ }
851
+ if (input?.emailSent === false) {
852
+ return `Verification email failed to send. ${input?.emailError || 'Try Resend Verification again.'}`;
853
+ }
854
+ return 'Verification email resent.';
855
+ };
856
+ const buildPasswordResetNotice = (input) => {
857
+ if (input?.deliveryAttempted === false) {
858
+ return 'If that account exists, we sent password reset instructions.';
859
+ }
860
+ if (input?.emailSent === false) {
861
+ return `Password reset email failed to send. ${input?.emailError || 'Try again in a minute.'}`;
862
+ }
863
+ return 'Password reset instructions sent.';
864
+ };
691
865
  useEffect(() => {
692
866
  if (mode === 'widget')
693
867
  return;
694
- if (workspaceBootstrapPending)
868
+ if (isLoginRoute && loginBusy) {
869
+ return;
870
+ }
871
+ const shouldBypassWorkspacePendingForAccessGate = runtimeMode === 'cloud'
872
+ && isAuthenticated
873
+ && accountAccessGateResolved
874
+ && accountAccessGate?.canAccessApp === false
875
+ && accountAccessGate?.canAccessPlans !== false;
876
+ if (bootstrapState.workspace.pending && !shouldBypassWorkspacePendingForAccessGate)
695
877
  return;
696
878
  if (shouldForceComingSoon) {
697
879
  if (!isComingSoonRoute)
@@ -710,10 +892,11 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
710
892
  && isAuthenticated
711
893
  && !shouldStayOnInviteRoute
712
894
  && (Boolean(pendingPostAuthRedirect)
713
- || workspaceBootstrapPending
895
+ || bootstrapState.workspace.pending
714
896
  || authMode === 'register'
715
897
  || authMode === 'verify'
716
- || (runtimeMode === 'cloud' && (!configLoaded || !accountAccessGateResolved)));
898
+ || !bootstrapState.config.loaded
899
+ || cloudAccountAccessPending);
717
900
  if (shouldDelayAuthenticatedLoginRedirect) {
718
901
  return;
719
902
  }
@@ -731,6 +914,23 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
731
914
  if (isLoginRoute && !isAuthenticated) {
732
915
  return;
733
916
  }
917
+ if (pendingPostAuthRedirect && isAuthenticated) {
918
+ return;
919
+ }
920
+ const shouldHoldAuthenticatedAuthRoute = isLoginRoute
921
+ && isAuthenticated
922
+ && (authMode === 'register'
923
+ || authMode === 'verify'
924
+ || (authMode === 'invite' && inviteToken.trim().length > 0));
925
+ if (shouldHoldAuthenticatedAuthRoute) {
926
+ return;
927
+ }
928
+ if (commercialOnboardingRedirectActive) {
929
+ return;
930
+ }
931
+ if (isPlansScreen) {
932
+ return;
933
+ }
734
934
  if (shouldForceAccountAccessGate) {
735
935
  if (!isPlansScreen) {
736
936
  const gate = encodeURIComponent(String(accountAccessGate?.gate || 'plan_selection_required'));
@@ -738,7 +938,23 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
738
938
  }
739
939
  return;
740
940
  }
741
- if (isSetupRoute && pendingCloudWorkspaceImport) {
941
+ if (activeCloudWorkspaceImport) {
942
+ const isCloudImportRoute = isSetupRoute
943
+ && setupStep === 'workspace'
944
+ && new URLSearchParams(location.search).get('source') === 'cloud';
945
+ if (!isCloudImportRoute) {
946
+ navigate(cloudWorkspaceSelectionPath, { replace: true });
947
+ }
948
+ return;
949
+ }
950
+ if (isPlansScreen && cloudAccountAccessPending) {
951
+ return;
952
+ }
953
+ if (isPlansScreen
954
+ && runtimeMode === 'cloud'
955
+ && accountAccessGateResolved
956
+ && accountAccessGate?.canAccessApp === false
957
+ && accountAccessGate?.canAccessPlans !== false) {
742
958
  return;
743
959
  }
744
960
  if (setupGateActive) {
@@ -778,34 +994,40 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
778
994
  isComingSoonRoute,
779
995
  isPlansRoute,
780
996
  isPlansScreen,
997
+ accountAccessGate,
781
998
  accountAccessGate?.gate,
782
999
  accountAccessGateResolved,
783
1000
  authMode,
1001
+ commercialOnboardingRedirectActive,
1002
+ cloudAccountAccessPending,
784
1003
  setupGateActive,
785
1004
  setupState,
786
1005
  hasSetupReadError,
787
1006
  isCreateWorkspaceIntent,
788
- pendingCloudWorkspaceImport,
1007
+ activeCloudWorkspaceImport,
789
1008
  needsGlobalSetup,
790
1009
  needsWorkspaceSetup,
791
1010
  setupStep,
792
- workspaceBootstrapPending,
793
- configLoaded,
1011
+ bootstrapState,
794
1012
  runtimeMode,
1013
+ loginBusy,
1014
+ isAuthenticated,
1015
+ accountAccessGateResolved,
795
1016
  inviteToken,
796
1017
  location.pathname,
797
1018
  location.search,
798
1019
  location.hash,
799
1020
  navigate,
800
1021
  nextPath,
801
- pendingPostAuthRedirect
1022
+ pendingPostAuthRedirect,
1023
+ cloudWorkspaceSelectionPath
802
1024
  ]);
803
1025
  useEffect(() => {
804
1026
  if (mode === 'widget')
805
1027
  return;
806
1028
  if (runtimeMode !== 'local')
807
1029
  return;
808
- if (!configLoaded)
1030
+ if (!bootstrapState.config.loaded)
809
1031
  return;
810
1032
  if (isLoginRoute || isSetupRoute)
811
1033
  return;
@@ -813,7 +1035,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
813
1035
  if (!workspaceId || workspaceId === 'default') {
814
1036
  navigate('/setup?step=workspace', { replace: true });
815
1037
  }
816
- }, [mode, runtimeMode, configLoaded, isLoginRoute, isSetupRoute, currentWorkspaceId, navigate]);
1038
+ }, [mode, runtimeMode, bootstrapState.config.loaded, isLoginRoute, isSetupRoute, currentWorkspaceId, navigate]);
817
1039
  const handleRetryBootstrap = useCallback(async () => {
818
1040
  setBootstrapRetryBusy(true);
819
1041
  try {
@@ -841,13 +1063,13 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
841
1063
  params.set('screen', 'plans');
842
1064
  return _jsx(Navigate, { to: `/?${params.toString()}${location.hash || ''}`, replace: true });
843
1065
  }
844
- if (shouldHoldForAuthBootstrap) {
1066
+ if (shellRenderState === 'auth-bootstrap') {
845
1067
  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: "Checking your session..." })] }) }) }));
846
1068
  }
847
- if (shouldHoldForSetupBootstrap) {
848
- 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: bootstrapSubtitle }), bootstrapPhase === 'stalled' && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.loginError, children: bootstrapError || 'Startup checks timed out.' }), _jsx("button", { className: styles.submitBtn, disabled: bootstrapRetryBusy, onClick: handleRetryBootstrap, children: bootstrapRetryBusy ? 'Retrying...' : 'Retry checks' }), isCloudRuntime && (_jsx("button", { className: styles.cancelBtn, disabled: bootstrapRetryBusy, onClick: handleGoToSignIn, children: "Go to sign-in" }))] }))] }) }) }));
1069
+ if (shellRenderState === 'app-bootstrap') {
1070
+ 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: bootstrapSubtitle }), bootstrapState.stalled && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.loginError, children: bootstrapState.error || 'Startup checks timed out.' }), _jsx("button", { className: styles.submitBtn, disabled: bootstrapRetryBusy, onClick: handleRetryBootstrap, children: bootstrapRetryBusy ? 'Retrying...' : 'Retry checks' }), isCloudRuntime && (_jsx("button", { className: styles.cancelBtn, disabled: bootstrapRetryBusy, onClick: handleGoToSignIn, children: "Go to sign-in" }))] }))] }) }) }));
849
1071
  }
850
- if (isLoginRoute) {
1072
+ if (shellRenderState === 'login') {
851
1073
  return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
852
1074
  backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
853
1075
  }, 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'
@@ -869,7 +1091,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
869
1091
  ? (invitePasswordRequired ? 'Create password' : 'Password')
870
1092
  : 'Password'), autoComplete: authMode === 'register' || authMode === 'reset' || (authMode === 'invite' && invitePasswordRequired)
871
1093
  ? '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 () => {
1094
+ : 'current-password', "data-testid": "auth-password-input", onChange: (event) => setLoginPassword(event.target.value) })] })), (authMode === 'register' || (authMode === 'invite' && invitePasswordRequired)) && (_jsx(_Fragment, { children: _jsxs("label", { className: authStyles.loginField, children: [_jsx("span", { className: authStyles.loginFieldLabel, children: "Confirm password" }), _jsx("input", { className: `${styles.input} ${authStyles.loginInput}`, type: "password", value: loginPasswordConfirm, placeholder: "Confirm password", autoComplete: "new-password", "data-testid": "auth-confirm-password-input", onChange: (event) => setLoginPasswordConfirm(event.target.value) })] }) })), (authMode === 'register' || (authMode === 'invite' && invitePasswordRequired)) && (_jsx("p", { className: authStyles.loginSubtitle, children: registerPasswordGuidance.join(' ') })), 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 () => {
873
1095
  setLoginBusy(true);
874
1096
  setLoginNotice(null);
875
1097
  const sent = await requestEmailVerification(loginEmail);
@@ -878,7 +1100,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
878
1100
  setLoginMode('verify');
879
1101
  if (sent.verificationToken)
880
1102
  setVerificationToken(sent.verificationToken);
881
- setLoginNotice('Verification email resent.');
1103
+ setLoginNotice(buildVerificationRequestNotice(sent));
882
1104
  setLoginError(null);
883
1105
  setLoginErrorCode(null);
884
1106
  }
@@ -896,6 +1118,24 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
896
1118
  setLoginBusy(false);
897
1119
  return;
898
1120
  }
1121
+ if (authMode === 'register') {
1122
+ const validation = validatePasswordPolicy(loginPassword, {
1123
+ email: loginEmail,
1124
+ displayName: loginDisplayName
1125
+ });
1126
+ if (!validation.ok) {
1127
+ setLoginError(validation.message || 'Password does not meet the password policy.');
1128
+ setLoginErrorCode(validation.code || null);
1129
+ setLoginBusy(false);
1130
+ return;
1131
+ }
1132
+ if (loginPassword !== loginPasswordConfirm) {
1133
+ setLoginError('Passwords do not match.');
1134
+ setLoginErrorCode('PASSWORD_CONFIRMATION_MISMATCH');
1135
+ setLoginBusy(false);
1136
+ return;
1137
+ }
1138
+ }
899
1139
  let result = { success: false };
900
1140
  if (authMode === 'login') {
901
1141
  result = await loginWithCredentials(loginEmail, loginPassword);
@@ -950,7 +1190,10 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
950
1190
  setLoginMode('verify');
951
1191
  if (sent.verificationToken)
952
1192
  setVerificationToken(sent.verificationToken);
953
- setLoginNotice('Email not verified. A verification token was sent.');
1193
+ setLoginNotice(buildVerificationRequestNotice({
1194
+ ...sent,
1195
+ intro: 'If that account exists and still needs verification, we sent an email.'
1196
+ }));
954
1197
  setLoginError(null);
955
1198
  setLoginErrorCode(null);
956
1199
  }
@@ -985,6 +1228,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
985
1228
  : 'Account created. Check your email for verification instructions.');
986
1229
  }
987
1230
  else {
1231
+ setCommercialOnboardingRedirectActive(commercialOnboardingGate);
988
1232
  setPendingPostAuthRedirect({
989
1233
  path: postRegistrationPath,
990
1234
  workspaceSetupRequired: Boolean(result.workspaceSetupRequired),
@@ -996,6 +1240,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
996
1240
  await retryBootstrapChecks();
997
1241
  const commercialOnboardingGate = pendingVerificationCommercialState === 'pending_plan_selection'
998
1242
  || pendingVerificationCommercialState === 'checkout_pending';
1243
+ setCommercialOnboardingRedirectActive(commercialOnboardingGate);
999
1244
  setPendingPostAuthRedirect({
1000
1245
  path: pendingVerificationRedirectPath || nextPath,
1001
1246
  workspaceSetupRequired: pendingVerificationWorkspaceSetupRequired,
@@ -1003,13 +1248,14 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1003
1248
  });
1004
1249
  }
1005
1250
  else if (authMode === 'forgot') {
1251
+ const passwordResetDeliveryFailed = result.deliveryAttempted === true && result.emailSent === false;
1006
1252
  if (result.resetToken)
1007
1253
  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.');
1254
+ setLoginNotice(buildPasswordResetNotice(result));
1255
+ if (!passwordResetDeliveryFailed) {
1256
+ setResetCooldownUntil(Date.now() + 30_000);
1257
+ setLoginMode('reset');
1258
+ }
1013
1259
  }
1014
1260
  else if (authMode === 'reset') {
1015
1261
  setLoginMode('login');
@@ -1017,9 +1263,33 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1017
1263
  }
1018
1264
  else if (authMode === 'invite') {
1019
1265
  if (inviteResolution === 'existing_user_signed_out') {
1020
- setAuthMode('invite');
1266
+ const normalizedInviteEmail = String(inviteEmail || '').trim().toLowerCase();
1267
+ const normalizedLoginEmail = loginEmail.trim().toLowerCase();
1268
+ // Make the invite flow deterministically join-ready as soon as
1269
+ // the invited account signs in successfully, instead of waiting
1270
+ // for a later invite inspection effect to catch up.
1271
+ if (normalizedInviteEmail && normalizedLoginEmail === normalizedInviteEmail) {
1272
+ setAuthMode('invite');
1273
+ setInviteResolution('existing_user_ready');
1274
+ setLoginPassword('');
1275
+ setLoginPasswordConfirm('');
1276
+ setLoginNotice('Signed in. Review the invite details to continue.');
1277
+ setLoginError(null);
1278
+ setLoginErrorCode(null);
1279
+ setLoginBusy(false);
1280
+ return;
1281
+ }
1282
+ if (normalizedInviteEmail) {
1283
+ setLoginEmail(inviteEmail || '');
1284
+ }
1285
+ setInviteResolution('existing_user_signed_out');
1021
1286
  setLoginPassword('');
1022
- setLoginNotice('Signed in. Review the invite details to continue.');
1287
+ setLoginPasswordConfirm('');
1288
+ setLoginNotice(normalizedInviteEmail
1289
+ ? `Signed in, but this invite is for ${inviteEmail}. Sign in with that account to continue.`
1290
+ : 'Signed in, but this invite requires the invited account. Sign in with that account to continue.');
1291
+ setLoginError('Sign in with the invited account to join this workspace.');
1292
+ setLoginErrorCode('INVITE_ACCOUNT_MISMATCH');
1023
1293
  setLoginBusy(false);
1024
1294
  return;
1025
1295
  }
@@ -1034,6 +1304,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1034
1304
  if (inviteToken.trim()) {
1035
1305
  setAuthMode('invite');
1036
1306
  setLoginPassword('');
1307
+ setLoginPasswordConfirm('');
1037
1308
  setLoginNotice('Signed in. Review the invite details to continue.');
1038
1309
  setLoginBusy(false);
1039
1310
  return;
@@ -1046,12 +1317,14 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1046
1317
  setLoginEmail('');
1047
1318
  setLoginDisplayName('');
1048
1319
  setLoginPassword('');
1320
+ setLoginPasswordConfirm('');
1049
1321
  navigate(nextPath, { replace: true });
1050
1322
  }
1051
1323
  else {
1052
1324
  setLoginEmail('');
1053
1325
  setLoginDisplayName('');
1054
1326
  setLoginPassword('');
1327
+ setLoginPasswordConfirm('');
1055
1328
  navigate(nextPath, { replace: true });
1056
1329
  }
1057
1330
  setLoginBusy(false);
@@ -1064,7 +1337,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1064
1337
  ? 'Reset Password'
1065
1338
  : (inviteResolution === 'existing_user_signed_out'
1066
1339
  ? '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: () => {
1340
+ : (invitePasswordRequired ? 'Create Account and Join' : 'Join Workspace'))) }) }), (authMode === 'login' || authMode === 'register') && availableAuthProviders.length > 0 && (_jsxs("div", { className: authStyles.oauthProviders, children: [_jsx("div", { className: authStyles.oauthDivider, children: _jsx("span", { children: "or continue with" }) }), _jsxs("div", { className: authStyles.oauthButtons, children: [availableAuthProviders.includes('google') && (_jsx("button", { type: "button", className: authStyles.oauthButton, disabled: loginBusy, onClick: () => beginOAuthLogin('google', location.pathname !== '/login' ? location.pathname : '/'), "aria-label": "Continue with Google", children: "Google" })), availableAuthProviders.includes('github') && (_jsx("button", { type: "button", className: authStyles.oauthButton, disabled: loginBusy, onClick: () => beginOAuthLogin('github', location.pathname !== '/login' ? location.pathname : '/'), "aria-label": "Continue with GitHub", children: "GitHub" })), availableAuthProviders.includes('apple') && (_jsx("button", { type: "button", className: authStyles.oauthButton, disabled: loginBusy, onClick: () => beginOAuthLogin('apple', location.pathname !== '/login' ? location.pathname : '/'), "aria-label": "Continue with Apple", children: "Apple" }))] })] })), authMode === 'invite' && inviteResolution === 'new_user' && inviteOAuthProviders.length > 0 && (_jsxs("div", { className: authStyles.oauthProviders, children: [_jsx("div", { className: authStyles.oauthDivider, children: _jsx("span", { children: "or join with" }) }), _jsxs("div", { className: authStyles.oauthButtons, children: [inviteOAuthProviders.includes('google') && (_jsx("button", { type: "button", className: authStyles.oauthButton, disabled: loginBusy, onClick: () => beginOAuthLogin('google', '/', inviteToken), "aria-label": "Join with Google", children: "Google" })), inviteOAuthProviders.includes('github') && (_jsx("button", { type: "button", className: authStyles.oauthButton, disabled: loginBusy, onClick: () => beginOAuthLogin('github', '/', inviteToken), "aria-label": "Join with GitHub", children: "GitHub" })), inviteOAuthProviders.includes('apple') && (_jsx("button", { type: "button", className: authStyles.oauthButton, disabled: loginBusy, onClick: () => beginOAuthLogin('apple', '/', inviteToken), "aria-label": "Join with Apple", children: "Apple" }))] })] })), 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
1341
  setLoginError(null);
1069
1342
  setLoginErrorCode(null);
1070
1343
  setLoginNotice(null);
@@ -1079,7 +1352,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1079
1352
  setVerifyCooldownUntil(Date.now() + 30_000);
1080
1353
  if (sent.verificationToken)
1081
1354
  setVerificationToken(sent.verificationToken);
1082
- setLoginNotice('Verification email resent.');
1355
+ setLoginNotice(buildVerificationRequestNotice(sent));
1083
1356
  }
1084
1357
  else {
1085
1358
  setLoginError(formatAuthError(sent));
@@ -1087,18 +1360,15 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1087
1360
  setLoginBusy(false);
1088
1361
  }, 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' }))] }))] })] })] }) }) }));
1089
1362
  }
1090
- if (shouldForceLogin && !isLoginRoute) {
1363
+ if (shellRenderState === 'login-redirect') {
1091
1364
  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..." })] }) }) }));
1092
1365
  }
1093
- if (isComingSoonRoute && shouldForceComingSoon) {
1366
+ if (shellRenderState === 'coming-soon') {
1094
1367
  return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
1095
1368
  backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
1096
1369
  }, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsx("h1", { className: authStyles.loginTitle, children: "Private Beta" })] }), _jsx("p", { className: authStyles.loginSubtitle, children: "You are signed in, but your account is not in the beta allowlist yet." }), _jsx("p", { className: authStyles.loginSubtitle, children: "You will see the full app as soon as beta access is enabled." }), _jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: "Sign Out" })] }) }) }));
1097
1370
  }
1098
- const showGuidedWorkspaceSetup = isCreateWorkspaceIntent
1099
- || (isSetupRoute && Boolean(pendingCloudWorkspaceImport))
1100
- || (isSetupRoute && setupGateActive && (hasSetupReadError || needsGlobalSetup || needsWorkspaceSetup));
1101
- if (showGuidedWorkspaceSetup) {
1371
+ if (shellRenderState === 'guided-setup') {
1102
1372
  const isError = !isCreateWorkspaceIntent && (hasSetupReadError || setupStep === 'error');
1103
1373
  const isGlobal = !isCreateWorkspaceIntent && !isError && (needsGlobalSetup || setupStep === 'global');
1104
1374
  const heading = isError
@@ -1114,11 +1384,11 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1114
1384
  const workspaceLabel = 'Workspace';
1115
1385
  const isLocalWorkspaceSetup = isCloudRuntime || workspaceSetupSource !== 'cloud';
1116
1386
  const isWorkspaceDetailsPage = isLocalWorkspaceSetup && workspaceSetupPage === 'details';
1117
- const isWorkspaceStarterPage = isLocalWorkspaceSetup && workspaceSetupPage === 'starter';
1387
+ const isWorkspaceLibraryPage = isLocalWorkspaceSetup && workspaceSetupPage === 'library';
1118
1388
  const showWorkspaceSourceSelector = !isCloudRuntime
1119
1389
  && canUseCloudWorkspaceSetup
1120
1390
  && isWorkspaceDetailsPage
1121
- && !pendingCloudWorkspaceImport;
1391
+ && !activeCloudWorkspaceImport;
1122
1392
  const runtimeIcon = runtimeCapabilities?.runtimeMode === 'cloud' ? Cloud : LaptopMinimal;
1123
1393
  const starterCategoryItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.categories) : '';
1124
1394
  const starterTypeItems = selectedSetupPack ? formatStarterSectionItems(selectedSetupPack.types) : '';
@@ -1126,23 +1396,23 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1126
1396
  const setupPrimaryActionLabel = setupBusy
1127
1397
  ? t('setup.saving')
1128
1398
  : wantsCloudWorkspaceSetup
1129
- ? (pendingCloudWorkspaceImport
1399
+ ? (activeCloudWorkspaceImport
1130
1400
  ? (isCloudWorkspaceImportErrored ? 'Retry Import' : 'Importing Workspace...')
1131
1401
  : 'Connect Workspace')
1132
1402
  : isWorkspaceDetailsPage
1133
1403
  ? 'Continue'
1134
- : isWorkspaceStarterPage
1404
+ : isWorkspaceLibraryPage
1135
1405
  ? 'Create Workspace'
1136
1406
  : 'Connect Workspace';
1137
1407
  return (_jsx("div", { className: shellStyles.standaloneWrapper, "data-theme": currentTheme, children: _jsx("div", { className: authStyles.loginView, style: {
1138
1408
  backgroundImage: `linear-gradient(rgba(15, 15, 26, 0.88), rgba(15, 15, 26, 0.9)), url(${taskforceBg})`
1139
- }, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.setupHeaderRow, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsxs("h1", { className: authStyles.loginTitle, children: ["TASKFORCE ", _jsx("span", { className: authStyles.loginTitleAccent, children: "HQ" })] })] }), runtimeCapabilities && (_jsx("span", { className: authStyles.runtimeIconBadge, title: runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', "aria-label": runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', children: React.createElement(runtimeIcon, { size: 16, 'aria-hidden': true }) }))] }), _jsx("p", { className: authStyles.loginSubtitle, children: _jsx("strong", { children: heading }) }), subtitle ? _jsx("p", { className: authStyles.loginSubtitle, children: subtitle }) : null, setupNotice && _jsx("p", { className: authStyles.loginSubtitle, children: setupNotice }), setupError && _jsx("p", { className: authStyles.loginError, children: setupError }), isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "core", checked: setupModeChoice === 'core', onChange: () => setSetupModeChoice('core'), disabled: setupBusy }), _jsx("span", { children: t('setup.coreModeOption', { workspaceLabel }) })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "operations", checked: setupModeChoice === 'operations', onChange: () => setSetupModeChoice('operations'), disabled: setupBusy }), _jsx("span", { children: t('setup.operationsModeOption') })] })] })), !isError && !isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [showWorkspaceSourceSelector && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "local", checked: workspaceSetupSource === 'local', onChange: () => setWorkspaceSetupSource('local'), disabled: setupBusy }), _jsxs("span", { children: ["Create new local ", workspaceLabel.toLowerCase()] })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "cloud", checked: workspaceSetupSource === 'cloud', onChange: () => setWorkspaceSetupSource('cloud'), disabled: setupBusy }), _jsxs("span", { children: ["Sync existing cloud ", workspaceLabel.toLowerCase()] })] })] })), wantsCloudWorkspaceSetup ? (_jsxs("div", { className: authStyles.optionGroup, children: [!pendingCloudWorkspaceImport && !canUseCloudWorkspaceSetup && (_jsx("p", { className: authStyles.loginError, children: "Cloud workspace sync is unavailable right now. Switch back to local setup or refresh runtime configuration." })), !pendingCloudWorkspaceImport && canUseCloudWorkspaceSetup && !isAuthenticated && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.inlineHint, children: "Sign in to choose one of your cloud projects." }), _jsxs("div", { className: authStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, disabled: setupBusy, onClick: () => {
1409
+ }, children: _jsxs("div", { className: authStyles.loginCard, children: [_jsxs("div", { className: authStyles.setupHeaderRow, children: [_jsxs("div", { className: authStyles.authBrandRow, children: [_jsx("img", { src: authBrandLogo, alt: "Taskforce", className: authStyles.loginLogo }), _jsxs("h1", { className: authStyles.loginTitle, children: ["TASKFORCE ", _jsx("span", { className: authStyles.loginTitleAccent, children: "HQ" })] })] }), runtimeCapabilities && (_jsx("span", { className: authStyles.runtimeIconBadge, title: runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', "aria-label": runtimeCapabilities.runtimeMode === 'cloud' ? 'Cloud runtime' : 'Local runtime', children: React.createElement(runtimeIcon, { size: 16, 'aria-hidden': true }) }))] }), _jsx("p", { className: authStyles.loginSubtitle, children: _jsx("strong", { children: heading }) }), subtitle ? _jsx("p", { className: authStyles.loginSubtitle, children: subtitle }) : null, setupNotice && _jsx("p", { className: authStyles.loginSubtitle, children: setupNotice }), setupError && _jsx("p", { className: authStyles.loginError, children: setupError }), isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "core", checked: setupModeChoice === 'core', onChange: () => setSetupModeChoice('core'), disabled: setupBusy }), _jsx("span", { children: t('setup.coreModeOption', { workspaceLabel }) })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "setup-mode", value: "operations", checked: setupModeChoice === 'operations', onChange: () => setSetupModeChoice('operations'), disabled: setupBusy }), _jsx("span", { children: t('setup.operationsModeOption') })] })] })), !isError && !isGlobal && (_jsxs("div", { className: authStyles.optionGrid, children: [showWorkspaceSourceSelector && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "local", checked: workspaceSetupSource === 'local', onChange: () => setWorkspaceSetupSource('local'), disabled: setupBusy }), _jsxs("span", { children: ["Create new local ", workspaceLabel.toLowerCase()] })] }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "radio", name: "workspace-setup-source", value: "cloud", checked: workspaceSetupSource === 'cloud', onChange: () => setWorkspaceSetupSource('cloud'), disabled: setupBusy }), _jsxs("span", { children: ["Sync existing cloud ", workspaceLabel.toLowerCase()] })] })] })), wantsCloudWorkspaceSetup ? (_jsxs("div", { className: authStyles.optionGroup, children: [!activeCloudWorkspaceImport && !canUseCloudWorkspaceSetup && (_jsx("p", { className: authStyles.loginError, children: "Cloud workspace sync is unavailable right now. Switch back to local setup or refresh runtime configuration." })), !activeCloudWorkspaceImport && canUseCloudWorkspaceSetup && !isAuthenticated && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.inlineHint, children: "Sign in to choose one of your cloud projects." }), _jsxs("div", { className: authStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, disabled: setupBusy, onClick: () => {
1140
1410
  navigate(`/login?mode=login&next=${encodeURIComponent(cloudWorkspaceSelectionPath)}`, { replace: true });
1141
1411
  }, children: "Sign In" }), directRegistrationEnabled && (_jsx("button", { className: styles.submitBtn, disabled: setupBusy, onClick: () => {
1142
1412
  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
1413
+ }, children: "Register" }))] })] })), !activeCloudWorkspaceImport && canUseCloudWorkspaceSetup && isAuthenticated && (_jsxs(_Fragment, { children: [_jsxs("select", { className: styles.input, value: cloudWorkspaceSelection, onChange: (event) => setCloudWorkspaceSelection(event.target.value), disabled: setupBusy || cloudWorkspaceLoading || cloudWorkspaceOptions.length === 0, children: [cloudWorkspaceOptions.length === 0 && (_jsx("option", { value: "", children: cloudWorkspaceLoading ? 'Loading cloud projects...' : 'No cloud projects found' })), cloudWorkspaceOptions.map((workspace) => (_jsxs("option", { value: workspace.id, children: [workspace.name, " (", workspace.id, ")"] }, workspace.id)))] }), _jsx("div", { className: authStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, disabled: setupBusy || cloudWorkspaceLoading, onClick: () => void fetchCloudWorkspaceOptions(), children: "Refresh Cloud Projects" }) }), cloudWorkspaceError && _jsx("p", { className: authStyles.loginError, children: cloudWorkspaceError })] })), activeCloudWorkspaceImport && (_jsxs(_Fragment, { children: [_jsxs("p", { className: authStyles.inlineHint, children: ["Importing \"", activeCloudWorkspaceImport.name, "\" from cloud into local."] }), _jsx("div", { className: authStyles.optionGroup, children: cloudWorkspaceImportProgressLines.map((line) => (_jsx("p", { className: authStyles.inlineHint, children: line }, line))) }), _jsx("p", { className: authStyles.inlineHint, children: cloudWorkspaceImportFinalizing
1144
1414
  ? '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: () => {
1415
+ : (workspaceSyncSummary || 'Pulling tasks, taxonomies, and settings into local.') }), !cloudWorkspaceImportFinalizing && workspaceSyncRecommendedAction && (_jsx("p", { className: authStyles.inlineHint, children: workspaceSyncRecommendedAction }))] }))] })) : (_jsxs(_Fragment, { children: [isWorkspaceDetailsPage && (_jsxs(_Fragment, { children: [_jsx("p", { className: authStyles.loginSubtitle, children: "Workspace name" }), _jsx("input", { className: styles.input, type: "text", value: workspaceNameInput, placeholder: "Workspace name", onChange: (event) => setWorkspaceNameInput(event.target.value), disabled: setupBusy }), _jsx("p", { className: authStyles.loginSubtitle, children: "Workspace description" }), _jsx("textarea", { className: styles.textarea, value: workspaceDescriptionInput, placeholder: "Workspace description (optional)", onChange: (event) => setWorkspaceDescriptionInput(event.target.value), disabled: setupBusy, rows: 4 })] })), isWorkspaceLibraryPage && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "What are you working on?" }), _jsxs("select", { className: styles.input, value: selectedSetupPackId, onChange: (event) => setSelectedSetupPackId(event.target.value), disabled: setupBusy || setupLibraryLoading || setupLibraryPacks.length === 0, children: [setupLibraryPacks.length === 0 && (_jsx("option", { value: "", children: setupLibraryLoading ? 'Loading setup options...' : 'No setup options available' })), setupLibraryPacks.map((pack) => (_jsx("option", { value: pack.id, children: pack.label }, pack.id)))] }), selectedSetupPack ? (_jsx(_Fragment, { children: selectedSetupPack.description ? (_jsx("p", { className: authStyles.inlineHint, children: selectedSetupPack.description })) : null })) : null, _jsxs("div", { className: authStyles.optionGroup, children: [_jsx("p", { className: authStyles.loginSubtitle, children: "Apply Setup Sections" }), _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.categories, onChange: () => setSetupTaxonomySections((current) => ({ ...current, categories: !current.categories })), disabled: setupBusy || !selectedSetupPack?.categories.length }), _jsx("span", { children: "Categories" })] }), starterCategoryItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterCategoryItems })) : null, _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.types, onChange: () => setSetupTaxonomySections((current) => ({ ...current, types: !current.types })), disabled: setupBusy || !selectedSetupPack?.types.length }), _jsx("span", { children: "Task Types" })] }), starterTypeItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterTypeItems })) : null, _jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: setupTaxonomySections.priorities, onChange: () => setSetupTaxonomySections((current) => ({ ...current, priorities: !current.priorities })), disabled: setupBusy || !selectedSetupPack?.priorities.length }), _jsx("span", { children: "Priorities" })] }), starterPriorityItems ? (_jsx("p", { className: authStyles.inlineHint, children: starterPriorityItems })) : null] }), setupLibraryLoading ? (_jsx("p", { className: authStyles.inlineHint, children: "Loading setup options\u2026" })) : null, setupLibraryError ? (_jsx("p", { className: authStyles.loginError, children: setupLibraryError })) : null] })), !isCloudRuntime && canUseCloudWorkspaceSetup && isAuthenticated && isWorkspaceDetailsPage && (_jsxs("div", { className: authStyles.optionGroup, children: [_jsxs("label", { className: `${authStyles.authModeLink} ${authStyles.optionRow}`, children: [_jsx("input", { type: "checkbox", checked: syncNewLocalWorkspaceToCloud, onChange: (event) => setSyncNewLocalWorkspaceToCloud(event.target.checked), disabled: setupBusy }), _jsx("span", { children: "Sync to cloud after setup" })] }), _jsx("p", { className: authStyles.inlineHint, children: "When enabled, this project will connect to cloud sync as soon as setup finishes." })] }))] }))] })), _jsxs("div", { className: authStyles.authModeLinks, children: [isWorkspaceLibraryPage ? (_jsx("button", { className: styles.cancelBtn, onClick: () => {
1146
1416
  setSetupError(null);
1147
1417
  setSetupNotice(null);
1148
1418
  setWorkspaceSetupPage('details');
@@ -1150,7 +1420,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1150
1420
  setSetupError(null);
1151
1421
  setSetupNotice(null);
1152
1422
  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 () => {
1423
+ }, children: "Cancel" })) : runtimeMode === 'cloud' ? (_jsx("button", { className: styles.cancelBtn, onClick: async () => { await taskforceState.logout(); navigate('/login', { replace: true }); }, children: t('setup.signOut') })) : null, isGlobal ? (_jsx("button", { className: styles.submitBtn, disabled: setupBusy || (isCloudWorkspaceSetupFlow && Boolean(activeCloudWorkspaceImport) && !isCloudWorkspaceImportErrored), onClick: async () => {
1154
1424
  setSetupBusy(true);
1155
1425
  setSetupNotice(null);
1156
1426
  setSetupError(null);
@@ -1177,19 +1447,19 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1177
1447
  setSetupBusy(false);
1178
1448
  }
1179
1449
  }, 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) {
1450
+ || (isCloudWorkspaceSetupFlow && !activeCloudWorkspaceImport && !isAuthenticated), onClick: async () => {
1451
+ if (isCloudWorkspaceSetupFlow && activeCloudWorkspaceImport) {
1182
1452
  if (!isCloudWorkspaceImportErrored)
1183
1453
  return;
1184
1454
  setSetupBusy(true);
1185
- setSetupNotice(`Retrying import for ${pendingCloudWorkspaceImport.name}...`);
1455
+ setSetupNotice(`Retrying import for ${activeCloudWorkspaceImport.name}...`);
1186
1456
  setSetupError(null);
1187
1457
  try {
1188
1458
  await retryWorkspaceCloudSync();
1189
1459
  }
1190
1460
  catch {
1191
1461
  setSetupNotice(null);
1192
- setSetupError(`Failed to retry import for "${pendingCloudWorkspaceImport.name}".`);
1462
+ setSetupError(`Failed to retry import for "${activeCloudWorkspaceImport.name}".`);
1193
1463
  }
1194
1464
  finally {
1195
1465
  setSetupBusy(false);
@@ -1229,33 +1499,28 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1229
1499
  setSetupBusy(false);
1230
1500
  return;
1231
1501
  }
1232
- const stateRes = await fetch('/api/taskforce/ui-state', {
1233
- method: 'POST',
1234
- headers: { 'Content-Type': 'application/json' },
1235
- credentials: 'include',
1236
- body: JSON.stringify({
1237
- workspaceId: workspaceIdForSync,
1238
- stateKey: 'workspace-sync',
1239
- patch: {
1240
- version: 2,
1241
- enabled: true,
1242
- phase: 'attach-cloud',
1243
- pullCursor: null,
1244
- lastPullAt: null,
1245
- lastPushAt: null,
1246
- lastSyncedAt: null
1247
- }
1248
- })
1249
- });
1250
- const statePayload = await stateRes.json().catch(() => ({}));
1251
- if (!stateRes.ok || statePayload?.success === false) {
1252
- setSetupError(statePayload?.error || `Failed to configure workspace sync (${stateRes.status})`);
1502
+ try {
1503
+ await persistWorkspaceSyncStatePatch(workspaceIdForSync, {
1504
+ version: 2,
1505
+ enabled: true,
1506
+ phase: 'attach-cloud',
1507
+ setupIntent: 'attach-cloud-import',
1508
+ pullCursor: null,
1509
+ lastPullAt: null,
1510
+ lastPushAt: null,
1511
+ lastSyncedAt: null,
1512
+ lastErrorMessage: null
1513
+ });
1514
+ }
1515
+ catch (error) {
1516
+ setSetupError(error instanceof Error ? error.message : 'Failed to configure workspace sync.');
1253
1517
  setSetupBusy(false);
1254
1518
  return;
1255
1519
  }
1256
1520
  applyWorkspaceSyncStateSnapshot({
1257
1521
  enabled: true,
1258
1522
  phase: 'attach-cloud',
1523
+ setupIntent: 'attach-cloud-import',
1259
1524
  pullCursor: null
1260
1525
  });
1261
1526
  await refreshSetupContext();
@@ -1276,10 +1541,10 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1276
1541
  }
1277
1542
  setSetupError(null);
1278
1543
  setSetupNotice(null);
1279
- setWorkspaceSetupPage('starter');
1544
+ setWorkspaceSetupPage('library');
1280
1545
  return;
1281
1546
  }
1282
- if (isWorkspaceStarterPage) {
1547
+ if (isWorkspaceLibraryPage) {
1283
1548
  // fall through to final create/save behavior below
1284
1549
  }
1285
1550
  setSetupBusy(true);
@@ -1324,7 +1589,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1324
1589
  workspaceIdOverride: workspaceIdForSetup
1325
1590
  });
1326
1591
  if (!applyResult?.success) {
1327
- setSetupError(applyResult?.error || 'Failed to apply starter taxonomy library.');
1592
+ setSetupError(applyResult?.error || 'Failed to apply setup library.');
1328
1593
  setSetupBusy(false);
1329
1594
  return;
1330
1595
  }
@@ -1342,6 +1607,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1342
1607
  version: 2,
1343
1608
  enabled: shouldEnableCloudSync,
1344
1609
  phase: shouldEnableCloudSync ? 'provision-local' : 'idle',
1610
+ setupIntent: null,
1345
1611
  pullCursor: null,
1346
1612
  lastPullAt: null,
1347
1613
  lastPushAt: null,
@@ -1358,6 +1624,7 @@ function TaskforceCoreWithRouter({ config = {}, initialTaskId, onTaskCountChange
1358
1624
  applyWorkspaceSyncStateSnapshot({
1359
1625
  enabled: shouldEnableCloudSync,
1360
1626
  phase: shouldEnableCloudSync ? 'provision-local' : 'idle',
1627
+ setupIntent: null,
1361
1628
  pullCursor: null
1362
1629
  });
1363
1630
  if (shouldEnableCloudSync) {