@taskforcehq/taskforce 0.3.305 → 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 (337) hide show
  1. package/dist/Taskforce.module.css +295 -89
  2. package/dist/TaskforceCore.js +215 -43
  3. package/dist/TaskforceCore.test.js +433 -28
  4. package/dist/assets/fonts/CourierPrime-Bold.woff2 +0 -0
  5. package/dist/assets/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  6. package/dist/assets/fonts/CourierPrime-Italic.woff2 +0 -0
  7. package/dist/assets/fonts/CourierPrime-Regular.woff2 +0 -0
  8. package/dist/assets/fonts/Noir_medium.woff2 +0 -0
  9. package/dist/assets/fonts/Noir_regular.woff2 +0 -0
  10. package/dist/assets/fonts/RussoOne-Regular.woff2 +0 -0
  11. package/dist/assets/fonts/SpecialElite-Regular.woff2 +0 -0
  12. package/dist/compat/workspaceSyncCompat.js +46 -2
  13. package/dist/compat/workspaceSyncCompat.test.js +38 -1
  14. package/dist/components/context/ContextAttachmentManager.d.ts +14 -0
  15. package/dist/components/context/ContextAttachmentManager.js +549 -0
  16. package/dist/components/features/AgentsModule.d.ts +10 -1
  17. package/dist/components/features/AgentsModule.js +260 -15
  18. package/dist/components/features/AgentsModule.test.js +255 -5
  19. package/dist/components/features/DocumentViewer.d.ts +5 -1
  20. package/dist/components/features/DocumentViewer.js +60 -3
  21. package/dist/components/features/DocumentWorkspace.js +1 -1
  22. package/dist/components/features/DocumentWorkspace.test.js +33 -0
  23. package/dist/components/features/documentWorkspaceModel.d.ts +1 -1
  24. package/dist/components/task/TaskCard.js +19 -28
  25. package/dist/components/task/TaskCard.test.js +38 -3
  26. package/dist/components/task/TaskContextUpload.js +4 -526
  27. package/dist/components/task/TaskForm.js +85 -90
  28. package/dist/components/task/TaskForm.test.js +110 -8
  29. package/dist/components/task/TaskKanban.d.ts +2 -1
  30. package/dist/components/task/TaskKanban.js +94 -13
  31. package/dist/components/task/TaskKanban.test.js +36 -0
  32. package/dist/components/ui/EditableAvatarButton.d.ts +18 -0
  33. package/dist/components/ui/EditableAvatarButton.js +18 -0
  34. package/dist/components/views/PlanComparisonPage.d.ts +9 -1
  35. package/dist/components/views/PlanComparisonPage.js +70 -14
  36. package/dist/components/views/PlanComparisonPage.test.js +238 -0
  37. package/dist/components/views/PlansPage.js +127 -36
  38. package/dist/components/views/PlansPage.test.js +294 -12
  39. package/dist/components/views/StandaloneLayout.js +283 -533
  40. package/dist/components/views/panels/PlanningDrawer.d.ts +8 -1
  41. package/dist/components/views/panels/PlanningDrawer.js +18 -11
  42. package/dist/components/views/panels/PlanningDrawer.test.d.ts +1 -0
  43. package/dist/components/views/panels/PlanningDrawer.test.js +141 -0
  44. package/dist/components/views/planningScope.d.ts +20 -0
  45. package/dist/components/views/planningScope.js +25 -0
  46. package/dist/components/views/planningScope.test.d.ts +1 -0
  47. package/dist/components/views/planningScope.test.js +60 -0
  48. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +26 -1
  49. package/dist/components/views/standalone/modals/AccountHubModal.js +76 -2
  50. package/dist/components/views/standalone/modals/AccountHubModal.test.js +20 -3
  51. package/dist/components/views/standalone/modals/AvatarImageManagerModal.d.ts +20 -0
  52. package/dist/components/views/standalone/modals/AvatarImageManagerModal.js +127 -0
  53. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +2 -9
  54. package/dist/components/views/standalone/modals/EditProfileModal.js +3 -2
  55. package/dist/components/views/standalone/modals/SyncEnableWarningModal.d.ts +9 -0
  56. package/dist/components/views/standalone/modals/SyncEnableWarningModal.js +6 -0
  57. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.d.ts +1 -0
  58. package/dist/components/views/standalone/modals/SyncEnableWarningModal.test.js +24 -0
  59. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +2 -1
  60. package/dist/components/views/standalone/modals/SyncStatusModal.js +2 -2
  61. package/dist/core/AiProfileService.d.ts +39 -2
  62. package/dist/core/AiProfileService.js +639 -73
  63. package/dist/core/AiProfileService.test.js +19 -1
  64. package/dist/core/AiProfiles.test.js +566 -7
  65. package/dist/core/AttachmentLinkService.js +57 -15
  66. package/dist/core/AuthIdentityService.d.ts +107 -0
  67. package/dist/core/AuthIdentityService.js +284 -0
  68. package/dist/core/AuthTokenService.d.ts +4 -0
  69. package/dist/core/AuthTokenService.js +34 -6
  70. package/dist/core/AuthTokenService.test.js +3 -3
  71. package/dist/core/EntitlementsPolicy.test.js +2 -2
  72. package/dist/core/GlobalSettingsService.js +115 -6
  73. package/dist/core/PlanEntitlementService.d.ts +23 -2
  74. package/dist/core/PlanEntitlementService.js +343 -57
  75. package/dist/core/PlanFeatureCatalog.test.d.ts +1 -0
  76. package/dist/core/PlanFeatureCatalog.test.js +84 -0
  77. package/dist/core/PlanVersionPolicy.test.js +35 -2
  78. package/dist/core/PlanningEntities.test.js +52 -0
  79. package/dist/core/SignupMonetizationSettings.test.js +199 -27
  80. package/dist/core/SyncReconciliationService.js +2 -65
  81. package/dist/core/SystemAdmin.test.js +42 -42
  82. package/dist/core/TaskAttachmentsCanonical.test.js +51 -1
  83. package/dist/core/Taskforce.d.ts +225 -14
  84. package/dist/core/Taskforce.js +765 -192
  85. package/dist/core/Taskforce.listTasksSlim.test.d.ts +1 -0
  86. package/dist/core/Taskforce.listTasksSlim.test.js +91 -0
  87. package/dist/core/UserProfileAvatarDrafts.test.js +30 -3
  88. package/dist/core/WorkspaceLifecycleService.js +0 -5
  89. package/dist/core/WorkspacePermissions.test.js +25 -1
  90. package/dist/core/WorkspacePlanMode.test.js +117 -16
  91. package/dist/core/shared.d.ts +1 -1
  92. package/dist/core/shared.js +5 -1
  93. package/dist/core/types.d.ts +68 -2
  94. package/dist/hooks/auth/useTaskforceAuthBootstrap.js +26 -1
  95. package/dist/hooks/sync/bootstrap.js +2 -0
  96. package/dist/hooks/sync/controlPlane.d.ts +37 -0
  97. package/dist/hooks/sync/controlPlane.js +78 -0
  98. package/dist/hooks/sync/controlPlane.test.d.ts +1 -0
  99. package/dist/hooks/sync/controlPlane.test.js +121 -0
  100. package/dist/hooks/sync/lifecyclePolicy.d.ts +45 -0
  101. package/dist/hooks/sync/lifecyclePolicy.js +93 -0
  102. package/dist/hooks/sync/lifecyclePolicy.test.d.ts +1 -0
  103. package/dist/hooks/sync/lifecyclePolicy.test.js +124 -0
  104. package/dist/hooks/sync/orchestratorShared.d.ts +10 -1
  105. package/dist/hooks/sync/orchestratorShared.js +68 -47
  106. package/dist/hooks/sync/orchestratorShared.test.js +50 -1
  107. package/dist/hooks/sync/pullLifecycle.d.ts +31 -0
  108. package/dist/hooks/sync/pullLifecycle.js +24 -0
  109. package/dist/hooks/sync/pullLifecycle.test.d.ts +1 -0
  110. package/dist/hooks/sync/pullLifecycle.test.js +80 -0
  111. package/dist/hooks/sync/recovery.d.ts +16 -2
  112. package/dist/hooks/sync/recovery.js +171 -53
  113. package/dist/hooks/sync/recovery.test.d.ts +1 -0
  114. package/dist/hooks/sync/recovery.test.js +292 -0
  115. package/dist/hooks/sync/transfers.d.ts +15 -3
  116. package/dist/hooks/sync/transfers.js +179 -62
  117. package/dist/hooks/sync/transfers.test.d.ts +1 -0
  118. package/dist/hooks/sync/transfers.test.js +396 -0
  119. package/dist/hooks/sync/useRecentSyncEvents.d.ts +21 -0
  120. package/dist/hooks/sync/useRecentSyncEvents.js +92 -0
  121. package/dist/hooks/sync/useRecentSyncEvents.test.d.ts +1 -0
  122. package/dist/hooks/sync/useRecentSyncEvents.test.js +124 -0
  123. package/dist/hooks/sync/useSyncStatusActions.d.ts +30 -0
  124. package/dist/hooks/sync/useSyncStatusActions.js +88 -0
  125. package/dist/hooks/sync/useSyncStatusActions.test.d.ts +1 -0
  126. package/dist/hooks/sync/useSyncStatusActions.test.js +133 -0
  127. package/dist/hooks/sync/useSyncStatusControls.d.ts +25 -0
  128. package/dist/hooks/sync/useSyncStatusControls.js +60 -0
  129. package/dist/hooks/sync/useSyncStatusControls.test.d.ts +1 -0
  130. package/dist/hooks/sync/useSyncStatusControls.test.js +88 -0
  131. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +12 -0
  132. package/dist/hooks/useSyncOrchestrator.d.ts +9 -2
  133. package/dist/hooks/useSyncOrchestrator.js +359 -178
  134. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +407 -0
  135. package/dist/hooks/useTaskData.js +8 -3
  136. package/dist/hooks/useTaskData.test.js +45 -0
  137. package/dist/hooks/useTaskforce.d.ts +60 -2
  138. package/dist/hooks/useTaskforce.js +549 -240
  139. package/dist/hooks/useTaskforce.runtime-routing.test.d.ts +1 -0
  140. package/dist/hooks/useTaskforce.runtime-routing.test.js +152 -0
  141. package/dist/hooks/useTaskforce.sync-behavior.test.js +2380 -212
  142. package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
  143. package/dist/hooks/useWorkspaceSyncController.js +6 -1
  144. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +16 -0
  145. package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +316 -35
  146. package/dist/localization/locales/en-US.d.ts +0 -1
  147. package/dist/localization/locales/en-US.js +0 -1
  148. package/dist/localization/locales/es-419.js +0 -1
  149. package/dist/localization/locales/pt-BR.js +0 -1
  150. package/dist/mcp/documentAssetRegistrar.js +3 -3
  151. package/dist/mcp/runtime.d.ts +2 -0
  152. package/dist/mcp/runtime.js +131 -64
  153. package/dist/mcp/runtime.test.js +291 -12
  154. package/dist/migrations/taskSchemaMigrations.d.ts +1 -1
  155. package/dist/migrations/taskSchemaMigrations.js +171 -61
  156. package/dist/migrations/taskSchemaMigrations.test.js +15 -0
  157. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  158. package/dist/resources/templates/workflows/collaborate.yaml +1 -1
  159. package/dist/resources/templates/workflows/evaluate.yaml +2 -2
  160. package/dist/resources/templates/workflows/plan.yaml +1 -1
  161. package/dist/resources/templates/workflows/review.yaml +2 -2
  162. package/dist/server/annotatedAttachmentsRoutes.test.js +3 -3
  163. package/dist/server/auth/providers/apple.d.ts +33 -0
  164. package/dist/server/auth/providers/apple.js +82 -0
  165. package/dist/server/auth/providers/appleClientSecret.d.ts +37 -0
  166. package/dist/server/auth/providers/appleClientSecret.js +65 -0
  167. package/dist/server/auth/providers/github.d.ts +26 -0
  168. package/dist/server/auth/providers/github.js +86 -0
  169. package/dist/server/auth/providers/google.d.ts +21 -0
  170. package/dist/server/auth/providers/google.js +56 -0
  171. package/dist/server/auth/providers/types.d.ts +21 -0
  172. package/dist/server/auth/providers/types.js +1 -0
  173. package/dist/server/auth.d.ts +2 -0
  174. package/dist/server/auth.js +6 -3
  175. package/dist/server/documentReviewRoutes.test.js +36 -3
  176. package/dist/server/index.cookieProxy.test.js +1 -0
  177. package/dist/server/index.d.ts +12 -0
  178. package/dist/server/index.js +120 -9
  179. package/dist/server/index.rateLimit.test.js +3 -0
  180. package/dist/server/index.test.js +106 -3
  181. package/dist/server/routes/admin.d.ts +3 -0
  182. package/dist/server/routes/admin.js +272 -19
  183. package/dist/server/routes/auth.d.ts +15 -2
  184. package/dist/server/routes/auth.js +994 -57
  185. package/dist/server/routes/authSupport.d.ts +1 -1
  186. package/dist/server/routes/authSupport.js +1 -1
  187. package/dist/server/routes/billing.js +361 -35
  188. package/dist/server/routes/billing.test.js +592 -12
  189. package/dist/server/routes/documents.d.ts +1 -0
  190. package/dist/server/routes/documents.js +172 -70
  191. package/dist/server/routes/shared.d.ts +28 -2
  192. package/dist/server/routes/shared.js +69 -4
  193. package/dist/server/routes/sync.integration.test.js +437 -39
  194. package/dist/server/routes/sync.js +61 -605
  195. package/dist/server/routes/syncAuxRoutes.d.ts +15 -0
  196. package/dist/server/routes/syncAuxRoutes.js +91 -0
  197. package/dist/server/routes/syncAuxRoutes.test.d.ts +1 -0
  198. package/dist/server/routes/syncAuxRoutes.test.js +158 -0
  199. package/dist/server/routes/syncPullApplyRoutes.d.ts +36 -0
  200. package/dist/server/routes/syncPullApplyRoutes.js +204 -0
  201. package/dist/server/routes/syncPushRoutes.d.ts +24 -0
  202. package/dist/server/routes/syncPushRoutes.js +212 -0
  203. package/dist/server/routes/syncRouteGuards.d.ts +36 -0
  204. package/dist/server/routes/syncRouteGuards.js +67 -0
  205. package/dist/server/routes/syncRouteGuards.test.d.ts +1 -0
  206. package/dist/server/routes/syncRouteGuards.test.js +94 -0
  207. package/dist/server/routes/syncRouteTypes.d.ts +13 -0
  208. package/dist/server/routes/syncRouteTypes.js +1 -0
  209. package/dist/server/routes/syncSnapshotRoutes.d.ts +66 -0
  210. package/dist/server/routes/syncSnapshotRoutes.js +180 -0
  211. package/dist/server/routes/tasks.js +43 -1
  212. package/dist/server/routes/workspaces.js +36 -5
  213. package/dist/server/routes.js +60 -3
  214. package/dist/server/routes.test.js +1824 -171
  215. package/dist/services/workflowExportSnapshots.test.js +2 -0
  216. package/dist/shared/aiProfileSeatScope.d.ts +5 -0
  217. package/dist/shared/aiProfileSeatScope.js +21 -0
  218. package/dist/shared/passwordPolicy.d.ts +13 -0
  219. package/dist/shared/passwordPolicy.js +84 -0
  220. package/dist/shared/passwordPolicy.test.d.ts +1 -0
  221. package/dist/shared/passwordPolicy.test.js +34 -0
  222. package/dist/storage/workspaceAssetStore.d.ts +15 -3
  223. package/dist/storage/workspaceAssetStore.js +132 -44
  224. package/dist/storage/workspaceAssetStore.test.js +93 -6
  225. package/dist/styles/fonts.css +70 -0
  226. package/dist/sync/collaborationSyncPayload.d.ts +6 -1
  227. package/dist/sync/collaborationSyncPayload.js +17 -11
  228. package/dist/sync/collaborationSyncPayload.test.js +13 -1
  229. package/dist/sync/contentSyncState.d.ts +4 -8
  230. package/dist/sync/contentSyncState.js +17 -70
  231. package/dist/sync/syncApplyHandlers.d.ts +7 -0
  232. package/dist/sync/syncApplyHandlers.js +28 -13
  233. package/dist/sync/syncApplyHandlers.test.js +70 -3
  234. package/dist/sync/syncFieldSemantics.d.ts +12 -0
  235. package/dist/sync/syncFieldSemantics.js +55 -0
  236. package/dist/sync/syncResourceAdapters.d.ts +44 -0
  237. package/dist/sync/syncResourceAdapters.js +77 -0
  238. package/dist/sync/syncService.d.ts +5 -0
  239. package/dist/sync/syncService.js +52 -0
  240. package/dist/sync/syncService.test.js +57 -3
  241. package/dist/sync/taskSyncChangeKey.d.ts +2 -0
  242. package/dist/sync/taskSyncChangeKey.js +143 -0
  243. package/dist/sync/taskSyncPayload.js +3 -8
  244. package/dist/sync/workspacePullFeed.js +4 -3
  245. package/dist/sync/workspacePullFeed.test.js +43 -0
  246. package/dist/sync/workspaceRepair.js +5 -2
  247. package/dist/sync/workspaceSyncModel.d.ts +45 -0
  248. package/dist/sync/workspaceSyncModel.js +183 -28
  249. package/dist/sync/workspaceSyncModel.test.js +291 -12
  250. package/dist/sync/workspaceSyncState.d.ts +17 -0
  251. package/dist/sync/workspaceSyncState.js +2 -1
  252. package/dist/sync/workspaceSyncSurface.js +3 -8
  253. package/dist/types.d.ts +17 -0
  254. package/dist/ui/assets/AgentsModule-DrLawwNl.js +1 -0
  255. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BG6P_GVW.js → AnnotatedAttachmentWorkspace-Z9_whf7F.js} +1 -1
  256. package/dist/ui/assets/CourierPrime-Bold-BuLj_dpw.woff2 +0 -0
  257. package/dist/ui/assets/CourierPrime-BoldItalic-BrK2pUkZ.woff2 +0 -0
  258. package/dist/ui/assets/CourierPrime-Italic-DFUZK5Ey.woff2 +0 -0
  259. package/dist/ui/assets/CourierPrime-Regular-BsKphzVf.woff2 +0 -0
  260. package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +252 -0
  261. package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +1 -0
  262. package/dist/ui/assets/{InitiativesModule-Dhm7M8e7.js → InitiativesModule-9E6ParrY.js} +1 -1
  263. package/dist/ui/assets/Noir_medium-bxQwKGzB.woff2 +0 -0
  264. package/dist/ui/assets/Noir_regular-ojf2kxlG.woff2 +0 -0
  265. package/dist/ui/assets/PlansPage-CvfnMiWq.css +1 -0
  266. package/dist/ui/assets/PlansPage-DRXscYxH.js +1 -0
  267. package/dist/ui/assets/RussoOne-Regular-Cu_qq_qC.woff2 +0 -0
  268. package/dist/ui/assets/SpecialElite-Regular-Di6gSvAS.woff2 +0 -0
  269. package/dist/ui/assets/{TaskSettings-ln0aF7xc.js → TaskSettings-DZX4jk7e.js} +1 -1
  270. package/dist/ui/assets/{WorkflowsModule-BcS4afRn.js → WorkflowsModule-BA7jcEpH.js} +1 -1
  271. package/dist/ui/assets/index-9eT8e0Lu.css +1 -0
  272. package/dist/ui/assets/index-C6k75jr8.js +6 -0
  273. package/dist/ui/assets/{vendor-icons-BSUaRwF8.js → vendor-icons-DuEd_65c.js} +1 -1
  274. package/dist/ui/fonts/CourierPrime-Bold.ttf +0 -0
  275. package/dist/ui/fonts/CourierPrime-Bold.woff2 +0 -0
  276. package/dist/ui/fonts/CourierPrime-BoldItalic.ttf +0 -0
  277. package/dist/ui/fonts/CourierPrime-BoldItalic.woff2 +0 -0
  278. package/dist/ui/fonts/CourierPrime-Italic.ttf +0 -0
  279. package/dist/ui/fonts/CourierPrime-Italic.woff2 +0 -0
  280. package/dist/ui/fonts/CourierPrime-Regular.ttf +0 -0
  281. package/dist/ui/fonts/CourierPrime-Regular.woff2 +0 -0
  282. package/dist/ui/fonts/Noir_medium.otf +0 -0
  283. package/dist/ui/fonts/Noir_medium.woff2 +0 -0
  284. package/dist/ui/fonts/Noir_regular.otf +0 -0
  285. package/dist/ui/fonts/Noir_regular.woff2 +0 -0
  286. package/dist/ui/fonts/RussoOne-Regular.woff2 +0 -0
  287. package/dist/ui/fonts/SpecialElite-Regular.ttf +0 -0
  288. package/dist/ui/fonts/SpecialElite-Regular.woff2 +0 -0
  289. package/dist/ui/index.html +3 -3
  290. package/dist/utils/accountProfileSummaryCache.d.ts +5 -0
  291. package/dist/utils/assignees.d.ts +2 -0
  292. package/dist/utils/assignees.js +2 -0
  293. package/dist/utils/avatarUpload.d.ts +27 -0
  294. package/dist/utils/avatarUpload.js +132 -0
  295. package/dist/utils/avatarUpload.test.d.ts +1 -0
  296. package/dist/utils/avatarUpload.test.js +40 -0
  297. package/dist/utils/bootstrapTrace.d.ts +7 -0
  298. package/dist/utils/bootstrapTrace.js +29 -0
  299. package/dist/utils/commercialLifecyclePresentation.js +3 -0
  300. package/dist/utils/contextAssetEvents.d.ts +2 -0
  301. package/dist/utils/syncEventDetails.d.ts +3 -0
  302. package/dist/utils/syncEventDetails.js +64 -0
  303. package/dist/utils/syncEventDetails.test.d.ts +1 -0
  304. package/dist/utils/syncEventDetails.test.js +23 -0
  305. package/dist/utils/syncEventPresentation.d.ts +15 -0
  306. package/dist/utils/syncEventPresentation.js +129 -0
  307. package/dist/utils/syncEventPresentation.test.d.ts +1 -0
  308. package/dist/utils/syncEventPresentation.test.js +64 -0
  309. package/dist/utils/syncStatusPresentation.d.ts +72 -0
  310. package/dist/utils/syncStatusPresentation.js +217 -0
  311. package/dist/utils/syncStatusPresentation.test.d.ts +1 -0
  312. package/dist/utils/syncStatusPresentation.test.js +164 -0
  313. package/dist/utils/taskActivity.d.ts +2 -0
  314. package/dist/utils/taskActivity.js +228 -22
  315. package/dist/utils/taskActivity.test.js +119 -8
  316. package/dist/utils/workspaceSyncPresentation.d.ts +4 -0
  317. package/dist/utils/workspaceSyncPresentation.js +68 -2
  318. package/dist/utils/workspaceSyncPresentation.test.js +36 -1
  319. package/package.json +3 -2
  320. package/scripts/export-sqlite-to-postgres.mjs +7 -9
  321. package/scripts/run-e2e-realtime.sh +17 -3
  322. package/scripts/run-smoke.sh +17 -5
  323. package/scripts/run-soak.sh +17 -5
  324. package/src/resources/templates/workflow-sources/workflowDocs.mjs +6 -6
  325. package/src/resources/templates/workflows/collaborate.yaml +1 -1
  326. package/src/resources/templates/workflows/evaluate.yaml +2 -2
  327. package/src/resources/templates/workflows/plan.yaml +1 -1
  328. package/src/resources/templates/workflows/review.yaml +2 -2
  329. package/dist/ui/assets/AgentsModule-N2MnQBZk.js +0 -1
  330. package/dist/ui/assets/DocumentWorkspace-B3nV_Gc5.css +0 -1
  331. package/dist/ui/assets/DocumentWorkspace-DRbDMwh8.js +0 -252
  332. package/dist/ui/assets/PlansPage-C2dd2n1X.css +0 -1
  333. package/dist/ui/assets/PlansPage-Cq0zXj3I.js +0 -1
  334. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  335. package/dist/ui/assets/index-C2-OXZV7.css +0 -1
  336. package/dist/ui/assets/index-DcSI5F86.js +0 -6
  337. 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,13 @@ 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';
38
42
  import { resolveCommercialLifecyclePresentation } from '../../utils/commercialLifecyclePresentation';
39
43
  import { AppShellHeader } from './AppShellHeader';
40
44
  import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
@@ -47,6 +51,9 @@ import { EmptyWorkspaceFilterBar, WorkspaceHeaderActions, WorkspaceHeaderDivider
47
51
  import { ScheduleSidebar, SCHEDULE_SIDEBAR_WIDTH_PX } from './panels/ScheduleSidebar';
48
52
  import { DOCUMENT_ATTACHMENT_FILTER_OPTIONS, DOCUMENT_TYPE_FILTER_OPTIONS, } from '../features/documentWorkspaceFilters';
49
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';
50
57
  import { toDateOnlyLocal, parseDateOnlyLocal, startOfWeek, getDayOffsetFromWeekStart, addDays, dateOnlyToIsoWeekKey, SCHEDULE_DAY_KEYS, SCHEDULE_DAY_LABELS, } from './panels/scheduleUtils';
51
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';
52
59
  // Heavy feature modules — lazy-loaded to reduce the initial app chunk size.
@@ -60,72 +67,6 @@ const PlansPage = React.lazy(() => import('./PlansPage').then(m => ({ default: m
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,7 +887,17 @@ 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 {
@@ -1198,17 +1172,7 @@ export function StandaloneLayout(props) {
1198
1172
  const [showAccountMenu, setShowAccountMenu] = useState(false);
1199
1173
  const [showAccountHub, setShowAccountHub] = useState(false);
1200
1174
  const [showEditProfileModal, setShowEditProfileModal] = useState(false);
1201
- const [showSyncStatusModal, setShowSyncStatusModal] = useState(false);
1202
- const [workspaceSyncToggleBusy, setWorkspaceSyncToggleBusy] = useState(false);
1203
- const [workspaceSyncError, setWorkspaceSyncError] = useState(null);
1204
- const [workspaceSyncRepairBusy, setWorkspaceSyncRepairBusy] = useState(false);
1205
- const [workspaceSyncRepairQueued, setWorkspaceSyncRepairQueued] = useState(false);
1206
- const [workspaceSyncCopied, setWorkspaceSyncCopied] = useState(false);
1207
- const [syncRecentEvents, setSyncRecentEvents] = useState([]);
1208
- const [syncRecentEventsLoading, setSyncRecentEventsLoading] = useState(false);
1209
- const [syncRecentEventsError, setSyncRecentEventsError] = useState(null);
1210
- const syncEventsListRef = useRef(null);
1211
- const pendingSyncEventsScrollRestoreRef = useRef(null);
1175
+ const [showAvatarPhotoManager, setShowAvatarPhotoManager] = useState(false);
1212
1176
  const [workspaceActionBusy, setWorkspaceActionBusy] = useState(false);
1213
1177
  const [workspaceActionError, setWorkspaceActionError] = useState('');
1214
1178
  const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
@@ -1249,7 +1213,6 @@ export function StandaloneLayout(props) {
1249
1213
  const [profileSaveNotice, setProfileSaveNotice] = useState(null);
1250
1214
  const TEAM_AUDIT_PAGE_SIZE = 25;
1251
1215
  const accountMenuRef = useRef(null);
1252
- const profileAvatarInputRef = useRef(null);
1253
1216
  const accountSurfaceOpenStartedAtRef = useRef(null);
1254
1217
  const accountSurfacePendingRef = useRef({
1255
1218
  billing: false,
@@ -1274,21 +1237,11 @@ export function StandaloneLayout(props) {
1274
1237
  const canManageCloudWorkspaces = runtimeMode === 'cloud' && workspaceSwitchingEnabled && isAuthenticated;
1275
1238
  const canAccessCloudTeamManagement = isAuthenticated && (runtimeMode === 'cloud' || cloudAuthConfigured);
1276
1239
  const canManageWorkspaceSync = runtimeMode === 'local' && isAuthenticated;
1277
- const handleWorkspaceSyncToggle = useCallback(async (enabled) => {
1278
- if (!canManageWorkspaceSync)
1279
- return;
1280
- setWorkspaceSyncError(null);
1281
- setWorkspaceSyncToggleBusy(true);
1282
- const result = await saveWorkspaceCloudSyncSettings({ enabled });
1283
- if (!result.success) {
1284
- setWorkspaceSyncError(result.error || (enabled ? 'Failed to enable sync.' : 'Failed to disable sync.'));
1285
- }
1286
- setWorkspaceSyncToggleBusy(false);
1287
- }, [
1240
+ const { showSyncStatusModal, showSyncEnableWarning, workspaceSyncError, syncControlBusy, openSyncStatusModal, closeSyncStatusModal, handleWorkspaceSyncToggle, cancelSyncEnableWarning, confirmSyncEnableWarning, } = useSyncStatusControls({
1288
1241
  canManageWorkspaceSync,
1289
- saveWorkspaceCloudSyncSettings
1290
- ]);
1291
- const syncControlBusy = workspaceSyncToggleBusy;
1242
+ workspaceCloudSyncEnabled,
1243
+ saveWorkspaceCloudSyncSettings,
1244
+ });
1292
1245
  const currentWorkspaceLabel = useMemo(() => {
1293
1246
  const current = availableWorkspaces.find((workspace) => workspace.id === currentWorkspaceId);
1294
1247
  const namedWorkspace = String(current?.name || '').trim();
@@ -1348,23 +1301,21 @@ export function StandaloneLayout(props) {
1348
1301
  if (!normalizedDraftId || !cloudAuthConfigured)
1349
1302
  return;
1350
1303
  try {
1351
- const baseUrl = runtimeMode === 'local'
1352
- ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1353
- : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1354
- await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/discard', {
1304
+ await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/discard'), {
1355
1305
  method: 'POST',
1356
1306
  headers: { 'Content-Type': 'application/json' },
1357
1307
  credentials: 'include',
1358
1308
  body: JSON.stringify({ draftId: normalizedDraftId })
1359
- }, baseUrl);
1309
+ });
1360
1310
  }
1361
1311
  catch {
1362
1312
  // Best-effort cleanup only.
1363
1313
  }
1364
- }, [cloudAuthConfigured, props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
1314
+ }, [cloudAuthConfigured, resolveCloudAuthUrl]);
1365
1315
  const closeEditProfileModal = useCallback(() => {
1366
1316
  const pendingDraftId = profileAvatarDraftId;
1367
1317
  setShowEditProfileModal(false);
1318
+ setShowAvatarPhotoManager(false);
1368
1319
  setProfileSaveError(null);
1369
1320
  setProfileSaveNotice(null);
1370
1321
  setProfileAvatarDraftId(null);
@@ -1375,47 +1326,53 @@ export function StandaloneLayout(props) {
1375
1326
  }, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
1376
1327
  const handleProfileAvatarSelected = useCallback(async (file) => {
1377
1328
  if (!file || !cloudAuthConfigured)
1378
- return;
1329
+ return false;
1379
1330
  if (!file.type.startsWith('image/')) {
1380
1331
  setProfileSaveError('Profile photo must be an image file.');
1381
1332
  setProfileSaveNotice(null);
1382
- return;
1383
- }
1384
- if (file.size > PROFILE_AVATAR_MAX_BYTES) {
1385
- setProfileSaveError('Profile photo must be 5 MB or smaller.');
1386
- setProfileSaveNotice(null);
1387
- return;
1333
+ return false;
1388
1334
  }
1389
1335
  setProfileAvatarBusy(true);
1390
1336
  setProfileSaveError(null);
1391
1337
  setProfileSaveNotice(null);
1392
1338
  try {
1393
- const baseUrl = runtimeMode === 'local'
1394
- ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1395
- : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1396
- 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'), {
1397
1350
  method: 'POST',
1398
1351
  headers: { 'Content-Type': 'application/json' },
1399
1352
  credentials: 'include',
1400
1353
  body: JSON.stringify({
1401
- originalName: file.name,
1402
- mimeType: file.type,
1403
- size: file.size
1354
+ originalName: uploadFile.name,
1355
+ mimeType: uploadFile.type,
1356
+ size: uploadFile.size
1404
1357
  })
1405
- }, baseUrl);
1358
+ });
1406
1359
  const initData = await initRes.json().catch(() => ({}));
1407
- 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') {
1408
1361
  throw new Error(initData?.error || 'Failed to start avatar upload.');
1409
1362
  }
1410
- const uploadRes = await fetch(initData.uploadUrl, {
1411
- method: String(initData.method || 'PUT'),
1412
- headers: initData.headers || { 'Content-Type': file.type || 'application/octet-stream' },
1413
- 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
1414
1371
  });
1415
1372
  if (!uploadRes.ok) {
1416
1373
  throw new Error('Failed to upload avatar.');
1417
1374
  }
1418
- const finalizeRes = await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/finalize', {
1375
+ const finalizeRes = await fetch(resolveCloudAuthUrl('/api/taskforce/auth/profile/avatar/finalize'), {
1419
1376
  method: 'POST',
1420
1377
  headers: { 'Content-Type': 'application/json' },
1421
1378
  credentials: 'include',
@@ -1423,7 +1380,7 @@ export function StandaloneLayout(props) {
1423
1380
  draftId: initData.draftId,
1424
1381
  relativePath: initData.relativePath
1425
1382
  })
1426
- }, baseUrl);
1383
+ });
1427
1384
  const finalizeData = await finalizeRes.json().catch(() => ({}));
1428
1385
  if (!finalizeRes.ok || !finalizeData?.success || typeof finalizeData?.draftId !== 'string') {
1429
1386
  throw new Error(finalizeData?.error || 'Failed to finalize avatar upload.');
@@ -1431,27 +1388,24 @@ export function StandaloneLayout(props) {
1431
1388
  const previousDraftId = profileAvatarDraftId;
1432
1389
  setProfileAvatarDraftId(finalizeData.draftId);
1433
1390
  setProfileAvatarPreviewUrl(typeof finalizeData?.avatarUrl === 'string' ? finalizeData.avatarUrl : '');
1434
- setProfileSaveNotice('Profile photo ready to save.');
1391
+ setProfileSaveNotice(null);
1435
1392
  if (previousDraftId && previousDraftId !== finalizeData.draftId) {
1436
1393
  void discardProfileAvatarDraft(previousDraftId);
1437
1394
  }
1395
+ return true;
1438
1396
  }
1439
1397
  catch (error) {
1440
1398
  setProfileSaveError(error instanceof Error ? error.message : 'Failed to upload profile photo.');
1399
+ return false;
1441
1400
  }
1442
1401
  finally {
1443
1402
  setProfileAvatarBusy(false);
1444
- if (profileAvatarInputRef.current) {
1445
- profileAvatarInputRef.current.value = '';
1446
- }
1447
1403
  }
1448
1404
  }, [
1449
1405
  cloudAuthConfigured,
1450
1406
  discardProfileAvatarDraft,
1451
1407
  profileAvatarDraftId,
1452
- props.config?.apiBaseUrl,
1453
- props.config?.cloudAuthBaseUrl,
1454
- runtimeMode
1408
+ resolveCloudAuthUrl
1455
1409
  ]);
1456
1410
  const handleDiscardPendingProfileAvatar = useCallback(() => {
1457
1411
  if (!profileAvatarDraftId)
@@ -1497,6 +1451,7 @@ export function StandaloneLayout(props) {
1497
1451
  setProfileAvatarDraftId(null);
1498
1452
  setProfileAvatarPreviewUrl('');
1499
1453
  setProfileSaveBusy(false);
1454
+ setShowAvatarPhotoManager(false);
1500
1455
  setShowEditProfileModal(false);
1501
1456
  }, [authAvatarUrl, profileAvatarDraftId, profileAvatarPreviewUrl, profileDisplayNameDraft, updateCurrentUserProfile]);
1502
1457
  const teamAdminHeaders = useCallback(() => {
@@ -1815,9 +1770,9 @@ export function StandaloneLayout(props) {
1815
1770
  const target = (!targetRaw || targetRaw === '/login' || !targetRaw.startsWith('/')) ? '/' : targetRaw;
1816
1771
  setShowAccountMenu(false);
1817
1772
  setShowAccountHub(false);
1818
- setShowSyncStatusModal(false);
1773
+ closeSyncStatusModal();
1819
1774
  navigate(`/login?mode=${mode}&next=${encodeURIComponent(target)}`);
1820
- }, [location.hash, location.pathname, location.search, navigate]);
1775
+ }, [closeSyncStatusModal, location.hash, location.pathname, location.search, navigate]);
1821
1776
  const openWorkspaceSetup = useCallback((options) => {
1822
1777
  setShowAccountMenu(false);
1823
1778
  setShowCreateWorkspaceConfirm(false);
@@ -1830,37 +1785,44 @@ export function StandaloneLayout(props) {
1830
1785
  const handlePlansBack = useCallback(async () => {
1831
1786
  if (plansNavigationBusy)
1832
1787
  return;
1788
+ setPlansNavigationBusy(true);
1833
1789
  if (!isAuthenticated) {
1834
- closePlansScreen();
1790
+ try {
1791
+ closePlansScreen();
1792
+ }
1793
+ finally {
1794
+ setPlansNavigationBusy(false);
1795
+ }
1835
1796
  return;
1836
1797
  }
1837
- const summary = await loadAccountProfileSummary({ force: true });
1838
- if (!resolveCommercialLifecyclePresentation(summary).allowReturnToApp) {
1798
+ const currentSummary = accountProfileSummaryRef.current;
1799
+ if (currentSummary && !resolveCommercialLifecyclePresentation(currentSummary).allowReturnToApp) {
1800
+ setPlansNavigationBusy(false);
1839
1801
  return;
1840
1802
  }
1841
- setPlansNavigationBusy(true);
1842
- try {
1843
- const result = await props.continueAfterCommercialOnboarding();
1844
- if (!result.success) {
1845
- pushNotice(result.error || 'Unable to finish onboarding.', 'error');
1846
- if (result.destination === 'login') {
1847
- 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();
1848
1816
  }
1849
- return;
1850
1817
  }
1851
- if (result.destination === 'setup') {
1852
- openWorkspaceSetup();
1853
- return;
1818
+ finally {
1819
+ setPlansNavigationBusy(false);
1854
1820
  }
1855
- closePlansScreen();
1856
- }
1857
- finally {
1858
- setPlansNavigationBusy(false);
1859
- }
1821
+ })();
1860
1822
  }, [
1823
+ accountProfileSummaryRef,
1861
1824
  closePlansScreen,
1862
1825
  isAuthenticated,
1863
- loadAccountProfileSummary,
1864
1826
  navigate,
1865
1827
  openWorkspaceSetup,
1866
1828
  plansNavigationBusy,
@@ -2046,6 +2008,7 @@ export function StandaloneLayout(props) {
2046
2008
  const formattedLastPullTime = useMemo(() => formatSyncTimestamp(workspaceLastPullAt), [formatSyncTimestamp, workspaceLastPullAt]);
2047
2009
  const formattedLastPushTime = useMemo(() => formatSyncTimestamp(workspaceLastPushAt), [formatSyncTimestamp, workspaceLastPushAt]);
2048
2010
  const rawSyncLastError = workspaceSyncError || workspaceLastErrorMessage || userGlobalSyncError || 'None';
2011
+ const workspaceSyncDiagnostics = getWorkspaceSyncDiagnostics();
2049
2012
  const headerSyncPresentation = useMemo(() => resolveHeaderWorkspaceSyncPresentation({
2050
2013
  runtimeMode,
2051
2014
  isAuthenticated,
@@ -2053,7 +2016,10 @@ export function StandaloneLayout(props) {
2053
2016
  workspaceCloudSyncEnabled,
2054
2017
  workspaceSyncSummary,
2055
2018
  workspaceSyncRecommendedAction,
2056
- workspaceSyncError: rawSyncLastError
2019
+ workspaceSyncError: rawSyncLastError,
2020
+ pushBlockedReason: workspaceSyncDiagnostics.pushBlockedReason || null,
2021
+ pullBlockedReason: workspaceSyncDiagnostics.pullBlockedReason || null,
2022
+ retryBlockedReason: workspaceSyncDiagnostics.retryBlockedReason || null
2057
2023
  }), [
2058
2024
  runtimeMode,
2059
2025
  isAuthenticated,
@@ -2061,75 +2027,24 @@ export function StandaloneLayout(props) {
2061
2027
  workspaceCloudSyncEnabled,
2062
2028
  workspaceSyncSummary,
2063
2029
  workspaceSyncRecommendedAction,
2064
- rawSyncLastError
2030
+ rawSyncLastError,
2031
+ workspaceSyncDiagnostics.pushBlockedReason,
2032
+ workspaceSyncDiagnostics.pullBlockedReason,
2033
+ workspaceSyncDiagnostics.retryBlockedReason
2065
2034
  ]);
2066
2035
  const syncLastError = headerSyncPresentation.lastError;
2067
2036
  const [headerSyncStatus, setHeaderSyncStatus] = useState(headerSyncPresentation.status);
2068
- const syncStageLabel = useMemo(() => {
2069
- if (workspaceSyncRepairBusy) {
2070
- return 'Repairing';
2071
- }
2072
- if (workspaceSyncBusy && workspaceSyncPhase === 'error') {
2073
- return 'Recovering';
2074
- }
2075
- switch (workspaceSyncPhase) {
2076
- case 'provision-local':
2077
- return 'Initial cloud upload';
2078
- case 'attach-cloud':
2079
- return 'Initial cloud pull';
2080
- case 'active':
2081
- return 'Active';
2082
- case 'error':
2083
- return 'Error';
2084
- default:
2085
- return 'Idle';
2086
- }
2087
- }, [workspaceSyncPhase, workspaceSyncRepairBusy]);
2088
- const workspaceSyncDiagnostics = getWorkspaceSyncDiagnostics();
2089
- const syncDiagnosticsSummary = useMemo(() => ([
2090
- {
2091
- label: 'AI profile snapshot',
2092
- value: `${workspaceSyncDiagnostics.aiProfileSnapshotCount} local profile${workspaceSyncDiagnostics.aiProfileSnapshotCount === 1 ? '' : 's'}`
2093
- },
2094
- {
2095
- label: 'AI profile raw response',
2096
- value: `${workspaceSyncDiagnostics.aiProfileSnapshotRawCount} from route`
2097
- },
2098
- {
2099
- label: 'Last pushed AI profiles',
2100
- value: `${workspaceSyncDiagnostics.lastPushedAiProfileCount} tracked`
2101
- },
2102
- {
2103
- label: 'AI watermark map',
2104
- value: `${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount} tracked`
2105
- },
2106
- {
2107
- label: 'Document snapshot',
2108
- value: `${workspaceSyncDiagnostics.documentSnapshotCount} local doc${workspaceSyncDiagnostics.documentSnapshotCount === 1 ? '' : 's'}`
2109
- },
2110
- {
2111
- label: 'Asset snapshot',
2112
- value: `${workspaceSyncDiagnostics.assetSnapshotCount} local asset${workspaceSyncDiagnostics.assetSnapshotCount === 1 ? '' : 's'}`
2113
- },
2114
- {
2115
- label: 'Queued full AI sync',
2116
- value: workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'
2117
- },
2118
- {
2119
- label: 'AI snapshot last fetch',
2120
- value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt
2121
- ? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString()
2122
- : 'Never'
2123
- },
2124
- {
2125
- label: 'AI snapshot fetch error',
2126
- value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'
2127
- },
2128
- {
2129
- label: 'AI snapshot skip reason',
2130
- value: workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'
2131
- }
2132
- ]), [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
+ });
2133
2048
  const shouldDelayHeaderSyncing = headerSyncPresentation.actionable
2134
2049
  && workspaceSyncStatus === 'syncing'
2135
2050
  && workspaceSyncPhase === 'active'
@@ -2144,282 +2059,34 @@ export function StandaloneLayout(props) {
2144
2059
  }, 1200);
2145
2060
  return () => window.clearTimeout(timeoutId);
2146
2061
  }, [headerSyncPresentation.status, shouldDelayHeaderSyncing]);
2147
- const formatSyncEventLine = useCallback((event) => {
2148
- const occurredAt = typeof event.occurredAt === 'string' && event.occurredAt.trim().length > 0
2149
- ? event.occurredAt
2150
- : 'unknown-time';
2151
- const eventType = typeof event.eventType === 'string' && event.eventType.trim().length > 0
2152
- ? event.eventType.trim()
2153
- : 'sync';
2154
- const status = event.status === 'error' ? 'failed' : 'succeeded';
2155
- const changeCountValue = Number(event.changeCount);
2156
- const changeCount = Number.isFinite(changeCountValue)
2157
- ? ` (${Math.max(0, Math.floor(changeCountValue))} change${Math.floor(changeCountValue) === 1 ? '' : 's'})`
2158
- : '';
2159
- const requestMsValue = Number(event.requestMs);
2160
- const requestMs = Number.isFinite(requestMsValue)
2161
- ? ` in ${Math.max(0, Math.floor(requestMsValue))}ms`
2162
- : '';
2163
- const statusCodeValue = Number(event.statusCode);
2164
- const statusCode = Number.isFinite(statusCodeValue)
2165
- ? ` [${Math.floor(statusCodeValue)}]`
2166
- : '';
2167
- const errorMessage = typeof event.errorMessage === 'string' && event.errorMessage.trim().length > 0
2168
- ? `: ${event.errorMessage.trim()}`
2169
- : '';
2170
- return `${occurredAt} ${eventType} ${status}${changeCount}${requestMs}${statusCode}${errorMessage}`;
2171
- }, []);
2172
- const syncEventRows = useMemo(() => {
2173
- if (syncRecentEventsLoading && syncRecentEvents.length === 0) {
2174
- return [{
2175
- key: 'loading',
2176
- text: 'Loading recent sync events...',
2177
- tone: 'muted'
2178
- }];
2179
- }
2180
- if (syncRecentEventsError) {
2181
- return [{
2182
- key: 'error',
2183
- text: syncRecentEventsError,
2184
- tone: 'error'
2185
- }];
2186
- }
2187
- if (syncRecentEvents.length === 0) {
2188
- return [{
2189
- key: 'empty',
2190
- text: 'No recent sync events recorded.',
2191
- tone: 'muted'
2192
- }];
2193
- }
2194
- return syncRecentEvents.map((event, index) => ({
2195
- key: getSyncEventStableKey(event, index),
2196
- text: formatSyncEventLine(event),
2197
- tone: event.status === 'error' ? 'error' : 'default'
2198
- }));
2199
- }, [formatSyncEventLine, syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading]);
2062
+ const syncEventRows = useMemo(() => buildSyncEventRows({
2063
+ syncRecentEvents,
2064
+ syncRecentEventsError,
2065
+ syncRecentEventsLoading
2066
+ }), [syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading]);
2200
2067
  const referenceMismatchCount = useMemo(() => getActiveReferenceMismatchCount(syncRecentEvents), [syncRecentEvents]);
2201
- const activeReferenceMismatchEvents = useMemo(() => getActiveReferenceMismatchEvents(syncRecentEvents), [syncRecentEvents]);
2202
- const activeReferenceMismatchSummaries = useMemo(() => (activeReferenceMismatchEvents.map((event, index) => {
2203
- const details = event.details && typeof event.details === 'object' ? event.details : null;
2204
- const pathValue = String(details?.path || '').trim();
2205
- const referenceLabelValue = String(details?.referenceLabel || '').trim();
2206
- const taskTitleValue = String(details?.taskTitle || details?.existingTaskTitle || '').trim();
2207
- const existingRefValue = Number(details?.existingReferenceNumber);
2208
- const incomingRefValue = Number(details?.incomingReferenceNumber);
2209
- const pathLabel = pathValue || taskTitleValue || referenceLabelValue || `Mismatch ${index + 1}`;
2210
- const refsLabel = (Number.isFinite(existingRefValue)
2211
- || Number.isFinite(incomingRefValue))
2212
- ? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
2213
- : referenceLabelValue
2214
- ? `Both claimed ${referenceLabelValue}`
2215
- : null;
2216
- return {
2217
- key: getSyncEventStableKey(event, index),
2218
- pathLabel,
2219
- refsLabel,
2220
- };
2221
- })), [activeReferenceMismatchEvents]);
2222
- useLayoutEffect(() => {
2223
- const pendingRestore = pendingSyncEventsScrollRestoreRef.current;
2224
- const listEl = syncEventsListRef.current;
2225
- if (!pendingRestore || !listEl)
2226
- return;
2227
- const delta = listEl.scrollHeight - pendingRestore.scrollHeight;
2228
- listEl.scrollTop = pendingRestore.scrollTop + Math.max(0, delta);
2229
- pendingSyncEventsScrollRestoreRef.current = null;
2230
- }, [syncRecentEvents]);
2231
- const loadRecentSyncEvents = useCallback(async () => {
2232
- const res = await fetch(`/api/taskforce/sync/events?workspace_id=${encodeURIComponent(currentWorkspaceId)}&limit=15`, {
2233
- method: 'GET',
2234
- credentials: 'include'
2235
- });
2236
- if (!res.ok) {
2237
- throw new Error(`Failed to load sync events (${res.status})`);
2238
- }
2239
- const data = await res.json().catch(() => ({}));
2240
- return Array.isArray(data?.events)
2241
- ? data.events.filter((event) => event && typeof event === 'object')
2242
- : [];
2243
- }, [currentWorkspaceId]);
2244
- useEffect(() => {
2245
- if (!showSyncStatusModal)
2246
- return;
2247
- let cancelled = false;
2248
- const isInitialLoad = syncRecentEvents.length === 0;
2249
- if (isInitialLoad) {
2250
- setSyncRecentEventsLoading(true);
2251
- }
2252
- void loadRecentSyncEvents()
2253
- .then((events) => {
2254
- if (cancelled)
2255
- return;
2256
- const listEl = syncEventsListRef.current;
2257
- pendingSyncEventsScrollRestoreRef.current = listEl && listEl.scrollTop > 8
2258
- ? {
2259
- scrollTop: listEl.scrollTop,
2260
- scrollHeight: listEl.scrollHeight
2261
- }
2262
- : null;
2263
- setSyncRecentEvents((current) => {
2264
- const next = current.length === 0 ? events : mergeRecentSyncEvents(current, events, 15);
2265
- return areSyncEventListsEqual(current, next) ? current : next;
2266
- });
2267
- setSyncRecentEventsError(null);
2268
- })
2269
- .catch((error) => {
2270
- if (cancelled)
2271
- return;
2272
- const message = error instanceof Error && error.message.trim().length > 0
2273
- ? error.message.trim()
2274
- : 'Unable to load recent sync events.';
2275
- setSyncRecentEventsError(message);
2276
- if (isInitialLoad) {
2277
- setSyncRecentEvents([]);
2278
- }
2279
- })
2280
- .finally(() => {
2281
- if (cancelled)
2282
- return;
2283
- if (isInitialLoad) {
2284
- setSyncRecentEventsLoading(false);
2285
- }
2286
- });
2287
- return () => {
2288
- cancelled = true;
2289
- };
2290
- }, [
2291
- showSyncStatusModal,
2292
- loadRecentSyncEvents,
2293
- syncRecentEvents.length,
2294
- workspaceLastPushAt,
2295
- workspaceLastPullAt,
2296
- workspaceLastErrorAt,
2297
- workspaceSyncStatus
2298
- ]);
2299
- const syncStatusMeta = useMemo(() => {
2300
- switch (headerSyncStatus) {
2301
- case 'off':
2302
- return {
2303
- label: 'Off',
2304
- icon: 'off',
2305
- color: '#94a3b8',
2306
- border: 'rgba(148, 163, 184, 0.35)',
2307
- background: 'rgba(148, 163, 184, 0.12)'
2308
- };
2309
- case 'syncing':
2310
- return {
2311
- label: 'Syncing',
2312
- icon: 'cloud',
2313
- color: '#60a5fa',
2314
- border: 'rgba(96, 165, 250, 0.35)',
2315
- background: 'rgba(59, 130, 246, 0.12)'
2316
- };
2317
- case 'attention':
2318
- return {
2319
- label: 'Needs Attention',
2320
- icon: 'cloud',
2321
- color: '#fda4af',
2322
- border: 'rgba(253, 164, 175, 0.4)',
2323
- background: 'rgba(239, 68, 68, 0.12)'
2324
- };
2325
- default:
2326
- return {
2327
- label: 'Healthy',
2328
- icon: 'cloud',
2329
- color: '#86efac',
2330
- border: 'rgba(134, 239, 172, 0.4)',
2331
- background: 'rgba(34, 197, 94, 0.12)'
2332
- };
2333
- }
2334
- }, [headerSyncStatus]);
2335
- const handleCopySyncDetails = useCallback(async () => {
2336
- const details = [
2337
- 'Taskforce Sync Manager',
2338
- `Workspace ID: ${currentWorkspaceId}`,
2339
- `Status: ${syncStatusMeta.label}`,
2340
- `Summary: ${headerSyncPresentation.summary}`,
2341
- `Last successful sync: ${formattedLastSyncTime}`,
2342
- `Last pull from cloud: ${formattedLastPullTime}`,
2343
- `Last push to cloud: ${formattedLastPushTime}`,
2344
- `Last error at: ${workspaceSyncDiagnostics.lastErrorAt ? new Date(workspaceSyncDiagnostics.lastErrorAt).toLocaleString() : 'Never'}`,
2345
- `Pending local changes: ${workspaceSyncPendingChanges}`,
2346
- `Last error: ${syncLastError}`,
2347
- `Sync stage: ${syncStageLabel}`,
2348
- `AI profile snapshot: ${workspaceSyncDiagnostics.aiProfileSnapshotCount}`,
2349
- `AI profile raw response: ${workspaceSyncDiagnostics.aiProfileSnapshotRawCount}`,
2350
- `Last pushed AI profiles tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileCount}`,
2351
- `AI profile watermarks tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount}`,
2352
- `AI snapshot last fetch: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt ? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString() : 'Never'}`,
2353
- `AI snapshot fetch error: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'}`,
2354
- `AI snapshot skip reason: ${workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'}`,
2355
- `Document snapshot: ${workspaceSyncDiagnostics.documentSnapshotCount}`,
2356
- `Asset snapshot: ${workspaceSyncDiagnostics.assetSnapshotCount}`,
2357
- `Reference mismatches detected: ${referenceMismatchCount}`,
2358
- `Queued full AI sync: ${workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'}`
2359
- ];
2360
- try {
2361
- const events = syncRecentEvents.length > 0 ? syncRecentEvents : await loadRecentSyncEvents();
2362
- const recentEventLines = events.map((event) => formatSyncEventLine(event));
2363
- const copyText = [
2364
- ...details,
2365
- '',
2366
- 'Recent sync events',
2367
- ...(recentEventLines.length > 0 ? recentEventLines : ['No recent sync events recorded.'])
2368
- ].join('\n');
2369
- await navigator.clipboard.writeText(copyText);
2370
- setWorkspaceSyncCopied(true);
2371
- window.setTimeout(() => {
2372
- setWorkspaceSyncCopied(false);
2373
- }, 1800);
2374
- pushNotice('Sync details copied to clipboard.', 'success');
2375
- }
2376
- catch {
2377
- setWorkspaceSyncCopied(false);
2378
- pushNotice('Failed to copy sync details.', 'error');
2379
- }
2380
- }, [
2068
+ const activeReferenceMismatchSummaries = useMemo(() => buildReferenceMismatchSummaries(syncRecentEvents), [syncRecentEvents]);
2069
+ const syncStatusMeta = useMemo(() => resolveSyncStatusMeta(headerSyncStatus), [headerSyncStatus]);
2070
+ const { workspaceSyncRepairBusy, workspaceSyncRepairQueued, workspaceSyncCopied, handleCopySyncDetails, handleQueueOrRunRepairSync, } = useSyncStatusActions({
2381
2071
  currentWorkspaceId,
2382
- syncStatusMeta.label,
2383
- headerSyncPresentation.summary,
2072
+ syncStatusLabel: syncStatusMeta.label,
2073
+ workspaceSyncSummary: headerSyncPresentation.summary,
2074
+ workspaceSyncRecommendedAction: headerSyncPresentation.recommendedAction,
2384
2075
  formattedLastSyncTime,
2385
2076
  formattedLastPullTime,
2386
2077
  formattedLastPushTime,
2387
2078
  workspaceSyncDiagnostics,
2388
2079
  workspaceSyncPendingChanges,
2389
2080
  syncLastError,
2390
- syncStageLabel,
2081
+ workspaceSyncPhase,
2391
2082
  referenceMismatchCount,
2392
- pushNotice,
2393
2083
  syncRecentEvents,
2394
2084
  loadRecentSyncEvents,
2395
- formatSyncEventLine
2396
- ]);
2397
- const handleRepairSync = useCallback(async () => {
2398
- setWorkspaceSyncRepairQueued(false);
2399
- setWorkspaceSyncRepairBusy(true);
2400
- try {
2401
- await resetWorkspaceSyncCursorAndPull();
2402
- }
2403
- finally {
2404
- setWorkspaceSyncRepairBusy(false);
2405
- }
2406
- }, [resetWorkspaceSyncCursorAndPull]);
2407
- const handleQueueOrRunRepairSync = useCallback(() => {
2408
- if (workspaceSyncRepairBusy)
2409
- return;
2410
- if (workspaceSyncBusy) {
2411
- setWorkspaceSyncRepairQueued(true);
2412
- return;
2413
- }
2414
- void handleRepairSync();
2415
- }, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy]);
2416
- useEffect(() => {
2417
- if (!workspaceSyncRepairQueued)
2418
- return;
2419
- if (workspaceSyncBusy || workspaceSyncRepairBusy)
2420
- return;
2421
- void handleRepairSync();
2422
- }, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy, workspaceSyncRepairQueued]);
2085
+ pushNotice,
2086
+ resetWorkspaceSyncCursorAndPull,
2087
+ workspaceSyncBusy,
2088
+ });
2089
+ const syncStageLabel = useMemo(() => resolveSyncStageLabel(workspaceSyncPhase, workspaceSyncRepairBusy, workspaceSyncBusy), [workspaceSyncBusy, workspaceSyncPhase, workspaceSyncRepairBusy]);
2423
2090
  const selectedDateObj = useMemo(() => parseDateOnlyLocal(scheduleSelectedDate) || new Date(), [scheduleSelectedDate]);
2424
2091
  const scheduleWeekStart = useMemo(() => startOfWeek(selectedDateObj, globalWeekStartsOn), [selectedDateObj, globalWeekStartsOn]);
2425
2092
  const scheduleDates = useMemo(() => {
@@ -2640,6 +2307,8 @@ export function StandaloneLayout(props) {
2640
2307
  setActiveTab('add');
2641
2308
  }, [props.workstreams, resetForm, setActiveTab, setActiveWorkspaceModule, setWorkstreamInput]);
2642
2309
  const openPlanningEditor = useCallback((editor) => {
2310
+ planningEditorSubmitInFlightRef.current = false;
2311
+ setIsSubmittingPlanningEditor(false);
2643
2312
  setPlanningEditor(editor);
2644
2313
  setPlanningPrimaryCollapsed(false);
2645
2314
  setPlanningSecondaryEditor(null);
@@ -2654,6 +2323,8 @@ export function StandaloneLayout(props) {
2654
2323
  setPlanningNestedWorkstreamDetailId(null);
2655
2324
  }, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
2656
2325
  const openPlanningSecondaryEditor = useCallback((editor, initiativeId) => {
2326
+ planningEditorSubmitInFlightRef.current = false;
2327
+ setIsSubmittingPlanningEditor(false);
2657
2328
  setPlanningSecondaryEditor(editor);
2658
2329
  setPlanningSecondaryCollapsed(false);
2659
2330
  setPlanningDraftTitle('');
@@ -2667,6 +2338,8 @@ export function StandaloneLayout(props) {
2667
2338
  setPlanningNestedWorkstreamDetailId(null);
2668
2339
  }, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
2669
2340
  const closePlanningEditor = useCallback(() => {
2341
+ planningEditorSubmitInFlightRef.current = false;
2342
+ setIsSubmittingPlanningEditor(false);
2670
2343
  setPlanningEditor(null);
2671
2344
  setPlanningDraftTitle('');
2672
2345
  setPlanningDraftDescription('');
@@ -2674,6 +2347,8 @@ export function StandaloneLayout(props) {
2674
2347
  setPlanningDraftInitiativeId('');
2675
2348
  }, []);
2676
2349
  const closePlanningSecondaryEditor = useCallback(() => {
2350
+ planningEditorSubmitInFlightRef.current = false;
2351
+ setIsSubmittingPlanningEditor(false);
2677
2352
  setPlanningSecondaryEditor(null);
2678
2353
  setPlanningDraftTitle('');
2679
2354
  setPlanningDraftDescription('');
@@ -2682,8 +2357,10 @@ export function StandaloneLayout(props) {
2682
2357
  }, []);
2683
2358
  const submitPlanningEditor = useCallback(async () => {
2684
2359
  const activeEditor = planningSecondaryEditor || planningEditor;
2685
- if (!activeEditor)
2360
+ if (!activeEditor || planningEditorSubmitInFlightRef.current)
2686
2361
  return;
2362
+ planningEditorSubmitInFlightRef.current = true;
2363
+ setIsSubmittingPlanningEditor(true);
2687
2364
  const isSecondaryEditor = Boolean(planningSecondaryEditor);
2688
2365
  const ownerId = planningDraftOwner.trim() || null;
2689
2366
  try {
@@ -2757,6 +2434,10 @@ export function StandaloneLayout(props) {
2757
2434
  catch (error) {
2758
2435
  pushNotice(error instanceof Error ? error.message : 'Failed to save planning item.', 'error');
2759
2436
  }
2437
+ finally {
2438
+ planningEditorSubmitInFlightRef.current = false;
2439
+ setIsSubmittingPlanningEditor(false);
2440
+ }
2760
2441
  }, [
2761
2442
  closePlanningEditor,
2762
2443
  closePlanningSecondaryEditor,
@@ -2966,6 +2647,54 @@ export function StandaloneLayout(props) {
2966
2647
  }
2967
2648
  await handleAssignInitiativeToWorkstream(matchedWorkstream.id, formatInitiativeReference(initiative));
2968
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]);
2969
2698
  const handleInitiativeFilterChange = useCallback((initiativeId) => {
2970
2699
  setSelectedPlanningInitiativeId(initiativeId);
2971
2700
  setSelectedPlanningWorkstreamId('');
@@ -3202,10 +2931,12 @@ export function StandaloneLayout(props) {
3202
2931
  }, title: "Workspace Settings", "aria-label": "Workspace Settings", children: _jsx(Settings, { size: 18 }) })] })] }));
3203
2932
  }, [activeTab, handleModuleChange, handlePlanningToolToggle, planningDrawerOpen, resolvedWorkspaceModule, setSettingsSection, setActiveTab, workspaceToolModules]);
3204
2933
  const planningDrawerPortalRef = useRef(null);
3205
- 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);
3206
2937
  setPlanningSecondaryEditor(null);
3207
2938
  setPlanningNestedWorkstreamDetailId(null);
3208
- }, 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) => {
3209
2940
  void props.archiveInitiative(initiativeId).then(() => {
3210
2941
  pushNotice('Initiative archived', 'success');
3211
2942
  }).catch((error) => {
@@ -3240,7 +2971,7 @@ export function StandaloneLayout(props) {
3240
2971
  const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
3241
2972
  ? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
3242
2973
  : null;
3243
- 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: {
3244
2975
  marginLeft: '6px',
3245
2976
  height: '24px',
3246
2977
  width: '24px',
@@ -3252,10 +2983,25 @@ export function StandaloneLayout(props) {
3252
2983
  display: 'inline-flex',
3253
2984
  alignItems: 'center',
3254
2985
  justifyContent: 'center'
3255
- }, 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: () => {
3256
3002
  setRequestedDocPath(null);
3257
3003
  setRequestedDocAssetId(null);
3258
- }, 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: {
3259
3005
  position: 'relative',
3260
3006
  opacity: props.loadingTasks ? 0.7 : 1,
3261
3007
  transition: 'opacity 0.2s ease, padding 220ms cubic-bezier(0.4, 0, 0.2, 1)',
@@ -3271,7 +3017,7 @@ export function StandaloneLayout(props) {
3271
3017
  inset: 0,
3272
3018
  zIndex: 35,
3273
3019
  pointerEvents: 'none',
3274
- } }), _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) => {
3275
3021
  setScheduleSelectedDate(dateOnly);
3276
3022
  const parsed = parseDateOnlyLocal(dateOnly);
3277
3023
  if (parsed) {
@@ -3382,11 +3128,15 @@ export function StandaloneLayout(props) {
3382
3128
  return;
3383
3129
  }
3384
3130
  void handleUnarchive(taskId);
3385
- }, 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: () => {
3386
3132
  setShowHelpModal(false);
3387
3133
  setActiveTab('settings');
3388
3134
  setSettingsSection('general');
3389
- } }), _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: () => {
3390
3140
  setShowAccountHub(false);
3391
3141
  void openTeamManagementModal();
3392
3142
  setTeamManagementTab('audit');
@@ -3442,7 +3192,7 @@ export function StandaloneLayout(props) {
3442
3192
  headers: teamAdminHeaders(),
3443
3193
  credentials: 'include'
3444
3194
  }));
3445
- }, 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) => {
3446
3196
  void handleWorkspaceSyncToggle(enabled);
3447
3197
  }, onRepairSync: handleQueueOrRunRepairSync, onCopyReport: () => { void handleCopySyncDetails(); }, onOpenLogin: () => {
3448
3198
  openSharedLoginRoute('login');