@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, { useMemo, useCallback, useState, useEffect, useRef, useLayoutEffect, Suspense } from 'react';
2
+ import React, { useMemo, useCallback, useState, useEffect, useRef, Suspense } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { useLocation, useNavigate } from 'react-router-dom';
5
5
  import { Settings, Plus, Filter, Layers, User, Maximize2, Minimize2, Check, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, LogOut, NotebookTabs, ClipboardCheck, Cloud, CloudOff, Users, ClockAlert, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, Bot, Image, Workflow } from 'lucide-react';
@@ -12,9 +12,11 @@ import folderBrowserStyles from './FolderBrowser.module.css';
12
12
  import modalStyles from '../ui/Modal.module.css';
13
13
  import { CreateWorkspaceConfirmModal } from './standalone/modals/CreateWorkspaceConfirmModal';
14
14
  import { AccountHubModal } from './standalone/modals/AccountHubModal';
15
+ import { AvatarImageManagerModal } from './standalone/modals/AvatarImageManagerModal';
15
16
  import { EditProfileModal } from './standalone/modals/EditProfileModal';
16
17
  import { HelpModal } from './standalone/modals/HelpModal';
17
18
  import { ScheduleWarningModal } from './standalone/modals/ScheduleWarningModal';
19
+ import { SyncEnableWarningModal } from './standalone/modals/SyncEnableWarningModal';
18
20
  import { SyncStatusModal } from './standalone/modals/SyncStatusModal';
19
21
  import { TeamManagementModal } from './standalone/modals/TeamManagementModal';
20
22
  import { Modal } from '../ui/Modal';
@@ -30,11 +32,14 @@ import { getTaskReferenceLabel, parseTaskReference } from '../../utils/taskRefer
30
32
  import { formatInitiativeReference, formatWorkstreamReference, parseWorkstreamReference, resolveInitiativeReference } from '../../utils/planningReferences';
31
33
  import { DEFAULT_TYPE_VALUE } from '../../shared/taxonomyDefaults.js';
32
34
  import { getImageReferenceLabel } from '../../utils/imageReferences';
35
+ import { isPlanningEntityVisibleForScope, scopePlanningSummaries } from './planningScope';
36
+ import { buildReferenceMismatchSummaries, buildSyncDiagnosticsSummary, buildSyncEventRows, getActiveReferenceMismatchCount, resolveSyncStageLabel, resolveSyncStatusMeta, } from '../../utils/syncStatusPresentation';
33
37
  import { buildCustomTaxonomySortOptions } from '../../utils/taxonomySorting.js';
34
38
  import { getLocale, t } from '../../localization';
35
39
  import { resolveAnnotatedAttachmentDetail } from '../../utils/annotatedAttachments';
36
40
  import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loadAccountProfileSummaryCached } from '../../utils/accountProfileSummaryCache';
37
- import { fetchTaskforceApi } from '../../utils/taskforceApiClient';
41
+ import { prepareAvatarUploadFile } from '../../utils/avatarUpload';
42
+ import { resolveCommercialLifecyclePresentation } from '../../utils/commercialLifecyclePresentation';
38
43
  import { AppShellHeader } from './AppShellHeader';
39
44
  import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
40
45
  import { logBootstrapDebug, persistRemoteUiState } from '../../hooks/workspace/workspaceLocalState';
@@ -46,6 +51,9 @@ import { EmptyWorkspaceFilterBar, WorkspaceHeaderActions, WorkspaceHeaderDivider
46
51
  import { ScheduleSidebar, SCHEDULE_SIDEBAR_WIDTH_PX } from './panels/ScheduleSidebar';
47
52
  import { DOCUMENT_ATTACHMENT_FILTER_OPTIONS, DOCUMENT_TYPE_FILTER_OPTIONS, } from '../features/documentWorkspaceFilters';
48
53
  import { canManageTeamWorkspace as computeCanManageTeamWorkspace, canOpenTeamManagement as computeCanOpenTeamManagement } from './teamManagementAccess';
54
+ import { useRecentSyncEvents } from '../../hooks/sync/useRecentSyncEvents';
55
+ import { useSyncStatusActions } from '../../hooks/sync/useSyncStatusActions';
56
+ import { useSyncStatusControls } from '../../hooks/sync/useSyncStatusControls';
49
57
  import { toDateOnlyLocal, parseDateOnlyLocal, startOfWeek, getDayOffsetFromWeekStart, addDays, dateOnlyToIsoWeekKey, SCHEDULE_DAY_KEYS, SCHEDULE_DAY_LABELS, } from './panels/scheduleUtils';
50
58
  import { AGENTS_WORKSPACE_FEATURE_KEY, ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, DOCUMENT_WORKSPACE_FEATURE_KEY, INITIATIVES_WORKSPACE_FEATURE_KEY, WORKFLOWS_WORKSPACE_FEATURE_KEY, resolveFeatureAccess, } from '../../runtime/appGates';
51
59
  // Heavy feature modules — lazy-loaded to reduce the initial app chunk size.
@@ -56,76 +64,9 @@ const WorkflowsModule = React.lazy(() => import('../features/WorkflowsModule').t
56
64
  const AgentsModule = React.lazy(() => import('../features/AgentsModule').then(m => ({ default: m.AgentsModule })));
57
65
  const InitiativesModule = React.lazy(() => import('../features/InitiativesModule').then(m => ({ default: m.InitiativesModule })));
58
66
  const PlansPage = React.lazy(() => import('./PlansPage').then(m => ({ default: m.PlansPage })));
59
- const ACTIVE_COMMERCIAL_STATES = new Set(['active', 'trialing', 'grace']);
60
67
  const PROFILE_AVATAR_ACCEPT = 'image/png,image/jpeg,image/webp,image/gif';
61
68
  const PROFILE_AVATAR_MAX_BYTES = 5 * 1024 * 1024;
62
69
  const resolveSameOriginTaskforcePath = (path) => path;
63
- function getSyncEventStableKey(event, index = 0) {
64
- const explicitId = String(event.id ?? '').trim();
65
- if (explicitId)
66
- return `id:${explicitId}`;
67
- return [
68
- String(event.occurredAt || '').trim() || 'unknown-time',
69
- String(event.eventType || '').trim() || 'sync',
70
- String(event.status || '').trim() || 'unknown-status',
71
- String(event.statusCode ?? ''),
72
- String(event.changeCount ?? ''),
73
- String(event.requestMs ?? ''),
74
- String(event.errorMessage || '').trim(),
75
- JSON.stringify(event.details || null),
76
- String(index)
77
- ].join('|');
78
- }
79
- function mergeRecentSyncEvents(previous, incoming, limit = 15) {
80
- const merged = new Map();
81
- for (const [index, event] of previous.entries()) {
82
- merged.set(getSyncEventStableKey(event, index), event);
83
- }
84
- for (const [index, event] of incoming.entries()) {
85
- merged.set(getSyncEventStableKey(event, index), event);
86
- }
87
- return Array.from(merged.values())
88
- .sort((left, right) => String(right.occurredAt || '').localeCompare(String(left.occurredAt || '')))
89
- .slice(0, Math.max(1, limit));
90
- }
91
- function areSyncEventListsEqual(left, right) {
92
- if (left.length !== right.length)
93
- return false;
94
- for (let index = 0; index < left.length; index += 1) {
95
- if (getSyncEventStableKey(left[index], index) !== getSyncEventStableKey(right[index], index)) {
96
- return false;
97
- }
98
- }
99
- return true;
100
- }
101
- function getActiveReferenceMismatchCount(events) {
102
- let count = 0;
103
- for (const event of events) {
104
- const message = String(event.errorMessage || '').toLowerCase();
105
- if (message.includes('reference number mismatch')) {
106
- count += 1;
107
- continue;
108
- }
109
- if (String(event.status || '').toLowerCase() === 'success') {
110
- break;
111
- }
112
- }
113
- return count;
114
- }
115
- function getActiveReferenceMismatchEvents(events) {
116
- const mismatches = [];
117
- for (const event of events) {
118
- const message = String(event.errorMessage || '').toLowerCase();
119
- if (message.includes('reference number mismatch')) {
120
- mismatches.push(event);
121
- continue;
122
- }
123
- if (String(event.status || '').toLowerCase() === 'success') {
124
- break;
125
- }
126
- }
127
- return mismatches;
128
- }
129
70
  function resolveConnectedEnvironmentLabel(explicitEnvironment, candidates) {
130
71
  const parsedExplicitEnvironment = parseTaskforceCloudEnvironment(explicitEnvironment);
131
72
  if (parsedExplicitEnvironment) {
@@ -203,7 +144,7 @@ export function StandaloneLayout(props) {
203
144
  // Form Actions
204
145
  handleAddComment, handleSetWorkstreamForCurrentTask, handleOpenTaskById,
205
146
  // Other Props for Form
206
- taxonomies, activeTypes, priorities, taxonomyDisplayLabels, approaches, copiedId, handleCopyId, handleToggleInProgress, handleToggleComplete, handleToggleReview, handleToggleCancel, handleArchiveTask, handleDelete, handleUnarchive, handleRestoreDeletedTask, handlePermanentlyDeleteDeletedTask, handleEmptyDeletedTasks, fetchTasks, searchQuery, setSearchQuery, filterCategories, setFilterCategories, filterTypes, setFilterTypes, filterPriorities, setFilterPriorities, filterAssignees, setFilterAssignees, assigneeOptions, taskScope, setTaskScope,
147
+ taxonomies, activeTypes, priorities, taxonomyDisplayLabels, approaches, copiedId, handleCopyId, handleToggleInProgress, handleToggleComplete, handleToggleReview, handleToggleCancel, handleArchiveTask, handleDelete, handleUnarchive, handleRestoreDeletedTask, handlePermanentlyDeleteDeletedTask, handleEmptyDeletedTasks, fetchTasks, searchQuery, setSearchQuery, filterCategories, setFilterCategories, filterTypes, setFilterTypes, filterPriorities, setFilterPriorities, filterAssignees, setFilterAssignees, hasInitedFilters, assigneeOptions, taskScope, setTaskScope,
207
148
  // filterStatus, setFilterStatus, // This was duplicated, removed one instance
208
149
  sortBy, setSortBy, clearFilters, // setSearchQuery, // This was duplicated, removed one instance
209
150
  // filterCategories, setFilterCategories, // This was duplicated, removed one instance
@@ -214,7 +155,7 @@ export function StandaloneLayout(props) {
214
155
  // clearFilters, // This was duplicated, removed one instance
215
156
  // activeTypes, // This was duplicated, removed one instance
216
157
  // Settings State & Props
217
- settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authUserId, authWorkspaceId, authUserEmail, authUserDisplayName, authUserAvatarUrl, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncStatus, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceSyncPendingChanges, saveWorkspaceCloudSyncSettings, pushNotice, userGlobalSyncStatus, workspaceLastSuccessfulSyncAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, userGlobalSyncError, workspaceLastErrorMessage, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, getWorkspaceSyncDiagnostics, currentWorkspaceId, currentWorkspaceRole, availableWorkspaces, switchWorkspace, updateCurrentUserProfile, resolveCloudAuthUrl = resolveSameOriginTaskforcePath, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
158
+ settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authUserId, authWorkspaceId, authUserEmail, authUserDisplayName, authUserAvatarUrl, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, workspaceSyncPhase, workspaceSyncStatus, workspaceSyncSummary, workspaceSyncRecommendedAction, workspaceSyncBusy, workspaceSyncPendingChanges, saveWorkspaceCloudSyncSettings, pushNotice, userGlobalSyncStatus, workspaceLastSuccessfulSyncAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, userGlobalSyncError, workspaceLastErrorMessage, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, getWorkspaceSyncDiagnostics, currentWorkspaceId, currentWorkspaceRole, availableWorkspaces, switchWorkspace, updateCurrentUserProfile, resolveCloudAuthUrl = resolveSameOriginTaskforcePath, logout, fetchLoginMethods, unlinkLoginMethod, addPasswordToAccount, beginOAuthLogin, beginOAuthLink, availableAuthProviders, projectRoot, projectName, mcpScriptPath, serverHostRoot,
218
159
  // Browser Props
219
160
  showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget, handleSelectPath, handleAddPath, handleRemovePath,
220
161
  // Settings Managers
@@ -284,6 +225,9 @@ export function StandaloneLayout(props) {
284
225
  const [planningDraftDescription, setPlanningDraftDescription] = useState('');
285
226
  const [planningDraftOwner, setPlanningDraftOwner] = useState('');
286
227
  const [planningDraftInitiativeId, setPlanningDraftInitiativeId] = useState('');
228
+ const [isSubmittingPlanningEditor, setIsSubmittingPlanningEditor] = useState(false);
229
+ const planningContextAttachmentDraftsRef = useRef(new Map());
230
+ const planningContextAttachmentUpdateQueueRef = useRef(new Map());
287
231
  const [documentTypeFilters, setDocumentTypeFilters] = useState(() => DOCUMENT_TYPE_FILTER_OPTIONS.map((option) => option.value));
288
232
  const [documentAttachmentFilters, setDocumentAttachmentFilters] = useState(() => DOCUMENT_ATTACHMENT_FILTER_OPTIONS.map((option) => option.value));
289
233
  const [requestedDocPath, setRequestedDocPath] = useState(null);
@@ -293,6 +237,7 @@ export function StandaloneLayout(props) {
293
237
  const [requestedAnnotatedOpenVersion, setRequestedAnnotatedOpenVersion] = useState(0);
294
238
  const [canonicalAnnotatedImageReferenceLabels, setCanonicalAnnotatedImageReferenceLabels] = useState({});
295
239
  const [layoutStateReady, setLayoutStateReady] = useState(false);
240
+ const planningEditorSubmitInFlightRef = useRef(false);
296
241
  const loadedLayoutStateWorkspaceRef = useRef('');
297
242
  const requestedAnnotatedTargetRef = useRef(null);
298
243
  const requestedAnnotatedSessionIdRef = useRef(null);
@@ -836,84 +781,103 @@ export function StandaloneLayout(props) {
836
781
  : taskScope === 'deleted'
837
782
  ? deletedScopeAllTasks
838
783
  : props.tasks.filter((task) => !task.isArchived);
839
- const summarizeTasks = (scopeTasks) => {
840
- const taskCount = scopeTasks.length;
841
- const completedTaskCount = scopeTasks.filter((task) => task.isArchived || task.status === 'done' || task.status === 'cancelled').length;
784
+ const summarizeTaskCounts = (taskCount, completedTaskCount) => {
842
785
  return {
843
786
  progressPercent: taskCount > 0 ? Math.round((completedTaskCount / taskCount) * 100) : 0,
844
787
  taskCount,
845
788
  completedTaskCount,
846
789
  };
847
790
  };
791
+ const useBootstrapTaskSummaries = taskScope === 'open'
792
+ && props.planningBootstrapTaskSummaries.length > 0
793
+ && (props.loadingTasks || props.tasks.length === 0);
794
+ const workstreamTaskSummaryById = new Map();
795
+ if (useBootstrapTaskSummaries) {
796
+ props.planningBootstrapTaskSummaries.forEach((summary) => {
797
+ workstreamTaskSummaryById.set(summary.workstreamId, summary);
798
+ });
799
+ }
800
+ else {
801
+ planningScopedTasks.forEach((task) => {
802
+ const workstreamId = String(task.workstreamId || '').trim();
803
+ if (!workstreamId)
804
+ return;
805
+ const current = workstreamTaskSummaryById.get(workstreamId) || {
806
+ workstreamId,
807
+ taskCount: 0,
808
+ completedTaskCount: 0,
809
+ tasks: [],
810
+ };
811
+ current.taskCount += 1;
812
+ if (task.isArchived || task.status === 'done' || task.status === 'cancelled') {
813
+ current.completedTaskCount += 1;
814
+ }
815
+ current.tasks.push({
816
+ id: task.id,
817
+ referenceNumber: task.referenceNumber ?? null,
818
+ title: task.title,
819
+ status: task.status || null,
820
+ });
821
+ workstreamTaskSummaryById.set(workstreamId, current);
822
+ });
823
+ }
848
824
  const workstreamTaskIds = new Map();
849
825
  const workstreamInitiativeIds = new Map();
850
826
  const initiativeTaskIds = new Map();
851
- const workstreamById = new Map();
852
- const initiativeById = new Map();
827
+ const workstreamsByInitiativeId = new Map();
853
828
  const buildWorkstreamSummary = (workstream) => {
854
- const scopedTasks = planningScopedTasks.filter((task) => (task.workstreamId || null) === workstream.id);
855
- const taskPreviews = scopedTasks.map((task) => ({
856
- id: task.id,
857
- referenceNumber: task.referenceNumber ?? null,
858
- title: task.title,
859
- status: task.status || null,
860
- }));
829
+ const taskSummary = workstreamTaskSummaryById.get(workstream.id);
830
+ const taskPreviews = taskSummary?.tasks || [];
861
831
  const summary = {
862
832
  id: workstream.id,
863
833
  referenceNumber: workstream.referenceNumber ?? null,
864
834
  title: workstream.title,
865
835
  description: String(workstream.description || '').trim() || undefined,
866
836
  ownerLabel: workstream.ownerId ? assigneeLabelByValue.get(String(workstream.ownerId)) || String(workstream.ownerId) : null,
867
- ...summarizeTasks(scopedTasks),
837
+ ...summarizeTaskCounts(taskSummary?.taskCount || 0, taskSummary?.completedTaskCount || 0),
868
838
  initiativeId: workstream.initiativeId || null,
869
839
  commentCount: Array.isArray(workstream.comments) ? workstream.comments.length : 0,
870
840
  attachmentCount: Array.isArray(workstream.attachments) ? workstream.attachments.length : 0,
841
+ attachments: Array.isArray(workstream.attachments) ? workstream.attachments : [],
871
842
  isArchived: Boolean(workstream.isArchived),
872
843
  tasks: taskPreviews,
873
844
  };
874
- workstreamTaskIds.set(workstream.id, scopedTasks.map((task) => task.id));
875
- if (workstream.initiativeId)
845
+ workstreamTaskIds.set(workstream.id, taskPreviews.map((task) => task.id));
846
+ if (workstream.initiativeId) {
876
847
  workstreamInitiativeIds.set(workstream.id, workstream.initiativeId);
877
- workstreamById.set(summary.id, summary);
848
+ const current = workstreamsByInitiativeId.get(workstream.initiativeId) || [];
849
+ current.push(summary);
850
+ workstreamsByInitiativeId.set(workstream.initiativeId, current);
851
+ }
878
852
  return summary;
879
853
  };
880
854
  const allWorkstreamSummaries = props.workstreams.map(buildWorkstreamSummary);
855
+ const scopedWorkstreamSummaries = allWorkstreamSummaries.filter((workstream) => (isPlanningEntityVisibleForScope(taskScope, Boolean(workstream.isArchived))));
881
856
  const allInitiatives = props.initiatives.map((initiative) => {
882
- const workstreams = allWorkstreamSummaries
883
- .filter((workstream) => (workstream.initiativeId || null) === initiative.id);
884
- const initiativeScopedTasks = workstreams
885
- .flatMap((workstream) => workstreamTaskIds.get(workstream.id) || [])
886
- .map((taskId) => hierarchyTaskById.get(taskId))
887
- .filter((task) => Boolean(task));
857
+ const workstreams = (workstreamsByInitiativeId.get(initiative.id) || [])
858
+ .filter((workstream) => isPlanningEntityVisibleForScope(taskScope, Boolean(workstream.isArchived)));
859
+ const taskCount = workstreams.reduce((sum, workstream) => sum + workstream.taskCount, 0);
860
+ const completedTaskCount = workstreams.reduce((sum, workstream) => sum + workstream.completedTaskCount, 0);
888
861
  const initiativeSummary = {
889
862
  id: initiative.id,
890
863
  referenceNumber: initiative.referenceNumber ?? null,
891
864
  title: initiative.title,
892
865
  description: String(initiative.description || '').trim() || undefined,
893
866
  ownerLabel: initiative.ownerId ? assigneeLabelByValue.get(String(initiative.ownerId)) || String(initiative.ownerId) : null,
894
- ...summarizeTasks(initiativeScopedTasks),
867
+ ...summarizeTaskCounts(taskCount, completedTaskCount),
895
868
  workstreamCount: workstreams.length,
896
869
  workstreams,
897
870
  commentCount: Array.isArray(initiative.comments) ? initiative.comments.length : 0,
898
871
  attachmentCount: Array.isArray(initiative.attachments) ? initiative.attachments.length : 0,
872
+ attachments: Array.isArray(initiative.attachments) ? initiative.attachments : [],
899
873
  isArchived: Boolean(initiative.isArchived),
900
874
  };
901
- initiativeTaskIds.set(initiative.id, initiativeScopedTasks.map((task) => task.id));
902
- initiativeById.set(initiativeSummary.id, initiativeSummary);
903
875
  return initiativeSummary;
904
876
  });
905
- const initiatives = taskScope === 'deleted'
906
- ? []
907
- : allInitiatives.filter((initiative) => taskScope === 'archived'
908
- ? Boolean(initiative.isArchived)
909
- : !initiative.isArchived);
910
- const standaloneWorkstreams = allWorkstreamSummaries
911
- .filter((workstream) => !workstream.initiativeId)
912
- .filter((workstream) => taskScope === 'deleted'
913
- ? false
914
- : taskScope === 'archived'
915
- ? Boolean(workstream.isArchived)
916
- : !workstream.isArchived);
877
+ const { initiatives, standaloneWorkstreams, visibleWorkstreams, initiativeById, workstreamById, } = scopePlanningSummaries(allInitiatives, scopedWorkstreamSummaries, taskScope);
878
+ initiatives.forEach((initiative) => {
879
+ initiativeTaskIds.set(initiative.id, initiative.workstreams.flatMap((workstream) => (workstream.tasks || []).map((task) => task.id)));
880
+ });
917
881
  return {
918
882
  initiatives,
919
883
  standaloneWorkstreams,
@@ -923,33 +887,51 @@ export function StandaloneLayout(props) {
923
887
  workstreamById,
924
888
  initiativeById,
925
889
  };
926
- }, [archivedScopeAllTasks, assigneeLabelByValue, deletedScopeAllTasks, hierarchyTaskById, props.initiatives, props.tasks, props.workstreams, taskScope]);
890
+ }, [
891
+ archivedScopeAllTasks,
892
+ assigneeLabelByValue,
893
+ deletedScopeAllTasks,
894
+ props.initiatives,
895
+ props.loadingTasks,
896
+ props.planningBootstrapTaskSummaries,
897
+ props.tasks,
898
+ props.workstreams,
899
+ taskScope,
900
+ ]);
927
901
  const initiativeFilterOptions = useMemo(() => planningStructure.initiatives.map((initiative) => {
928
902
  const referenceLabel = formatInitiativeReference(initiative);
929
903
  return {
930
904
  value: initiative.id,
931
- label: referenceLabel ? `${referenceLabel} - ${initiative.title}` : initiative.title,
932
- selectedLabel: referenceLabel || initiative.title,
905
+ label: referenceLabel || initiative.title,
933
906
  };
934
907
  }), [planningStructure.initiatives]);
935
- const workstreamFilterOptions = useMemo(() => [
936
- ...planningStructure.initiatives.flatMap((initiative) => initiative.workstreams.map((workstream) => {
937
- const referenceLabel = formatWorkstreamReference(workstream);
938
- return {
939
- value: workstream.id,
940
- label: referenceLabel ? `${referenceLabel} - ${workstream.title}` : workstream.title,
941
- selectedLabel: referenceLabel || workstream.title,
942
- };
943
- })),
944
- ...planningStructure.standaloneWorkstreams.map((workstream) => {
908
+ const workstreamFilterOptions = useMemo(() => {
909
+ const visibleInitiativeWorkstreams = selectedPlanningInitiativeId
910
+ ? (planningStructure.initiativeById.get(selectedPlanningInitiativeId)?.workstreams || [])
911
+ : planningStructure.initiatives.flatMap((initiative) => initiative.workstreams);
912
+ const initiativeWorkstreams = visibleInitiativeWorkstreams.map((workstream) => {
945
913
  const referenceLabel = formatWorkstreamReference(workstream);
946
914
  return {
947
915
  value: workstream.id,
948
- label: referenceLabel ? `${referenceLabel} - ${workstream.title}` : workstream.title,
949
- selectedLabel: referenceLabel || workstream.title,
916
+ label: referenceLabel || workstream.title,
950
917
  };
951
- }),
952
- ], [planningStructure.initiatives, planningStructure.standaloneWorkstreams]);
918
+ });
919
+ const standaloneWorkstreams = selectedPlanningInitiativeId
920
+ ? []
921
+ : planningStructure.standaloneWorkstreams.map((workstream) => {
922
+ const referenceLabel = formatWorkstreamReference(workstream);
923
+ return {
924
+ value: workstream.id,
925
+ label: referenceLabel || workstream.title,
926
+ };
927
+ });
928
+ return [...initiativeWorkstreams, ...standaloneWorkstreams];
929
+ }, [
930
+ planningStructure.initiativeById,
931
+ planningStructure.initiatives,
932
+ planningStructure.standaloneWorkstreams,
933
+ selectedPlanningInitiativeId,
934
+ ]);
953
935
  const selectedPlanningTaskIds = useMemo(() => {
954
936
  if (selectedPlanningWorkstreamId) {
955
937
  return new Set(planningStructure.workstreamTaskIds.get(selectedPlanningWorkstreamId) || []);
@@ -1190,17 +1172,7 @@ export function StandaloneLayout(props) {
1190
1172
  const [showAccountMenu, setShowAccountMenu] = useState(false);
1191
1173
  const [showAccountHub, setShowAccountHub] = useState(false);
1192
1174
  const [showEditProfileModal, setShowEditProfileModal] = useState(false);
1193
- const [showSyncStatusModal, setShowSyncStatusModal] = useState(false);
1194
- const [workspaceSyncToggleBusy, setWorkspaceSyncToggleBusy] = useState(false);
1195
- const [workspaceSyncError, setWorkspaceSyncError] = useState(null);
1196
- const [workspaceSyncRepairBusy, setWorkspaceSyncRepairBusy] = useState(false);
1197
- const [workspaceSyncRepairQueued, setWorkspaceSyncRepairQueued] = useState(false);
1198
- const [workspaceSyncCopied, setWorkspaceSyncCopied] = useState(false);
1199
- const [syncRecentEvents, setSyncRecentEvents] = useState([]);
1200
- const [syncRecentEventsLoading, setSyncRecentEventsLoading] = useState(false);
1201
- const [syncRecentEventsError, setSyncRecentEventsError] = useState(null);
1202
- const syncEventsListRef = useRef(null);
1203
- const pendingSyncEventsScrollRestoreRef = useRef(null);
1175
+ const [showAvatarPhotoManager, setShowAvatarPhotoManager] = useState(false);
1204
1176
  const [workspaceActionBusy, setWorkspaceActionBusy] = useState(false);
1205
1177
  const [workspaceActionError, setWorkspaceActionError] = useState('');
1206
1178
  const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
@@ -1241,7 +1213,6 @@ export function StandaloneLayout(props) {
1241
1213
  const [profileSaveNotice, setProfileSaveNotice] = useState(null);
1242
1214
  const TEAM_AUDIT_PAGE_SIZE = 25;
1243
1215
  const accountMenuRef = useRef(null);
1244
- const profileAvatarInputRef = useRef(null);
1245
1216
  const accountSurfaceOpenStartedAtRef = useRef(null);
1246
1217
  const accountSurfacePendingRef = useRef({
1247
1218
  billing: false,
@@ -1266,21 +1237,11 @@ export function StandaloneLayout(props) {
1266
1237
  const canManageCloudWorkspaces = runtimeMode === 'cloud' && workspaceSwitchingEnabled && isAuthenticated;
1267
1238
  const canAccessCloudTeamManagement = isAuthenticated && (runtimeMode === 'cloud' || cloudAuthConfigured);
1268
1239
  const canManageWorkspaceSync = runtimeMode === 'local' && isAuthenticated;
1269
- const handleWorkspaceSyncToggle = useCallback(async (enabled) => {
1270
- if (!canManageWorkspaceSync)
1271
- return;
1272
- setWorkspaceSyncError(null);
1273
- setWorkspaceSyncToggleBusy(true);
1274
- const result = await saveWorkspaceCloudSyncSettings({ enabled });
1275
- if (!result.success) {
1276
- setWorkspaceSyncError(result.error || (enabled ? 'Failed to enable sync.' : 'Failed to disable sync.'));
1277
- }
1278
- setWorkspaceSyncToggleBusy(false);
1279
- }, [
1240
+ const { showSyncStatusModal, showSyncEnableWarning, workspaceSyncError, syncControlBusy, openSyncStatusModal, closeSyncStatusModal, handleWorkspaceSyncToggle, cancelSyncEnableWarning, confirmSyncEnableWarning, } = useSyncStatusControls({
1280
1241
  canManageWorkspaceSync,
1281
- saveWorkspaceCloudSyncSettings
1282
- ]);
1283
- const syncControlBusy = workspaceSyncToggleBusy;
1242
+ workspaceCloudSyncEnabled,
1243
+ saveWorkspaceCloudSyncSettings,
1244
+ });
1284
1245
  const currentWorkspaceLabel = useMemo(() => {
1285
1246
  const current = availableWorkspaces.find((workspace) => workspace.id === currentWorkspaceId);
1286
1247
  const namedWorkspace = String(current?.name || '').trim();
@@ -1340,23 +1301,21 @@ export function StandaloneLayout(props) {
1340
1301
  if (!normalizedDraftId || !cloudAuthConfigured)
1341
1302
  return;
1342
1303
  try {
1343
- const baseUrl = runtimeMode === 'local'
1344
- ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1345
- : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1346
- await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/discard', {
1304
+ await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/discard'), {
1347
1305
  method: 'POST',
1348
1306
  headers: { 'Content-Type': 'application/json' },
1349
1307
  credentials: 'include',
1350
1308
  body: JSON.stringify({ draftId: normalizedDraftId })
1351
- }, baseUrl);
1309
+ });
1352
1310
  }
1353
1311
  catch {
1354
1312
  // Best-effort cleanup only.
1355
1313
  }
1356
- }, [cloudAuthConfigured, props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
1314
+ }, [cloudAuthConfigured, resolveCloudAuthUrl]);
1357
1315
  const closeEditProfileModal = useCallback(() => {
1358
1316
  const pendingDraftId = profileAvatarDraftId;
1359
1317
  setShowEditProfileModal(false);
1318
+ setShowAvatarPhotoManager(false);
1360
1319
  setProfileSaveError(null);
1361
1320
  setProfileSaveNotice(null);
1362
1321
  setProfileAvatarDraftId(null);
@@ -1367,47 +1326,53 @@ export function StandaloneLayout(props) {
1367
1326
  }, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
1368
1327
  const handleProfileAvatarSelected = useCallback(async (file) => {
1369
1328
  if (!file || !cloudAuthConfigured)
1370
- return;
1329
+ return false;
1371
1330
  if (!file.type.startsWith('image/')) {
1372
1331
  setProfileSaveError('Profile photo must be an image file.');
1373
1332
  setProfileSaveNotice(null);
1374
- return;
1375
- }
1376
- if (file.size > PROFILE_AVATAR_MAX_BYTES) {
1377
- setProfileSaveError('Profile photo must be 5 MB or smaller.');
1378
- setProfileSaveNotice(null);
1379
- return;
1333
+ return false;
1380
1334
  }
1381
1335
  setProfileAvatarBusy(true);
1382
1336
  setProfileSaveError(null);
1383
1337
  setProfileSaveNotice(null);
1384
1338
  try {
1385
- const baseUrl = runtimeMode === 'local'
1386
- ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1387
- : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1388
- const initRes = await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/init', {
1339
+ const preparedAvatar = await prepareAvatarUploadFile(file, {
1340
+ maxBytes: PROFILE_AVATAR_MAX_BYTES
1341
+ });
1342
+ if (preparedAvatar.exceededLimit) {
1343
+ const message = file.type === 'image/gif'
1344
+ ? 'Animated GIF profile photos must be 5 MB or smaller.'
1345
+ : 'Profile photo must be 5 MB or smaller.';
1346
+ throw new Error(message);
1347
+ }
1348
+ const uploadFile = preparedAvatar.file;
1349
+ const initRes = await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/init'), {
1389
1350
  method: 'POST',
1390
1351
  headers: { 'Content-Type': 'application/json' },
1391
1352
  credentials: 'include',
1392
1353
  body: JSON.stringify({
1393
- originalName: file.name,
1394
- mimeType: file.type,
1395
- size: file.size
1354
+ originalName: uploadFile.name,
1355
+ mimeType: uploadFile.type,
1356
+ size: uploadFile.size
1396
1357
  })
1397
- }, baseUrl);
1358
+ });
1398
1359
  const initData = await initRes.json().catch(() => ({}));
1399
- if (!initRes.ok || !initData?.success || typeof initData?.uploadUrl !== 'string' || typeof initData?.relativePath !== 'string') {
1360
+ if (!initRes.ok || !initData?.success || typeof initData?.draftId !== 'string' || typeof initData?.relativePath !== 'string') {
1400
1361
  throw new Error(initData?.error || 'Failed to start avatar upload.');
1401
1362
  }
1402
- const uploadRes = await fetch(initData.uploadUrl, {
1403
- method: String(initData.method || 'PUT'),
1404
- headers: initData.headers || { 'Content-Type': file.type || 'application/octet-stream' },
1405
- body: file
1363
+ const uploadRes = await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/upload'), {
1364
+ method: 'POST',
1365
+ headers: {
1366
+ 'Content-Type': uploadFile.type || 'application/octet-stream',
1367
+ 'x-taskforce-avatar-draft-id': initData.draftId
1368
+ },
1369
+ credentials: 'include',
1370
+ body: uploadFile
1406
1371
  });
1407
1372
  if (!uploadRes.ok) {
1408
1373
  throw new Error('Failed to upload avatar.');
1409
1374
  }
1410
- const finalizeRes = await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/finalize', {
1375
+ const finalizeRes = await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/finalize'), {
1411
1376
  method: 'POST',
1412
1377
  headers: { 'Content-Type': 'application/json' },
1413
1378
  credentials: 'include',
@@ -1415,7 +1380,7 @@ export function StandaloneLayout(props) {
1415
1380
  draftId: initData.draftId,
1416
1381
  relativePath: initData.relativePath
1417
1382
  })
1418
- }, baseUrl);
1383
+ });
1419
1384
  const finalizeData = await finalizeRes.json().catch(() => ({}));
1420
1385
  if (!finalizeRes.ok || !finalizeData?.success || typeof finalizeData?.draftId !== 'string') {
1421
1386
  throw new Error(finalizeData?.error || 'Failed to finalize avatar upload.');
@@ -1423,27 +1388,24 @@ export function StandaloneLayout(props) {
1423
1388
  const previousDraftId = profileAvatarDraftId;
1424
1389
  setProfileAvatarDraftId(finalizeData.draftId);
1425
1390
  setProfileAvatarPreviewUrl(typeof finalizeData?.avatarUrl === 'string' ? finalizeData.avatarUrl : '');
1426
- setProfileSaveNotice('Profile photo ready to save.');
1391
+ setProfileSaveNotice(null);
1427
1392
  if (previousDraftId && previousDraftId !== finalizeData.draftId) {
1428
1393
  void discardProfileAvatarDraft(previousDraftId);
1429
1394
  }
1395
+ return true;
1430
1396
  }
1431
1397
  catch (error) {
1432
1398
  setProfileSaveError(error instanceof Error ? error.message : 'Failed to upload profile photo.');
1399
+ return false;
1433
1400
  }
1434
1401
  finally {
1435
1402
  setProfileAvatarBusy(false);
1436
- if (profileAvatarInputRef.current) {
1437
- profileAvatarInputRef.current.value = '';
1438
- }
1439
1403
  }
1440
1404
  }, [
1441
1405
  cloudAuthConfigured,
1442
1406
  discardProfileAvatarDraft,
1443
1407
  profileAvatarDraftId,
1444
- props.config?.apiBaseUrl,
1445
- props.config?.cloudAuthBaseUrl,
1446
- runtimeMode
1408
+ resolveCloudAuthUrl
1447
1409
  ]);
1448
1410
  const handleDiscardPendingProfileAvatar = useCallback(() => {
1449
1411
  if (!profileAvatarDraftId)
@@ -1489,6 +1451,7 @@ export function StandaloneLayout(props) {
1489
1451
  setProfileAvatarDraftId(null);
1490
1452
  setProfileAvatarPreviewUrl('');
1491
1453
  setProfileSaveBusy(false);
1454
+ setShowAvatarPhotoManager(false);
1492
1455
  setShowEditProfileModal(false);
1493
1456
  }, [authAvatarUrl, profileAvatarDraftId, profileAvatarPreviewUrl, profileDisplayNameDraft, updateCurrentUserProfile]);
1494
1457
  const teamAdminHeaders = useCallback(() => {
@@ -1630,9 +1593,8 @@ export function StandaloneLayout(props) {
1630
1593
  setTeamPlanMode(nextMode);
1631
1594
  }, []);
1632
1595
  const hasActiveCommercialEntitlement = useMemo(() => {
1633
- const entitlementState = String(accountProfileSummary?.entitlementState || '').trim().toLowerCase();
1634
- return ACTIVE_COMMERCIAL_STATES.has(entitlementState);
1635
- }, [accountProfileSummary?.entitlementState]);
1596
+ return resolveCommercialLifecyclePresentation(accountProfileSummary).allowReturnToApp;
1597
+ }, [accountProfileSummary]);
1636
1598
  const handleStartCheckout = useCallback(async () => {
1637
1599
  const params = new URLSearchParams();
1638
1600
  params.set('screen', 'plans');
@@ -1808,9 +1770,9 @@ export function StandaloneLayout(props) {
1808
1770
  const target = (!targetRaw || targetRaw === '/login' || !targetRaw.startsWith('/')) ? '/' : targetRaw;
1809
1771
  setShowAccountMenu(false);
1810
1772
  setShowAccountHub(false);
1811
- setShowSyncStatusModal(false);
1773
+ closeSyncStatusModal();
1812
1774
  navigate(`/login?mode=${mode}&next=${encodeURIComponent(target)}`);
1813
- }, [location.hash, location.pathname, location.search, navigate]);
1775
+ }, [closeSyncStatusModal, location.hash, location.pathname, location.search, navigate]);
1814
1776
  const openWorkspaceSetup = useCallback((options) => {
1815
1777
  setShowAccountMenu(false);
1816
1778
  setShowCreateWorkspaceConfirm(false);
@@ -1823,38 +1785,44 @@ export function StandaloneLayout(props) {
1823
1785
  const handlePlansBack = useCallback(async () => {
1824
1786
  if (plansNavigationBusy)
1825
1787
  return;
1788
+ setPlansNavigationBusy(true);
1826
1789
  if (!isAuthenticated) {
1827
- closePlansScreen();
1790
+ try {
1791
+ closePlansScreen();
1792
+ }
1793
+ finally {
1794
+ setPlansNavigationBusy(false);
1795
+ }
1828
1796
  return;
1829
1797
  }
1830
- const summary = await loadAccountProfileSummary({ force: true });
1831
- const entitlementState = String(summary?.entitlementState || '').trim().toLowerCase();
1832
- if (!ACTIVE_COMMERCIAL_STATES.has(entitlementState)) {
1798
+ const currentSummary = accountProfileSummaryRef.current;
1799
+ if (currentSummary && !resolveCommercialLifecyclePresentation(currentSummary).allowReturnToApp) {
1800
+ setPlansNavigationBusy(false);
1833
1801
  return;
1834
1802
  }
1835
- setPlansNavigationBusy(true);
1836
- try {
1837
- const result = await props.continueAfterCommercialOnboarding();
1838
- if (!result.success) {
1839
- pushNotice(result.error || 'Unable to finish onboarding.', 'error');
1840
- if (result.destination === 'login') {
1841
- navigate('/login', { replace: true });
1803
+ closePlansScreen();
1804
+ void (async () => {
1805
+ try {
1806
+ const result = await props.continueAfterCommercialOnboarding();
1807
+ if (!result.success) {
1808
+ pushNotice(result.error || 'Unable to finish onboarding.', 'error');
1809
+ if (result.destination === 'login') {
1810
+ navigate('/login', { replace: true });
1811
+ }
1812
+ return;
1813
+ }
1814
+ if (result.destination === 'setup') {
1815
+ openWorkspaceSetup();
1842
1816
  }
1843
- return;
1844
1817
  }
1845
- if (result.destination === 'setup') {
1846
- openWorkspaceSetup();
1847
- return;
1818
+ finally {
1819
+ setPlansNavigationBusy(false);
1848
1820
  }
1849
- closePlansScreen();
1850
- }
1851
- finally {
1852
- setPlansNavigationBusy(false);
1853
- }
1821
+ })();
1854
1822
  }, [
1823
+ accountProfileSummaryRef,
1855
1824
  closePlansScreen,
1856
1825
  isAuthenticated,
1857
- loadAccountProfileSummary,
1858
1826
  navigate,
1859
1827
  openWorkspaceSetup,
1860
1828
  plansNavigationBusy,
@@ -2040,6 +2008,7 @@ export function StandaloneLayout(props) {
2040
2008
  const formattedLastPullTime = useMemo(() => formatSyncTimestamp(workspaceLastPullAt), [formatSyncTimestamp, workspaceLastPullAt]);
2041
2009
  const formattedLastPushTime = useMemo(() => formatSyncTimestamp(workspaceLastPushAt), [formatSyncTimestamp, workspaceLastPushAt]);
2042
2010
  const rawSyncLastError = workspaceSyncError || workspaceLastErrorMessage || userGlobalSyncError || 'None';
2011
+ const workspaceSyncDiagnostics = getWorkspaceSyncDiagnostics();
2043
2012
  const headerSyncPresentation = useMemo(() => resolveHeaderWorkspaceSyncPresentation({
2044
2013
  runtimeMode,
2045
2014
  isAuthenticated,
@@ -2047,7 +2016,10 @@ export function StandaloneLayout(props) {
2047
2016
  workspaceCloudSyncEnabled,
2048
2017
  workspaceSyncSummary,
2049
2018
  workspaceSyncRecommendedAction,
2050
- workspaceSyncError: rawSyncLastError
2019
+ workspaceSyncError: rawSyncLastError,
2020
+ pushBlockedReason: workspaceSyncDiagnostics.pushBlockedReason || null,
2021
+ pullBlockedReason: workspaceSyncDiagnostics.pullBlockedReason || null,
2022
+ retryBlockedReason: workspaceSyncDiagnostics.retryBlockedReason || null
2051
2023
  }), [
2052
2024
  runtimeMode,
2053
2025
  isAuthenticated,
@@ -2055,75 +2027,24 @@ export function StandaloneLayout(props) {
2055
2027
  workspaceCloudSyncEnabled,
2056
2028
  workspaceSyncSummary,
2057
2029
  workspaceSyncRecommendedAction,
2058
- rawSyncLastError
2030
+ rawSyncLastError,
2031
+ workspaceSyncDiagnostics.pushBlockedReason,
2032
+ workspaceSyncDiagnostics.pullBlockedReason,
2033
+ workspaceSyncDiagnostics.retryBlockedReason
2059
2034
  ]);
2060
2035
  const syncLastError = headerSyncPresentation.lastError;
2061
2036
  const [headerSyncStatus, setHeaderSyncStatus] = useState(headerSyncPresentation.status);
2062
- const syncStageLabel = useMemo(() => {
2063
- if (workspaceSyncRepairBusy) {
2064
- return 'Repairing';
2065
- }
2066
- if (workspaceSyncBusy && workspaceSyncPhase === 'error') {
2067
- return 'Recovering';
2068
- }
2069
- switch (workspaceSyncPhase) {
2070
- case 'provision-local':
2071
- return 'Initial cloud upload';
2072
- case 'attach-cloud':
2073
- return 'Initial cloud pull';
2074
- case 'active':
2075
- return 'Active';
2076
- case 'error':
2077
- return 'Error';
2078
- default:
2079
- return 'Idle';
2080
- }
2081
- }, [workspaceSyncPhase, workspaceSyncRepairBusy]);
2082
- const workspaceSyncDiagnostics = getWorkspaceSyncDiagnostics();
2083
- const syncDiagnosticsSummary = useMemo(() => ([
2084
- {
2085
- label: 'AI profile snapshot',
2086
- value: `${workspaceSyncDiagnostics.aiProfileSnapshotCount} local profile${workspaceSyncDiagnostics.aiProfileSnapshotCount === 1 ? '' : 's'}`
2087
- },
2088
- {
2089
- label: 'AI profile raw response',
2090
- value: `${workspaceSyncDiagnostics.aiProfileSnapshotRawCount} from route`
2091
- },
2092
- {
2093
- label: 'Last pushed AI profiles',
2094
- value: `${workspaceSyncDiagnostics.lastPushedAiProfileCount} tracked`
2095
- },
2096
- {
2097
- label: 'AI watermark map',
2098
- value: `${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount} tracked`
2099
- },
2100
- {
2101
- label: 'Document snapshot',
2102
- value: `${workspaceSyncDiagnostics.documentSnapshotCount} local doc${workspaceSyncDiagnostics.documentSnapshotCount === 1 ? '' : 's'}`
2103
- },
2104
- {
2105
- label: 'Asset snapshot',
2106
- value: `${workspaceSyncDiagnostics.assetSnapshotCount} local asset${workspaceSyncDiagnostics.assetSnapshotCount === 1 ? '' : 's'}`
2107
- },
2108
- {
2109
- label: 'Queued full AI sync',
2110
- value: workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'
2111
- },
2112
- {
2113
- label: 'AI snapshot last fetch',
2114
- value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt
2115
- ? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString()
2116
- : 'Never'
2117
- },
2118
- {
2119
- label: 'AI snapshot fetch error',
2120
- value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'
2121
- },
2122
- {
2123
- label: 'AI snapshot skip reason',
2124
- value: workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'
2125
- }
2126
- ]), [workspaceSyncDiagnostics]);
2037
+ const syncDiagnosticsSummary = useMemo(() => buildSyncDiagnosticsSummary(workspaceSyncDiagnostics), [workspaceSyncDiagnostics]);
2038
+ const { syncRecentEvents, syncRecentEventsLoading, syncRecentEventsError, syncEventsListRef, loadRecentSyncEvents, } = useRecentSyncEvents({
2039
+ isOpen: showSyncStatusModal,
2040
+ workspaceId: currentWorkspaceId,
2041
+ refreshKeys: [
2042
+ workspaceLastPushAt,
2043
+ workspaceLastPullAt,
2044
+ workspaceLastErrorAt,
2045
+ workspaceSyncStatus
2046
+ ]
2047
+ });
2127
2048
  const shouldDelayHeaderSyncing = headerSyncPresentation.actionable
2128
2049
  && workspaceSyncStatus === 'syncing'
2129
2050
  && workspaceSyncPhase === 'active'
@@ -2138,282 +2059,34 @@ export function StandaloneLayout(props) {
2138
2059
  }, 1200);
2139
2060
  return () => window.clearTimeout(timeoutId);
2140
2061
  }, [headerSyncPresentation.status, shouldDelayHeaderSyncing]);
2141
- const formatSyncEventLine = useCallback((event) => {
2142
- const occurredAt = typeof event.occurredAt === 'string' && event.occurredAt.trim().length > 0
2143
- ? event.occurredAt
2144
- : 'unknown-time';
2145
- const eventType = typeof event.eventType === 'string' && event.eventType.trim().length > 0
2146
- ? event.eventType.trim()
2147
- : 'sync';
2148
- const status = event.status === 'error' ? 'failed' : 'succeeded';
2149
- const changeCountValue = Number(event.changeCount);
2150
- const changeCount = Number.isFinite(changeCountValue)
2151
- ? ` (${Math.max(0, Math.floor(changeCountValue))} change${Math.floor(changeCountValue) === 1 ? '' : 's'})`
2152
- : '';
2153
- const requestMsValue = Number(event.requestMs);
2154
- const requestMs = Number.isFinite(requestMsValue)
2155
- ? ` in ${Math.max(0, Math.floor(requestMsValue))}ms`
2156
- : '';
2157
- const statusCodeValue = Number(event.statusCode);
2158
- const statusCode = Number.isFinite(statusCodeValue)
2159
- ? ` [${Math.floor(statusCodeValue)}]`
2160
- : '';
2161
- const errorMessage = typeof event.errorMessage === 'string' && event.errorMessage.trim().length > 0
2162
- ? `: ${event.errorMessage.trim()}`
2163
- : '';
2164
- return `${occurredAt} ${eventType} ${status}${changeCount}${requestMs}${statusCode}${errorMessage}`;
2165
- }, []);
2166
- const syncEventRows = useMemo(() => {
2167
- if (syncRecentEventsLoading && syncRecentEvents.length === 0) {
2168
- return [{
2169
- key: 'loading',
2170
- text: 'Loading recent sync events...',
2171
- tone: 'muted'
2172
- }];
2173
- }
2174
- if (syncRecentEventsError) {
2175
- return [{
2176
- key: 'error',
2177
- text: syncRecentEventsError,
2178
- tone: 'error'
2179
- }];
2180
- }
2181
- if (syncRecentEvents.length === 0) {
2182
- return [{
2183
- key: 'empty',
2184
- text: 'No recent sync events recorded.',
2185
- tone: 'muted'
2186
- }];
2187
- }
2188
- return syncRecentEvents.map((event, index) => ({
2189
- key: getSyncEventStableKey(event, index),
2190
- text: formatSyncEventLine(event),
2191
- tone: event.status === 'error' ? 'error' : 'default'
2192
- }));
2193
- }, [formatSyncEventLine, syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading]);
2062
+ const syncEventRows = useMemo(() => buildSyncEventRows({
2063
+ syncRecentEvents,
2064
+ syncRecentEventsError,
2065
+ syncRecentEventsLoading
2066
+ }), [syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading]);
2194
2067
  const referenceMismatchCount = useMemo(() => getActiveReferenceMismatchCount(syncRecentEvents), [syncRecentEvents]);
2195
- const activeReferenceMismatchEvents = useMemo(() => getActiveReferenceMismatchEvents(syncRecentEvents), [syncRecentEvents]);
2196
- const activeReferenceMismatchSummaries = useMemo(() => (activeReferenceMismatchEvents.map((event, index) => {
2197
- const details = event.details && typeof event.details === 'object' ? event.details : null;
2198
- const pathValue = String(details?.path || '').trim();
2199
- const referenceLabelValue = String(details?.referenceLabel || '').trim();
2200
- const taskTitleValue = String(details?.taskTitle || details?.existingTaskTitle || '').trim();
2201
- const existingRefValue = Number(details?.existingReferenceNumber);
2202
- const incomingRefValue = Number(details?.incomingReferenceNumber);
2203
- const pathLabel = pathValue || taskTitleValue || referenceLabelValue || `Mismatch ${index + 1}`;
2204
- const refsLabel = (Number.isFinite(existingRefValue)
2205
- || Number.isFinite(incomingRefValue))
2206
- ? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
2207
- : referenceLabelValue
2208
- ? `Both claimed ${referenceLabelValue}`
2209
- : null;
2210
- return {
2211
- key: getSyncEventStableKey(event, index),
2212
- pathLabel,
2213
- refsLabel,
2214
- };
2215
- })), [activeReferenceMismatchEvents]);
2216
- useLayoutEffect(() => {
2217
- const pendingRestore = pendingSyncEventsScrollRestoreRef.current;
2218
- const listEl = syncEventsListRef.current;
2219
- if (!pendingRestore || !listEl)
2220
- return;
2221
- const delta = listEl.scrollHeight - pendingRestore.scrollHeight;
2222
- listEl.scrollTop = pendingRestore.scrollTop + Math.max(0, delta);
2223
- pendingSyncEventsScrollRestoreRef.current = null;
2224
- }, [syncRecentEvents]);
2225
- const loadRecentSyncEvents = useCallback(async () => {
2226
- const res = await fetch(`/api/taskforce/sync/events?workspace_id=${encodeURIComponent(currentWorkspaceId)}&limit=15`, {
2227
- method: 'GET',
2228
- credentials: 'include'
2229
- });
2230
- if (!res.ok) {
2231
- throw new Error(`Failed to load sync events (${res.status})`);
2232
- }
2233
- const data = await res.json().catch(() => ({}));
2234
- return Array.isArray(data?.events)
2235
- ? data.events.filter((event) => event && typeof event === 'object')
2236
- : [];
2237
- }, [currentWorkspaceId]);
2238
- useEffect(() => {
2239
- if (!showSyncStatusModal)
2240
- return;
2241
- let cancelled = false;
2242
- const isInitialLoad = syncRecentEvents.length === 0;
2243
- if (isInitialLoad) {
2244
- setSyncRecentEventsLoading(true);
2245
- }
2246
- void loadRecentSyncEvents()
2247
- .then((events) => {
2248
- if (cancelled)
2249
- return;
2250
- const listEl = syncEventsListRef.current;
2251
- pendingSyncEventsScrollRestoreRef.current = listEl && listEl.scrollTop > 8
2252
- ? {
2253
- scrollTop: listEl.scrollTop,
2254
- scrollHeight: listEl.scrollHeight
2255
- }
2256
- : null;
2257
- setSyncRecentEvents((current) => {
2258
- const next = current.length === 0 ? events : mergeRecentSyncEvents(current, events, 15);
2259
- return areSyncEventListsEqual(current, next) ? current : next;
2260
- });
2261
- setSyncRecentEventsError(null);
2262
- })
2263
- .catch((error) => {
2264
- if (cancelled)
2265
- return;
2266
- const message = error instanceof Error && error.message.trim().length > 0
2267
- ? error.message.trim()
2268
- : 'Unable to load recent sync events.';
2269
- setSyncRecentEventsError(message);
2270
- if (isInitialLoad) {
2271
- setSyncRecentEvents([]);
2272
- }
2273
- })
2274
- .finally(() => {
2275
- if (cancelled)
2276
- return;
2277
- if (isInitialLoad) {
2278
- setSyncRecentEventsLoading(false);
2279
- }
2280
- });
2281
- return () => {
2282
- cancelled = true;
2283
- };
2284
- }, [
2285
- showSyncStatusModal,
2286
- loadRecentSyncEvents,
2287
- syncRecentEvents.length,
2288
- workspaceLastPushAt,
2289
- workspaceLastPullAt,
2290
- workspaceLastErrorAt,
2291
- workspaceSyncStatus
2292
- ]);
2293
- const syncStatusMeta = useMemo(() => {
2294
- switch (headerSyncStatus) {
2295
- case 'off':
2296
- return {
2297
- label: 'Off',
2298
- icon: 'off',
2299
- color: '#94a3b8',
2300
- border: 'rgba(148, 163, 184, 0.35)',
2301
- background: 'rgba(148, 163, 184, 0.12)'
2302
- };
2303
- case 'syncing':
2304
- return {
2305
- label: 'Syncing',
2306
- icon: 'cloud',
2307
- color: '#60a5fa',
2308
- border: 'rgba(96, 165, 250, 0.35)',
2309
- background: 'rgba(59, 130, 246, 0.12)'
2310
- };
2311
- case 'attention':
2312
- return {
2313
- label: 'Needs Attention',
2314
- icon: 'cloud',
2315
- color: '#fda4af',
2316
- border: 'rgba(253, 164, 175, 0.4)',
2317
- background: 'rgba(239, 68, 68, 0.12)'
2318
- };
2319
- default:
2320
- return {
2321
- label: 'Healthy',
2322
- icon: 'cloud',
2323
- color: '#86efac',
2324
- border: 'rgba(134, 239, 172, 0.4)',
2325
- background: 'rgba(34, 197, 94, 0.12)'
2326
- };
2327
- }
2328
- }, [headerSyncStatus]);
2329
- const handleCopySyncDetails = useCallback(async () => {
2330
- const details = [
2331
- 'Taskforce Sync Manager',
2332
- `Workspace ID: ${currentWorkspaceId}`,
2333
- `Status: ${syncStatusMeta.label}`,
2334
- `Summary: ${headerSyncPresentation.summary}`,
2335
- `Last successful sync: ${formattedLastSyncTime}`,
2336
- `Last pull from cloud: ${formattedLastPullTime}`,
2337
- `Last push to cloud: ${formattedLastPushTime}`,
2338
- `Last error at: ${workspaceSyncDiagnostics.lastErrorAt ? new Date(workspaceSyncDiagnostics.lastErrorAt).toLocaleString() : 'Never'}`,
2339
- `Pending local changes: ${workspaceSyncPendingChanges}`,
2340
- `Last error: ${syncLastError}`,
2341
- `Sync stage: ${syncStageLabel}`,
2342
- `AI profile snapshot: ${workspaceSyncDiagnostics.aiProfileSnapshotCount}`,
2343
- `AI profile raw response: ${workspaceSyncDiagnostics.aiProfileSnapshotRawCount}`,
2344
- `Last pushed AI profiles tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileCount}`,
2345
- `AI profile watermarks tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount}`,
2346
- `AI snapshot last fetch: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt ? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString() : 'Never'}`,
2347
- `AI snapshot fetch error: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'}`,
2348
- `AI snapshot skip reason: ${workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'}`,
2349
- `Document snapshot: ${workspaceSyncDiagnostics.documentSnapshotCount}`,
2350
- `Asset snapshot: ${workspaceSyncDiagnostics.assetSnapshotCount}`,
2351
- `Reference mismatches detected: ${referenceMismatchCount}`,
2352
- `Queued full AI sync: ${workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'}`
2353
- ];
2354
- try {
2355
- const events = syncRecentEvents.length > 0 ? syncRecentEvents : await loadRecentSyncEvents();
2356
- const recentEventLines = events.map((event) => formatSyncEventLine(event));
2357
- const copyText = [
2358
- ...details,
2359
- '',
2360
- 'Recent sync events',
2361
- ...(recentEventLines.length > 0 ? recentEventLines : ['No recent sync events recorded.'])
2362
- ].join('\n');
2363
- await navigator.clipboard.writeText(copyText);
2364
- setWorkspaceSyncCopied(true);
2365
- window.setTimeout(() => {
2366
- setWorkspaceSyncCopied(false);
2367
- }, 1800);
2368
- pushNotice('Sync details copied to clipboard.', 'success');
2369
- }
2370
- catch {
2371
- setWorkspaceSyncCopied(false);
2372
- pushNotice('Failed to copy sync details.', 'error');
2373
- }
2374
- }, [
2068
+ const activeReferenceMismatchSummaries = useMemo(() => buildReferenceMismatchSummaries(syncRecentEvents), [syncRecentEvents]);
2069
+ const syncStatusMeta = useMemo(() => resolveSyncStatusMeta(headerSyncStatus), [headerSyncStatus]);
2070
+ const { workspaceSyncRepairBusy, workspaceSyncRepairQueued, workspaceSyncCopied, handleCopySyncDetails, handleQueueOrRunRepairSync, } = useSyncStatusActions({
2375
2071
  currentWorkspaceId,
2376
- syncStatusMeta.label,
2377
- headerSyncPresentation.summary,
2072
+ syncStatusLabel: syncStatusMeta.label,
2073
+ workspaceSyncSummary: headerSyncPresentation.summary,
2074
+ workspaceSyncRecommendedAction: headerSyncPresentation.recommendedAction,
2378
2075
  formattedLastSyncTime,
2379
2076
  formattedLastPullTime,
2380
2077
  formattedLastPushTime,
2381
2078
  workspaceSyncDiagnostics,
2382
2079
  workspaceSyncPendingChanges,
2383
2080
  syncLastError,
2384
- syncStageLabel,
2081
+ workspaceSyncPhase,
2385
2082
  referenceMismatchCount,
2386
- pushNotice,
2387
2083
  syncRecentEvents,
2388
2084
  loadRecentSyncEvents,
2389
- formatSyncEventLine
2390
- ]);
2391
- const handleRepairSync = useCallback(async () => {
2392
- setWorkspaceSyncRepairQueued(false);
2393
- setWorkspaceSyncRepairBusy(true);
2394
- try {
2395
- await resetWorkspaceSyncCursorAndPull();
2396
- }
2397
- finally {
2398
- setWorkspaceSyncRepairBusy(false);
2399
- }
2400
- }, [resetWorkspaceSyncCursorAndPull]);
2401
- const handleQueueOrRunRepairSync = useCallback(() => {
2402
- if (workspaceSyncRepairBusy)
2403
- return;
2404
- if (workspaceSyncBusy) {
2405
- setWorkspaceSyncRepairQueued(true);
2406
- return;
2407
- }
2408
- void handleRepairSync();
2409
- }, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy]);
2410
- useEffect(() => {
2411
- if (!workspaceSyncRepairQueued)
2412
- return;
2413
- if (workspaceSyncBusy || workspaceSyncRepairBusy)
2414
- return;
2415
- void handleRepairSync();
2416
- }, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy, workspaceSyncRepairQueued]);
2085
+ pushNotice,
2086
+ resetWorkspaceSyncCursorAndPull,
2087
+ workspaceSyncBusy,
2088
+ });
2089
+ const syncStageLabel = useMemo(() => resolveSyncStageLabel(workspaceSyncPhase, workspaceSyncRepairBusy, workspaceSyncBusy), [workspaceSyncBusy, workspaceSyncPhase, workspaceSyncRepairBusy]);
2417
2090
  const selectedDateObj = useMemo(() => parseDateOnlyLocal(scheduleSelectedDate) || new Date(), [scheduleSelectedDate]);
2418
2091
  const scheduleWeekStart = useMemo(() => startOfWeek(selectedDateObj, globalWeekStartsOn), [selectedDateObj, globalWeekStartsOn]);
2419
2092
  const scheduleDates = useMemo(() => {
@@ -2634,6 +2307,8 @@ export function StandaloneLayout(props) {
2634
2307
  setActiveTab('add');
2635
2308
  }, [props.workstreams, resetForm, setActiveTab, setActiveWorkspaceModule, setWorkstreamInput]);
2636
2309
  const openPlanningEditor = useCallback((editor) => {
2310
+ planningEditorSubmitInFlightRef.current = false;
2311
+ setIsSubmittingPlanningEditor(false);
2637
2312
  setPlanningEditor(editor);
2638
2313
  setPlanningPrimaryCollapsed(false);
2639
2314
  setPlanningSecondaryEditor(null);
@@ -2648,6 +2323,8 @@ export function StandaloneLayout(props) {
2648
2323
  setPlanningNestedWorkstreamDetailId(null);
2649
2324
  }, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
2650
2325
  const openPlanningSecondaryEditor = useCallback((editor, initiativeId) => {
2326
+ planningEditorSubmitInFlightRef.current = false;
2327
+ setIsSubmittingPlanningEditor(false);
2651
2328
  setPlanningSecondaryEditor(editor);
2652
2329
  setPlanningSecondaryCollapsed(false);
2653
2330
  setPlanningDraftTitle('');
@@ -2661,6 +2338,8 @@ export function StandaloneLayout(props) {
2661
2338
  setPlanningNestedWorkstreamDetailId(null);
2662
2339
  }, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
2663
2340
  const closePlanningEditor = useCallback(() => {
2341
+ planningEditorSubmitInFlightRef.current = false;
2342
+ setIsSubmittingPlanningEditor(false);
2664
2343
  setPlanningEditor(null);
2665
2344
  setPlanningDraftTitle('');
2666
2345
  setPlanningDraftDescription('');
@@ -2668,6 +2347,8 @@ export function StandaloneLayout(props) {
2668
2347
  setPlanningDraftInitiativeId('');
2669
2348
  }, []);
2670
2349
  const closePlanningSecondaryEditor = useCallback(() => {
2350
+ planningEditorSubmitInFlightRef.current = false;
2351
+ setIsSubmittingPlanningEditor(false);
2671
2352
  setPlanningSecondaryEditor(null);
2672
2353
  setPlanningDraftTitle('');
2673
2354
  setPlanningDraftDescription('');
@@ -2676,8 +2357,10 @@ export function StandaloneLayout(props) {
2676
2357
  }, []);
2677
2358
  const submitPlanningEditor = useCallback(async () => {
2678
2359
  const activeEditor = planningSecondaryEditor || planningEditor;
2679
- if (!activeEditor)
2360
+ if (!activeEditor || planningEditorSubmitInFlightRef.current)
2680
2361
  return;
2362
+ planningEditorSubmitInFlightRef.current = true;
2363
+ setIsSubmittingPlanningEditor(true);
2681
2364
  const isSecondaryEditor = Boolean(planningSecondaryEditor);
2682
2365
  const ownerId = planningDraftOwner.trim() || null;
2683
2366
  try {
@@ -2751,6 +2434,10 @@ export function StandaloneLayout(props) {
2751
2434
  catch (error) {
2752
2435
  pushNotice(error instanceof Error ? error.message : 'Failed to save planning item.', 'error');
2753
2436
  }
2437
+ finally {
2438
+ planningEditorSubmitInFlightRef.current = false;
2439
+ setIsSubmittingPlanningEditor(false);
2440
+ }
2754
2441
  }, [
2755
2442
  closePlanningEditor,
2756
2443
  closePlanningSecondaryEditor,
@@ -2960,6 +2647,54 @@ export function StandaloneLayout(props) {
2960
2647
  }
2961
2648
  await handleAssignInitiativeToWorkstream(matchedWorkstream.id, formatInitiativeReference(initiative));
2962
2649
  }, [handleAssignInitiativeToWorkstream, planningStructure.initiativeById, props.workstreams, pushNotice]);
2650
+ const getPlanningContextAttachmentDraft = useCallback((entityType, entityId) => {
2651
+ const key = `${entityType}:${entityId}`;
2652
+ const draft = planningContextAttachmentDraftsRef.current.get(key);
2653
+ if (draft)
2654
+ return draft;
2655
+ const entity = entityType === 'initiative'
2656
+ ? props.initiatives.find((initiative) => initiative.id === entityId)
2657
+ : props.workstreams.find((workstream) => workstream.id === entityId);
2658
+ return Array.isArray(entity?.attachments) ? entity.attachments : [];
2659
+ }, [props.initiatives, props.workstreams]);
2660
+ const updatePlanningContextAttachments = useCallback((entityType, entityId, nextAttachments) => {
2661
+ const key = `${entityType}:${entityId}`;
2662
+ planningContextAttachmentDraftsRef.current.set(key, nextAttachments);
2663
+ const previousUpdate = planningContextAttachmentUpdateQueueRef.current.get(key) || Promise.resolve();
2664
+ const nextUpdate = previousUpdate
2665
+ .catch(() => undefined)
2666
+ .then(() => (entityType === 'initiative'
2667
+ ? props.updateInitiative(entityId, { attachments: nextAttachments })
2668
+ : props.updateWorkstream(entityId, { attachments: nextAttachments })));
2669
+ planningContextAttachmentUpdateQueueRef.current.set(key, nextUpdate);
2670
+ void nextUpdate.catch((error) => {
2671
+ pushNotice(error instanceof Error ? error.message : 'Failed to update planning context.', 'error');
2672
+ }).finally(() => {
2673
+ if (planningContextAttachmentUpdateQueueRef.current.get(key) === nextUpdate) {
2674
+ planningContextAttachmentUpdateQueueRef.current.delete(key);
2675
+ }
2676
+ });
2677
+ }, [props, pushNotice]);
2678
+ const handleAddPlanningContextFile = useCallback((entityType, entityId, file) => {
2679
+ const current = getPlanningContextAttachmentDraft(entityType, entityId);
2680
+ updatePlanningContextAttachments(entityType, entityId, [...current, file]);
2681
+ }, [getPlanningContextAttachmentDraft, updatePlanningContextAttachments]);
2682
+ const handleRemovePlanningContextFile = useCallback((entityType, entityId, index) => {
2683
+ const current = getPlanningContextAttachmentDraft(entityType, entityId);
2684
+ updatePlanningContextAttachments(entityType, entityId, current.filter((_, i) => i !== index));
2685
+ }, [getPlanningContextAttachmentDraft, updatePlanningContextAttachments]);
2686
+ const handleUpdatePlanningContextCaption = useCallback((entityType, entityId, index, caption) => {
2687
+ const current = getPlanningContextAttachmentDraft(entityType, entityId);
2688
+ const nextAttachments = current.map((attachment, i) => {
2689
+ if (i !== index)
2690
+ return attachment;
2691
+ if (typeof attachment === 'string') {
2692
+ return { path: attachment, caption, timestamp: new Date().toISOString() };
2693
+ }
2694
+ return { ...attachment, caption };
2695
+ });
2696
+ updatePlanningContextAttachments(entityType, entityId, nextAttachments);
2697
+ }, [getPlanningContextAttachmentDraft, updatePlanningContextAttachments]);
2963
2698
  const handleInitiativeFilterChange = useCallback((initiativeId) => {
2964
2699
  setSelectedPlanningInitiativeId(initiativeId);
2965
2700
  setSelectedPlanningWorkstreamId('');
@@ -3196,10 +2931,12 @@ export function StandaloneLayout(props) {
3196
2931
  }, title: "Workspace Settings", "aria-label": "Workspace Settings", children: _jsx(Settings, { size: 18 }) })] })] }));
3197
2932
  }, [activeTab, handleModuleChange, handlePlanningToolToggle, planningDrawerOpen, resolvedWorkspaceModule, setSettingsSection, setActiveTab, workspaceToolModules]);
3198
2933
  const planningDrawerPortalRef = useRef(null);
3199
- const planningDrawerNode = (_jsx(PlanningDrawer, { open: planningDrawerOpen, leftOffset: WORKSPACE_TOOL_RAIL_WIDTH, initiatives: planningStructure.initiatives, standaloneWorkstreams: planningStructure.standaloneWorkstreams, activeInitiativeId: selectedPlanningInitiativeId, activeWorkstreamId: selectedPlanningWorkstreamId, expandedInitiativeIds: expandedPlanningInitiativeIds, detail: planningDrawerItem, editor: planningEditor, secondaryPane: planningSecondaryPane, assigneeOptions: assigneeOptions.map((option) => ({ value: String(option.value), label: option.label })), draftInitiativeSummary: planningDraftInitiativeSummary, draftTitle: planningDraftTitle, draftDescription: planningDraftDescription, draftOwner: planningDraftOwner, draftInitiativeId: planningDraftInitiativeId, onChangeDraftTitle: setPlanningDraftTitle, onChangeDraftDescription: setPlanningDraftDescription, onChangeDraftOwner: setPlanningDraftOwner, onChangeDraftInitiativeId: setPlanningDraftInitiativeId, onCollapseTreePane: () => setPlanningTreeCollapsed(true), onExpandTreePane: () => setPlanningTreeCollapsed(false), onCollapsePrimaryPane: () => setPlanningPrimaryCollapsed(true), onExpandPrimaryPane: () => setPlanningPrimaryCollapsed(false), onCollapseSecondaryPane: () => setPlanningSecondaryCollapsed(true), onExpandSecondaryPane: () => setPlanningSecondaryCollapsed(false), onBackFromSecondary: () => {
2934
+ const planningDrawerNode = (_jsx(PlanningDrawer, { open: planningDrawerOpen, leftOffset: WORKSPACE_TOOL_RAIL_WIDTH, initiatives: planningStructure.initiatives, standaloneWorkstreams: planningStructure.standaloneWorkstreams, activeInitiativeId: selectedPlanningInitiativeId, activeWorkstreamId: selectedPlanningWorkstreamId, expandedInitiativeIds: expandedPlanningInitiativeIds, detail: planningDrawerItem, editor: planningEditor, secondaryPane: planningSecondaryPane, currentWorkspaceId: currentWorkspaceId, assigneeOptions: assigneeOptions.map((option) => ({ value: String(option.value), label: option.label })), draftInitiativeSummary: planningDraftInitiativeSummary, draftTitle: planningDraftTitle, draftDescription: planningDraftDescription, draftOwner: planningDraftOwner, draftInitiativeId: planningDraftInitiativeId, onChangeDraftTitle: setPlanningDraftTitle, onChangeDraftDescription: setPlanningDraftDescription, onChangeDraftOwner: setPlanningDraftOwner, onChangeDraftInitiativeId: setPlanningDraftInitiativeId, onCollapseTreePane: () => setPlanningTreeCollapsed(true), onExpandTreePane: () => setPlanningTreeCollapsed(false), onCollapsePrimaryPane: () => setPlanningPrimaryCollapsed(true), onExpandPrimaryPane: () => setPlanningPrimaryCollapsed(false), onCollapseSecondaryPane: () => setPlanningSecondaryCollapsed(true), onExpandSecondaryPane: () => setPlanningSecondaryCollapsed(false), onBackFromSecondary: () => {
2935
+ planningEditorSubmitInFlightRef.current = false;
2936
+ setIsSubmittingPlanningEditor(false);
3200
2937
  setPlanningSecondaryEditor(null);
3201
2938
  setPlanningNestedWorkstreamDetailId(null);
3202
- }, treeCollapsed: planningTreeCollapsed, primaryCollapsed: planningPrimaryCollapsed, secondaryCollapsed: planningSecondaryCollapsed, onCancelEditor: closePlanningEditor, onSubmitEditor: submitPlanningEditor, onCreateInitiative: () => openPlanningCreateEditor('initiative'), onCreateWorkstream: () => openPlanningCreateEditor('workstream'), onCreateTaskInWorkstream: openAddTaskModalForWorkstream, onCreateWorkstreamInInitiative: (initiativeId) => openPlanningCreateEditor('workstream', initiativeId), onAssignInitiativeToWorkstream: handleAssignInitiativeToWorkstream, onOpenTaskById: handleOpenTaskById, onAttachTaskToWorkstreamByReference: handleAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference: handleAttachWorkstreamToInitiativeByReference, onToggleInitiative: togglePlanningInitiative, onSelectInitiative: selectPlanningInitiative, onSelectWorkstream: selectPlanningWorkstream, onOpenInitiativeDetails: openInitiativeDetails, onOpenWorkstreamDetails: openWorkstreamDetails, onOpenNestedWorkstreamDetails: openNestedWorkstreamDetails, onEditInitiative: (initiativeId) => openPlanningEditDialog('initiative', initiativeId), onEditWorkstream: (workstreamId) => openPlanningEditDialog('workstream', workstreamId), onArchiveInitiative: (initiativeId) => {
2939
+ }, treeCollapsed: planningTreeCollapsed, primaryCollapsed: planningPrimaryCollapsed, secondaryCollapsed: planningSecondaryCollapsed, onCancelEditor: closePlanningEditor, onSubmitEditor: submitPlanningEditor, isSubmittingEditor: isSubmittingPlanningEditor, onCreateInitiative: () => openPlanningCreateEditor('initiative'), onCreateWorkstream: () => openPlanningCreateEditor('workstream'), onCreateTaskInWorkstream: openAddTaskModalForWorkstream, onCreateWorkstreamInInitiative: (initiativeId) => openPlanningCreateEditor('workstream', initiativeId), onAssignInitiativeToWorkstream: handleAssignInitiativeToWorkstream, onOpenTaskById: handleOpenTaskById, onAttachTaskToWorkstreamByReference: handleAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference: handleAttachWorkstreamToInitiativeByReference, onAddPlanningContextFile: handleAddPlanningContextFile, onRemovePlanningContextFile: handleRemovePlanningContextFile, onUpdatePlanningContextCaption: handleUpdatePlanningContextCaption, onToggleInitiative: togglePlanningInitiative, onSelectInitiative: selectPlanningInitiative, onSelectWorkstream: selectPlanningWorkstream, onOpenInitiativeDetails: openInitiativeDetails, onOpenWorkstreamDetails: openWorkstreamDetails, onOpenNestedWorkstreamDetails: openNestedWorkstreamDetails, onEditInitiative: (initiativeId) => openPlanningEditDialog('initiative', initiativeId), onEditWorkstream: (workstreamId) => openPlanningEditDialog('workstream', workstreamId), onArchiveInitiative: (initiativeId) => {
3203
2940
  void props.archiveInitiative(initiativeId).then(() => {
3204
2941
  pushNotice('Initiative archived', 'success');
3205
2942
  }).catch((error) => {
@@ -3234,7 +2971,7 @@ export function StandaloneLayout(props) {
3234
2971
  const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
3235
2972
  ? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
3236
2973
  : null;
3237
- return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, meta: isPlansScreen ? null : (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), normalizedAuthUserId && normalizedAuthUserId !== 'anonymous' && (_jsxs(_Fragment, { children: [_jsxs("span", { className: styles.taskCountBadge, title: "Tasks assigned to me", children: [_jsx(User, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedTaskCount] }), _jsxs("span", { className: `${styles.taskCountBadge} ${assignedOverdueTaskCount > 0 ? styles.taskCountBadgeAlert : ''}`.trim(), title: "Tasks overdue", children: [_jsx(ClockAlert, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedOverdueTaskCount] })] })), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: () => setShowSyncStatusModal(true), title: `Sync manager: ${syncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
2974
+ return (_jsxs("div", { className: `${shellStyles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsx(AppShellHeader, { projectName: projectName, currentWorkspaceId: currentWorkspaceId, runtimeMode: runtimeMode, theme: currentTheme, meta: (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), normalizedAuthUserId && normalizedAuthUserId !== 'anonymous' && (_jsxs(_Fragment, { children: [_jsxs("span", { className: styles.taskCountBadge, title: "Tasks assigned to me", children: [_jsx(User, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedTaskCount] }), _jsxs("span", { className: `${styles.taskCountBadge} ${assignedOverdueTaskCount > 0 ? styles.taskCountBadgeAlert : ''}`.trim(), title: "Tasks overdue", children: [_jsx(ClockAlert, { size: 13, className: styles.taskCountBadgeIcon, "aria-hidden": "true" }), assignedOverdueTaskCount] })] })), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: openSyncStatusModal, title: `Sync manager: ${syncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
3238
2975
  marginLeft: '6px',
3239
2976
  height: '24px',
3240
2977
  width: '24px',
@@ -3246,10 +2983,25 @@ export function StandaloneLayout(props) {
3246
2983
  display: 'inline-flex',
3247
2984
  alignItems: 'center',
3248
2985
  justifyContent: 'center'
3249
- }, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: () => { void handlePlansBack(); }, title: isAuthenticated && !hasActiveCommercialEntitlement ? 'Choose a plan to continue.' : 'Continue to Taskforce', disabled: plansNavigationBusy || (isAuthenticated && !hasActiveCommercialEntitlement), children: hasActiveCommercialEntitlement ? 'Take Me to Taskforce' : 'Back' })), !isPlansScreen && (_jsxs(_Fragment, { children: [workspaceHeaderSections, workspacePrimaryAction] })), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalDisplayActions }), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalFilterActions }), accountMenuControl] })) }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), authBlocked && (_jsx("div", { className: authStyles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), !isPlansScreen && showFilters && workspaceFilterBar, isPlansScreen ? (_jsx("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} ${styles.appScrollbar} tf-scrollbar`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflowY: 'auto', overflowX: 'hidden', scrollbarGutter: 'stable' }, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(PlansPage, { ...props, currentTheme: currentTheme, projectName: projectName, currentWorkspaceId: currentWorkspaceId, authUserId: authUserId, apiBaseUrl: apiBaseUrl, connectedEnvironmentSource: props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '', resolveCloudAuthUrl: resolveCloudAuthUrl, embedded: true, shellOwnsScroll: true, onAccountProfileSummaryChange: applyPlansAccountProfileSummary, onContinueToTaskforce: handlePlansBack, continueBusy: plansNavigationBusy }) }) })) : resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(DocumentWorkspaceShell, { tasks: tasks, runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', typeFilters: documentTypeFilters, attachmentFilters: documentAttachmentFilters, onTypeFiltersChange: setDocumentTypeFilters, onAttachmentFiltersChange: setDocumentAttachmentFilters, requestedDocPath: requestedDocPath, requestedDocAssetId: requestedDocAssetId, onRequestedDocHandled: () => {
2986
+ }, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsxs(_Fragment, { children: [isAuthenticated && accountProfileSummary?.stripeCustomerId && (_jsx("button", { className: "tf-control-icon", onClick: () => { void handleOpenBillingPortal(); }, title: "Manage billing", disabled: billingActionBusy, children: "Manage billing" })), _jsx("button", { className: "tf-control-icon", onClick: () => {
2987
+ if (hasActiveCommercialEntitlement) {
2988
+ void handlePlansBack();
2989
+ return;
2990
+ }
2991
+ closePlansScreen();
2992
+ }, title: hasActiveCommercialEntitlement ? 'Continue to Taskforce' : 'Back', disabled: plansNavigationBusy, children: hasActiveCommercialEntitlement ? 'Take Me to Taskforce' : 'Back' })] })), !isPlansScreen && (_jsxs(_Fragment, { children: [workspaceHeaderSections, workspacePrimaryAction] })), _jsx(WorkspaceHeaderActions, { actions: universalDisplayActions }), !isPlansScreen && _jsx(WorkspaceHeaderActions, { actions: universalFilterActions }), accountMenuControl] })) }), _jsx(TopNoticeLayer, { notice: uiNotice, onDismiss: clearNotice }), authBlocked && (_jsx("div", { className: authStyles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), !isPlansScreen && showFilters && workspaceFilterBar, isPlansScreen ? (_jsx("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} ${styles.appScrollbar} tf-scrollbar`, style: {
2993
+ position: 'relative',
2994
+ display: 'flex',
2995
+ flex: 1,
2996
+ minHeight: 0,
2997
+ overflowY: 'auto',
2998
+ overflowX: 'hidden',
2999
+ scrollbarGutter: 'stable',
3000
+ background: 'var(--surface-page)'
3001
+ }, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(PlansPage, { ...props, currentTheme: currentTheme, projectName: projectName, currentWorkspaceId: currentWorkspaceId, authUserId: authUserId, apiBaseUrl: apiBaseUrl, connectedEnvironmentSource: props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '', resolveCloudAuthUrl: resolveCloudAuthUrl, embedded: true, shellOwnsScroll: true, onAccountProfileSummaryChange: applyPlansAccountProfileSummary, onContinueToTaskforce: handlePlansBack, continueBusy: plansNavigationBusy }) }) })) : resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(DocumentWorkspaceShell, { tasks: tasks, runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', typeFilters: documentTypeFilters, attachmentFilters: documentAttachmentFilters, onTypeFiltersChange: setDocumentTypeFilters, onAttachmentFiltersChange: setDocumentAttachmentFilters, requestedDocPath: requestedDocPath, requestedDocAssetId: requestedDocAssetId, onRequestedDocHandled: () => {
3250
3002
  setRequestedDocPath(null);
3251
3003
  setRequestedDocAssetId(null);
3252
- }, enableTaskGeneration: true }) })] })) : resolvedWorkspaceModule === 'annotate' && annotatedAttachmentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AnnotatedAttachmentWorkspaceShell, { runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', workspaceId: currentWorkspaceId, sessionLoadReady: annotatedWorkspaceReady, requestedTarget: requestedAnnotatedTarget, requestedSessionId: requestedAnnotatedSessionId, requestedOpenVersion: requestedAnnotatedOpenVersion, resolveTaskReferenceLabel: resolveAnnotatedTaskReferenceLabel, resolveImageReferenceLabel: resolveAnnotatedImageReferenceLabel, onOpenTarget: handleAnnotatedAttachmentOpenTarget, onContextChange: handleAnnotatedAttachmentContextChange, onBackToTask: handleBackToTaskFromAnnotatedAttachment }) })] })) : resolvedWorkspaceModule === 'workflows' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(WorkflowsModule, { availableWorkflows: availableWorkflows, availableEnvironments: availableEnvironments, exportEnvironment: exportEnvironment, exportWorkflowsPath: props.exportWorkflowsPath, exportingResource: exportingResource, exportResult: exportResult, onExportEnvironmentChange: props.setExportEnvironment, onExportWorkflows: onExportWorkflows, onRefreshWorkflows: fetchWorkflows, onFetchWorkflowTemplate: props.fetchWorkflowTemplate, onFetchWorkflowOverrideNames: props.fetchWorkflowOverrideNames, onSaveWorkflowTemplateDraft: props.saveWorkflowTemplateDraft, onResetWorkflowTemplateDraft: props.resetWorkflowTemplateDraft }) })] })) : resolvedWorkspaceModule === 'agents' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AgentsModule, { workspaceId: currentWorkspaceId }) })] })) : (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} `, style: {
3004
+ }, enableTaskGeneration: true }) })] })) : resolvedWorkspaceModule === 'annotate' && annotatedAttachmentsWorkspaceEnabled ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AnnotatedAttachmentWorkspaceShell, { runtimeMode: runtimeMode, apiBaseUrl: props.config?.apiBaseUrl || '', cloudAuthBaseUrl: props.config?.cloudAuthBaseUrl || '', workspaceId: currentWorkspaceId, sessionLoadReady: annotatedWorkspaceReady, requestedTarget: requestedAnnotatedTarget, requestedSessionId: requestedAnnotatedSessionId, requestedOpenVersion: requestedAnnotatedOpenVersion, resolveTaskReferenceLabel: resolveAnnotatedTaskReferenceLabel, resolveImageReferenceLabel: resolveAnnotatedImageReferenceLabel, onOpenTarget: handleAnnotatedAttachmentOpenTarget, onContextChange: handleAnnotatedAttachmentContextChange, onBackToTask: handleBackToTaskFromAnnotatedAttachment }) })] })) : resolvedWorkspaceModule === 'workflows' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(WorkflowsModule, { availableWorkflows: availableWorkflows, availableEnvironments: availableEnvironments, exportEnvironment: exportEnvironment, exportWorkflowsPath: props.exportWorkflowsPath, exportingResource: exportingResource, exportResult: exportResult, onExportEnvironmentChange: props.setExportEnvironment, onExportWorkflows: onExportWorkflows, onRefreshWorkflows: fetchWorkflows, onFetchWorkflowTemplate: props.fetchWorkflowTemplate, onFetchWorkflowOverrideNames: props.fetchWorkflowOverrideNames, onSaveWorkflowTemplateDraft: props.saveWorkflowTemplateDraft, onResetWorkflowTemplateDraft: props.resetWorkflowTemplateDraft }) })] })) : resolvedWorkspaceModule === 'agents' ? (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden', paddingLeft: `${WORKSPACE_TOOL_RAIL_WIDTH}px` }, children: [workspaceToolRail, _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', flex: 1 }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(AgentsModule, { workspaceId: currentWorkspaceId, cloudAuthConfigured: cloudAuthConfigured, authSessionResolved: authSessionResolved, isAuthenticated: isAuthenticated, cloudAiProfileSeatUsage: accountProfileSummary?.aiProfileSeatUsage ?? null }) })] })) : (_jsxs("div", { className: `${shellStyles.standalonePage} ${shellStyles.standaloneContent} `, style: {
3253
3005
  position: 'relative',
3254
3006
  opacity: props.loadingTasks ? 0.7 : 1,
3255
3007
  transition: 'opacity 0.2s ease, padding 220ms cubic-bezier(0.4, 0, 0.2, 1)',
@@ -3265,7 +3017,7 @@ export function StandaloneLayout(props) {
3265
3017
  inset: 0,
3266
3018
  zIndex: 35,
3267
3019
  pointerEvents: 'none',
3268
- } }), _jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [props.loadingTasks && boardVisibleTasks.length > 0 && (_jsx("div", { className: styles.boardRefreshIndicator, "aria-live": "polite", "aria-label": "Refreshing tasks", children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks: scopedKanbanAllTasks, columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, assigneeOptions: assigneeOptions, scheduleFilteredTaskIds: Array.from(scheduleFilteredTaskIds), copiedId: copiedId, taxonomies: taxonomies, types: activeTypes, priorities: priorities, onUpdateTask: handleUpdateTask, onTaskClick: (task) => handleEdit(task), onOpenTaskById: handleOpenTaskById, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onAddTaskToColumn: handleAddTaskToColumn, showTaskCardStatusLabel: showTaskCardStatusLabel, onScheduleDaySelected: (dateOnly) => {
3020
+ } }), _jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [props.loadingTasks && boardVisibleTasks.length > 0 && (_jsx("div", { className: styles.boardRefreshIndicator, "aria-live": "polite", "aria-label": "Refreshing tasks", children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), _jsx(TaskKanban, { tasks: scopedBoardVisibleTasks, allTasks: scopedKanbanAllTasks, columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, filtersReady: hasInitedFilters, assigneeOptions: assigneeOptions, scheduleFilteredTaskIds: Array.from(scheduleFilteredTaskIds), copiedId: copiedId, taxonomies: taxonomies, types: activeTypes, priorities: priorities, onUpdateTask: handleUpdateTask, onTaskClick: (task) => handleEdit(task), onOpenTaskById: handleOpenTaskById, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onAddTaskToColumn: handleAddTaskToColumn, showTaskCardStatusLabel: showTaskCardStatusLabel, onScheduleDaySelected: (dateOnly) => {
3269
3021
  setScheduleSelectedDate(dateOnly);
3270
3022
  const parsed = parseDateOnlyLocal(dateOnly);
3271
3023
  if (parsed) {
@@ -3376,11 +3128,15 @@ export function StandaloneLayout(props) {
3376
3128
  return;
3377
3129
  }
3378
3130
  void handleUnarchive(taskId);
3379
- }, currentTask: currentTask })] }), _jsx(Modal, { isOpen: activeTab === 'settings', onClose: handleCloseModal, title: "Settings", size: "xl", theme: currentTheme, draggable: true, isSettings: true, closeOnOverlayClick: false, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem' }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(TaskSettings, { settingsModel: settingsModel, onSectionChange: setSettingsSection }) }) }), _jsx(ScheduleWarningModal, { prompt: scheduleWarningPrompt, theme: currentTheme, onClose: cancelScheduleWarning, onConfirm: confirmScheduleWarning }), _jsx(HelpModal, { isOpen: showHelpModal, theme: currentTheme, onClose: () => setShowHelpModal(false), onOpenSettings: () => {
3131
+ }, currentTask: currentTask })] }), _jsx(Modal, { isOpen: activeTab === 'settings', onClose: handleCloseModal, title: "Settings", size: "xl", theme: currentTheme, draggable: true, isSettings: true, closeOnOverlayClick: false, children: _jsx(Suspense, { fallback: _jsx("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem' }, children: _jsx(Loader2, { size: 20, className: styles.spinner }) }), children: _jsx(TaskSettings, { settingsModel: settingsModel, onSectionChange: setSettingsSection }) }) }), _jsx(ScheduleWarningModal, { prompt: scheduleWarningPrompt, theme: currentTheme, onClose: cancelScheduleWarning, onConfirm: confirmScheduleWarning }), _jsx(SyncEnableWarningModal, { isOpen: showSyncEnableWarning, theme: currentTheme, onClose: cancelSyncEnableWarning, onConfirm: confirmSyncEnableWarning }), _jsx(HelpModal, { isOpen: showHelpModal, theme: currentTheme, onClose: () => setShowHelpModal(false), onOpenSettings: () => {
3380
3132
  setShowHelpModal(false);
3381
3133
  setActiveTab('settings');
3382
3134
  setSettingsSection('general');
3383
- } }), _jsx(EditProfileModal, { isOpen: showEditProfileModal, theme: currentTheme, onClose: closeEditProfileModal, onSave: () => { void handleSaveProfile(); }, displayName: profileDisplayNameDraft, email: authEmailLabel, avatarDisplayUrl: profileAvatarDisplayUrl, accountBadgeInitial: accountBadgeInitial, avatarInputRef: profileAvatarInputRef, avatarAccept: PROFILE_AVATAR_ACCEPT, avatarDraftId: profileAvatarDraftId, saveBusy: profileSaveBusy, avatarBusy: profileAvatarBusy, saveError: profileSaveError, saveNotice: profileSaveNotice, onDisplayNameChange: setProfileDisplayNameDraft, onAvatarInputChange: (file) => { void handleProfileAvatarSelected(file); }, onStartAvatarUpload: () => profileAvatarInputRef.current?.click(), onDiscardUpload: handleDiscardPendingProfileAvatar, onRemovePhoto: handleRemoveProfileAvatar }), _jsx(AccountHubModal, { isOpen: showAccountHub, theme: currentTheme, runtimeMode: runtimeMode, authRequiredForApi: authRequiredForApi, isAuthenticated: isAuthenticated, hasAuthIdentity: hasAuthIdentity, authIdentityLabel: authIdentityLabel, billingLoading: billingLoading, billingError: billingError, billingActionError: billingActionError, billingNotice: billingNotice, billingActionBusy: billingActionBusy, billingIntervalChoice: billingIntervalChoice, accountProfileSummary: accountProfileSummary, currentWorkspaceId: currentWorkspaceId, canOpenTeamManagement: canOpenTeamManagement, canManageWorkspaceSync: canManageWorkspaceSync, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncStatusLabel: syncStatusMeta.label, workspaceSyncError: workspaceSyncError, syncControlBusy: syncControlBusy, onClose: () => setShowAccountHub(false), onOpenWorkspaceAudit: () => {
3135
+ } }), _jsx(EditProfileModal, { isOpen: showEditProfileModal, theme: currentTheme, onClose: closeEditProfileModal, onSave: () => { void handleSaveProfile(); }, displayName: profileDisplayNameDraft, email: authEmailLabel, avatarDisplayUrl: profileAvatarDisplayUrl, accountBadgeInitial: accountBadgeInitial, saveBusy: profileSaveBusy, avatarBusy: profileAvatarBusy, saveError: profileSaveError, saveNotice: profileSaveNotice, onDisplayNameChange: setProfileDisplayNameDraft, onOpenAvatarManager: () => {
3136
+ setProfileSaveError(null);
3137
+ setProfileSaveNotice(null);
3138
+ setShowAvatarPhotoManager(true);
3139
+ } }), _jsx(AvatarImageManagerModal, { isOpen: showAvatarPhotoManager, theme: currentTheme, title: "Edit Profile Photo", currentImageUrl: profileAvatarDisplayUrl, fallbackInitial: accountBadgeInitial, accept: PROFILE_AVATAR_ACCEPT, busy: profileAvatarBusy, hasPendingImage: Boolean(profileAvatarDraftId), canRemove: Boolean(profileAvatarDisplayUrl), error: profileSaveError, notice: profileSaveNotice, onClose: () => setShowAvatarPhotoManager(false), onApplyImage: handleProfileAvatarSelected, onRemoveImage: handleRemoveProfileAvatar, onDiscardPendingImage: handleDiscardPendingProfileAvatar }), _jsx(AccountHubModal, { isOpen: showAccountHub, theme: currentTheme, runtimeMode: runtimeMode, authRequiredForApi: authRequiredForApi, isAuthenticated: isAuthenticated, hasAuthIdentity: hasAuthIdentity, authIdentityLabel: authIdentityLabel, billingLoading: billingLoading, billingError: billingError, billingActionError: billingActionError, billingNotice: billingNotice, billingActionBusy: billingActionBusy, billingIntervalChoice: billingIntervalChoice, accountProfileSummary: accountProfileSummary, currentWorkspaceId: currentWorkspaceId, canOpenTeamManagement: canOpenTeamManagement, canManageWorkspaceSync: canManageWorkspaceSync, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncStatusLabel: syncStatusMeta.label, workspaceSyncError: workspaceSyncError, syncControlBusy: syncControlBusy, cloudAuthEnabled: cloudAuthConfigured, availableAuthProviders: availableAuthProviders, onFetchLoginMethods: fetchLoginMethods, onUnlinkLoginMethod: unlinkLoginMethod, onAddPassword: addPasswordToAccount, onLinkProvider: (provider) => beginOAuthLink(provider), onClose: () => setShowAccountHub(false), onOpenWorkspaceAudit: () => {
3384
3140
  setShowAccountHub(false);
3385
3141
  void openTeamManagementModal();
3386
3142
  setTeamManagementTab('audit');
@@ -3436,7 +3192,7 @@ export function StandaloneLayout(props) {
3436
3192
  headers: teamAdminHeaders(),
3437
3193
  credentials: 'include'
3438
3194
  }));
3439
- }, onInviteEmailChange: setTeamInviteEmail, onInviteRoleChange: setTeamInviteRole, onInvitePermissionModeChange: setTeamInvitePermissionMode, onSubmitInvite: () => { void submitTeamInvite(); }, onLoadAuditPrevious: () => { void loadTeamAuditPage(teamAuditPage - 1); }, onLoadAuditNext: () => { void loadTeamAuditPage(teamAuditPage + 1); } }), _jsx(CreateWorkspaceConfirmModal, { isOpen: showCreateWorkspaceConfirm, theme: currentTheme, onClose: () => setShowCreateWorkspaceConfirm(false), onConfirm: () => openWorkspaceSetup({ intent: 'create-workspace' }) }), _jsx(SyncStatusModal, { isOpen: showSyncStatusModal, theme: currentTheme, currentWorkspaceLabel: currentWorkspaceLabel, syncStatusMeta: syncStatusMeta, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncControlBusy: syncControlBusy, canManageWorkspaceSync: canManageWorkspaceSync, workspaceSyncSummary: workspaceSyncSummary, workspaceSyncRepairBusy: workspaceSyncRepairBusy, referenceMismatchCount: referenceMismatchCount, syncStageLabel: syncStageLabel, workspaceSyncPendingChanges: workspaceSyncPendingChanges, formattedLastSyncTime: formattedLastSyncTime, formattedLastPullTime: formattedLastPullTime, formattedLastPushTime: formattedLastPushTime, syncLastError: syncLastError, workspaceSyncDiagnostics: workspaceSyncDiagnostics, activeReferenceMismatchSummaries: activeReferenceMismatchSummaries, syncDiagnosticsSummary: syncDiagnosticsSummary, syncEventRows: syncEventRows, syncEventsListRef: syncEventsListRef, workspaceSyncRepairQueued: workspaceSyncRepairQueued, workspaceSyncBusy: workspaceSyncBusy, workspaceSyncCopied: workspaceSyncCopied, runtimeMode: runtimeMode, isAuthenticated: isAuthenticated, onClose: () => setShowSyncStatusModal(false), onToggleWorkspaceSync: (enabled) => {
3195
+ }, onInviteEmailChange: setTeamInviteEmail, onInviteRoleChange: setTeamInviteRole, onInvitePermissionModeChange: setTeamInvitePermissionMode, onSubmitInvite: () => { void submitTeamInvite(); }, onLoadAuditPrevious: () => { void loadTeamAuditPage(teamAuditPage - 1); }, onLoadAuditNext: () => { void loadTeamAuditPage(teamAuditPage + 1); } }), _jsx(CreateWorkspaceConfirmModal, { isOpen: showCreateWorkspaceConfirm, theme: currentTheme, onClose: () => setShowCreateWorkspaceConfirm(false), onConfirm: () => openWorkspaceSetup({ intent: 'create-workspace' }) }), _jsx(SyncStatusModal, { isOpen: showSyncStatusModal, theme: currentTheme, currentWorkspaceLabel: currentWorkspaceLabel, syncStatusMeta: syncStatusMeta, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncControlBusy: syncControlBusy, canManageWorkspaceSync: canManageWorkspaceSync, workspaceSyncSummary: headerSyncPresentation.summary, workspaceSyncRecommendedAction: headerSyncPresentation.recommendedAction, workspaceSyncRepairBusy: workspaceSyncRepairBusy, referenceMismatchCount: referenceMismatchCount, syncStageLabel: syncStageLabel, workspaceSyncPendingChanges: workspaceSyncPendingChanges, formattedLastSyncTime: formattedLastSyncTime, formattedLastPullTime: formattedLastPullTime, formattedLastPushTime: formattedLastPushTime, syncLastError: syncLastError, workspaceSyncDiagnostics: workspaceSyncDiagnostics, activeReferenceMismatchSummaries: activeReferenceMismatchSummaries, syncDiagnosticsSummary: syncDiagnosticsSummary, syncEventRows: syncEventRows, syncEventsListRef: syncEventsListRef, workspaceSyncRepairQueued: workspaceSyncRepairQueued, workspaceSyncBusy: workspaceSyncBusy, workspaceSyncCopied: workspaceSyncCopied, runtimeMode: runtimeMode, isAuthenticated: isAuthenticated, onClose: closeSyncStatusModal, onToggleWorkspaceSync: (enabled) => {
3440
3196
  void handleWorkspaceSyncToggle(enabled);
3441
3197
  }, onRepairSync: handleQueueOrRunRepairSync, onCopyReport: () => { void handleCopySyncDetails(); }, onOpenLogin: () => {
3442
3198
  openSharedLoginRoute('login');