@taskforcehq/taskforce 0.3.170 → 0.3.301

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 (698) hide show
  1. package/README.md +71 -21
  2. package/dist/Taskforce.module.css +3394 -5981
  3. package/dist/TaskforceCore.CategoryFallback.test.js +5 -2
  4. package/dist/TaskforceCore.filter-persistence.test.js +402 -0
  5. package/dist/TaskforceCore.js +388 -71
  6. package/dist/TaskforceCore.routing.test.js +311 -0
  7. package/dist/TaskforceCore.test.js +3344 -615
  8. package/dist/TaskforceWidget.js +3 -2
  9. package/dist/__tests__/localizationCatalogs.test.js +48 -31
  10. package/dist/__tests__/workflowSimulation.test.js +2 -2
  11. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.d.ts +60 -0
  12. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.js +324 -0
  13. package/dist/annotatedAttachments/AnnotatedAttachmentSessionStore.test.js +278 -0
  14. package/dist/annotatedAttachments/payload.d.ts +2 -0
  15. package/dist/annotatedAttachments/payload.js +54 -0
  16. package/dist/annotatedAttachments/payload.test.js +67 -0
  17. package/dist/annotatedAttachments/service.d.ts +49 -0
  18. package/dist/annotatedAttachments/service.js +121 -0
  19. package/dist/annotatedAttachments/types.d.ts +86 -0
  20. package/dist/cli.js +74 -25
  21. package/dist/compat/deploymentCompat.d.ts +12 -0
  22. package/dist/compat/deploymentCompat.js +43 -0
  23. package/dist/compat/workspaceSyncCompat.d.ts +6 -0
  24. package/dist/compat/workspaceSyncCompat.js +79 -0
  25. package/dist/compat/workspaceSyncCompat.test.js +87 -0
  26. package/dist/components/features/AgentsModule.d.ts +5 -0
  27. package/dist/components/features/AgentsModule.js +131 -0
  28. package/dist/components/features/AgentsModule.test.js +102 -0
  29. package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +24 -0
  30. package/dist/components/features/AnnotatedAttachmentWorkspace.js +1942 -0
  31. package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +2831 -0
  32. package/dist/components/features/DocumentGenerateModal.js +8 -16
  33. package/dist/components/features/DocumentIndex.d.ts +3 -4
  34. package/dist/components/features/DocumentIndex.js +22 -10
  35. package/dist/components/features/DocumentViewer.d.ts +6 -2
  36. package/dist/components/features/DocumentViewer.js +1000 -18
  37. package/dist/components/features/DocumentWorkspace.d.ts +40 -2
  38. package/dist/components/features/DocumentWorkspace.js +96 -16
  39. package/dist/components/features/DocumentWorkspace.test.js +1202 -0
  40. package/dist/components/features/InitiativesModule.d.ts +22 -0
  41. package/dist/components/features/InitiativesModule.js +33 -0
  42. package/dist/components/features/TaskSettings.d.ts +41 -32
  43. package/dist/components/features/TaskSettings.js +798 -313
  44. package/dist/components/features/TaskSettings.test.js +1221 -47
  45. package/dist/components/features/WorkflowsModule.d.ts +27 -0
  46. package/dist/components/features/WorkflowsModule.js +21 -0
  47. package/dist/components/features/documentWorkspaceFilters.d.ts +10 -0
  48. package/dist/components/features/documentWorkspaceFilters.js +11 -0
  49. package/dist/components/features/settings/CategoryManager.d.ts +2 -0
  50. package/dist/components/features/settings/CategoryManager.js +15 -8
  51. package/dist/components/features/settings/PriorityManager.d.ts +18 -0
  52. package/dist/components/features/settings/PriorityManager.js +51 -0
  53. package/dist/components/features/settings/TaxonomyEditor.d.ts +2 -1
  54. package/dist/components/features/settings/TaxonomyEditor.js +41 -18
  55. package/dist/components/features/settings/TaxonomyLibraryManager.d.ts +26 -0
  56. package/dist/components/features/settings/TaxonomyLibraryManager.js +301 -0
  57. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +141 -0
  58. package/dist/components/features/settings/TaxonomyManager.d.ts +35 -12
  59. package/dist/components/features/settings/TaxonomyManager.js +59 -27
  60. package/dist/components/features/settings/TypeManager.d.ts +11 -0
  61. package/dist/components/features/settings/TypeManager.js +48 -11
  62. package/dist/components/features/settings/{WorkflowEditorPrototype.d.ts → WorkflowEditor.d.ts} +2 -2
  63. package/dist/components/features/settings/{WorkflowEditorPrototype.js → WorkflowEditor.js} +3 -3
  64. package/dist/components/task/TaskActionHeader.d.ts +12 -2
  65. package/dist/components/task/TaskActionHeader.js +64 -8
  66. package/dist/components/task/TaskActionHeader.test.js +73 -0
  67. package/dist/components/task/TaskCard.d.ts +14 -16
  68. package/dist/components/task/TaskCard.js +144 -174
  69. package/dist/components/task/TaskCard.test.js +416 -64
  70. package/dist/components/task/TaskContextUpload.d.ts +6 -5
  71. package/dist/components/task/TaskContextUpload.js +199 -64
  72. package/dist/components/task/TaskContextUpload.test.js +331 -14
  73. package/dist/components/task/TaskForm.d.ts +23 -29
  74. package/dist/components/task/TaskForm.js +348 -169
  75. package/dist/components/task/TaskForm.test.js +419 -23
  76. package/dist/components/task/TaskKanban.d.ts +20 -21
  77. package/dist/components/task/TaskKanban.js +176 -316
  78. package/dist/components/task/TaskKanban.planning-drop.test.js +93 -0
  79. package/dist/components/task/TaskList.d.ts +22 -11
  80. package/dist/components/task/TaskList.js +98 -12
  81. package/dist/components/task/TaskList.test.js +206 -7
  82. package/dist/components/task/TaskReferenceBadge.d.ts +12 -0
  83. package/dist/components/task/TaskReferenceBadge.js +5 -0
  84. package/dist/components/task/TaskStatusActions.d.ts +12 -0
  85. package/dist/components/task/TaskStatusActions.js +18 -0
  86. package/dist/components/task/TaskStatusActions.test.js +45 -0
  87. package/dist/components/ui/ImageReferenceBadge.d.ts +12 -0
  88. package/dist/components/ui/ImageReferenceBadge.js +5 -0
  89. package/dist/components/ui/LevelSelector.d.ts +2 -1
  90. package/dist/components/ui/LevelSelector.js +2 -2
  91. package/dist/components/ui/Markdown.d.ts +1 -2
  92. package/dist/components/ui/Markdown.js +2 -13
  93. package/dist/components/ui/Modal.d.ts +3 -2
  94. package/dist/components/ui/Modal.js +4 -3
  95. package/dist/components/ui/MultiSelectFilter.d.ts +3 -1
  96. package/dist/components/ui/MultiSelectFilter.js +2 -2
  97. package/dist/components/ui/ReferenceBadgeButton.d.ts +13 -0
  98. package/dist/components/ui/ReferenceBadgeButton.js +6 -0
  99. package/dist/components/ui/TaxonomyDropdown.d.ts +7 -1
  100. package/dist/components/ui/TaxonomyDropdown.js +58 -23
  101. package/dist/components/ui/TopNoticeLayer.d.ts +7 -0
  102. package/dist/components/ui/TopNoticeLayer.js +14 -0
  103. package/dist/components/ui/TopNoticeLayer.test.js +25 -0
  104. package/dist/components/views/AppShellHeader.d.ts +13 -0
  105. package/dist/components/views/AppShellHeader.js +19 -0
  106. package/dist/components/views/AppShellHeader.test.js +21 -0
  107. package/dist/components/views/PlanComparisonPage.d.ts +73 -0
  108. package/dist/components/views/PlanComparisonPage.js +196 -0
  109. package/dist/components/views/PlanComparisonPage.test.js +251 -0
  110. package/dist/components/views/PlansPage.d.ts +14 -0
  111. package/dist/components/views/PlansPage.js +355 -0
  112. package/dist/components/views/StandaloneLayout.d.ts +2 -2
  113. package/dist/components/views/StandaloneLayout.js +2227 -1188
  114. package/dist/components/views/WidgetView.d.ts +2 -2
  115. package/dist/components/views/WidgetView.js +149 -43
  116. package/dist/components/views/chrome/WorkspaceChromeSections.d.ts +57 -0
  117. package/dist/components/views/chrome/WorkspaceChromeSections.js +38 -0
  118. package/dist/components/views/chrome/WorkspaceChromeSections.test.js +54 -0
  119. package/dist/components/views/chrome/workspaceChrome.d.ts +31 -0
  120. package/dist/components/views/chrome/workspaceChrome.js +108 -0
  121. package/dist/components/views/chrome/workspaceChrome.test.js +52 -0
  122. package/dist/components/views/panels/DocumentFilterToolbar.d.ts +10 -0
  123. package/dist/components/views/panels/DocumentFilterToolbar.js +7 -0
  124. package/dist/components/views/panels/DocumentFilterToolbar.test.js +19 -0
  125. package/dist/components/views/panels/FilterToolbar.d.ts +68 -0
  126. package/dist/components/views/panels/FilterToolbar.js +21 -0
  127. package/dist/components/views/panels/FilterToolbar.test.d.ts +1 -0
  128. package/dist/components/views/panels/FilterToolbar.test.js +45 -0
  129. package/dist/components/views/panels/FilterToolbarControls.d.ts +47 -0
  130. package/dist/components/views/panels/FilterToolbarControls.js +61 -0
  131. package/dist/components/views/panels/PlanningDrawer.d.ts +115 -0
  132. package/dist/components/views/panels/PlanningDrawer.js +220 -0
  133. package/dist/components/views/panels/ScheduleSidebar.d.ts +52 -0
  134. package/dist/components/views/panels/ScheduleSidebar.js +127 -0
  135. package/dist/components/views/panels/scheduleUtils.d.ts +14 -0
  136. package/dist/components/views/panels/scheduleUtils.js +79 -0
  137. package/dist/components/views/teamManagementAccess.d.ts +6 -0
  138. package/dist/components/views/teamManagementAccess.js +18 -0
  139. package/dist/components/views/teamManagementAccess.test.d.ts +1 -0
  140. package/dist/components/views/teamManagementAccess.test.js +44 -0
  141. package/dist/config/clientDeployment.d.ts +2 -0
  142. package/dist/config/clientDeployment.js +13 -0
  143. package/dist/config/cloudEnvironment.d.ts +15 -0
  144. package/dist/config/cloudEnvironment.js +60 -0
  145. package/dist/config/deployment.d.ts +20 -0
  146. package/dist/config/deployment.js +67 -0
  147. package/dist/config/deployment.test.d.ts +1 -0
  148. package/dist/config/deployment.test.js +88 -0
  149. package/dist/config/envSchema.js +36 -8
  150. package/dist/config/envSchema.test.js +28 -0
  151. package/dist/core/AiProfileService.d.ts +66 -0
  152. package/dist/core/AiProfileService.js +320 -0
  153. package/dist/core/AiProfileService.test.d.ts +1 -0
  154. package/dist/core/AiProfileService.test.js +164 -0
  155. package/dist/core/AiProfiles.test.d.ts +1 -0
  156. package/dist/core/AiProfiles.test.js +449 -0
  157. package/dist/core/AttachmentLinkService.d.ts +15 -0
  158. package/dist/core/AttachmentLinkService.js +157 -0
  159. package/dist/core/AuthTokenService.d.ts +44 -0
  160. package/dist/core/AuthTokenService.js +207 -0
  161. package/dist/core/CreatedBy.test.js +134 -6
  162. package/dist/core/DeletedTaskLifecycleService.d.ts +33 -0
  163. package/dist/core/DeletedTaskLifecycleService.js +158 -0
  164. package/dist/core/DeletedTaskTrash.test.d.ts +1 -0
  165. package/dist/core/DeletedTaskTrash.test.js +104 -0
  166. package/dist/core/EntitlementsPolicy.test.js +78 -5
  167. package/dist/core/GlobalSettingsService.d.ts +16 -0
  168. package/dist/core/GlobalSettingsService.js +370 -0
  169. package/dist/core/JsonBackupRuntime.test.d.ts +1 -0
  170. package/dist/core/JsonBackupRuntime.test.js +155 -0
  171. package/dist/core/McpTokenService.d.ts +118 -0
  172. package/dist/core/McpTokenService.js +921 -0
  173. package/dist/core/PlanEntitlementService.d.ts +201 -0
  174. package/dist/core/PlanEntitlementService.js +1442 -0
  175. package/dist/core/PlanVersionPolicy.test.d.ts +1 -0
  176. package/dist/core/PlanVersionPolicy.test.js +178 -0
  177. package/dist/core/PlanningEntities.test.d.ts +1 -0
  178. package/dist/core/PlanningEntities.test.js +95 -0
  179. package/dist/core/ScheduleFields.test.js +15 -19
  180. package/dist/core/SignupMonetizationSettings.test.d.ts +1 -0
  181. package/dist/core/SignupMonetizationSettings.test.js +159 -0
  182. package/dist/core/Stats.test.d.ts +1 -0
  183. package/dist/core/Stats.test.js +36 -0
  184. package/dist/core/SyncReconciliationService.d.ts +93 -0
  185. package/dist/core/SyncReconciliationService.js +406 -0
  186. package/dist/core/SystemAdmin.test.js +578 -13
  187. package/dist/core/TaskActivityService.d.ts +21 -0
  188. package/dist/core/TaskActivityService.js +130 -0
  189. package/dist/core/TaskAttachmentsCanonical.test.d.ts +1 -0
  190. package/dist/core/TaskAttachmentsCanonical.test.js +306 -0
  191. package/dist/core/TaskAuditTimeline.test.d.ts +1 -0
  192. package/dist/core/TaskAuditTimeline.test.js +132 -0
  193. package/dist/core/TaskTaxonomyValidation.test.js +338 -15
  194. package/dist/core/Taskforce.d.ts +930 -183
  195. package/dist/core/Taskforce.ids.test.js +252 -0
  196. package/dist/core/Taskforce.js +3848 -2532
  197. package/dist/core/Taskforce.mcpAuth.test.d.ts +1 -0
  198. package/dist/core/Taskforce.mcpAuth.test.js +343 -0
  199. package/dist/core/Taskforce.realtimeMutation.test.js +57 -2
  200. package/dist/core/TaxonomySettingsService.d.ts +18 -0
  201. package/dist/core/TaxonomySettingsService.js +162 -0
  202. package/dist/core/WorkspaceDelete.test.js +32 -13
  203. package/dist/core/WorkspaceLifecycleService.d.ts +11 -0
  204. package/dist/core/WorkspaceLifecycleService.js +88 -0
  205. package/dist/core/WorkspacePermissions.test.js +95 -0
  206. package/dist/core/WorkspacePlanMode.test.js +114 -1
  207. package/dist/core/WorkspacePreferenceService.d.ts +41 -0
  208. package/dist/core/WorkspacePreferenceService.js +246 -0
  209. package/dist/core/WorkspaceSyncSettings.test.d.ts +1 -0
  210. package/dist/core/WorkspaceSyncSettings.test.js +65 -0
  211. package/dist/core/__tests__/TaskAttachmentsCanonical.test.d.ts +1 -0
  212. package/dist/core/__tests__/TaskAttachmentsCanonical.test.js +212 -0
  213. package/dist/core/shared.d.ts +36 -0
  214. package/dist/core/shared.js +128 -0
  215. package/dist/core/types.d.ts +614 -0
  216. package/dist/core/types.js +27 -0
  217. package/dist/documentReviews/DocumentReviewCommentStore.d.ts +56 -0
  218. package/dist/documentReviews/DocumentReviewCommentStore.js +230 -0
  219. package/dist/documentReviews/DocumentReviewSessionStore.d.ts +52 -0
  220. package/dist/documentReviews/DocumentReviewSessionStore.js +193 -0
  221. package/dist/documentReviews/DocumentReviewSessionStore.test.d.ts +1 -0
  222. package/dist/documentReviews/DocumentReviewSessionStore.test.js +127 -0
  223. package/dist/documentReviews/service.d.ts +68 -0
  224. package/dist/documentReviews/service.js +210 -0
  225. package/dist/documentReviews/types.d.ts +58 -0
  226. package/dist/documentReviews/types.js +1 -0
  227. package/dist/hooks/auth/useAuthGuardPolicy.d.ts +2 -0
  228. package/dist/hooks/auth/useAuthGuardPolicy.js +1 -0
  229. package/dist/hooks/tasks/useCurrentTaskRelations.d.ts +15 -0
  230. package/dist/hooks/tasks/useCurrentTaskRelations.js +37 -0
  231. package/dist/hooks/tasks/useTaskActions.d.ts +3 -0
  232. package/dist/hooks/tasks/useTaskActions.js +2 -0
  233. package/dist/hooks/tasks/useTaskEditorNavigation.d.ts +65 -0
  234. package/dist/hooks/tasks/useTaskEditorNavigation.js +235 -0
  235. package/dist/hooks/tasks/useTaskFilters.d.ts +2 -0
  236. package/dist/hooks/tasks/useTaskFilters.js +2 -0
  237. package/dist/hooks/tasks/useTaskFormActions.d.ts +58 -0
  238. package/dist/hooks/tasks/useTaskFormActions.js +365 -0
  239. package/dist/hooks/tasks/useTaskRelationships.d.ts +16 -0
  240. package/dist/hooks/tasks/useTaskRelationships.js +64 -0
  241. package/dist/hooks/ui/useResourceExport.d.ts +19 -0
  242. package/dist/hooks/ui/useResourceExport.js +84 -0
  243. package/dist/hooks/ui/useSettingsModel.d.ts +203 -0
  244. package/dist/hooks/ui/useSettingsModel.js +125 -0
  245. package/dist/hooks/ui/useSettingsPersistence.d.ts +36 -0
  246. package/dist/hooks/ui/useSettingsPersistence.js +254 -0
  247. package/dist/hooks/ui/useSettingsTaxonomyManagement.d.ts +119 -0
  248. package/dist/hooks/ui/useSettingsTaxonomyManagement.js +536 -0
  249. package/dist/hooks/ui/useUiNotice.d.ts +1 -0
  250. package/dist/hooks/ui/useUiNotice.js +1 -0
  251. package/dist/hooks/ui/useWorkflowTemplates.d.ts +37 -0
  252. package/dist/hooks/ui/useWorkflowTemplates.js +111 -0
  253. package/dist/hooks/ui/useZenMode.d.ts +5 -0
  254. package/dist/hooks/ui/useZenMode.js +32 -0
  255. package/dist/hooks/useFilterSort.d.ts +60 -0
  256. package/dist/hooks/useFilterSort.js +362 -0
  257. package/dist/hooks/useFilterSort.test.d.ts +1 -0
  258. package/dist/hooks/useFilterSort.test.js +333 -0
  259. package/dist/hooks/useRealtimeSync.d.ts +1 -0
  260. package/dist/hooks/useRealtimeSync.js +24 -9
  261. package/dist/hooks/useRealtimeSync.test.js +28 -1
  262. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.d.ts +1 -0
  263. package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +34 -0
  264. package/dist/hooks/useSyncOrchestrator.d.ts +65 -103
  265. package/dist/hooks/useSyncOrchestrator.js +1538 -670
  266. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +700 -174
  267. package/dist/hooks/useTaskData.d.ts +35 -0
  268. package/dist/hooks/useTaskData.js +243 -0
  269. package/dist/hooks/useTaskData.test.d.ts +1 -0
  270. package/dist/hooks/useTaskData.test.js +180 -0
  271. package/dist/hooks/useTaskMutations.d.ts +46 -0
  272. package/dist/hooks/useTaskMutations.js +472 -0
  273. package/dist/hooks/useTaskMutations.test.d.ts +1 -0
  274. package/dist/hooks/useTaskMutations.test.js +80 -0
  275. package/dist/hooks/useTaskforce.contract.test.d.ts +1 -0
  276. package/dist/hooks/useTaskforce.contract.test.js +7 -0
  277. package/dist/hooks/useTaskforce.d.ts +260 -295
  278. package/dist/hooks/useTaskforce.js +1954 -2623
  279. package/dist/hooks/useTaskforce.priority-filters.test.js +6 -11
  280. package/dist/hooks/useTaskforce.sync-behavior.test.d.ts +1 -0
  281. package/dist/hooks/useTaskforce.sync-behavior.test.js +1120 -0
  282. package/dist/hooks/useUiNotice.d.ts +16 -0
  283. package/dist/hooks/useUiNotice.js +49 -0
  284. package/dist/hooks/useWorkspaceSyncController.d.ts +23 -46
  285. package/dist/hooks/useWorkspaceSyncController.js +280 -461
  286. package/dist/hooks/useWorkspaceSyncController.test.js +106 -199
  287. package/dist/hooks/workspace/useTaskforceApiRouting.d.ts +8 -0
  288. package/dist/hooks/workspace/useTaskforceApiRouting.js +91 -0
  289. package/dist/hooks/workspace/useTaskforceApiRouting.test.d.ts +1 -0
  290. package/dist/hooks/workspace/useTaskforceApiRouting.test.js +60 -0
  291. package/dist/hooks/workspace/useWorkspaceSyncController.d.ts +1 -0
  292. package/dist/hooks/workspace/useWorkspaceSyncController.js +1 -0
  293. package/dist/hooks/workspace/workspaceLocalState.d.ts +30 -0
  294. package/dist/hooks/workspace/workspaceLocalState.js +101 -0
  295. package/dist/localization/locales/en-US.d.ts +1 -8
  296. package/dist/localization/locales/en-US.js +4 -11
  297. package/dist/localization/locales/es-419.js +2 -9
  298. package/dist/localization/locales/pt-BR.js +2 -9
  299. package/dist/localization/localizationCatalogs.test.d.ts +1 -0
  300. package/dist/localization/localizationCatalogs.test.js +37 -0
  301. package/dist/main.d.ts +1 -0
  302. package/dist/main.js +9 -15
  303. package/dist/mcp/clientIntegrations.d.ts +33 -0
  304. package/dist/mcp/clientIntegrations.js +199 -0
  305. package/dist/mcp/clientIntegrations.test.d.ts +1 -0
  306. package/dist/mcp/clientIntegrations.test.js +110 -0
  307. package/dist/mcp/index.d.ts +2 -0
  308. package/dist/mcp/index.js +2 -0
  309. package/dist/mcp/optionResolution.test.js +6 -2
  310. package/dist/mcp/runtime.d.ts +120 -0
  311. package/dist/mcp/runtime.js +5668 -0
  312. package/dist/mcp/runtime.test.d.ts +1 -0
  313. package/dist/mcp/runtime.test.js +3299 -0
  314. package/dist/mcp/server.js +2 -1793
  315. package/dist/mcp/structuredCommentValidation.test.d.ts +1 -0
  316. package/dist/{__tests__ → mcp}/structuredCommentValidation.test.js +1 -1
  317. package/dist/mcp/structuredDocValidation.js +1 -2
  318. package/dist/mcp/structuredDocValidation.test.d.ts +1 -0
  319. package/dist/{__tests__ → mcp}/structuredDocValidation.test.js +9 -4
  320. package/dist/mcp/toolRegistry.d.ts +570 -0
  321. package/dist/mcp/toolRegistry.js +90 -0
  322. package/dist/mcp/toolRegistry.test.d.ts +1 -0
  323. package/dist/mcp/toolRegistry.test.js +30 -0
  324. package/dist/migrations/billingSchemaParity.test.js +111 -0
  325. package/dist/migrations/taskSchemaMigrations.d.ts +5 -0
  326. package/dist/migrations/taskSchemaMigrations.js +681 -39
  327. package/dist/migrations/taskSchemaMigrations.test.d.ts +1 -0
  328. package/dist/migrations/taskSchemaMigrations.test.js +210 -0
  329. package/dist/plugins/vite.js +13 -7
  330. package/dist/public/apple-touch-icon.png +0 -0
  331. package/dist/public/archive/phoenix/apple-touch-icon.png +0 -0
  332. package/dist/public/archive/phoenix/favicon.ico +0 -0
  333. package/dist/public/archive/phoenix/site.webmanifest +1 -0
  334. package/dist/public/favicon-96x96.png +0 -0
  335. package/dist/public/favicon.ico +0 -0
  336. package/dist/public/favicon.svg +17 -0
  337. package/dist/public/site.webmanifest +21 -1
  338. package/dist/public/web-app-manifest-192x192.png +0 -0
  339. package/dist/public/web-app-manifest-512x512.png +0 -0
  340. package/dist/releaseVersion.test.d.ts +1 -0
  341. package/dist/{__tests__/releaseVersion.test.js → releaseVersion.test.js} +1 -1
  342. package/dist/resources/templates/WORKFLOW_V2_POLICY.md +1 -1
  343. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +62 -52
  344. package/dist/resources/templates/workflows/collaborate.yaml +11 -5
  345. package/dist/resources/templates/workflows/do.yaml +8 -2
  346. package/dist/resources/templates/workflows/evaluate.yaml +13 -7
  347. package/dist/resources/templates/workflows/execute.yaml +40 -23
  348. package/dist/resources/templates/workflows/plan.yaml +15 -8
  349. package/dist/resources/templates/workflows/review.yaml +14 -8
  350. package/dist/runtime/appGateDefinitions.d.ts +32 -0
  351. package/dist/runtime/appGateDefinitions.js +32 -0
  352. package/dist/runtime/appGates.d.ts +29 -0
  353. package/dist/runtime/appGates.js +53 -0
  354. package/dist/runtime/appGates.test.d.ts +1 -0
  355. package/dist/runtime/appGates.test.js +42 -0
  356. package/dist/runtime/migrationFlags.d.ts +1 -0
  357. package/dist/runtime/migrationFlags.js +1 -0
  358. package/dist/server/annotatedAttachmentsRoutes.test.d.ts +1 -0
  359. package/dist/server/annotatedAttachmentsRoutes.test.js +473 -0
  360. package/dist/server/auth.js +14 -2
  361. package/dist/server/auth.test.js +21 -1
  362. package/dist/server/cors.js +11 -5
  363. package/dist/server/cors.test.js +6 -0
  364. package/dist/server/documentReviewRoutes.test.d.ts +1 -0
  365. package/dist/server/documentReviewRoutes.test.js +157 -0
  366. package/dist/server/index.cookieProxy.test.js +46 -1
  367. package/dist/server/index.d.ts +20 -0
  368. package/dist/server/index.js +168 -41
  369. package/dist/server/index.rateLimit.test.js +6 -0
  370. package/dist/server/index.test.js +133 -2
  371. package/dist/server/mockStripe.d.ts +72 -0
  372. package/dist/server/mockStripe.js +357 -0
  373. package/dist/server/realtimeSyncWs.js +4 -0
  374. package/dist/server/routes/admin.d.ts +114 -0
  375. package/dist/server/routes/admin.js +2226 -0
  376. package/dist/server/routes/annotatedAttachments.d.ts +24 -0
  377. package/dist/server/routes/annotatedAttachments.js +235 -0
  378. package/dist/server/routes/auth.d.ts +61 -0
  379. package/dist/server/routes/auth.js +1296 -0
  380. package/dist/server/{routes.billing.d.ts → routes/billing.d.ts} +3 -2
  381. package/dist/server/routes/billing.js +2001 -0
  382. package/dist/server/routes/billing.test.d.ts +1 -0
  383. package/dist/server/routes/billing.test.js +1631 -0
  384. package/dist/server/routes/documentReviews.d.ts +11 -0
  385. package/dist/server/routes/documentReviews.js +191 -0
  386. package/dist/server/routes/documents.d.ts +118 -0
  387. package/dist/server/routes/documents.js +1113 -0
  388. package/dist/server/routes/resources.d.ts +25 -0
  389. package/dist/server/routes/resources.js +225 -0
  390. package/dist/server/routes/shared.d.ts +35 -0
  391. package/dist/server/routes/shared.js +305 -0
  392. package/dist/server/routes/shared.test.d.ts +1 -0
  393. package/dist/server/routes/shared.test.js +32 -0
  394. package/dist/server/{routes.sync.d.ts → routes/sync.d.ts} +5 -5
  395. package/dist/server/routes/sync.integration.test.d.ts +1 -0
  396. package/dist/server/routes/sync.integration.test.js +2003 -0
  397. package/dist/server/routes/sync.js +3143 -0
  398. package/dist/server/routes/tasks.d.ts +72 -0
  399. package/dist/server/routes/tasks.js +1252 -0
  400. package/dist/server/routes/workspaces.d.ts +32 -0
  401. package/dist/server/routes/workspaces.js +307 -0
  402. package/dist/server/routes.d.ts +8 -9
  403. package/dist/server/routes.js +968 -4092
  404. package/dist/server/routes.test.js +9043 -2415
  405. package/dist/server/runtimeMode.d.ts +1 -1
  406. package/dist/server/runtimeMode.js +3 -18
  407. package/dist/server/runtimeMode.test.js +3 -3
  408. package/dist/server/securityHeaders.d.ts +1 -1
  409. package/dist/server/securityHeaders.js +65 -6
  410. package/dist/server/securityHeaders.test.js +40 -2
  411. package/dist/services/templateEngine.test.d.ts +1 -0
  412. package/dist/{__tests__ → services}/templateEngine.test.js +1 -1
  413. package/dist/services/templates.test.d.ts +1 -0
  414. package/dist/{__tests__ → services}/templates.test.js +1 -1
  415. package/dist/services/workflowDocumentFixtures.test.d.ts +1 -0
  416. package/dist/{__tests__ → services}/workflowDocumentFixtures.test.js +4 -4
  417. package/dist/services/workflowExportMatrix.test.d.ts +1 -0
  418. package/dist/{__tests__ → services}/workflowExportMatrix.test.js +1 -1
  419. package/dist/services/workflowExportSnapshots.test.d.ts +1 -0
  420. package/dist/services/workflowGeneration.test.d.ts +1 -0
  421. package/dist/services/workflowSimulation.test.d.ts +1 -0
  422. package/dist/services/workflowSimulation.test.js +113 -0
  423. package/dist/services/workflowV2Policy.test.d.ts +1 -0
  424. package/dist/{__tests__ → services}/workflowV2Policy.test.js +2 -2
  425. package/dist/shared/aiProfileSurfaceType.d.ts +9 -0
  426. package/dist/shared/aiProfileSurfaceType.js +49 -0
  427. package/dist/shared/customTaxonomyLibrary.d.ts +11 -0
  428. package/dist/shared/customTaxonomyLibrary.js +276 -0
  429. package/dist/shared/customTaxonomyLibrary.test.d.ts +1 -0
  430. package/dist/shared/customTaxonomyLibrary.test.js +20 -0
  431. package/dist/shared/taxonomyCatalog.d.ts +19 -0
  432. package/dist/shared/taxonomyCatalog.js +36 -0
  433. package/dist/shared/taxonomyDefaults.d.ts +29 -0
  434. package/dist/shared/taxonomyDefaults.js +26 -0
  435. package/dist/shared/taxonomyLibrary.d.ts +15 -0
  436. package/dist/shared/taxonomyLibrary.js +98 -0
  437. package/dist/shared/taxonomyLibrary.test.d.ts +1 -0
  438. package/dist/shared/taxonomyLibrary.test.js +26 -0
  439. package/dist/shared/taxonomyState.d.ts +36 -0
  440. package/dist/shared/taxonomyState.js +73 -0
  441. package/dist/shared/taxonomyState.test.d.ts +1 -0
  442. package/dist/shared/taxonomyState.test.js +71 -0
  443. package/dist/shared/workflowDefaults.d.ts +9 -0
  444. package/dist/shared/workflowDefaults.js +11 -0
  445. package/dist/storage/documentIntegrity.d.ts +2 -0
  446. package/dist/storage/documentIntegrity.js +34 -17
  447. package/dist/storage/documentPurge.d.ts +2 -1
  448. package/dist/storage/documentPurge.js +35 -8
  449. package/dist/storage/index.d.ts +4 -0
  450. package/dist/storage/index.js +2 -0
  451. package/dist/storage/objectStorage.d.ts +1 -1
  452. package/dist/storage/objectStorageClient.d.ts +6 -4
  453. package/dist/storage/objectStorageClient.js +77 -29
  454. package/dist/storage/objectStorageClient.test.d.ts +1 -0
  455. package/dist/storage/objectStorageClient.test.js +57 -0
  456. package/dist/storage/objectStorageConfig.js +3 -1
  457. package/dist/storage/objectStorageConfig.test.d.ts +1 -0
  458. package/dist/storage/objectStorageConfig.test.js +38 -0
  459. package/dist/storage/postgresAdapter.d.ts +5 -0
  460. package/dist/storage/postgresAdapter.js +2 -2
  461. package/dist/storage/postgresAdapter.test.d.ts +1 -0
  462. package/dist/storage/postgresAdapter.test.js +29 -0
  463. package/dist/storage/taskAssetStore.d.ts +7 -4
  464. package/dist/storage/taskAssetStore.js +65 -60
  465. package/dist/storage/workspaceAssetStore.d.ts +122 -0
  466. package/dist/storage/workspaceAssetStore.js +496 -0
  467. package/dist/storage/workspaceAssetStore.test.d.ts +1 -0
  468. package/dist/storage/workspaceAssetStore.test.js +501 -0
  469. package/dist/sync/cloudSyncApi.d.ts +33 -2
  470. package/dist/sync/cloudSyncApi.js +37 -15
  471. package/dist/sync/cloudSyncApi.test.js +60 -3
  472. package/dist/sync/index.d.ts +2 -0
  473. package/dist/sync/index.js +3 -0
  474. package/dist/sync/syncService.d.ts +25 -0
  475. package/dist/sync/syncService.js +184 -16
  476. package/dist/sync/syncService.test.js +289 -25
  477. package/dist/sync/workspaceRepair.d.ts +114 -0
  478. package/dist/sync/workspaceRepair.js +566 -0
  479. package/dist/sync/workspaceSyncModel.d.ts +230 -2
  480. package/dist/sync/workspaceSyncModel.js +456 -17
  481. package/dist/sync/workspaceSyncModel.test.js +642 -0
  482. package/dist/sync/workspaceSyncState.d.ts +12 -0
  483. package/dist/sync/workspaceSyncState.js +12 -0
  484. package/dist/sync/workspaceSyncSurface.d.ts +33 -0
  485. package/dist/sync/workspaceSyncSurface.js +74 -0
  486. package/dist/types.d.ts +143 -18
  487. package/dist/types.js +9 -24
  488. package/dist/ui/assets/AgentsModule-Bdq3T1Ts.js +1 -0
  489. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +1 -0
  490. package/dist/ui/assets/AnnotatedAttachmentWorkspace-jKUVo-PJ.js +3 -0
  491. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +1 -0
  492. package/dist/ui/assets/DocumentWorkspace-DuQa-uIC.js +250 -0
  493. package/dist/ui/assets/InitiativesModule-C8Pi2VTk.js +1 -0
  494. package/dist/ui/assets/PlansPage-B6BDECSi.js +1 -0
  495. package/dist/ui/assets/PlansPage-BlVC_lRq.css +1 -0
  496. package/dist/ui/assets/TaskSettings-BwB9NtmJ.css +1 -0
  497. package/dist/ui/assets/TaskSettings-DUJgRTPU.js +8 -0
  498. package/dist/ui/assets/WorkflowsModule-b6TLuHwJ.js +5 -0
  499. package/dist/ui/assets/index-BiiF1cLQ.css +1 -0
  500. package/dist/ui/assets/index-xBWLPOEb.js +6 -0
  501. package/dist/ui/assets/vendor-dnd-DRzYolkg.js +5 -0
  502. package/dist/ui/assets/{vendor-icons-ZFG3SGwo.js → vendor-icons-QZyhEwgT.js} +1 -1
  503. package/dist/ui/assets/{vendor-markdown-CKYkQHfj.js → vendor-markdown-BUxTU7dS.js} +1 -1
  504. package/dist/ui/assets/vendor-react-CKJs5o3c.js +61 -0
  505. package/dist/ui/assets/vendor-router-BbWMxlnO.js +3 -0
  506. package/dist/ui/branding/logos/logo.png +0 -0
  507. package/dist/ui/branding/logos/logo_dark.svg +27 -0
  508. package/dist/ui/branding/logos/logo_favicon.svg +35 -0
  509. package/dist/ui/branding/logos/logo_light.svg +27 -0
  510. package/dist/ui/favicon/apple-touch-icon.png +0 -0
  511. package/dist/ui/favicon/archive/phoenix/apple-touch-icon.png +0 -0
  512. package/dist/ui/favicon/archive/phoenix/favicon.ico +0 -0
  513. package/dist/ui/favicon/archive/phoenix/site.webmanifest +1 -0
  514. package/dist/ui/favicon/favicon-96x96.png +0 -0
  515. package/dist/ui/favicon/favicon.ico +0 -0
  516. package/dist/ui/favicon/favicon.svg +17 -0
  517. package/dist/ui/favicon/site.webmanifest +21 -1
  518. package/dist/ui/favicon/web-app-manifest-192x192.png +0 -0
  519. package/dist/ui/favicon/web-app-manifest-512x512.png +0 -0
  520. package/dist/ui/index.html +10 -7
  521. package/dist/ui/product/screen_01.png +0 -0
  522. package/dist/ui/product/screen_02.png +0 -0
  523. package/dist/ui/product/screen_03.png +0 -0
  524. package/dist/utils/annotatedAttachments.d.ts +20 -0
  525. package/dist/utils/annotatedAttachments.js +74 -0
  526. package/dist/utils/annotatedAttachments.test.d.ts +1 -0
  527. package/dist/utils/annotatedAttachments.test.js +108 -0
  528. package/dist/utils/assignees.d.ts +26 -0
  529. package/dist/utils/assignees.js +132 -0
  530. package/dist/utils/canonicalAssetKind.d.ts +16 -0
  531. package/dist/utils/canonicalAssetKind.js +39 -0
  532. package/dist/utils/canonicalAssetKind.test.d.ts +1 -0
  533. package/dist/utils/canonicalAssetKind.test.js +24 -0
  534. package/dist/utils/coalescedRefresh.d.ts +5 -0
  535. package/dist/utils/coalescedRefresh.js +16 -0
  536. package/dist/utils/coalescedRefresh.test.d.ts +1 -0
  537. package/dist/utils/coalescedRefresh.test.js +30 -0
  538. package/dist/utils/constants.d.ts +16 -3
  539. package/dist/utils/constants.icons.test.js +2 -1
  540. package/dist/utils/constants.js +70 -13
  541. package/dist/utils/contextAssetEvents.d.ts +7 -0
  542. package/dist/utils/contextAssetEvents.js +6 -0
  543. package/dist/utils/contextFiles.d.ts +14 -3
  544. package/dist/utils/contextFiles.js +40 -8
  545. package/dist/utils/documentManagerApi.d.ts +6 -0
  546. package/dist/utils/documentManagerApi.js +19 -0
  547. package/dist/utils/documentNames.d.ts +24 -0
  548. package/dist/utils/documentNames.js +91 -0
  549. package/dist/utils/documentReferences.d.ts +9 -0
  550. package/dist/utils/documentReferences.js +11 -0
  551. package/dist/utils/entityReferences.d.ts +9 -0
  552. package/dist/utils/entityReferences.js +32 -0
  553. package/dist/utils/imageReferences.d.ts +9 -0
  554. package/dist/utils/imageReferences.js +11 -0
  555. package/dist/utils/planParser.d.ts +2 -2
  556. package/dist/utils/planParser.js +11 -11
  557. package/dist/utils/planParser.test.d.ts +1 -0
  558. package/dist/{__tests__ → utils}/planParser.test.js +22 -22
  559. package/dist/utils/planningReferences.d.ts +17 -0
  560. package/dist/utils/planningReferences.js +30 -0
  561. package/dist/utils/planningReferences.test.d.ts +1 -0
  562. package/dist/utils/planningReferences.test.js +17 -0
  563. package/dist/utils/taskActivity.d.ts +9 -0
  564. package/dist/utils/taskActivity.js +147 -0
  565. package/dist/utils/taskActivity.test.d.ts +1 -0
  566. package/dist/utils/taskActivity.test.js +34 -0
  567. package/dist/utils/taskDraftDefaults.d.ts +21 -0
  568. package/dist/utils/taskDraftDefaults.js +40 -0
  569. package/dist/utils/taskDraftDefaults.test.d.ts +1 -0
  570. package/dist/utils/taskDraftDefaults.test.js +34 -0
  571. package/dist/utils/taskNormalization.d.ts +25 -0
  572. package/dist/utils/taskNormalization.js +161 -0
  573. package/dist/utils/taskNormalization.test.d.ts +1 -0
  574. package/dist/utils/taskNormalization.test.js +158 -0
  575. package/dist/utils/taskReferences.d.ts +5 -0
  576. package/dist/utils/taskReferences.js +11 -0
  577. package/dist/utils/taskforceApiClient.d.ts +2 -0
  578. package/dist/utils/taskforceApiClient.js +101 -3
  579. package/dist/utils/taskforceApiClient.test.d.ts +1 -0
  580. package/dist/utils/taskforceApiClient.test.js +44 -0
  581. package/dist/utils/taxonomyFilterOptions.d.ts +2 -0
  582. package/dist/utils/taxonomyFilterOptions.js +17 -0
  583. package/dist/utils/taxonomyFormDefaults.d.ts +3 -0
  584. package/dist/utils/taxonomyFormDefaults.js +28 -0
  585. package/dist/utils/taxonomyFormDefaults.test.d.ts +1 -0
  586. package/dist/utils/taxonomyFormDefaults.test.js +63 -0
  587. package/dist/utils/taxonomyLifecycle.d.ts +3 -0
  588. package/dist/utils/taxonomyLifecycle.js +17 -0
  589. package/dist/utils/taxonomySorting.d.ts +7 -0
  590. package/dist/utils/taxonomySorting.js +74 -0
  591. package/dist/utils/theme.d.ts +15 -0
  592. package/dist/utils/theme.js +23 -0
  593. package/dist/utils/uiNotice.d.ts +7 -0
  594. package/dist/utils/uiNotice.js +6 -0
  595. package/dist/utils/uiNotice.test.d.ts +1 -0
  596. package/dist/utils/uiNotice.test.js +18 -0
  597. package/dist/utils/workspaceIdentity.d.ts +14 -0
  598. package/dist/utils/workspaceIdentity.js +21 -0
  599. package/dist/utils/workspaceIdentity.test.js +30 -1
  600. package/dist/utils/workspaceSyncPresentation.d.ts +20 -0
  601. package/dist/utils/workspaceSyncPresentation.js +21 -0
  602. package/dist/utils/workspaceSyncPresentation.test.d.ts +1 -0
  603. package/dist/utils/workspaceSyncPresentation.test.js +36 -0
  604. package/package.json +30 -23
  605. package/scripts/bootstrap-playwright-auth.mjs +155 -0
  606. package/scripts/check-compat-boundaries.mjs +41 -0
  607. package/scripts/check-env.ts +9 -2
  608. package/scripts/debug-login.mjs +32 -0
  609. package/scripts/deploy-prod.sh +41 -0
  610. package/scripts/deploy-staging.sh +23 -0
  611. package/scripts/migrate-planning-model.ts +233 -0
  612. package/scripts/migrate-task-data-assets.mjs +408 -0
  613. package/scripts/playwright-auth.sh +11 -0
  614. package/scripts/run-dev-proxy.sh +2 -0
  615. package/scripts/update-homebrew-formula.sh +2 -2
  616. package/scripts/with-esbuild.sh +11 -0
  617. package/src/resources/templates/WORKFLOW_V2_POLICY.md +1 -1
  618. package/src/resources/templates/workflow-sources/workflowDocs.mjs +62 -52
  619. package/src/resources/templates/workflows/collaborate.yaml +11 -5
  620. package/src/resources/templates/workflows/do.yaml +8 -2
  621. package/src/resources/templates/workflows/evaluate.yaml +13 -7
  622. package/src/resources/templates/workflows/execute.yaml +40 -23
  623. package/src/resources/templates/workflows/plan.yaml +15 -8
  624. package/src/resources/templates/workflows/review.yaml +14 -8
  625. package/dist/TaskforceScreenshot.d.ts +0 -11
  626. package/dist/TaskforceScreenshot.js +0 -113
  627. package/dist/compat/priorityCompat.d.ts +0 -2
  628. package/dist/compat/priorityCompat.js +0 -24
  629. package/dist/components/features/settings/TaxonomyManager 2.d.ts +0 -52
  630. package/dist/components/features/settings/TaxonomyManager 2.js +0 -75
  631. package/dist/components/task/TaskKanban 2.d.ts +0 -76
  632. package/dist/components/task/TaskKanban 2.js +0 -1004
  633. package/dist/components/task/TaskScreenshotSection.d.ts +0 -15
  634. package/dist/components/task/TaskScreenshotSection.js +0 -22
  635. package/dist/components/views/StandaloneLayout 2.d.ts +0 -8
  636. package/dist/components/views/StandaloneLayout 2.js +0 -1660
  637. package/dist/core/InitiativeTemplates.test.js +0 -50
  638. package/dist/core/ParentLifecycle.test.js +0 -98
  639. package/dist/migrations/clientMigrations.d.ts +0 -9
  640. package/dist/migrations/clientMigrations.js +0 -74
  641. package/dist/migrations/flags.d.ts +0 -6
  642. package/dist/migrations/flags.js +0 -8
  643. package/dist/migrations/taskMigrations.d.ts +0 -71
  644. package/dist/migrations/taskMigrations.js +0 -236
  645. package/dist/migrations/taskMigrations.test.js +0 -34
  646. package/dist/server/routes.billing.js +0 -991
  647. package/dist/server/routes.billing.test.js +0 -387
  648. package/dist/server/routes.sync.integration.test.js +0 -470
  649. package/dist/server/routes.sync.js +0 -1141
  650. package/dist/soak/syncSoakHarness.d.ts +0 -6
  651. package/dist/soak/syncSoakHarness.js +0 -10
  652. package/dist/ui/assets/index-0W5vyCpn.css +0 -1
  653. package/dist/ui/assets/index-MCZuNNfn.js +0 -23
  654. package/dist/ui/assets/logo_white_01-0Vx-iIIv.png +0 -0
  655. package/dist/ui/assets/vendor-dnd-BI1K77C1.js +0 -5
  656. package/dist/ui/assets/vendor-react-DdIDsh7f.js +0 -9
  657. package/dist/ui/assets/vendor-router-BtiNU7Dl.js +0 -3
  658. package/dist/utils/taskHierarchy.d.ts +0 -4
  659. package/dist/utils/taskHierarchy.js +0 -69
  660. package/dist/utils/taskHierarchy.test.js +0 -38
  661. package/dist/utils/taskParenting.d.ts +0 -3
  662. package/dist/utils/taskParenting.js +0 -24
  663. package/dist/utils/taskParenting.test.js +0 -21
  664. package/dist/utils/taskRelations.d.ts +0 -6
  665. package/dist/utils/taskRelations.js +0 -24
  666. package/dist/utils/taskRelations.test.js +0 -34
  667. package/scripts/check-compat-boundaries.js +0 -48
  668. /package/dist/{__tests__/planParser.test.d.ts → TaskforceCore.filter-persistence.test.d.ts} +0 -0
  669. /package/dist/{__tests__/releaseVersion.test.d.ts → TaskforceCore.routing.test.d.ts} +0 -0
  670. /package/dist/{__tests__/structuredCommentValidation.test.d.ts → annotatedAttachments/AnnotatedAttachmentSessionStore.test.d.ts} +0 -0
  671. /package/dist/{__tests__/structuredDocValidation.test.d.ts → annotatedAttachments/payload.test.d.ts} +0 -0
  672. /package/dist/{__tests__/templateEngine.test.d.ts → annotatedAttachments/types.js} +0 -0
  673. /package/dist/{__tests__/templates.test.d.ts → compat/workspaceSyncCompat.test.d.ts} +0 -0
  674. /package/dist/{__tests__/workflowDocumentFixtures.test.d.ts → components/features/AgentsModule.test.d.ts} +0 -0
  675. /package/dist/{__tests__/workflowExportMatrix.test.d.ts → components/features/AnnotatedAttachmentWorkspace.test.d.ts} +0 -0
  676. /package/dist/{__tests__/workflowExportSnapshots.test.d.ts → components/features/DocumentWorkspace.test.d.ts} +0 -0
  677. /package/dist/{__tests__/workflowGeneration.test.d.ts → components/features/settings/TaxonomyLibraryManager.test.d.ts} +0 -0
  678. /package/dist/{__tests__/workflowV2Policy.test.d.ts → components/task/TaskActionHeader.test.d.ts} +0 -0
  679. /package/dist/{core/InitiativeTemplates.test.d.ts → components/task/TaskKanban.planning-drop.test.d.ts} +0 -0
  680. /package/dist/{core/ParentLifecycle.test.d.ts → components/task/TaskStatusActions.test.d.ts} +0 -0
  681. /package/dist/{migrations/taskMigrations.test.d.ts → components/ui/TopNoticeLayer.test.d.ts} +0 -0
  682. /package/dist/{server/routes.billing.test.d.ts → components/views/AppShellHeader.test.d.ts} +0 -0
  683. /package/dist/{server/routes.sync.integration.test.d.ts → components/views/PlanComparisonPage.test.d.ts} +0 -0
  684. /package/dist/{utils/taskHierarchy.test.d.ts → components/views/chrome/WorkspaceChromeSections.test.d.ts} +0 -0
  685. /package/dist/{utils/taskParenting.test.d.ts → components/views/chrome/workspaceChrome.test.d.ts} +0 -0
  686. /package/dist/{utils/taskRelations.test.d.ts → components/views/panels/DocumentFilterToolbar.test.d.ts} +0 -0
  687. /package/dist/public/{android-chrome-192x192.png → archive/phoenix/android-chrome-192x192.png} +0 -0
  688. /package/dist/public/{android-chrome-512x512.png → archive/phoenix/android-chrome-512x512.png} +0 -0
  689. /package/dist/public/{favicon-16x16.png → archive/phoenix/favicon-16x16.png} +0 -0
  690. /package/dist/public/{favicon-32x32.png → archive/phoenix/favicon-32x32.png} +0 -0
  691. /package/dist/public/{favicon.png → archive/phoenix/favicon.png} +0 -0
  692. /package/dist/{__tests__ → services}/workflowExportSnapshots.test.js +0 -0
  693. /package/dist/{__tests__ → services}/workflowGeneration.test.js +0 -0
  694. /package/dist/ui/favicon/{android-chrome-192x192.png → archive/phoenix/android-chrome-192x192.png} +0 -0
  695. /package/dist/ui/favicon/{android-chrome-512x512.png → archive/phoenix/android-chrome-512x512.png} +0 -0
  696. /package/dist/ui/favicon/{favicon-16x16.png → archive/phoenix/favicon-16x16.png} +0 -0
  697. /package/dist/ui/favicon/{favicon-32x32.png → archive/phoenix/favicon-32x32.png} +0 -0
  698. /package/dist/ui/favicon/{favicon.png → archive/phoenix/favicon.png} +0 -0
@@ -1,117 +1,197 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useMemo, useCallback, useState, useEffect, useRef } from 'react';
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';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { useLocation, useNavigate } from 'react-router-dom';
5
- import { Settings, Plus, Search, Filter, Layers, User, Maximize2, Minimize2, Check, X, Ban, ChevronLeft, ChevronRight, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, Trash2, LogOut, Cloud, CloudOff, RefreshCw, Users, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, CalendarDays, PanelRightClose, PanelRightOpen } from 'lucide-react';
6
- import { STATUS_OPTIONS } from '../../utils/constants';
7
- import { MultiSelectFilter } from '../ui/MultiSelectFilter';
5
+ import { Settings, Plus, Filter, Layers, User, Maximize2, Minimize2, Check, ChevronLeft, ArrowUp, ArrowDown, ArrowUpDown, AlertTriangle, Save, Loader2, Folder, FileText, LogOut, NotebookTabs, ClipboardCheck, Cloud, CloudOff, RefreshCw, Users, Copy, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, Bot, Image, Workflow } from 'lucide-react';
6
+ import { STATUS_OPTIONS, getStatusDefinition } from '../../utils/constants';
8
7
  import styles from '../../Taskforce.module.css';
8
+ import shellStyles from './WorkspaceShell.module.css';
9
+ import accountStyles from './StandaloneAccount.module.css';
10
+ import authStyles from './AuthShell.module.css';
11
+ import folderBrowserStyles from './FolderBrowser.module.css';
12
+ import managementStyles from './StandaloneManagement.module.css';
13
+ import syncStatusStyles from './SyncStatusModal.module.css';
9
14
  import modalStyles from '../ui/Modal.module.css';
10
15
  import { Modal } from '../ui/Modal';
11
16
  import { TaskForm } from '../task/TaskForm';
12
- import { TaskSettings } from '../features/TaskSettings';
13
- import { DocumentWorkspace } from '../features/DocumentWorkspace';
14
17
  import { TaskKanban } from '../task/TaskKanban';
15
18
  import { TaskActionHeader } from '../task/TaskActionHeader';
19
+ import { TopNoticeLayer } from '../ui/TopNoticeLayer';
16
20
  import { TypeMeta } from '../../utils/constants';
17
- import { buildHierarchyDepthMap, collectSubtreeTaskIds } from '../../utils/taskHierarchy';
21
+ import { resolveDraftDefaultsForGrouping } from '../../utils/taskDraftDefaults';
18
22
  import { formatDate } from '../../utils/formatting';
23
+ import { resolveHeaderWorkspaceSyncPresentation } from '../../utils/workspaceSyncPresentation';
24
+ import { getTaskReferenceLabel, parseTaskReference } from '../../utils/taskReferences';
25
+ import { formatInitiativeReference, formatWorkstreamReference, parseWorkstreamReference, resolveInitiativeReference } from '../../utils/planningReferences';
26
+ import { DEFAULT_TYPE_VALUE } from '../../shared/taxonomyDefaults.js';
27
+ import { getImageReferenceLabel } from '../../utils/imageReferences';
28
+ import { buildCustomTaxonomySortOptions } from '../../utils/taxonomySorting.js';
19
29
  import { getLocale, t } from '../../localization';
20
- import logo from '../../assets/branding/logos/logo_white_01.png';
21
- import favicon from '../../assets/favicon/favicon.png';
22
- import taskforceBg from '../../assets/images/taskforce.png';
23
- const SCHEDULE_DAY_KEYS = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
24
- const SCHEDULE_DAY_LABELS = {
25
- mon: 'Mon',
26
- tue: 'Tue',
27
- wed: 'Wed',
28
- thu: 'Thu',
29
- fri: 'Fri',
30
- sat: 'Sat',
31
- sun: 'Sun'
32
- };
33
- const SYNCING_DISPLAY_DELAY_MS = 220;
34
- const SYNCING_MIN_VISIBLE_MS = 1200;
35
- const ERROR_MIN_VISIBLE_MS = 1800;
36
- function toDateOnlyLocal(date) {
37
- const y = date.getFullYear();
38
- const m = String(date.getMonth() + 1).padStart(2, '0');
39
- const d = String(date.getDate()).padStart(2, '0');
40
- return `${y}-${m}-${d}`;
30
+ import { resolveAnnotatedAttachmentDetail } from '../../utils/annotatedAttachments';
31
+ import { AppShellHeader } from './AppShellHeader';
32
+ import { getTaskforceCloudEnvironmentConfig, parseTaskforceCloudEnvironment, resolveTaskforceCloudEnvironmentFromBaseUrl } from '../../config/cloudEnvironment';
33
+ import { FilterToolbar } from './panels/FilterToolbar';
34
+ import { DocumentFilterToolbar } from './panels/DocumentFilterToolbar';
35
+ import { PlanningDrawer, getPlanningDrawerWidthPx, } from './panels/PlanningDrawer';
36
+ import { getWorkspaceChromeDefinition, getWorkspaceModuleDefinitions, resolveWorkspaceModule, } from './chrome/workspaceChrome';
37
+ import { EmptyWorkspaceFilterBar, WorkspaceHeaderActions, WorkspaceHeaderDivider, WorkspaceHeaderSearch, WorkspaceHeaderSelect, WorkspacePrimaryAction, } from './chrome/WorkspaceChromeSections';
38
+ import { ScheduleSidebar, SCHEDULE_SIDEBAR_WIDTH_PX } from './panels/ScheduleSidebar';
39
+ import { DOCUMENT_ATTACHMENT_FILTER_OPTIONS, DOCUMENT_TYPE_FILTER_OPTIONS, } from '../features/documentWorkspaceFilters';
40
+ import { canManageTeamWorkspace as computeCanManageTeamWorkspace, canOpenTeamManagement as computeCanOpenTeamManagement, resolveTeamPlanModeFromEntitlements } from './teamManagementAccess';
41
+ import { toDateOnlyLocal, parseDateOnlyLocal, startOfWeek, getDayOffsetFromWeekStart, addDays, dateOnlyToIsoWeekKey, SCHEDULE_DAY_KEYS, SCHEDULE_DAY_LABELS, } from './panels/scheduleUtils';
42
+ 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';
43
+ // Heavy feature modules — lazy-loaded to reduce the initial app chunk size.
44
+ const TaskSettings = React.lazy(() => import('../features/TaskSettings').then(m => ({ default: m.TaskSettings })));
45
+ const AnnotatedAttachmentWorkspaceShell = React.lazy(() => import('../features/AnnotatedAttachmentWorkspace').then(m => ({ default: m.AnnotatedAttachmentWorkspaceShell })));
46
+ const DocumentWorkspaceShell = React.lazy(() => import('../features/DocumentWorkspace').then(m => ({ default: m.DocumentWorkspaceShell })));
47
+ const WorkflowsModule = React.lazy(() => import('../features/WorkflowsModule').then(m => ({ default: m.WorkflowsModule })));
48
+ const AgentsModule = React.lazy(() => import('../features/AgentsModule').then(m => ({ default: m.AgentsModule })));
49
+ const InitiativesModule = React.lazy(() => import('../features/InitiativesModule').then(m => ({ default: m.InitiativesModule })));
50
+ const PlansPage = React.lazy(() => import('./PlansPage').then(m => ({ default: m.PlansPage })));
51
+ function getSyncEventStableKey(event, index = 0) {
52
+ const explicitId = String(event.id ?? '').trim();
53
+ if (explicitId)
54
+ return `id:${explicitId}`;
55
+ return [
56
+ String(event.occurredAt || '').trim() || 'unknown-time',
57
+ String(event.eventType || '').trim() || 'sync',
58
+ String(event.status || '').trim() || 'unknown-status',
59
+ String(event.statusCode ?? ''),
60
+ String(event.changeCount ?? ''),
61
+ String(event.requestMs ?? ''),
62
+ String(event.errorMessage || '').trim(),
63
+ JSON.stringify(event.details || null),
64
+ String(index)
65
+ ].join('|');
41
66
  }
42
- function parseDateOnlyLocal(dateOnly) {
43
- const match = dateOnly.match(/^(\d{4})-(\d{2})-(\d{2})$/);
44
- if (!match)
45
- return null;
46
- const parsed = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
47
- if (Number.isNaN(parsed.getTime()))
48
- return null;
49
- return parsed;
67
+ function mergeRecentSyncEvents(previous, incoming, limit = 15) {
68
+ const merged = new Map();
69
+ for (const [index, event] of previous.entries()) {
70
+ merged.set(getSyncEventStableKey(event, index), event);
71
+ }
72
+ for (const [index, event] of incoming.entries()) {
73
+ merged.set(getSyncEventStableKey(event, index), event);
74
+ }
75
+ return Array.from(merged.values())
76
+ .sort((left, right) => String(right.occurredAt || '').localeCompare(String(left.occurredAt || '')))
77
+ .slice(0, Math.max(1, limit));
50
78
  }
51
- function startOfWeek(date, weekStartsOn) {
52
- const day = date.getDay();
53
- const delta = weekStartsOn === 'sunday'
54
- ? -day
55
- : (day === 0 ? -6 : 1 - day);
56
- const result = new Date(date);
57
- result.setHours(0, 0, 0, 0);
58
- result.setDate(result.getDate() + delta);
59
- return result;
79
+ function areSyncEventListsEqual(left, right) {
80
+ if (left.length !== right.length)
81
+ return false;
82
+ for (let index = 0; index < left.length; index += 1) {
83
+ if (getSyncEventStableKey(left[index], index) !== getSyncEventStableKey(right[index], index)) {
84
+ return false;
85
+ }
86
+ }
87
+ return true;
60
88
  }
61
- function getDayOffsetFromWeekStart(dayKey, weekStartsOn) {
62
- if (weekStartsOn === 'sunday') {
63
- const sundayFirst = {
64
- sun: 0,
65
- mon: 1,
66
- tue: 2,
67
- wed: 3,
68
- thu: 4,
69
- fri: 5,
70
- sat: 6
71
- };
72
- return sundayFirst[dayKey];
89
+ function getActiveReferenceMismatchCount(events) {
90
+ let count = 0;
91
+ for (const event of events) {
92
+ const message = String(event.errorMessage || '').toLowerCase();
93
+ if (message.includes('reference number mismatch')) {
94
+ count += 1;
95
+ continue;
96
+ }
97
+ if (String(event.status || '').toLowerCase() === 'success') {
98
+ break;
99
+ }
73
100
  }
74
- const mondayFirst = {
75
- mon: 0,
76
- tue: 1,
77
- wed: 2,
78
- thu: 3,
79
- fri: 4,
80
- sat: 5,
81
- sun: 6
82
- };
83
- return mondayFirst[dayKey];
101
+ return count;
84
102
  }
85
- function addDays(base, days) {
86
- const next = new Date(base);
87
- next.setDate(base.getDate() + days);
88
- return next;
103
+ function getActiveReferenceMismatchEvents(events) {
104
+ const mismatches = [];
105
+ for (const event of events) {
106
+ const message = String(event.errorMessage || '').toLowerCase();
107
+ if (message.includes('reference number mismatch')) {
108
+ mismatches.push(event);
109
+ continue;
110
+ }
111
+ if (String(event.status || '').toLowerCase() === 'success') {
112
+ break;
113
+ }
114
+ }
115
+ return mismatches;
89
116
  }
90
- function dateOnlyToIsoWeekKey(dateOnly) {
91
- const match = dateOnly.match(/^(\d{4})-(\d{2})-(\d{2})$/);
92
- if (!match)
93
- return null;
94
- const dt = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
95
- const day = dt.getUTCDay() || 7;
96
- dt.setUTCDate(dt.getUTCDate() + 4 - day);
97
- const yearStart = new Date(Date.UTC(dt.getUTCFullYear(), 0, 1));
98
- const week = Math.ceil((((dt.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
99
- return `${dt.getUTCFullYear()}-W${String(week).padStart(2, '0')}`;
117
+ function resolveConnectedEnvironmentLabel(explicitEnvironment, candidates) {
118
+ const parsedExplicitEnvironment = parseTaskforceCloudEnvironment(explicitEnvironment);
119
+ if (parsedExplicitEnvironment) {
120
+ return getTaskforceCloudEnvironmentConfig(parsedExplicitEnvironment).label.toUpperCase();
121
+ }
122
+ for (const candidate of candidates) {
123
+ const source = String(candidate || '').trim();
124
+ if (!source)
125
+ continue;
126
+ const parsedEnvironment = resolveTaskforceCloudEnvironmentFromBaseUrl(source);
127
+ if (parsedEnvironment) {
128
+ return getTaskforceCloudEnvironmentConfig(parsedEnvironment).label.toUpperCase();
129
+ }
130
+ let hostname = source.toLowerCase();
131
+ try {
132
+ hostname = new URL(source.includes('://') ? source : `https://${source}`).hostname.toLowerCase();
133
+ }
134
+ catch {
135
+ hostname = source.toLowerCase();
136
+ }
137
+ if (hostname.includes('localhost') || hostname.includes('127.0.0.1') || hostname.includes('::1')) {
138
+ continue;
139
+ }
140
+ return hostname.toUpperCase();
141
+ }
142
+ return 'UNKNOWN';
143
+ }
144
+ function isDebugModeEnabled() {
145
+ const runtimeOverride = globalThis.__DEBUG_MODE__;
146
+ if (typeof runtimeOverride === 'boolean')
147
+ return runtimeOverride;
148
+ return typeof __DEBUG_MODE__ !== 'undefined' && __DEBUG_MODE__;
100
149
  }
101
150
  export function StandaloneLayout(props) {
151
+ const WORKSPACE_TOOL_RAIL_WIDTH = 56;
152
+ const WORKSPACE_CONTENT_GUTTER = 6;
102
153
  const navigate = useNavigate();
103
154
  const location = useLocation();
155
+ const routeQuery = useMemo(() => new URLSearchParams(location.search), [location.search]);
156
+ const isPlansScreen = String(routeQuery.get('screen') || '').trim().toLowerCase() === 'plans';
157
+ const openPlansScreen = useCallback((extraParams) => {
158
+ const params = new URLSearchParams(location.search);
159
+ params.set('screen', 'plans');
160
+ for (const [key, value] of Object.entries(extraParams || {})) {
161
+ if (value === null || value === undefined || String(value).trim() === '') {
162
+ params.delete(key);
163
+ }
164
+ else {
165
+ params.set(key, String(value));
166
+ }
167
+ }
168
+ navigate(`/?${params.toString()}${location.hash || ''}`);
169
+ }, [location.hash, location.search, navigate]);
170
+ const closePlansScreen = useCallback(() => {
171
+ const params = new URLSearchParams(location.search);
172
+ params.delete('screen');
173
+ params.delete('gate');
174
+ params.delete('checkout');
175
+ params.delete('planId');
176
+ params.delete('planVersionId');
177
+ params.delete('interval');
178
+ const nextQuery = params.toString();
179
+ navigate(`${location.pathname === '/' ? '/' : location.pathname}${nextQuery ? `?${nextQuery}` : ''}${location.hash || ''}`);
180
+ }, [location.hash, location.pathname, location.search, navigate]);
181
+ const handlePlansBack = useCallback(() => {
182
+ closePlansScreen();
183
+ }, [closePlansScreen]);
104
184
  const {
105
185
  // State
106
- activeTab, setActiveTab, activeCategories, groupBy, setGroupBy, emptyColumnMode, setEmptyColumnMode, zenMode, setZenMode, filterStatus, setFilterStatus, parentTaskFilterMode, setParentTaskFilterMode,
186
+ activeTab, setActiveTab, activeCategories, groupBy, setGroupBy, activeWorkspaceModule, setActiveWorkspaceModule, emptyColumnMode, setEmptyColumnMode, zenMode, setZenMode, filterStatus, setFilterStatus,
107
187
  // Data & Actions
108
188
  tasks, handleEdit, handleSubmit, resetForm, handleUpdateTask,
109
189
  // Form State (destructure all needed for TaskForm)
110
- editingTaskId, loading, error, title, setTitle, description, setDescription, category, setCategory, type, setType, priority, setPriority, complexity, setComplexity, approach, setApproach, assignee, setAssignee, scheduledDate, setScheduledDate, dueDate, setDueDate, parentTaskIdInput, setParentTaskIdInput, addChildTaskIdInput, setAddChildTaskIdInput, manualComplexityEnabled, checklistDropdownEnabled, formTaxonomies, setFormTaxonomies, selectedSpecialists, setSelectedSpecialists, comments, newCommentText, setNewCommentText, screenshots, setScreenshots, descriptionFocused, setDescriptionFocused, showMarkdownHelp, setShowMarkdownHelp, showSpecialists, setShowSpecialists, showChecklist, setShowChecklist, showTaskRelationships, setShowTaskRelationships, showChildTasks, setShowChildTasks, showComments, setShowComments,
190
+ editingTaskId, loading, error, title, setTitle, description, setDescription, checklistItems, setChecklistItems, category, setCategory, type, setType, priority, setPriority, complexity, setComplexity, setStatus, approach, setApproach, assignee, setAssignee, scheduledDate, setScheduledDate, dueDate, setDueDate, workstreamInput, setWorkstreamInput, manualComplexityEnabled, checklistDropdownEnabled, showTaskCardStatusLabel, formTaxonomies, setFormTaxonomies, comments, newCommentText, setNewCommentText, attachments, setAttachments, setAttachmentsDirty, descriptionFocused, setDescriptionFocused, showMarkdownHelp, setShowMarkdownHelp, showChecklist, setShowChecklist, showComments, setShowComments,
111
191
  // Form Actions
112
- handleAddComment, handleSetParentForCurrentTask, handleAddChildToCurrentTask, handleCreateChildForCurrentTask, handleUnlinkChildTask, handleReorderChildTasks, handleClearParentTask, handleOpenTaskById,
192
+ handleAddComment, handleSetWorkstreamForCurrentTask, handleOpenTaskById,
113
193
  // Other Props for Form
114
- taxonomies, activeTypes, priorities, approaches, availableSpecialists, copiedId, handleCopyId, handleToggleInProgress, handleToggleComplete, handleToggleReview, handleToggleCancel, handleArchiveTask, handleDelete, handleUnarchive, fetchTasks, searchQuery, setSearchQuery, filterCategories, setFilterCategories, filterTypes, setFilterTypes, filterPriorities, setFilterPriorities, filterAssignees, setFilterAssignees,
194
+ 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,
115
195
  // filterStatus, setFilterStatus, // This was duplicated, removed one instance
116
196
  sortBy, setSortBy, clearFilters, // setSearchQuery, // This was duplicated, removed one instance
117
197
  // filterCategories, setFilterCategories, // This was duplicated, removed one instance
@@ -122,21 +202,19 @@ export function StandaloneLayout(props) {
122
202
  // clearFilters, // This was duplicated, removed one instance
123
203
  // activeTypes, // This was duplicated, removed one instance
124
204
  // Settings State & Props
125
- settingsModel, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, assigneeFilterEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authUserEmail, authSessionResolved, realtimeSyncEnabled, realtimeSyncFlagSource, workspaceCloudSyncEnabled, realtimeConnectionState, realtimeTelemetry, workspaceSyncSourceOfTruth, workspaceSyncOnboardingCompleted, workspaceSyncReady, workspaceCloudHydrated, saveWorkspaceCloudSyncSettings, userGlobalSyncStatus, userGlobalLastSyncedAt, workspaceLastPullAt, workspaceLastPushAt, workspaceLastErrorAt, workspaceRetryAt, workspaceBootstrapSyncInProgress, workspaceBootstrapSyncPages, workspaceBootstrapSyncAppliedChanges, userGlobalSyncPendingChanges, userGlobalSyncError, workspaceLastErrorMessage, workspaceLastWarningMessage, workspaceSyncFailureCount, workspaceSyncRetryScheduledCount, workspaceSyncAuthFailureCount, workspaceSyncLastFailureSource, workspaceSyncLastFailureStatusCode, retryWorkspaceCloudSync, resetWorkspaceSyncCursorAndPull, currentWorkspaceId, availableWorkspaces, createWorkspace, switchWorkspace, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
205
+ settingsModel, configLoaded, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, workspaceSwitchingEnabled, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, authWorkspaceId, authUserEmail, authUserDisplayName, 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, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
126
206
  // Browser Props
127
- showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget, contextLinkBrowsePath, handleSelectPath, handleAddPath, handleRemovePath,
207
+ showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget, handleSelectPath, handleAddPath, handleRemovePath,
128
208
  // Settings Managers
129
- handleUpdateCategory, handleRemoveCategory, handleSaveCategory, handleUpdateCategoryIcon, handleUpdateCategoryColor, handleSaveType, handleRemoveType, handleUpdateTaxonomies, handleUpdatePriorities, handleUpdateApproaches, pathValidation,
209
+ handleUpdateCategory, handleRemoveCategory, handleSaveCategory, handleUpdateCategoryIcon, handleUpdateCategoryColor, handleSaveType, handleRemoveType, handleUpdateTaxonomies, handleUpdatePriorities, pathValidation,
130
210
  // Exports
131
- exportEnvironment, setExportEnvironment, exportWorkflowsPath, setExportWorkflowsPath, exportSpecialistsPath, setExportSpecialistsPath, exportResult, handleExportResources, exportingResource, availableWorkflows, onExportWorkflows, fetchWorkflows, availableEnvironments,
211
+ exportEnvironment, setExportEnvironment, exportWorkflowsPath, setExportWorkflowsPath, exportResult, exportingResource, availableWorkflows, onExportWorkflows, fetchWorkflows, availableEnvironments, initiativeTemplates, fetchInitiativeTemplates, createInitiativeFromTemplate,
132
212
  // Misc
133
- successBanner, returnToParentTaskId, returnFromChildTaskId, clearReturnToParentTask, tasksScrollRef, setTasksScrollPos,
213
+ uiNotice, clearNotice, taskReturnTrail, clearReturnToParentTask, returnToPreviousTask, tasksScrollRef, setTasksScrollPos,
134
214
  // Refs
135
215
  commentsEndRef,
136
216
  // Modals - Unsaved Changes
137
- unsavedModalOpen, setUnsavedModalOpen, pendingNavigation, handleNavigation, currentTask, currentTaskParent, currentTaskChildren,
138
- // Modals - Cancellation
139
- cancellationPromptTask, cancellationOpenDescendantIds, cancellationMode, setCancellationMode, confirmCancellation, cancelCancellationPrompt, scheduleWarningPrompt, confirmScheduleWarning, cancelScheduleWarning } = props;
217
+ autoSaveState, unsavedModalOpen, setUnsavedModalOpen, pendingNavigation, handleNavigation, currentTask, currentTaskWorkstream, currentTaskInitiative, scheduleWarningPrompt, confirmScheduleWarning, cancelScheduleWarning } = props;
140
218
  const resolvedLocale = locale || getLocale();
141
219
  useEffect(() => {
142
220
  const normalizedProject = String(projectName || '').trim();
@@ -171,11 +249,6 @@ export function StandaloneLayout(props) {
171
249
  const [scheduleShowBacklog, setScheduleShowBacklog] = useState(true);
172
250
  const [scheduleOnlyExpired, setScheduleOnlyExpired] = useState(false);
173
251
  const [scheduleSidebarOpen, setScheduleSidebarOpen] = useState(true);
174
- const [hierarchySidebarOpen, setHierarchySidebarOpen] = useState(true);
175
- const [hierarchyRootTaskId, setHierarchyRootTaskId] = useState('');
176
- const [hierarchyShowUnlinkedColumn, setHierarchyShowUnlinkedColumn] = useState(true);
177
- const [hierarchyChildOrderMode, setHierarchyChildOrderMode] = useState('manual');
178
- const [hierarchyRootSearch, setHierarchyRootSearch] = useState('');
179
252
  const [scheduleSelectedDate, setScheduleSelectedDate] = useState(toDateOnlyLocal(new Date()));
180
253
  const [scheduleCalendarMonth, setScheduleCalendarMonth] = useState(() => {
181
254
  const now = new Date();
@@ -183,10 +256,35 @@ export function StandaloneLayout(props) {
183
256
  });
184
257
  const [scheduleScrollLeft, setScheduleScrollLeft] = useState(0);
185
258
  const [showFilters, setShowFilters] = useState(false);
259
+ const [taskModalFullscreen, setTaskModalFullscreen] = useState(false);
260
+ const [planningDrawerOpen, setPlanningDrawerOpen] = useState(false);
261
+ const [planningTreeCollapsed, setPlanningTreeCollapsed] = useState(false);
262
+ const [planningPrimaryCollapsed, setPlanningPrimaryCollapsed] = useState(false);
263
+ const [planningSecondaryCollapsed, setPlanningSecondaryCollapsed] = useState(false);
264
+ const [selectedPlanningInitiativeId, setSelectedPlanningInitiativeId] = useState('');
265
+ const [selectedPlanningWorkstreamId, setSelectedPlanningWorkstreamId] = useState('');
266
+ const [expandedPlanningInitiativeIds, setExpandedPlanningInitiativeIds] = useState(new Set());
267
+ const [planningDrawerDetail, setPlanningDrawerDetail] = useState(null);
268
+ const [planningNestedWorkstreamDetailId, setPlanningNestedWorkstreamDetailId] = useState(null);
269
+ const [planningEditor, setPlanningEditor] = useState(null);
270
+ const [planningSecondaryEditor, setPlanningSecondaryEditor] = useState(null);
271
+ const [planningDraftTitle, setPlanningDraftTitle] = useState('');
272
+ const [planningDraftDescription, setPlanningDraftDescription] = useState('');
273
+ const [planningDraftOwner, setPlanningDraftOwner] = useState('');
274
+ const [planningDraftInitiativeId, setPlanningDraftInitiativeId] = useState('');
275
+ const [documentTypeFilters, setDocumentTypeFilters] = useState(() => DOCUMENT_TYPE_FILTER_OPTIONS.map((option) => option.value));
276
+ const [documentAttachmentFilters, setDocumentAttachmentFilters] = useState(() => DOCUMENT_ATTACHMENT_FILTER_OPTIONS.map((option) => option.value));
186
277
  const [requestedDocPath, setRequestedDocPath] = useState(null);
278
+ const [requestedDocAssetId, setRequestedDocAssetId] = useState(null);
279
+ const [requestedAnnotatedTarget, setRequestedAnnotatedTarget] = useState(null);
280
+ const [requestedAnnotatedSessionId, setRequestedAnnotatedSessionId] = useState(null);
281
+ const [requestedAnnotatedOpenVersion, setRequestedAnnotatedOpenVersion] = useState(0);
282
+ const [canonicalAnnotatedImageReferenceLabels, setCanonicalAnnotatedImageReferenceLabels] = useState({});
187
283
  const [layoutStateReady, setLayoutStateReady] = useState(false);
188
- const lastNonDocsGroupByRef = useRef('category');
189
284
  const loadedLayoutStateWorkspaceRef = useRef('');
285
+ const requestedAnnotatedTargetRef = useRef(null);
286
+ const requestedAnnotatedSessionIdRef = useRef(null);
287
+ const annotatedImageReferenceFetchesRef = useRef(new Set());
190
288
  const workspaceUiStateHeaders = useMemo(() => {
191
289
  const headers = {};
192
290
  const workspaceIdHeader = String(currentWorkspaceId || '').trim();
@@ -196,23 +294,93 @@ export function StandaloneLayout(props) {
196
294
  return headers;
197
295
  }, [runtimeMode, currentWorkspaceId]);
198
296
  const layoutStateWorkspaceKey = useMemo(() => `${runtimeMode}:${String(currentWorkspaceId || 'default').trim() || 'default'}`, [runtimeMode, currentWorkspaceId]);
297
+ const annotatedLayoutCacheKey = useMemo(() => `taskforce:annotate-layout:${layoutStateWorkspaceKey}`, [layoutStateWorkspaceKey]);
298
+ const annotatedWorkspaceReady = useMemo(() => (runtimeMode === 'cloud' ? authSessionResolved : configLoaded), [authSessionResolved, configLoaded, runtimeMode]);
299
+ useEffect(() => {
300
+ requestedAnnotatedTargetRef.current = requestedAnnotatedTarget;
301
+ }, [requestedAnnotatedTarget]);
302
+ useEffect(() => {
303
+ requestedAnnotatedSessionIdRef.current = requestedAnnotatedSessionId;
304
+ }, [requestedAnnotatedSessionId]);
305
+ const workspaceFeatureAccess = useMemo(() => ({
306
+ [DOCUMENT_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
307
+ featureKey: DOCUMENT_WORKSPACE_FEATURE_KEY,
308
+ runtimeMode,
309
+ }),
310
+ [ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
311
+ featureKey: ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY,
312
+ runtimeMode,
313
+ }),
314
+ [WORKFLOWS_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
315
+ featureKey: WORKFLOWS_WORKSPACE_FEATURE_KEY,
316
+ runtimeMode,
317
+ }),
318
+ [AGENTS_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
319
+ featureKey: AGENTS_WORKSPACE_FEATURE_KEY,
320
+ runtimeMode,
321
+ }),
322
+ [INITIATIVES_WORKSPACE_FEATURE_KEY]: resolveFeatureAccess({
323
+ featureKey: INITIATIVES_WORKSPACE_FEATURE_KEY,
324
+ runtimeMode,
325
+ }),
326
+ }), [runtimeMode]);
327
+ const workspaceModules = useMemo(() => getWorkspaceModuleDefinitions({ featureAccess: workspaceFeatureAccess }), [workspaceFeatureAccess]);
328
+ const resolvedWorkspaceModule = useMemo(() => resolveWorkspaceModule(activeWorkspaceModule, workspaceModules), [activeWorkspaceModule, workspaceModules]);
329
+ const documentsWorkspaceEnabled = workspaceFeatureAccess[DOCUMENT_WORKSPACE_FEATURE_KEY]?.allowed ?? false;
330
+ const annotatedAttachmentsWorkspaceEnabled = workspaceFeatureAccess[ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY]?.allowed ?? false;
331
+ const documentsWorkspaceUnavailableMessage = 'Documents is not available in this build yet.';
332
+ const annotatedAttachmentsWorkspaceUnavailableMessage = 'Annotate is not available in this build yet.';
199
333
  useEffect(() => {
200
334
  const handleOpenMarkdownDocument = (event) => {
201
335
  const customEvent = event;
202
336
  const fsPath = customEvent.detail?.fsPath;
203
- if (!fsPath)
337
+ const assetId = String(customEvent.detail?.assetId || '').trim() || null;
338
+ if (!fsPath && !assetId)
204
339
  return;
205
340
  customEvent.preventDefault();
206
- setRequestedDocPath(fsPath);
207
- if (groupBy !== 'docs') {
208
- setGroupBy('docs');
341
+ if (!documentsWorkspaceEnabled) {
342
+ pushNotice(documentsWorkspaceUnavailableMessage, 'error');
343
+ return;
209
344
  }
345
+ setRequestedDocPath(fsPath || null);
346
+ setRequestedDocAssetId(assetId);
347
+ setActiveWorkspaceModule('docs');
210
348
  };
211
349
  window.addEventListener('taskforce:open-markdown-document', handleOpenMarkdownDocument);
212
350
  return () => {
213
351
  window.removeEventListener('taskforce:open-markdown-document', handleOpenMarkdownDocument);
214
352
  };
215
- }, [groupBy, setGroupBy]);
353
+ }, [documentsWorkspaceEnabled, documentsWorkspaceUnavailableMessage, pushNotice, setActiveWorkspaceModule]);
354
+ useEffect(() => {
355
+ if (resolvedWorkspaceModule !== activeWorkspaceModule) {
356
+ setActiveWorkspaceModule(resolvedWorkspaceModule);
357
+ }
358
+ }, [activeWorkspaceModule, resolvedWorkspaceModule, setActiveWorkspaceModule]);
359
+ useEffect(() => {
360
+ if (resolvedWorkspaceModule === 'docs' && documentsWorkspaceEnabled)
361
+ return;
362
+ setRequestedDocPath(null);
363
+ setRequestedDocAssetId(null);
364
+ }, [documentsWorkspaceEnabled, resolvedWorkspaceModule]);
365
+ useEffect(() => {
366
+ if (typeof window === 'undefined')
367
+ return;
368
+ try {
369
+ const cachedRaw = window.sessionStorage.getItem(annotatedLayoutCacheKey);
370
+ if (!cachedRaw)
371
+ return;
372
+ const cached = JSON.parse(cachedRaw);
373
+ if (cached?.annotatedTarget && typeof cached.annotatedTarget === 'object') {
374
+ setRequestedAnnotatedTarget(cached.annotatedTarget);
375
+ }
376
+ if (typeof cached?.annotatedSessionId === 'string') {
377
+ setRequestedAnnotatedSessionId(cached.annotatedSessionId.trim() || null);
378
+ }
379
+ }
380
+ catch {
381
+ // ignore cache read failure
382
+ }
383
+ }, [annotatedLayoutCacheKey]);
216
384
  useEffect(() => {
217
385
  if (loadedLayoutStateWorkspaceRef.current === layoutStateWorkspaceKey)
218
386
  return;
@@ -242,15 +410,6 @@ export function StandaloneLayout(props) {
242
410
  setScheduleOnlyExpired(state.scheduleOnlyExpired);
243
411
  if (typeof state.scheduleSidebarOpen === 'boolean')
244
412
  setScheduleSidebarOpen(state.scheduleSidebarOpen);
245
- if (typeof state.hierarchySidebarOpen === 'boolean')
246
- setHierarchySidebarOpen(state.hierarchySidebarOpen);
247
- if (typeof state.hierarchyRootTaskId === 'string')
248
- setHierarchyRootTaskId(state.hierarchyRootTaskId);
249
- if (typeof state.hierarchyShowUnlinkedColumn === 'boolean')
250
- setHierarchyShowUnlinkedColumn(state.hierarchyShowUnlinkedColumn);
251
- if (state.hierarchyChildOrderMode === 'manual' || state.hierarchyChildOrderMode === 'auto') {
252
- setHierarchyChildOrderMode(state.hierarchyChildOrderMode);
253
- }
254
413
  if (typeof state.scheduleSelectedDate === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(state.scheduleSelectedDate)) {
255
414
  setScheduleSelectedDate(state.scheduleSelectedDate);
256
415
  }
@@ -262,6 +421,47 @@ export function StandaloneLayout(props) {
262
421
  }
263
422
  if (typeof state.showFilters === 'boolean')
264
423
  setShowFilters(state.showFilters);
424
+ if (Array.isArray(state.documentTypeFilters)) {
425
+ setDocumentTypeFilters(state.documentTypeFilters);
426
+ }
427
+ if (Array.isArray(state.documentAttachmentFilters)) {
428
+ setDocumentAttachmentFilters(state.documentAttachmentFilters);
429
+ }
430
+ if (typeof state.planningDrawerOpen === 'boolean') {
431
+ setPlanningDrawerOpen(state.planningDrawerOpen);
432
+ }
433
+ if (Array.isArray(state.planningExpandedInitiativeIds)) {
434
+ setExpandedPlanningInitiativeIds(new Set(state.planningExpandedInitiativeIds
435
+ .map((value) => String(value || '').trim())
436
+ .filter(Boolean)));
437
+ }
438
+ if (state.planningDrawerDetail
439
+ && typeof state.planningDrawerDetail === 'object'
440
+ && (state.planningDrawerDetail.type === 'initiative' || state.planningDrawerDetail.type === 'workstream')
441
+ && typeof state.planningDrawerDetail.id === 'string') {
442
+ const detailId = state.planningDrawerDetail.id.trim();
443
+ setPlanningDrawerDetail(detailId ? { type: state.planningDrawerDetail.type, id: detailId } : null);
444
+ }
445
+ else if (state.planningDrawerDetail === null) {
446
+ setPlanningDrawerDetail(null);
447
+ }
448
+ if (typeof state.planningNestedWorkstreamDetailId === 'string') {
449
+ setPlanningNestedWorkstreamDetailId(state.planningNestedWorkstreamDetailId.trim() || null);
450
+ }
451
+ else if (state.planningNestedWorkstreamDetailId === null) {
452
+ setPlanningNestedWorkstreamDetailId(null);
453
+ }
454
+ const annotatedTarget = state.annotatedTarget;
455
+ if (annotatedTarget && typeof annotatedTarget === 'object') {
456
+ if (!requestedAnnotatedTargetRef.current) {
457
+ setRequestedAnnotatedTarget(annotatedTarget);
458
+ }
459
+ }
460
+ if (typeof state.annotatedSessionId === 'string') {
461
+ if (!requestedAnnotatedSessionIdRef.current) {
462
+ setRequestedAnnotatedSessionId(state.annotatedSessionId.trim() || null);
463
+ }
464
+ }
265
465
  }
266
466
  catch {
267
467
  // ignore read failure
@@ -292,14 +492,18 @@ export function StandaloneLayout(props) {
292
492
  scheduleShowBacklog,
293
493
  scheduleOnlyExpired,
294
494
  scheduleSidebarOpen,
295
- hierarchySidebarOpen,
296
- hierarchyRootTaskId,
297
- hierarchyShowUnlinkedColumn,
298
- hierarchyChildOrderMode,
299
495
  scheduleSelectedDate,
300
496
  scheduleCalendarMonth,
301
497
  scheduleScrollLeft,
302
- showFilters
498
+ showFilters,
499
+ documentTypeFilters,
500
+ documentAttachmentFilters,
501
+ planningDrawerOpen,
502
+ planningExpandedInitiativeIds: Array.from(expandedPlanningInitiativeIds),
503
+ planningDrawerDetail,
504
+ planningNestedWorkstreamDetailId,
505
+ annotatedTarget: requestedAnnotatedTarget,
506
+ annotatedSessionId: requestedAnnotatedSessionId
303
507
  }
304
508
  })
305
509
  });
@@ -315,137 +519,490 @@ export function StandaloneLayout(props) {
315
519
  scheduleShowBacklog,
316
520
  scheduleOnlyExpired,
317
521
  scheduleSidebarOpen,
318
- hierarchySidebarOpen,
319
- hierarchyRootTaskId,
320
- hierarchyShowUnlinkedColumn,
321
- hierarchyChildOrderMode,
322
522
  scheduleSelectedDate,
323
523
  scheduleCalendarMonth,
324
524
  scheduleScrollLeft,
325
525
  showFilters,
526
+ documentTypeFilters,
527
+ documentAttachmentFilters,
528
+ planningDrawerOpen,
529
+ expandedPlanningInitiativeIds,
530
+ planningDrawerDetail,
531
+ planningNestedWorkstreamDetailId,
532
+ requestedAnnotatedTarget,
533
+ requestedAnnotatedSessionId,
326
534
  workspaceUiStateHeaders
327
535
  ]);
328
536
  useEffect(() => {
329
- if (groupBy !== 'docs') {
330
- lastNonDocsGroupByRef.current = groupBy;
537
+ if (typeof window === 'undefined')
538
+ return;
539
+ try {
540
+ window.sessionStorage.setItem(annotatedLayoutCacheKey, JSON.stringify({
541
+ annotatedTarget: requestedAnnotatedTarget,
542
+ annotatedSessionId: requestedAnnotatedSessionId,
543
+ }));
331
544
  }
332
- }, [groupBy]);
545
+ catch {
546
+ // ignore cache write failure
547
+ }
548
+ }, [annotatedLayoutCacheKey, requestedAnnotatedSessionId, requestedAnnotatedTarget]);
333
549
  useEffect(() => {
334
550
  if (groupBy === 'schedule') {
335
551
  setScheduleSidebarOpen(true);
336
552
  }
337
- if (groupBy === 'hierarchy') {
338
- setHierarchySidebarOpen(true);
339
- }
340
553
  }, [groupBy]);
341
554
  const { showArchive, setShowArchive, fetchArchive, filteredArchive } = props;
555
+ const archivedScopeTasks = useMemo(() => filteredArchive.map((task) => ({ ...task, isArchived: true })), [filteredArchive]);
556
+ const archivedScopeAllTasks = useMemo(() => props.archivedTasks.map((task) => ({ ...task, isArchived: true })), [props.archivedTasks]);
557
+ const annotatedAttachmentTasks = useMemo(() => [...props.searchAgnosticTasks, ...archivedScopeAllTasks], [archivedScopeAllTasks, props.searchAgnosticTasks]);
558
+ const resolveAnnotatedTaskReferenceLabel = useCallback((taskId) => {
559
+ const normalizedTaskId = String(taskId || '').trim();
560
+ if (!normalizedTaskId)
561
+ return '';
562
+ const matchedTask = annotatedAttachmentTasks.find((task) => task.id === normalizedTaskId);
563
+ return getTaskReferenceLabel(matchedTask) || '';
564
+ }, [annotatedAttachmentTasks]);
565
+ const resolveAnnotatedImageReferenceLabel = useCallback((assetId) => {
566
+ const normalizedAssetId = String(assetId || '').trim();
567
+ if (!normalizedAssetId)
568
+ return '';
569
+ const canonicalReferenceLabel = String(canonicalAnnotatedImageReferenceLabels[normalizedAssetId] || '').trim();
570
+ if (canonicalReferenceLabel)
571
+ return canonicalReferenceLabel;
572
+ for (const task of annotatedAttachmentTasks) {
573
+ for (const attachment of task.attachments || []) {
574
+ if (!attachment || typeof attachment !== 'object')
575
+ continue;
576
+ if (String(attachment.assetId || '').trim() !== normalizedAssetId)
577
+ continue;
578
+ const referenceLabel = getImageReferenceLabel({
579
+ referenceNumber: typeof attachment.referenceNumber === 'number' ? attachment.referenceNumber : null,
580
+ referenceLabel: String(attachment.referenceLabel || '').trim() || null,
581
+ });
582
+ if (referenceLabel)
583
+ return referenceLabel;
584
+ }
585
+ }
586
+ return '';
587
+ }, [annotatedAttachmentTasks, canonicalAnnotatedImageReferenceLabels]);
588
+ useEffect(() => {
589
+ const handleOpenAnnotatedAttachment = (event) => {
590
+ const customEvent = event;
591
+ const detail = resolveAnnotatedAttachmentDetail(customEvent.detail, annotatedAttachmentTasks);
592
+ if (!detail)
593
+ return;
594
+ customEvent.preventDefault();
595
+ if (!annotatedAttachmentsWorkspaceEnabled) {
596
+ pushNotice(annotatedAttachmentsWorkspaceUnavailableMessage, 'error');
597
+ return;
598
+ }
599
+ setRequestedAnnotatedTarget(detail);
600
+ setRequestedAnnotatedSessionId(null);
601
+ setRequestedAnnotatedOpenVersion((current) => current + 1);
602
+ setActiveWorkspaceModule('annotate');
603
+ };
604
+ window.addEventListener('taskforce:open-annotated-attachment', handleOpenAnnotatedAttachment);
605
+ return () => {
606
+ window.removeEventListener('taskforce:open-annotated-attachment', handleOpenAnnotatedAttachment);
607
+ };
608
+ }, [
609
+ annotatedAttachmentTasks,
610
+ annotatedAttachmentsWorkspaceEnabled,
611
+ annotatedAttachmentsWorkspaceUnavailableMessage,
612
+ pushNotice,
613
+ setActiveWorkspaceModule
614
+ ]);
615
+ useEffect(() => {
616
+ if (!requestedAnnotatedTarget)
617
+ return;
618
+ const resolvedTarget = resolveAnnotatedAttachmentDetail(requestedAnnotatedTarget, annotatedAttachmentTasks);
619
+ if (!resolvedTarget)
620
+ return;
621
+ if (resolvedTarget.taskReferenceLabel === requestedAnnotatedTarget.taskReferenceLabel
622
+ && resolvedTarget.imageReferenceLabel === requestedAnnotatedTarget.imageReferenceLabel)
623
+ return;
624
+ setRequestedAnnotatedTarget(resolvedTarget);
625
+ }, [annotatedAttachmentTasks, requestedAnnotatedTarget]);
626
+ useEffect(() => {
627
+ const activeTarget = requestedAnnotatedTargetRef.current;
628
+ const normalizedAssetId = String(activeTarget?.assetId || '').trim();
629
+ if (!normalizedAssetId)
630
+ return;
631
+ const canonicalReferenceLabel = String(canonicalAnnotatedImageReferenceLabels[normalizedAssetId] || '').trim();
632
+ if (!canonicalReferenceLabel)
633
+ return;
634
+ if (canonicalReferenceLabel === String(activeTarget?.imageReferenceLabel || '').trim())
635
+ return;
636
+ setRequestedAnnotatedTarget((current) => {
637
+ if (!current || String(current.assetId || '').trim() !== normalizedAssetId)
638
+ return current;
639
+ return {
640
+ ...current,
641
+ imageReferenceLabel: canonicalReferenceLabel,
642
+ };
643
+ });
644
+ }, [canonicalAnnotatedImageReferenceLabels, requestedAnnotatedTarget]);
645
+ useEffect(() => {
646
+ if (!annotatedWorkspaceReady)
647
+ return;
648
+ const activeTarget = requestedAnnotatedTarget;
649
+ const normalizedAssetId = String(activeTarget?.assetId || '').trim();
650
+ if (!normalizedAssetId)
651
+ return;
652
+ if (annotatedImageReferenceFetchesRef.current.has(normalizedAssetId))
653
+ return;
654
+ if (String(canonicalAnnotatedImageReferenceLabels[normalizedAssetId] || '').trim())
655
+ return;
656
+ let cancelled = false;
657
+ annotatedImageReferenceFetchesRef.current.add(normalizedAssetId);
658
+ const workspaceIdQuery = String(currentWorkspaceId || 'default').trim() || 'default';
659
+ const query = new URLSearchParams({ workspaceId: workspaceIdQuery });
660
+ const loadCanonicalImageReferenceLabel = async () => {
661
+ try {
662
+ const response = await fetch(`/api/taskforce/annotated-attachments/images/${encodeURIComponent(normalizedAssetId)}?${query.toString()}`, {
663
+ method: 'GET',
664
+ credentials: 'include',
665
+ headers: workspaceUiStateHeaders,
666
+ });
667
+ if (!response.ok)
668
+ return;
669
+ const data = await response.json().catch(() => ({}));
670
+ const canonicalReferenceLabel = String(data?.target?.imageReferenceLabel || '').trim();
671
+ if (!canonicalReferenceLabel || cancelled)
672
+ return;
673
+ setCanonicalAnnotatedImageReferenceLabels((current) => {
674
+ if (current[normalizedAssetId] === canonicalReferenceLabel)
675
+ return current;
676
+ return {
677
+ ...current,
678
+ [normalizedAssetId]: canonicalReferenceLabel,
679
+ };
680
+ });
681
+ }
682
+ catch {
683
+ // Ignore canonical image reference refresh failures and keep the current hint.
684
+ }
685
+ finally {
686
+ annotatedImageReferenceFetchesRef.current.delete(normalizedAssetId);
687
+ }
688
+ };
689
+ void loadCanonicalImageReferenceLabel();
690
+ return () => {
691
+ cancelled = true;
692
+ };
693
+ }, [annotatedWorkspaceReady, canonicalAnnotatedImageReferenceLabels, currentWorkspaceId, requestedAnnotatedTarget, workspaceUiStateHeaders]);
694
+ const deletedScopeAllTasks = useMemo(() => props.deletedTasks.map((entry) => ({
695
+ ...entry.taskSnapshot,
696
+ isDeleted: true,
697
+ deletedRecordId: entry.id
698
+ })), [props.deletedTasks]);
699
+ const deletedTaskRecordByTaskId = useMemo(() => {
700
+ const map = new Map();
701
+ props.deletedTasks.forEach((entry) => {
702
+ map.set(entry.taskSnapshot.id, entry);
703
+ });
704
+ return map;
705
+ }, [props.deletedTasks]);
706
+ const filteredDeletedTasks = useMemo(() => {
707
+ const deletedTasks = deletedScopeAllTasks;
708
+ const searchNeedle = searchQuery.trim().toLowerCase();
709
+ const allCategoriesSelected = activeCategories.every((taskCategory) => filterCategories.includes(taskCategory.value));
710
+ const allTypesSelected = activeTypes.every((taskType) => filterTypes.includes(taskType.value));
711
+ const selectedPriorities = new Set(filterPriorities.map((value) => String(value)));
712
+ const allPrioritiesSelected = priorities.every((priorityOption) => selectedPriorities.has(String(priorityOption.value)));
713
+ const selectedStatuses = new Set(filterStatus.map((value) => String(value)));
714
+ const allStatusesSelected = STATUS_OPTIONS.every((statusOption) => selectedStatuses.has(String(statusOption.value)));
715
+ return deletedTasks.filter((task) => {
716
+ const referenceLabel = (getTaskReferenceLabel(task) || task.id).toLowerCase();
717
+ const matchesSearch = !searchNeedle
718
+ || task.title.toLowerCase().includes(searchNeedle)
719
+ || String(task.description || '').toLowerCase().includes(searchNeedle)
720
+ || task.id.toLowerCase().includes(searchNeedle)
721
+ || referenceLabel.includes(searchNeedle);
722
+ const matchesCategory = allCategoriesSelected || filterCategories.includes(task.category);
723
+ const matchesType = allTypesSelected || filterTypes.includes(task.type || DEFAULT_TYPE_VALUE);
724
+ const matchesPriority = allPrioritiesSelected || selectedPriorities.has(String(task.priority));
725
+ const matchesStatus = allStatusesSelected || selectedStatuses.has(String(task.status));
726
+ const matchesAssignee = filterAssignees.length === 0 || filterAssignees.includes((task.assignee || 'unassigned'));
727
+ return matchesSearch && matchesCategory && matchesType && matchesPriority && matchesStatus && matchesAssignee;
728
+ });
729
+ }, [
730
+ activeCategories,
731
+ activeTypes,
732
+ deletedScopeAllTasks,
733
+ filterAssignees,
734
+ filterCategories,
735
+ filterPriorities,
736
+ filterStatus,
737
+ filterTypes,
738
+ priorities,
739
+ searchQuery
740
+ ]);
741
+ const taskFilterRecords = useMemo(() => [...props.tasks, ...archivedScopeAllTasks, ...deletedScopeAllTasks], [archivedScopeAllTasks, deletedScopeAllTasks, props.tasks]);
742
+ const categoryFilterOptions = useMemo(() => {
743
+ const visibleCategories = activeCategories
744
+ .filter((category) => !category.disabled)
745
+ .map((category) => ({ value: category.value, label: category.label }));
746
+ const legacyCategoriesInUse = activeCategories
747
+ .filter((category) => category.disabled && taskFilterRecords.some((task) => task.category === category.value))
748
+ .map((category) => ({ value: category.value, label: `${category.label} (Legacy)` }));
749
+ return [...visibleCategories, ...legacyCategoriesInUse];
750
+ }, [activeCategories, taskFilterRecords]);
751
+ const typeFilterOptions = useMemo(() => {
752
+ const activeTypeOptions = activeTypes
753
+ .filter((type) => type.status !== 'retired')
754
+ .map((type) => ({ value: type.value, label: type.label }));
755
+ const retiredTypesInUse = activeTypes
756
+ .filter((type) => type.status === 'retired' && taskFilterRecords.some((task) => (task.type || DEFAULT_TYPE_VALUE) === type.value))
757
+ .map((type) => ({ value: type.value, label: `${type.label} (Retired)` }));
758
+ return [...activeTypeOptions, ...retiredTypesInUse];
759
+ }, [activeTypes, taskFilterRecords]);
342
760
  const kanbanTasks = useMemo(() => {
761
+ if (taskScope === 'archived')
762
+ return archivedScopeTasks;
763
+ if (taskScope === 'deleted')
764
+ return filteredDeletedTasks;
343
765
  if (!showArchive)
344
766
  return props.filteredTasks;
345
- const archivedWithFlag = filteredArchive.map(t => ({ ...t, isArchived: true }));
346
- return [...props.filteredTasks, ...archivedWithFlag];
347
- }, [props.filteredTasks, filteredArchive, showArchive]);
348
- const scheduleBaseTasks = useMemo(() => props.tasks.filter(task => !task.isArchived), [props.tasks]);
349
- const scheduleFilteredTaskIds = useMemo(() => new Set(props.filteredTasks.map(task => task.id)), [props.filteredTasks]);
767
+ return [...props.filteredTasks, ...archivedScopeTasks];
768
+ }, [archivedScopeTasks, filteredDeletedTasks, props.filteredTasks, showArchive, taskScope]);
769
+ const scheduleBaseTasks = useMemo(() => taskScope === 'open'
770
+ ? props.tasks.filter(task => !task.isArchived)
771
+ : kanbanTasks, [kanbanTasks, props.tasks, taskScope]);
772
+ const scheduleFilteredTaskIds = useMemo(() => new Set(kanbanTasks.map(task => task.id)), [kanbanTasks]);
350
773
  const kanbanAllTasks = useMemo(() => {
774
+ if (taskScope === 'archived')
775
+ return archivedScopeAllTasks;
776
+ if (taskScope === 'deleted')
777
+ return deletedScopeAllTasks;
351
778
  if (!showArchive)
352
779
  return props.searchAgnosticTasks;
353
- return [...props.searchAgnosticTasks, ...filteredArchive];
354
- }, [props.searchAgnosticTasks, filteredArchive, showArchive]);
780
+ return [...props.searchAgnosticTasks, ...archivedScopeAllTasks];
781
+ }, [archivedScopeAllTasks, deletedScopeAllTasks, props.searchAgnosticTasks, showArchive, taskScope]);
355
782
  const hierarchyTasks = useMemo(() => {
356
783
  const byId = new Map();
357
- for (const task of props.archivedTasks)
784
+ for (const task of archivedScopeAllTasks)
785
+ byId.set(task.id, task);
786
+ for (const task of deletedScopeAllTasks)
358
787
  byId.set(task.id, task);
359
788
  for (const task of props.tasks)
360
789
  byId.set(task.id, task);
361
790
  return Array.from(byId.values());
362
- }, [props.tasks, props.archivedTasks]);
363
- const hierarchyParentIdSet = useMemo(() => {
364
- const set = new Set();
365
- for (const task of hierarchyTasks) {
366
- if (task.parentTaskId)
367
- set.add(task.parentTaskId);
368
- }
369
- return set;
370
- }, [hierarchyTasks]);
791
+ }, [archivedScopeAllTasks, deletedScopeAllTasks, props.tasks]);
371
792
  const hierarchyTaskById = useMemo(() => {
372
793
  const map = new Map();
373
794
  hierarchyTasks.forEach((task) => map.set(task.id, task));
374
795
  return map;
375
796
  }, [hierarchyTasks]);
376
- const hierarchyChildrenByParent = useMemo(() => {
797
+ const assigneeLabelByValue = useMemo(() => {
377
798
  const map = new Map();
378
- hierarchyTasks.forEach((task) => {
379
- if (!task.parentTaskId)
380
- return;
381
- const list = map.get(task.parentTaskId) || [];
382
- list.push(task);
383
- map.set(task.parentTaskId, list);
799
+ assigneeOptions.forEach((option) => {
800
+ map.set(String(option.value), option.label);
384
801
  });
385
802
  return map;
386
- }, [hierarchyTasks]);
387
- const hierarchySearchMatches = useMemo(() => {
388
- const needle = hierarchyRootSearch.trim().toLowerCase();
389
- if (!needle)
390
- return [];
391
- const rankForTask = (task) => {
392
- const id = task.id.toLowerCase();
393
- const shortId = (task.id.split('-').slice(-1)[0] || task.id).toLowerCase();
394
- const title = task.title.toLowerCase();
395
- if (id === needle)
396
- return 0;
397
- if (shortId === needle)
398
- return 1;
399
- if (id.startsWith(needle) || shortId.startsWith(needle))
400
- return 2;
401
- if (title.startsWith(needle))
402
- return 3;
403
- if (title.includes(needle))
404
- return 4;
405
- if (id.includes(needle) || shortId.includes(needle))
406
- return 5;
407
- return null;
803
+ }, [assigneeOptions]);
804
+ const planningStructure = useMemo(() => {
805
+ const planningInitiatives = taskScope === 'deleted'
806
+ ? []
807
+ : props.initiatives.filter((initiative) => taskScope === 'archived'
808
+ ? Boolean(initiative.isArchived)
809
+ : !initiative.isArchived);
810
+ const planningWorkstreams = taskScope === 'deleted'
811
+ ? []
812
+ : props.workstreams.filter((workstream) => taskScope === 'archived'
813
+ ? Boolean(workstream.isArchived)
814
+ : !workstream.isArchived);
815
+ const summarizeTasks = (scopeTasks) => {
816
+ const taskCount = scopeTasks.length;
817
+ const completedTaskCount = scopeTasks.filter((task) => task.isArchived || task.status === 'done' || task.status === 'cancelled').length;
818
+ return {
819
+ progressPercent: taskCount > 0 ? Math.round((completedTaskCount / taskCount) * 100) : 0,
820
+ taskCount,
821
+ completedTaskCount,
822
+ };
408
823
  };
409
- return hierarchyTasks
410
- .map((task) => ({ task, rank: rankForTask(task) }))
411
- .filter((entry) => entry.rank !== null)
412
- .sort((a, b) => {
413
- if (a.rank !== b.rank)
414
- return a.rank - b.rank;
415
- return a.task.title.localeCompare(b.task.title);
416
- })
417
- .map((entry) => entry.task);
418
- }, [hierarchyTasks, hierarchyRootSearch]);
419
- const hierarchyDescendantIdSet = useMemo(() => {
420
- if (!hierarchyRootTaskId)
824
+ const workstreamTaskIds = new Map();
825
+ const workstreamInitiativeIds = new Map();
826
+ const initiativeTaskIds = new Map();
827
+ const workstreamById = new Map();
828
+ const initiativeById = new Map();
829
+ const buildWorkstreamSummary = (workstream) => {
830
+ const scopedTasks = hierarchyTasks.filter((task) => (task.workstreamId || null) === workstream.id);
831
+ const taskPreviews = scopedTasks.map((task) => ({
832
+ id: task.id,
833
+ referenceNumber: task.referenceNumber ?? null,
834
+ title: task.title,
835
+ status: task.status || null,
836
+ }));
837
+ const summary = {
838
+ id: workstream.id,
839
+ referenceNumber: workstream.referenceNumber ?? null,
840
+ title: workstream.title,
841
+ description: String(workstream.description || '').trim() || undefined,
842
+ ownerLabel: workstream.ownerId ? assigneeLabelByValue.get(String(workstream.ownerId)) || String(workstream.ownerId) : null,
843
+ ...summarizeTasks(scopedTasks),
844
+ initiativeId: workstream.initiativeId || null,
845
+ commentCount: Array.isArray(workstream.comments) ? workstream.comments.length : 0,
846
+ attachmentCount: Array.isArray(workstream.attachments) ? workstream.attachments.length : 0,
847
+ isArchived: Boolean(workstream.isArchived),
848
+ tasks: taskPreviews,
849
+ };
850
+ workstreamTaskIds.set(workstream.id, scopedTasks.map((task) => task.id));
851
+ if (workstream.initiativeId)
852
+ workstreamInitiativeIds.set(workstream.id, workstream.initiativeId);
853
+ workstreamById.set(summary.id, summary);
854
+ return summary;
855
+ };
856
+ const initiatives = planningInitiatives.map((initiative) => {
857
+ const workstreams = planningWorkstreams
858
+ .filter((workstream) => (workstream.initiativeId || null) === initiative.id)
859
+ .map(buildWorkstreamSummary);
860
+ const initiativeScopedTasks = workstreams
861
+ .flatMap((workstream) => workstreamTaskIds.get(workstream.id) || [])
862
+ .map((taskId) => hierarchyTaskById.get(taskId))
863
+ .filter((task) => Boolean(task));
864
+ const initiativeSummary = {
865
+ id: initiative.id,
866
+ referenceNumber: initiative.referenceNumber ?? null,
867
+ title: initiative.title,
868
+ description: String(initiative.description || '').trim() || undefined,
869
+ ownerLabel: initiative.ownerId ? assigneeLabelByValue.get(String(initiative.ownerId)) || String(initiative.ownerId) : null,
870
+ ...summarizeTasks(initiativeScopedTasks),
871
+ workstreamCount: workstreams.length,
872
+ workstreams,
873
+ commentCount: Array.isArray(initiative.comments) ? initiative.comments.length : 0,
874
+ attachmentCount: Array.isArray(initiative.attachments) ? initiative.attachments.length : 0,
875
+ isArchived: Boolean(initiative.isArchived),
876
+ };
877
+ initiativeTaskIds.set(initiative.id, initiativeScopedTasks.map((task) => task.id));
878
+ initiativeById.set(initiativeSummary.id, initiativeSummary);
879
+ return initiativeSummary;
880
+ });
881
+ const standaloneWorkstreams = planningWorkstreams
882
+ .filter((workstream) => !workstream.initiativeId)
883
+ .map(buildWorkstreamSummary);
884
+ return {
885
+ initiatives,
886
+ standaloneWorkstreams,
887
+ workstreamTaskIds,
888
+ workstreamInitiativeIds,
889
+ initiativeTaskIds,
890
+ workstreamById,
891
+ initiativeById,
892
+ };
893
+ }, [assigneeLabelByValue, hierarchyTaskById, hierarchyTasks, props.initiatives, props.workstreams, taskScope]);
894
+ const initiativeFilterOptions = useMemo(() => planningStructure.initiatives.map((initiative) => {
895
+ const referenceLabel = formatInitiativeReference(initiative);
896
+ return {
897
+ value: initiative.id,
898
+ label: referenceLabel ? `${referenceLabel} - ${initiative.title}` : initiative.title,
899
+ selectedLabel: referenceLabel || initiative.title,
900
+ };
901
+ }), [planningStructure.initiatives]);
902
+ const workstreamFilterOptions = useMemo(() => [
903
+ ...planningStructure.initiatives.flatMap((initiative) => initiative.workstreams.map((workstream) => {
904
+ const referenceLabel = formatWorkstreamReference(workstream);
905
+ return {
906
+ value: workstream.id,
907
+ label: referenceLabel ? `${referenceLabel} - ${workstream.title}` : workstream.title,
908
+ selectedLabel: referenceLabel || workstream.title,
909
+ };
910
+ })),
911
+ ...planningStructure.standaloneWorkstreams.map((workstream) => {
912
+ const referenceLabel = formatWorkstreamReference(workstream);
913
+ return {
914
+ value: workstream.id,
915
+ label: referenceLabel ? `${referenceLabel} - ${workstream.title}` : workstream.title,
916
+ selectedLabel: referenceLabel || workstream.title,
917
+ };
918
+ }),
919
+ ], [planningStructure.initiatives, planningStructure.standaloneWorkstreams]);
920
+ const selectedPlanningTaskIds = useMemo(() => {
921
+ if (selectedPlanningWorkstreamId) {
922
+ return new Set(planningStructure.workstreamTaskIds.get(selectedPlanningWorkstreamId) || []);
923
+ }
924
+ if (selectedPlanningInitiativeId) {
925
+ return new Set(planningStructure.initiativeTaskIds.get(selectedPlanningInitiativeId) || []);
926
+ }
927
+ return null;
928
+ }, [planningStructure.initiativeTaskIds, planningStructure.workstreamTaskIds, selectedPlanningInitiativeId, selectedPlanningWorkstreamId]);
929
+ const planningDrawerItem = useMemo(() => {
930
+ if (!planningDrawerDetail)
421
931
  return null;
422
- return collectSubtreeTaskIds(hierarchyTasks, hierarchyRootTaskId);
423
- }, [hierarchyRootTaskId, hierarchyTasks]);
424
- const hierarchyVisibleTasks = useMemo(() => {
425
- if (groupBy === 'schedule')
426
- return scheduleBaseTasks;
427
- if (groupBy !== 'hierarchy')
428
- return kanbanTasks;
429
- const isUnlinkedTask = (task) => !task.parentTaskId && !hierarchyParentIdSet.has(task.id);
430
- let base = kanbanTasks;
431
- if (parentTaskFilterMode === 'parents') {
432
- base = kanbanTasks.filter(task => hierarchyParentIdSet.has(task.id));
932
+ if (planningDrawerDetail.type === 'initiative') {
933
+ const item = planningStructure.initiativeById.get(planningDrawerDetail.id);
934
+ return item ? { type: 'initiative', item } : null;
935
+ }
936
+ const item = planningStructure.workstreamById.get(planningDrawerDetail.id)
937
+ || planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === planningDrawerDetail.id);
938
+ return item ? { type: 'workstream', item } : null;
939
+ }, [planningDrawerDetail, planningStructure.initiativeById, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
940
+ const planningNestedWorkstreamItem = useMemo(() => {
941
+ if (!planningNestedWorkstreamDetailId)
942
+ return null;
943
+ const item = planningStructure.workstreamById.get(planningNestedWorkstreamDetailId)
944
+ || planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === planningNestedWorkstreamDetailId);
945
+ return item ? { type: 'workstream', item } : null;
946
+ }, [planningNestedWorkstreamDetailId, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
947
+ const planningSecondaryPane = useMemo(() => {
948
+ if (planningSecondaryEditor) {
949
+ return { kind: 'editor', editor: planningSecondaryEditor };
950
+ }
951
+ if (planningNestedWorkstreamItem) {
952
+ return { kind: 'detail', detail: planningNestedWorkstreamItem };
953
+ }
954
+ return null;
955
+ }, [planningNestedWorkstreamItem, planningSecondaryEditor]);
956
+ const planningDrawerWidthPx = getPlanningDrawerWidthPx(planningTreeCollapsed, Boolean(planningDrawerItem || planningEditor), planningPrimaryCollapsed, Boolean(planningSecondaryPane), planningSecondaryCollapsed);
957
+ const planningDraftInitiativeSummary = useMemo(() => {
958
+ if (!planningDraftInitiativeId.trim())
959
+ return null;
960
+ const exactInitiative = planningStructure.initiativeById.get(planningDraftInitiativeId);
961
+ if (exactInitiative)
962
+ return exactInitiative;
963
+ return resolveInitiativeReference(planningStructure.initiatives, planningDraftInitiativeId);
964
+ }, [planningDraftInitiativeId, planningStructure.initiativeById, planningStructure.initiatives]);
965
+ useEffect(() => {
966
+ if (!planningDrawerOpen)
967
+ return;
968
+ setPlanningTreeCollapsed(false);
969
+ setPlanningPrimaryCollapsed(false);
970
+ setPlanningSecondaryCollapsed(false);
971
+ }, [planningDrawerOpen]);
972
+ useEffect(() => {
973
+ if (selectedPlanningInitiativeId && !planningStructure.initiativeById.has(selectedPlanningInitiativeId)) {
974
+ setSelectedPlanningInitiativeId('');
975
+ }
976
+ if (selectedPlanningWorkstreamId && !planningStructure.workstreamById.has(selectedPlanningWorkstreamId)) {
977
+ setSelectedPlanningWorkstreamId('');
433
978
  }
434
- else if (parentTaskFilterMode === 'linked') {
435
- base = kanbanTasks.filter(task => !!task.parentTaskId || hierarchyParentIdSet.has(task.id));
979
+ if (planningDrawerDetail?.type === 'initiative' && !planningStructure.initiativeById.has(planningDrawerDetail.id)) {
980
+ setPlanningDrawerDetail(null);
436
981
  }
437
- else if (parentTaskFilterMode === 'unlinked') {
438
- base = kanbanTasks.filter(task => isUnlinkedTask(task));
982
+ if (planningDrawerDetail?.type === 'workstream' && !planningStructure.workstreamById.has(planningDrawerDetail.id)) {
983
+ setPlanningDrawerDetail(null);
439
984
  }
440
- if (!hierarchyShowUnlinkedColumn && parentTaskFilterMode !== 'unlinked') {
441
- base = base.filter((task) => !isUnlinkedTask(task));
985
+ if (planningNestedWorkstreamDetailId && !planningStructure.workstreamById.has(planningNestedWorkstreamDetailId)) {
986
+ setPlanningNestedWorkstreamDetailId(null);
442
987
  }
443
- if (hierarchyDescendantIdSet) {
444
- base = base.filter(task => hierarchyDescendantIdSet.has(task.id)
445
- || (hierarchyShowUnlinkedColumn && (parentTaskFilterMode === 'all' || parentTaskFilterMode === 'unlinked') && isUnlinkedTask(task)));
988
+ if (planningSecondaryEditor?.mode === 'edit'
989
+ && !planningStructure.workstreamById.has(planningSecondaryEditor.targetId)) {
990
+ setPlanningSecondaryEditor(null);
446
991
  }
447
- return base;
448
- }, [groupBy, parentTaskFilterMode, kanbanTasks, hierarchyParentIdSet, scheduleBaseTasks, hierarchyDescendantIdSet, hierarchyShowUnlinkedColumn]);
992
+ }, [
993
+ planningDrawerDetail,
994
+ planningNestedWorkstreamDetailId,
995
+ planningSecondaryEditor,
996
+ planningStructure.initiativeById,
997
+ planningStructure.workstreamById,
998
+ selectedPlanningInitiativeId,
999
+ selectedPlanningWorkstreamId,
1000
+ ]);
1001
+ const hierarchyVisibleTasks = useMemo(() => {
1002
+ if (groupBy === 'schedule')
1003
+ return scheduleBaseTasks;
1004
+ return kanbanTasks;
1005
+ }, [groupBy, kanbanTasks, scheduleBaseTasks]);
449
1006
  const todayDateOnly = useMemo(() => {
450
1007
  const now = new Date();
451
1008
  const y = now.getFullYear();
@@ -465,6 +1022,11 @@ export function StandaloneLayout(props) {
465
1022
  return expiredScheduled || overdueDue;
466
1023
  });
467
1024
  }, [groupBy, scheduleOnlyExpired, hierarchyVisibleTasks, todayDateOnly]);
1025
+ const scopedBoardVisibleTasks = useMemo(() => {
1026
+ if (!selectedPlanningTaskIds || selectedPlanningTaskIds.size === 0)
1027
+ return boardVisibleTasks;
1028
+ return boardVisibleTasks.filter((task) => selectedPlanningTaskIds.has(task.id));
1029
+ }, [boardVisibleTasks, selectedPlanningTaskIds]);
468
1030
  const expiredRecoveryCandidates = useMemo(() => {
469
1031
  if (groupBy !== 'schedule')
470
1032
  return [];
@@ -507,108 +1069,78 @@ export function StandaloneLayout(props) {
507
1069
  return !!task.scheduledDate && task.scheduledDate < todayDateOnly;
508
1070
  });
509
1071
  }, [groupBy, hierarchyVisibleTasks, todayDateOnly]);
510
- const expiredLeafCandidates = useMemo(() => expiredRecoveryCandidates.filter(task => !hierarchyParentIdSet.has(task.id)), [expiredRecoveryCandidates, hierarchyParentIdSet]);
1072
+ const expiredLeafCandidates = expiredRecoveryCandidates;
511
1073
  const [scheduleBulkBusy, setScheduleBulkBusy] = useState(false);
512
- const [scheduleParentDropBusy, setScheduleParentDropBusy] = useState(false);
513
- const [scheduleParentDropPrompt, setScheduleParentDropPrompt] = useState(null);
514
- const hierarchyDepthTasks = useMemo(() => hierarchyVisibleTasks.filter(task => !!task.parentTaskId || hierarchyParentIdSet.has(task.id)), [hierarchyVisibleTasks, hierarchyParentIdSet]);
515
- const hierarchyGlobalDepthMap = useMemo(() => buildHierarchyDepthMap(hierarchyTasks), [hierarchyTasks]);
516
- const maxHierarchyDepth = useMemo(() => hierarchyDepthTasks.reduce((maxDepth, task) => {
517
- const depth = hierarchyGlobalDepthMap.get(task.id) ?? 0;
518
- return depth > maxDepth ? depth : maxDepth;
519
- }, 0), [hierarchyDepthTasks, hierarchyGlobalDepthMap]);
520
- const tryReturnToParent = () => {
521
- if (activeTab === 'add'
522
- && editingTaskId
523
- && returnToParentTaskId
524
- && returnFromChildTaskId === editingTaskId) {
525
- const parentTask = tasks.find(t => t.id === returnToParentTaskId);
526
- clearReturnToParentTask();
527
- if (parentTask) {
528
- handleEdit(parentTask);
529
- return true;
530
- }
531
- }
532
- return false;
533
- };
534
1074
  const handleCloseModal = () => {
535
1075
  handleNavigation(() => {
536
- if (tryReturnToParent())
1076
+ if (returnToPreviousTask())
537
1077
  return;
1078
+ if (activeTab === 'add' && taskReturnTrail.length > 0) {
1079
+ clearReturnToParentTask();
1080
+ }
1081
+ setTaskModalFullscreen(false);
538
1082
  if (activeTab === 'add')
539
1083
  resetForm();
540
1084
  setActiveTab('tasks');
541
1085
  });
542
1086
  };
543
- const [localCancellationReason, setLocalCancellationReason] = useState('');
544
- const handleConfirmCancellation = async () => {
545
- const success = await confirmCancellation(localCancellationReason, cancellationMode);
546
- if (success) {
547
- setLocalCancellationReason('');
1087
+ useEffect(() => {
1088
+ if (activeTab !== 'add') {
1089
+ setTaskModalFullscreen(false);
548
1090
  }
549
- };
550
- const handleCancelCancellation = () => {
551
- cancelCancellationPrompt();
552
- setLocalCancellationReason('');
553
- };
554
- const handleDeleteFromCancellation = async () => {
555
- if (!cancellationPromptTask)
1091
+ }, [activeTab]);
1092
+ const handleSetStatus = useCallback((task, status) => {
1093
+ if (task.status === status)
556
1094
  return;
557
- const confirmed = window.confirm('Delete this task permanently? This cannot be undone.');
558
- if (!confirmed)
1095
+ if (status === 'in-progress') {
1096
+ void handleToggleInProgress(task);
559
1097
  return;
560
- const deleted = await handleDelete(cancellationPromptTask.id, false, { skipConfirm: true });
561
- if (deleted) {
562
- handleCancelCancellation();
563
- }
564
- };
565
- const handleAddTaskToColumn = useCallback((groupField, value) => {
566
- resetForm();
567
- // Handle different grouping fields
568
- if (groupField === 'category') {
569
- const cat = activeCategories.find(c => String(c.value) === String(value) || c.label === String(value));
570
- if (cat)
571
- setCategory(cat.label);
572
- }
573
- else if (groupField === 'type') {
574
- setType(String(value));
575
- }
576
- else if (groupField === 'priority') {
577
- setPriority(Number(value));
578
- }
579
- else if (groupField === 'approach') {
580
- const app = approaches.find(a => String(a.value) === String(value) || a.label === String(value));
581
- if (app) {
582
- setApproach(app.value);
583
- setFormTaxonomies(prev => ({ ...prev, approach: app.value }));
584
- }
585
1098
  }
586
- else if (groupField === 'status') {
587
- // When adding to a status column, we just open the form.
588
- // But we can preset the status flags if we want, or just let users set them.
589
- // Currently, the TaskForm doesn't expose a simple 'status' dropdown to preset.
590
- // So for now, we'll just open the form.
591
- // Ideally, we would preset the flags (inProgress, readyForReview, etc.)
592
- // implementing basic presetting:
593
- if (value === 'inProgress') {
594
- handleToggleInProgress({}); // Hacky: assumes handleToggle works on new task? No.
595
- // Better: we can't easily preset status flags in the current form setup without modifying resetForm/TaskForm props deeply.
596
- // For now, simple open.
597
- // Actually, let's try to pass initial state if possible, but useTaskforce API is limited.
598
- // We'll leave it as default (Task) for now, or users can toggle after adding.
599
- }
1099
+ if (status === 'review') {
1100
+ void handleToggleReview(task);
1101
+ return;
600
1102
  }
601
- else if (groupField === 'complexity') {
602
- const direct = Number(value);
603
- if (Number.isFinite(direct)) {
604
- setComplexity(direct);
605
- }
1103
+ if (status === 'done') {
1104
+ void handleToggleComplete(task);
1105
+ return;
606
1106
  }
607
- else if (groupField === 'hierarchy') {
608
- // Hierarchy columns are depth-based and do not map to form defaults.
1107
+ if (status === 'cancelled') {
1108
+ void handleToggleCancel(task);
1109
+ return;
609
1110
  }
1111
+ void handleUpdateTask(task.id, {
1112
+ status,
1113
+ completedAt: null
1114
+ });
1115
+ }, [handleToggleInProgress, handleToggleReview, handleToggleComplete, handleToggleCancel, handleUpdateTask]);
1116
+ const handleAddTaskToColumn = useCallback((groupField, value) => {
1117
+ resetForm();
1118
+ const defaults = resolveDraftDefaultsForGrouping(groupField, value, {
1119
+ categories: activeCategories,
1120
+ approaches
1121
+ });
1122
+ if (defaults.category)
1123
+ setCategory(defaults.category);
1124
+ if (defaults.type)
1125
+ setType(defaults.type);
1126
+ if (typeof defaults.priority === 'number')
1127
+ setPriority(defaults.priority);
1128
+ if (typeof defaults.complexity === 'number')
1129
+ setComplexity(defaults.complexity);
1130
+ if (defaults.approach)
1131
+ setApproach(defaults.approach);
1132
+ const taxonomyApproach = defaults.taxonomyApproach;
1133
+ if (typeof taxonomyApproach === 'string' && taxonomyApproach.length > 0) {
1134
+ setFormTaxonomies(prev => ({ ...prev, approach: taxonomyApproach }));
1135
+ }
1136
+ if (defaults.assignee)
1137
+ setAssignee(defaults.assignee);
1138
+ if (defaults.status)
1139
+ setStatus(defaults.status);
1140
+ if (defaults.scheduledDate)
1141
+ setScheduledDate(defaults.scheduledDate);
610
1142
  setActiveTab('add');
611
- }, [resetForm, activeCategories, approaches, setCategory, setType, setPriority, setComplexity, setApproach, setFormTaxonomies, setActiveTab]);
1143
+ }, [resetForm, activeCategories, approaches, setCategory, setType, setPriority, setComplexity, setStatus, setApproach, setAssignee, setScheduledDate, setFormTaxonomies, setActiveTab]);
612
1144
  // Ensure 'done' status is shown by default as requested
613
1145
  React.useEffect(() => {
614
1146
  if (filterStatus && !filterStatus.includes('done')) {
@@ -619,14 +1151,21 @@ export function StandaloneLayout(props) {
619
1151
  const [showHelpModal, setShowHelpModal] = useState(false);
620
1152
  const [showAccountMenu, setShowAccountMenu] = useState(false);
621
1153
  const [showAccountHub, setShowAccountHub] = useState(false);
1154
+ const [showEditProfileModal, setShowEditProfileModal] = useState(false);
622
1155
  const [showSyncStatusModal, setShowSyncStatusModal] = useState(false);
623
- const [workspaceSyncBusy, setWorkspaceSyncBusy] = useState(false);
1156
+ const [workspaceSyncToggleBusy, setWorkspaceSyncToggleBusy] = useState(false);
624
1157
  const [workspaceSyncError, setWorkspaceSyncError] = useState(null);
625
- const [workspacePanelOpen, setWorkspacePanelOpen] = useState(false);
626
- const [workspaceFormName, setWorkspaceFormName] = useState('');
627
- const [workspaceFormDescription, setWorkspaceFormDescription] = useState('');
1158
+ const [workspaceSyncRepairBusy, setWorkspaceSyncRepairBusy] = useState(false);
1159
+ const [workspaceSyncRepairQueued, setWorkspaceSyncRepairQueued] = useState(false);
1160
+ const [workspaceSyncCopied, setWorkspaceSyncCopied] = useState(false);
1161
+ const [syncRecentEvents, setSyncRecentEvents] = useState([]);
1162
+ const [syncRecentEventsLoading, setSyncRecentEventsLoading] = useState(false);
1163
+ const [syncRecentEventsError, setSyncRecentEventsError] = useState(null);
1164
+ const syncEventsListRef = useRef(null);
1165
+ const pendingSyncEventsScrollRestoreRef = useRef(null);
628
1166
  const [workspaceActionBusy, setWorkspaceActionBusy] = useState(false);
629
1167
  const [workspaceActionError, setWorkspaceActionError] = useState('');
1168
+ const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
630
1169
  const [showTeamManagementModal, setShowTeamManagementModal] = useState(false);
631
1170
  const [teamManagementTab, setTeamManagementTab] = useState('members');
632
1171
  const [teamPlanMode, setTeamPlanMode] = useState('unknown');
@@ -651,9 +1190,12 @@ export function StandaloneLayout(props) {
651
1190
  const [billingActionError, setBillingActionError] = useState(null);
652
1191
  const [billingNotice, setBillingNotice] = useState(null);
653
1192
  const [billingIntervalChoice, setBillingIntervalChoice] = useState('month');
1193
+ const [profileDisplayNameDraft, setProfileDisplayNameDraft] = useState('');
1194
+ const [profileSaveBusy, setProfileSaveBusy] = useState(false);
1195
+ const [profileSaveError, setProfileSaveError] = useState(null);
1196
+ const [profileSaveNotice, setProfileSaveNotice] = useState(null);
654
1197
  const TEAM_AUDIT_PAGE_SIZE = 25;
655
1198
  const accountMenuRef = useRef(null);
656
- const billingSuccessPollRef = useRef(null);
657
1199
  // showSearch state removed - search always visible in actions area
658
1200
  useEffect(() => {
659
1201
  if (!showAccountMenu)
@@ -666,45 +1208,119 @@ export function StandaloneLayout(props) {
666
1208
  document.addEventListener('mousedown', handlePointerDown);
667
1209
  return () => document.removeEventListener('mousedown', handlePointerDown);
668
1210
  }, [showAccountMenu]);
669
- useEffect(() => () => {
670
- if (billingSuccessPollRef.current) {
671
- window.clearInterval(billingSuccessPollRef.current);
672
- billingSuccessPollRef.current = null;
673
- }
674
- }, []);
675
1211
  const canManageCloudWorkspaces = runtimeMode === 'cloud' && workspaceSwitchingEnabled && isAuthenticated;
1212
+ const canAccessCloudTeamManagement = isAuthenticated && (runtimeMode === 'cloud' || cloudAuthConfigured);
676
1213
  const canManageWorkspaceSync = runtimeMode === 'local' && isAuthenticated;
677
- const currentWorkspaceRole = useMemo(() => {
1214
+ const handleWorkspaceSyncToggle = useCallback(async (enabled) => {
1215
+ if (!canManageWorkspaceSync)
1216
+ return;
1217
+ setWorkspaceSyncError(null);
1218
+ setWorkspaceSyncToggleBusy(true);
1219
+ const result = await saveWorkspaceCloudSyncSettings({ enabled });
1220
+ if (!result.success) {
1221
+ setWorkspaceSyncError(result.error || (enabled ? 'Failed to enable sync.' : 'Failed to disable sync.'));
1222
+ }
1223
+ setWorkspaceSyncToggleBusy(false);
1224
+ }, [
1225
+ canManageWorkspaceSync,
1226
+ saveWorkspaceCloudSyncSettings
1227
+ ]);
1228
+ const syncControlBusy = workspaceSyncToggleBusy;
1229
+ const currentWorkspaceLabel = useMemo(() => {
678
1230
  const current = availableWorkspaces.find((workspace) => workspace.id === currentWorkspaceId);
679
- if (!current)
680
- return null;
681
- const role = String(current.role || '').trim().toLowerCase();
682
- if (role === 'owner' || role === 'admin' || role === 'member' || role === 'read-only')
683
- return role;
684
- return null;
685
- }, [availableWorkspaces, currentWorkspaceId]);
686
- const canOpenTeamManagement = canManageCloudWorkspaces
687
- && (currentWorkspaceRole === 'owner' || currentWorkspaceRole === 'admin')
688
- && teamPlanMode === 'team';
689
- const authIdentityLabel = String(authUserEmail || '').trim();
1231
+ const namedWorkspace = String(current?.name || '').trim();
1232
+ if (namedWorkspace)
1233
+ return namedWorkspace;
1234
+ if (runtimeMode === 'local') {
1235
+ const localProjectName = String(projectName || '').trim();
1236
+ if (localProjectName)
1237
+ return localProjectName;
1238
+ }
1239
+ const fallbackId = String(currentWorkspaceId || '').trim();
1240
+ return fallbackId || 'Workspace';
1241
+ }, [availableWorkspaces, currentWorkspaceId, projectName, runtimeMode]);
1242
+ const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement, currentWorkspaceRole);
1243
+ const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement, currentWorkspaceRole, teamPlanMode);
1244
+ const authDisplayName = String(authUserDisplayName || '').trim();
1245
+ const authEmailLabel = String(authUserEmail || '').trim();
1246
+ const authIdentityLabel = authDisplayName || authEmailLabel;
690
1247
  const hasAuthIdentity = authIdentityLabel.length > 0;
1248
+ const hasAuthEmail = authEmailLabel.length > 0;
1249
+ const accountBadgeLabel = (authIdentityLabel || authEmailLabel || 'U').trim();
1250
+ const accountBadgeInitial = accountBadgeLabel.charAt(0).toUpperCase() || 'U';
1251
+ const subscriptionLabel = String(billingStatus?.planName || billingStatus?.planId || '').trim();
1252
+ const hasSubscription = subscriptionLabel.length > 0;
1253
+ const teamManagementWorkspaceId = String(authWorkspaceId || currentWorkspaceId || '').trim();
1254
+ const runtimeLabel = runtimeMode === 'cloud' ? 'CLOUD' : 'LOCAL';
1255
+ const apiBaseUrl = props.config?.apiBaseUrl || '';
1256
+ const billingApiBaseUrl = useMemo(() => {
1257
+ const preferredBase = runtimeMode === 'local'
1258
+ ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1259
+ : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1260
+ return String(preferredBase || '').trim().replace(/\/+$/, '');
1261
+ }, [props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
1262
+ const buildBillingApiUrl = useCallback((path) => {
1263
+ const normalizedPath = String(path || '').trim();
1264
+ if (!billingApiBaseUrl)
1265
+ return normalizedPath;
1266
+ return `${billingApiBaseUrl}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`;
1267
+ }, [billingApiBaseUrl]);
1268
+ const connectedEnvironmentLabel = useMemo(() => resolveConnectedEnvironmentLabel(props.config?.cloudEnvironment, [
1269
+ String(props.config?.cloudBaseUrl || ''),
1270
+ String(props.config?.cloudAuthBaseUrl || ''),
1271
+ String(props.config?.apiBaseUrl || ''),
1272
+ String(props.config?.baseUrl || '')
1273
+ ]), [
1274
+ props.config?.cloudEnvironment,
1275
+ props.config?.cloudBaseUrl,
1276
+ props.config?.cloudAuthBaseUrl,
1277
+ props.config?.apiBaseUrl,
1278
+ props.config?.baseUrl
1279
+ ]);
691
1280
  const isAuthBootstrapPending = cloudAuthConfigured && !authSessionResolved;
1281
+ useEffect(() => {
1282
+ if (!showEditProfileModal)
1283
+ return;
1284
+ setProfileDisplayNameDraft(authDisplayName);
1285
+ setProfileSaveError(null);
1286
+ setProfileSaveNotice(null);
1287
+ }, [authDisplayName, showEditProfileModal]);
1288
+ const handleSaveProfile = useCallback(async () => {
1289
+ const trimmedDisplayName = profileDisplayNameDraft.trim();
1290
+ if (!trimmedDisplayName) {
1291
+ setProfileSaveError('Display name is required.');
1292
+ setProfileSaveNotice(null);
1293
+ return;
1294
+ }
1295
+ setProfileSaveBusy(true);
1296
+ setProfileSaveError(null);
1297
+ setProfileSaveNotice(null);
1298
+ const result = await updateCurrentUserProfile({ displayName: trimmedDisplayName });
1299
+ if (!result.success) {
1300
+ setProfileSaveError(result.error || 'Failed to update profile.');
1301
+ setProfileSaveBusy(false);
1302
+ return;
1303
+ }
1304
+ setProfileSaveNotice('Profile updated.');
1305
+ setProfileSaveBusy(false);
1306
+ setShowEditProfileModal(false);
1307
+ }, [profileDisplayNameDraft, updateCurrentUserProfile]);
692
1308
  const teamAdminHeaders = useCallback(() => {
693
1309
  const headers = {};
694
- const workspaceId = String(currentWorkspaceId || '').trim();
1310
+ const workspaceId = String(teamManagementWorkspaceId || '').trim();
695
1311
  if (workspaceId)
696
1312
  headers['x-taskforce-workspace-id'] = workspaceId;
697
1313
  return headers;
698
- }, [currentWorkspaceId]);
1314
+ }, [teamManagementWorkspaceId]);
699
1315
  const loadBillingStatus = useCallback(async () => {
700
- if (runtimeMode !== 'cloud' || !isAuthenticated) {
1316
+ if (!isAuthenticated) {
701
1317
  setBillingStatus(null);
702
1318
  return null;
703
1319
  }
704
1320
  setBillingLoading(true);
705
1321
  setBillingError(null);
706
1322
  try {
707
- const res = await fetch('/api/taskforce/billing/status', {
1323
+ const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/status'), {
708
1324
  method: 'GET',
709
1325
  credentials: 'include'
710
1326
  });
@@ -725,46 +1341,19 @@ export function StandaloneLayout(props) {
725
1341
  finally {
726
1342
  setBillingLoading(false);
727
1343
  }
728
- }, [isAuthenticated, runtimeMode]);
1344
+ }, [buildBillingApiUrl, isAuthenticated]);
729
1345
  const handleStartCheckout = useCallback(async () => {
730
- setBillingActionError(null);
731
- setBillingNotice(null);
732
- setBillingActionBusy(true);
733
- try {
734
- const res = await fetch('/api/taskforce/billing/checkout-session', {
735
- method: 'POST',
736
- credentials: 'include',
737
- headers: { 'Content-Type': 'application/json' },
738
- body: JSON.stringify({
739
- tier: 'team',
740
- interval: billingIntervalChoice
741
- })
742
- });
743
- const payload = await res.json().catch(() => ({}));
744
- if (!res.ok) {
745
- setBillingActionError(String(payload?.error || 'Failed to create checkout session.'));
746
- return;
747
- }
748
- const nextUrl = String(payload?.url || '').trim();
749
- if (!nextUrl) {
750
- setBillingActionError('Checkout session did not return a redirect URL.');
751
- return;
752
- }
753
- window.location.assign(nextUrl);
754
- }
755
- catch {
756
- setBillingActionError('Failed to start checkout.');
757
- }
758
- finally {
759
- setBillingActionBusy(false);
760
- }
761
- }, [billingIntervalChoice]);
1346
+ const params = new URLSearchParams();
1347
+ params.set('screen', 'plans');
1348
+ params.set('interval', billingIntervalChoice);
1349
+ navigate(`/?${params.toString()}`);
1350
+ }, [billingIntervalChoice, navigate]);
762
1351
  const handleOpenBillingPortal = useCallback(async () => {
763
1352
  setBillingActionError(null);
764
1353
  setBillingNotice(null);
765
1354
  setBillingActionBusy(true);
766
1355
  try {
767
- const res = await fetch('/api/taskforce/billing/portal-session', {
1356
+ const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/portal-session'), {
768
1357
  method: 'POST',
769
1358
  credentials: 'include'
770
1359
  });
@@ -786,18 +1375,23 @@ export function StandaloneLayout(props) {
786
1375
  finally {
787
1376
  setBillingActionBusy(false);
788
1377
  }
789
- }, []);
1378
+ }, [buildBillingApiUrl]);
790
1379
  const handleSwitchSubscriptionInterval = useCallback(async () => {
791
1380
  setBillingActionError(null);
792
1381
  setBillingNotice(null);
793
1382
  setBillingActionBusy(true);
794
1383
  try {
795
- const res = await fetch('/api/taskforce/billing/subscription', {
1384
+ const currentPlanVersionId = String(billingStatus?.planVersionId || '').trim();
1385
+ if (!currentPlanVersionId) {
1386
+ setBillingActionError('No active plan version is linked to this account.');
1387
+ return;
1388
+ }
1389
+ const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/subscription'), {
796
1390
  method: 'POST',
797
1391
  credentials: 'include',
798
1392
  headers: { 'Content-Type': 'application/json' },
799
1393
  body: JSON.stringify({
800
- tier: 'team',
1394
+ planVersionId: currentPlanVersionId,
801
1395
  interval: billingIntervalChoice
802
1396
  })
803
1397
  });
@@ -815,11 +1409,11 @@ export function StandaloneLayout(props) {
815
1409
  finally {
816
1410
  setBillingActionBusy(false);
817
1411
  }
818
- }, [billingIntervalChoice, loadBillingStatus]);
1412
+ }, [billingIntervalChoice, billingStatus?.planVersionId, buildBillingApiUrl, loadBillingStatus]);
819
1413
  const loadTeamPlanMode = useCallback(async () => {
820
- if (!canManageCloudWorkspaces || !(currentWorkspaceRole === 'owner' || currentWorkspaceRole === 'admin')) {
1414
+ if (!canManageTeamWorkspace) {
821
1415
  setTeamPlanMode('unknown');
822
- return;
1416
+ return 'unknown';
823
1417
  }
824
1418
  try {
825
1419
  const res = await fetch('/api/taskforce/account/entitlements', {
@@ -830,80 +1424,38 @@ export function StandaloneLayout(props) {
830
1424
  const payload = await res.json().catch(() => ({}));
831
1425
  if (!res.ok) {
832
1426
  setTeamPlanMode('unknown');
833
- return;
1427
+ return 'unknown';
834
1428
  }
835
- const planId = String(payload?.planId || '').trim().toLowerCase();
836
- const state = String(payload?.state || '').trim().toLowerCase();
837
- const teamAllowed = payload?.features?.['collaboration.team_management']?.allowed === true;
838
- const stateAllows = state === 'active' || state === 'trialing' || state === 'grace';
839
- setTeamPlanMode(planId === 'team' && teamAllowed && stateAllows ? 'team' : 'personal');
1429
+ const nextMode = resolveTeamPlanModeFromEntitlements(payload);
1430
+ setTeamPlanMode(nextMode);
1431
+ return nextMode;
840
1432
  }
841
1433
  catch {
842
1434
  setTeamPlanMode('unknown');
1435
+ return 'unknown';
843
1436
  }
844
- }, [canManageCloudWorkspaces, currentWorkspaceRole, teamAdminHeaders]);
1437
+ }, [canManageTeamWorkspace, teamAdminHeaders]);
845
1438
  useEffect(() => {
846
1439
  if (!showAccountMenu && !showTeamManagementModal && !showAccountHub)
847
1440
  return;
848
1441
  void loadTeamPlanMode();
849
1442
  }, [showAccountHub, showAccountMenu, showTeamManagementModal, loadTeamPlanMode]);
850
1443
  useEffect(() => {
851
- if (!showAccountHub)
852
- return;
853
- void loadBillingStatus();
854
- }, [loadBillingStatus, showAccountHub]);
855
- useEffect(() => {
856
- if (!isAuthenticated)
857
- return;
858
- if (location.pathname !== '/billing/success' && location.pathname !== '/billing/cancel')
1444
+ if (!isAuthenticated || !authSessionResolved)
859
1445
  return;
860
- setShowAccountHub(true);
861
- setShowAccountMenu(false);
862
- setBillingActionError(null);
863
- setBillingError(null);
864
- if (location.pathname === '/billing/cancel') {
865
- setBillingNotice('Checkout canceled. No billing changes were applied.');
866
- navigate('/', { replace: true });
1446
+ if (!canManageTeamWorkspace) {
1447
+ setTeamPlanMode('unknown');
867
1448
  return;
868
1449
  }
869
- setBillingNotice('Checkout complete. Waiting for billing confirmation...');
1450
+ void loadTeamPlanMode();
1451
+ }, [authSessionResolved, canManageTeamWorkspace, isAuthenticated, loadTeamPlanMode]);
1452
+ useEffect(() => {
1453
+ if (!showAccountHub && !showAccountMenu)
1454
+ return;
870
1455
  void loadBillingStatus();
871
- let attempts = 0;
872
- const maxAttempts = 30;
873
- if (billingSuccessPollRef.current) {
874
- window.clearInterval(billingSuccessPollRef.current);
875
- billingSuccessPollRef.current = null;
876
- }
877
- billingSuccessPollRef.current = window.setInterval(() => {
878
- attempts += 1;
879
- void loadBillingStatus().then((payload) => {
880
- const entitlementState = String(payload?.entitlementState || '').trim().toLowerCase();
881
- if (entitlementState && entitlementState !== 'free') {
882
- setBillingNotice('Subscription is active.');
883
- if (billingSuccessPollRef.current) {
884
- window.clearInterval(billingSuccessPollRef.current);
885
- billingSuccessPollRef.current = null;
886
- }
887
- navigate('/', { replace: true });
888
- return;
889
- }
890
- if (attempts >= maxAttempts && billingSuccessPollRef.current) {
891
- window.clearInterval(billingSuccessPollRef.current);
892
- billingSuccessPollRef.current = null;
893
- setBillingNotice('Checkout finished, but entitlement update is still pending. Refresh Billing Status in a few seconds.');
894
- navigate('/', { replace: true });
895
- }
896
- });
897
- }, 1500);
898
- return () => {
899
- if (billingSuccessPollRef.current) {
900
- window.clearInterval(billingSuccessPollRef.current);
901
- billingSuccessPollRef.current = null;
902
- }
903
- };
904
- }, [isAuthenticated, loadBillingStatus, location.pathname, navigate]);
1456
+ }, [loadBillingStatus, showAccountHub, showAccountMenu]);
905
1457
  const loadTeamUsers = useCallback(async () => {
906
- if (!canOpenTeamManagement)
1458
+ if (!canManageTeamWorkspace)
907
1459
  return;
908
1460
  setTeamUsersLoading(true);
909
1461
  setTeamMgmtError(null);
@@ -945,16 +1497,16 @@ export function StandaloneLayout(props) {
945
1497
  finally {
946
1498
  setTeamUsersLoading(false);
947
1499
  }
948
- }, [canOpenTeamManagement, teamAdminHeaders]);
1500
+ }, [canManageTeamWorkspace, teamAdminHeaders]);
949
1501
  const loadTeamAuditPage = useCallback(async (page) => {
950
- if (!canOpenTeamManagement)
1502
+ if (!canManageTeamWorkspace)
951
1503
  return;
952
1504
  const safePage = Math.max(0, Math.floor(page));
953
1505
  setTeamAuditLoading(true);
954
1506
  setTeamMgmtError(null);
955
1507
  try {
956
1508
  const offset = safePage * TEAM_AUDIT_PAGE_SIZE;
957
- const res = await fetch(`/api/taskforce/admin/audit-logs?limit=${TEAM_AUDIT_PAGE_SIZE}&offset=${offset}`, {
1509
+ const res = await fetch(`/api/taskforce/admin/workspace-audit-logs?limit=${TEAM_AUDIT_PAGE_SIZE}&offset=${offset}`, {
958
1510
  method: 'GET',
959
1511
  credentials: 'include',
960
1512
  headers: teamAdminHeaders()
@@ -983,7 +1535,7 @@ export function StandaloneLayout(props) {
983
1535
  finally {
984
1536
  setTeamAuditLoading(false);
985
1537
  }
986
- }, [canOpenTeamManagement, teamAdminHeaders]);
1538
+ }, [canManageTeamWorkspace, teamAdminHeaders]);
987
1539
  const reloadTeamManagementData = useCallback(async () => {
988
1540
  await Promise.all([loadTeamUsers(), loadTeamAuditPage(teamAuditPage)]);
989
1541
  }, [loadTeamUsers, loadTeamAuditPage, teamAuditPage]);
@@ -995,10 +1547,14 @@ export function StandaloneLayout(props) {
995
1547
  setShowSyncStatusModal(false);
996
1548
  navigate(`/login?mode=${mode}&next=${encodeURIComponent(target)}`);
997
1549
  }, [location.hash, location.pathname, location.search, navigate]);
998
- const openWorkspaceSetup = useCallback(() => {
1550
+ const openWorkspaceSetup = useCallback((options) => {
999
1551
  setShowAccountMenu(false);
1000
- setWorkspacePanelOpen(false);
1001
- navigate('/setup?step=workspace', { replace: true });
1552
+ setShowCreateWorkspaceConfirm(false);
1553
+ const params = new URLSearchParams({ step: 'workspace' });
1554
+ if (options?.intent === 'create-workspace') {
1555
+ params.set('intent', 'create-workspace');
1556
+ }
1557
+ navigate(`/setup?${params.toString()}`, { replace: true });
1002
1558
  }, [navigate]);
1003
1559
  const handleSwitchWorkspace = useCallback(async (workspaceId) => {
1004
1560
  if (!workspaceId || workspaceActionBusy)
@@ -1017,47 +1573,18 @@ export function StandaloneLayout(props) {
1017
1573
  }
1018
1574
  await fetchTasks(true);
1019
1575
  setShowAccountMenu(false);
1020
- setWorkspacePanelOpen(false);
1021
1576
  }
1022
1577
  finally {
1023
1578
  setWorkspaceActionBusy(false);
1024
1579
  }
1025
1580
  }, [switchWorkspace, workspaceActionBusy, fetchTasks, openWorkspaceSetup]);
1026
- const handleCreateWorkspace = useCallback(async () => {
1027
- if (workspaceActionBusy)
1028
- return;
1029
- const name = workspaceFormName.trim();
1030
- if (!name) {
1031
- setWorkspaceActionError('Workspace name is required.');
1581
+ const openTeamManagementModal = useCallback(async () => {
1582
+ setShowAccountMenu(false);
1583
+ setShowTeamManagementModal(true);
1584
+ setTeamManagementTab('members');
1585
+ const resolvedMode = await loadTeamPlanMode();
1586
+ if (resolvedMode !== 'team')
1032
1587
  return;
1033
- }
1034
- setWorkspaceActionError('');
1035
- setWorkspaceActionBusy(true);
1036
- try {
1037
- const result = await createWorkspace(name, workspaceFormDescription.trim() || undefined);
1038
- if (!result.success) {
1039
- if (result.code === 'WORKSPACE_NOT_FOUND' || result.code === 'WORKSPACE_ID_REQUIRED') {
1040
- openWorkspaceSetup();
1041
- return;
1042
- }
1043
- setWorkspaceActionError(result.error || 'Failed to create workspace.');
1044
- return;
1045
- }
1046
- await fetchTasks(true);
1047
- setWorkspaceFormName('');
1048
- setWorkspaceFormDescription('');
1049
- setWorkspacePanelOpen(false);
1050
- setShowAccountMenu(false);
1051
- }
1052
- finally {
1053
- setWorkspaceActionBusy(false);
1054
- }
1055
- }, [workspaceActionBusy, workspaceFormName, workspaceFormDescription, createWorkspace, fetchTasks, openWorkspaceSetup]);
1056
- const openTeamManagementModal = useCallback(async () => {
1057
- setShowAccountMenu(false);
1058
- setShowTeamManagementModal(true);
1059
- setTeamManagementTab('members');
1060
- await loadTeamPlanMode();
1061
1588
  await loadTeamUsers();
1062
1589
  await loadTeamAuditPage(0);
1063
1590
  }, [loadTeamAuditPage, loadTeamPlanMode, loadTeamUsers]);
@@ -1096,7 +1623,7 @@ export function StandaloneLayout(props) {
1096
1623
  },
1097
1624
  credentials: 'include',
1098
1625
  body: JSON.stringify({
1099
- workspaceId: currentWorkspaceId,
1626
+ workspaceId: teamManagementWorkspaceId,
1100
1627
  email,
1101
1628
  role: teamInviteRole,
1102
1629
  permissionMode: teamInviteRole === 'member' ? teamInvitePermissionMode : 'read-write'
@@ -1138,27 +1665,64 @@ export function StandaloneLayout(props) {
1138
1665
  const hasTypeFilter = filterTypes.length !== activeTypes.length;
1139
1666
  const hasPriorityFilter = filterPriorities.length !== priorities.length;
1140
1667
  const hasStatusFilter = filterStatus.length !== STATUS_OPTIONS.length;
1141
- const hasAssigneeFilter = assigneeFilterEnabled && filterAssignees.length !== 3;
1142
- const hasParentsOnlyFilter = parentTaskFilterMode !== 'all';
1143
- return hasCategoryFilter || hasTypeFilter || hasPriorityFilter || hasStatusFilter || hasAssigneeFilter || hasParentsOnlyFilter;
1144
- }, [filterCategories, activeCategories, filterTypes, activeTypes, filterPriorities, priorities, filterStatus, assigneeFilterEnabled, filterAssignees, parentTaskFilterMode]);
1668
+ const hasAssigneeFilter = filterAssignees.length !== assigneeOptions.length;
1669
+ const hasInitiativeScopeFilter = selectedPlanningInitiativeId.length > 0;
1670
+ const hasWorkstreamScopeFilter = selectedPlanningWorkstreamId.length > 0;
1671
+ return hasCategoryFilter
1672
+ || hasTypeFilter
1673
+ || hasPriorityFilter
1674
+ || hasStatusFilter
1675
+ || hasAssigneeFilter
1676
+ || hasInitiativeScopeFilter
1677
+ || hasWorkstreamScopeFilter;
1678
+ }, [
1679
+ activeCategories.length,
1680
+ activeTypes.length,
1681
+ assigneeOptions.length,
1682
+ filterAssignees.length,
1683
+ filterCategories.length,
1684
+ filterPriorities.length,
1685
+ filterStatus.length,
1686
+ filterTypes.length,
1687
+ priorities.length,
1688
+ selectedPlanningInitiativeId,
1689
+ selectedPlanningWorkstreamId,
1690
+ ]);
1691
+ const hasDocumentDropdownFilters = useMemo(() => (documentTypeFilters.length !== DOCUMENT_TYPE_FILTER_OPTIONS.length
1692
+ || documentAttachmentFilters.length !== DOCUMENT_ATTACHMENT_FILTER_OPTIONS.length), [documentAttachmentFilters.length, documentTypeFilters.length]);
1693
+ const workspaceChromeMode = resolvedWorkspaceModule;
1694
+ const workspaceChromeDefinition = useMemo(() => getWorkspaceChromeDefinition(workspaceChromeMode), [workspaceChromeMode]);
1695
+ const hasActiveWorkspaceFilterControls = useMemo(() => {
1696
+ switch (workspaceChromeDefinition.filterBar.kind) {
1697
+ case 'task-filters':
1698
+ return hasDropdownFilters;
1699
+ case 'document-filters':
1700
+ return hasDocumentDropdownFilters;
1701
+ default:
1702
+ return false;
1703
+ }
1704
+ }, [
1705
+ hasDocumentDropdownFilters,
1706
+ hasDropdownFilters,
1707
+ workspaceChromeDefinition.filterBar.kind,
1708
+ ]);
1145
1709
  // Check if any filters are set (search + dropdown filters)
1146
1710
  const isFiltered = hasSearchFilter || hasDropdownFilters;
1147
- const totalActiveCount = tasks.length;
1148
- const visibleActiveCount = props.filteredTasks.length;
1711
+ const totalActiveCount = taskScope === 'archived'
1712
+ ? archivedScopeAllTasks.length
1713
+ : taskScope === 'deleted'
1714
+ ? deletedScopeAllTasks.length
1715
+ : tasks.length;
1716
+ const visibleActiveCount = taskScope === 'archived'
1717
+ ? archivedScopeTasks.length
1718
+ : taskScope === 'deleted'
1719
+ ? filteredDeletedTasks.length
1720
+ : props.filteredTasks.length;
1149
1721
  const activeCountLabel = isFiltered ? `${visibleActiveCount}/${totalActiveCount}` : `${totalActiveCount}`;
1150
1722
  const activeCountTitle = isFiltered
1151
- ? t('standalone.visibleActiveTasksTitle')
1152
- : t('standalone.totalActiveTasksTitle');
1153
- const shouldShowSyncStatus = runtimeMode === 'local';
1154
- const formattedLastSyncTime = useMemo(() => {
1155
- if (!userGlobalLastSyncedAt)
1156
- return 'Never';
1157
- const parsed = new Date(userGlobalLastSyncedAt);
1158
- if (Number.isNaN(parsed.getTime()))
1159
- return 'Never';
1160
- return parsed.toLocaleString();
1161
- }, [userGlobalLastSyncedAt]);
1723
+ ? `${taskScope.charAt(0).toUpperCase() + taskScope.slice(1)} tasks matching current filters`
1724
+ : `${taskScope.charAt(0).toUpperCase() + taskScope.slice(1)} tasks`;
1725
+ const shouldShowSyncStatus = runtimeMode === 'local' && isAuthenticated;
1162
1726
  const formatSyncTimestamp = useCallback((value) => {
1163
1727
  if (!value)
1164
1728
  return 'Never';
@@ -1167,259 +1731,379 @@ export function StandaloneLayout(props) {
1167
1731
  return 'Never';
1168
1732
  return parsed.toLocaleString();
1169
1733
  }, []);
1170
- const formattedLastPullTime = useMemo(() => formatSyncTimestamp(workspaceLastPullAt), [workspaceLastPullAt, formatSyncTimestamp]);
1171
- const formattedLastPushTime = useMemo(() => formatSyncTimestamp(workspaceLastPushAt), [workspaceLastPushAt, formatSyncTimestamp]);
1172
- const formattedLastErrorTime = useMemo(() => formatSyncTimestamp(workspaceLastErrorAt), [workspaceLastErrorAt, formatSyncTimestamp]);
1173
- const formattedRetryTime = useMemo(() => formatSyncTimestamp(workspaceRetryAt), [workspaceRetryAt, formatSyncTimestamp]);
1174
- const [displaySyncState, setDisplaySyncState] = useState(() => (workspaceCloudSyncEnabled ? userGlobalSyncStatus : 'disconnected'));
1175
- const syncStateSinceRef = useRef(Date.now());
1176
- const syncStateTimerRef = useRef(null);
1177
- const rawSyncState = useMemo(() => {
1178
- if (!workspaceCloudSyncEnabled)
1179
- return 'disconnected';
1180
- if (userGlobalSyncStatus === 'error')
1181
- return 'error';
1182
- if (realtimeSyncEnabled && realtimeConnectionState === 'degraded-fallback')
1183
- return 'degraded-fallback';
1184
- if (realtimeSyncEnabled && realtimeConnectionState === 'reconnecting')
1185
- return 'reconnecting';
1186
- return userGlobalSyncStatus;
1187
- }, [workspaceCloudSyncEnabled, userGlobalSyncStatus, realtimeSyncEnabled, realtimeConnectionState]);
1734
+ const formattedLastSyncTime = useMemo(() => formatSyncTimestamp(workspaceLastSuccessfulSyncAt), [formatSyncTimestamp, workspaceLastSuccessfulSyncAt]);
1735
+ const formattedLastPullTime = useMemo(() => formatSyncTimestamp(workspaceLastPullAt), [formatSyncTimestamp, workspaceLastPullAt]);
1736
+ const formattedLastPushTime = useMemo(() => formatSyncTimestamp(workspaceLastPushAt), [formatSyncTimestamp, workspaceLastPushAt]);
1737
+ const rawSyncLastError = workspaceSyncError || workspaceLastErrorMessage || userGlobalSyncError || 'None';
1738
+ const headerSyncPresentation = useMemo(() => resolveHeaderWorkspaceSyncPresentation({
1739
+ runtimeMode,
1740
+ isAuthenticated,
1741
+ workspaceSyncStatus,
1742
+ workspaceCloudSyncEnabled,
1743
+ workspaceSyncSummary,
1744
+ workspaceSyncRecommendedAction,
1745
+ workspaceSyncError: rawSyncLastError
1746
+ }), [
1747
+ runtimeMode,
1748
+ isAuthenticated,
1749
+ workspaceSyncStatus,
1750
+ workspaceCloudSyncEnabled,
1751
+ workspaceSyncSummary,
1752
+ workspaceSyncRecommendedAction,
1753
+ rawSyncLastError
1754
+ ]);
1755
+ const syncLastError = headerSyncPresentation.lastError;
1756
+ const [headerSyncStatus, setHeaderSyncStatus] = useState(headerSyncPresentation.status);
1757
+ const syncStageLabel = useMemo(() => {
1758
+ if (workspaceSyncRepairBusy) {
1759
+ return 'Repairing';
1760
+ }
1761
+ if (workspaceSyncBusy && workspaceSyncPhase === 'error') {
1762
+ return 'Recovering';
1763
+ }
1764
+ switch (workspaceSyncPhase) {
1765
+ case 'provision-local':
1766
+ return 'Initial cloud upload';
1767
+ case 'attach-cloud':
1768
+ return 'Initial cloud pull';
1769
+ case 'active':
1770
+ return 'Active';
1771
+ case 'error':
1772
+ return 'Error';
1773
+ default:
1774
+ return 'Idle';
1775
+ }
1776
+ }, [workspaceSyncPhase, workspaceSyncRepairBusy]);
1777
+ const workspaceSyncDiagnostics = getWorkspaceSyncDiagnostics();
1778
+ const syncDiagnosticsSummary = useMemo(() => ([
1779
+ {
1780
+ label: 'AI profile snapshot',
1781
+ value: `${workspaceSyncDiagnostics.aiProfileSnapshotCount} local profile${workspaceSyncDiagnostics.aiProfileSnapshotCount === 1 ? '' : 's'}`
1782
+ },
1783
+ {
1784
+ label: 'AI profile raw response',
1785
+ value: `${workspaceSyncDiagnostics.aiProfileSnapshotRawCount} from route`
1786
+ },
1787
+ {
1788
+ label: 'Last pushed AI profiles',
1789
+ value: `${workspaceSyncDiagnostics.lastPushedAiProfileCount} tracked`
1790
+ },
1791
+ {
1792
+ label: 'AI watermark map',
1793
+ value: `${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount} tracked`
1794
+ },
1795
+ {
1796
+ label: 'Document snapshot',
1797
+ value: `${workspaceSyncDiagnostics.documentSnapshotCount} local doc${workspaceSyncDiagnostics.documentSnapshotCount === 1 ? '' : 's'}`
1798
+ },
1799
+ {
1800
+ label: 'Asset snapshot',
1801
+ value: `${workspaceSyncDiagnostics.assetSnapshotCount} local asset${workspaceSyncDiagnostics.assetSnapshotCount === 1 ? '' : 's'}`
1802
+ },
1803
+ {
1804
+ label: 'Queued full AI sync',
1805
+ value: workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'
1806
+ },
1807
+ {
1808
+ label: 'AI snapshot last fetch',
1809
+ value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt
1810
+ ? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString()
1811
+ : 'Never'
1812
+ },
1813
+ {
1814
+ label: 'AI snapshot fetch error',
1815
+ value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'
1816
+ },
1817
+ {
1818
+ label: 'AI snapshot skip reason',
1819
+ value: workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'
1820
+ }
1821
+ ]), [workspaceSyncDiagnostics]);
1822
+ const shouldDelayHeaderSyncing = headerSyncPresentation.actionable
1823
+ && workspaceSyncStatus === 'syncing'
1824
+ && workspaceSyncPhase === 'active'
1825
+ && workspaceSyncPendingChanges === 0;
1188
1826
  useEffect(() => {
1189
- if (syncStateTimerRef.current !== null) {
1190
- window.clearTimeout(syncStateTimerRef.current);
1191
- syncStateTimerRef.current = null;
1192
- }
1193
- const applyState = (next) => {
1194
- setDisplaySyncState(next);
1195
- syncStateSinceRef.current = Date.now();
1196
- };
1197
- if (rawSyncState === displaySyncState)
1198
- return;
1199
- // Delay "syncing" to avoid flicker on very fast sync cycles.
1200
- if (rawSyncState === 'syncing' || rawSyncState === 'reconnecting') {
1201
- syncStateTimerRef.current = window.setTimeout(() => {
1202
- applyState(rawSyncState);
1203
- }, SYNCING_DISPLAY_DELAY_MS);
1827
+ if (!shouldDelayHeaderSyncing) {
1828
+ setHeaderSyncStatus(headerSyncPresentation.status);
1204
1829
  return;
1205
1830
  }
1206
- // Hold syncing/error briefly so status doesn't flash rapidly.
1207
- if (displaySyncState === 'syncing' || displaySyncState === 'reconnecting') {
1208
- const elapsed = Date.now() - syncStateSinceRef.current;
1209
- const remaining = Math.max(0, SYNCING_MIN_VISIBLE_MS - elapsed);
1210
- syncStateTimerRef.current = window.setTimeout(() => {
1211
- applyState(rawSyncState);
1212
- }, remaining);
1831
+ const timeoutId = window.setTimeout(() => {
1832
+ setHeaderSyncStatus(headerSyncPresentation.status);
1833
+ }, 1200);
1834
+ return () => window.clearTimeout(timeoutId);
1835
+ }, [headerSyncPresentation.status, shouldDelayHeaderSyncing]);
1836
+ const formatSyncEventLine = useCallback((event) => {
1837
+ const occurredAt = typeof event.occurredAt === 'string' && event.occurredAt.trim().length > 0
1838
+ ? event.occurredAt
1839
+ : 'unknown-time';
1840
+ const eventType = typeof event.eventType === 'string' && event.eventType.trim().length > 0
1841
+ ? event.eventType.trim()
1842
+ : 'sync';
1843
+ const status = event.status === 'error' ? 'failed' : 'succeeded';
1844
+ const changeCountValue = Number(event.changeCount);
1845
+ const changeCount = Number.isFinite(changeCountValue)
1846
+ ? ` (${Math.max(0, Math.floor(changeCountValue))} change${Math.floor(changeCountValue) === 1 ? '' : 's'})`
1847
+ : '';
1848
+ const requestMsValue = Number(event.requestMs);
1849
+ const requestMs = Number.isFinite(requestMsValue)
1850
+ ? ` in ${Math.max(0, Math.floor(requestMsValue))}ms`
1851
+ : '';
1852
+ const statusCodeValue = Number(event.statusCode);
1853
+ const statusCode = Number.isFinite(statusCodeValue)
1854
+ ? ` [${Math.floor(statusCodeValue)}]`
1855
+ : '';
1856
+ const errorMessage = typeof event.errorMessage === 'string' && event.errorMessage.trim().length > 0
1857
+ ? `: ${event.errorMessage.trim()}`
1858
+ : '';
1859
+ return `${occurredAt} ${eventType} ${status}${changeCount}${requestMs}${statusCode}${errorMessage}`;
1860
+ }, []);
1861
+ const syncEventRows = useMemo(() => {
1862
+ if (syncRecentEventsLoading && syncRecentEvents.length === 0) {
1863
+ return [{
1864
+ key: 'loading',
1865
+ text: 'Loading recent sync events...',
1866
+ tone: 'muted'
1867
+ }];
1868
+ }
1869
+ if (syncRecentEventsError) {
1870
+ return [{
1871
+ key: 'error',
1872
+ text: syncRecentEventsError,
1873
+ tone: 'error'
1874
+ }];
1875
+ }
1876
+ if (syncRecentEvents.length === 0) {
1877
+ return [{
1878
+ key: 'empty',
1879
+ text: 'No recent sync events recorded.',
1880
+ tone: 'muted'
1881
+ }];
1882
+ }
1883
+ return syncRecentEvents.map((event, index) => ({
1884
+ key: getSyncEventStableKey(event, index),
1885
+ text: formatSyncEventLine(event),
1886
+ tone: event.status === 'error' ? 'error' : 'default'
1887
+ }));
1888
+ }, [formatSyncEventLine, syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading]);
1889
+ const referenceMismatchCount = useMemo(() => getActiveReferenceMismatchCount(syncRecentEvents), [syncRecentEvents]);
1890
+ const activeReferenceMismatchEvents = useMemo(() => getActiveReferenceMismatchEvents(syncRecentEvents), [syncRecentEvents]);
1891
+ const activeReferenceMismatchSummaries = useMemo(() => (activeReferenceMismatchEvents.map((event, index) => {
1892
+ const details = event.details && typeof event.details === 'object' ? event.details : null;
1893
+ const pathValue = String(details?.path || '').trim();
1894
+ const existingRefValue = Number(details?.existingReferenceNumber);
1895
+ const incomingRefValue = Number(details?.incomingReferenceNumber);
1896
+ const pathLabel = pathValue || `Mismatch ${index + 1}`;
1897
+ const refsLabel = (Number.isFinite(existingRefValue) || Number.isFinite(incomingRefValue))
1898
+ ? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
1899
+ : null;
1900
+ return {
1901
+ key: getSyncEventStableKey(event, index),
1902
+ pathLabel,
1903
+ refsLabel,
1904
+ };
1905
+ })), [activeReferenceMismatchEvents]);
1906
+ useLayoutEffect(() => {
1907
+ const pendingRestore = pendingSyncEventsScrollRestoreRef.current;
1908
+ const listEl = syncEventsListRef.current;
1909
+ if (!pendingRestore || !listEl)
1213
1910
  return;
1911
+ const delta = listEl.scrollHeight - pendingRestore.scrollHeight;
1912
+ listEl.scrollTop = pendingRestore.scrollTop + Math.max(0, delta);
1913
+ pendingSyncEventsScrollRestoreRef.current = null;
1914
+ }, [syncRecentEvents]);
1915
+ const loadRecentSyncEvents = useCallback(async () => {
1916
+ const res = await fetch(`/api/taskforce/sync/events?workspace_id=${encodeURIComponent(currentWorkspaceId)}&limit=15`, {
1917
+ method: 'GET',
1918
+ credentials: 'include'
1919
+ });
1920
+ if (!res.ok) {
1921
+ throw new Error(`Failed to load sync events (${res.status})`);
1214
1922
  }
1215
- if (displaySyncState === 'error' && rawSyncState !== 'error') {
1216
- const elapsed = Date.now() - syncStateSinceRef.current;
1217
- const remaining = Math.max(0, ERROR_MIN_VISIBLE_MS - elapsed);
1218
- syncStateTimerRef.current = window.setTimeout(() => {
1219
- applyState(rawSyncState);
1220
- }, remaining);
1923
+ const data = await res.json().catch(() => ({}));
1924
+ return Array.isArray(data?.events)
1925
+ ? data.events.filter((event) => event && typeof event === 'object')
1926
+ : [];
1927
+ }, [currentWorkspaceId]);
1928
+ useEffect(() => {
1929
+ if (!showSyncStatusModal)
1221
1930
  return;
1931
+ let cancelled = false;
1932
+ const isInitialLoad = syncRecentEvents.length === 0;
1933
+ if (isInitialLoad) {
1934
+ setSyncRecentEventsLoading(true);
1222
1935
  }
1223
- applyState(rawSyncState);
1224
- }, [rawSyncState, displaySyncState]);
1225
- useEffect(() => {
1226
- return () => {
1227
- if (syncStateTimerRef.current !== null) {
1228
- window.clearTimeout(syncStateTimerRef.current);
1229
- syncStateTimerRef.current = null;
1936
+ void loadRecentSyncEvents()
1937
+ .then((events) => {
1938
+ if (cancelled)
1939
+ return;
1940
+ const listEl = syncEventsListRef.current;
1941
+ pendingSyncEventsScrollRestoreRef.current = listEl && listEl.scrollTop > 8
1942
+ ? {
1943
+ scrollTop: listEl.scrollTop,
1944
+ scrollHeight: listEl.scrollHeight
1945
+ }
1946
+ : null;
1947
+ setSyncRecentEvents((current) => {
1948
+ const next = current.length === 0 ? events : mergeRecentSyncEvents(current, events, 15);
1949
+ return areSyncEventListsEqual(current, next) ? current : next;
1950
+ });
1951
+ setSyncRecentEventsError(null);
1952
+ })
1953
+ .catch((error) => {
1954
+ if (cancelled)
1955
+ return;
1956
+ const message = error instanceof Error && error.message.trim().length > 0
1957
+ ? error.message.trim()
1958
+ : 'Unable to load recent sync events.';
1959
+ setSyncRecentEventsError(message);
1960
+ if (isInitialLoad) {
1961
+ setSyncRecentEvents([]);
1230
1962
  }
1963
+ })
1964
+ .finally(() => {
1965
+ if (cancelled)
1966
+ return;
1967
+ if (isInitialLoad) {
1968
+ setSyncRecentEventsLoading(false);
1969
+ }
1970
+ });
1971
+ return () => {
1972
+ cancelled = true;
1231
1973
  };
1232
- }, []);
1233
- const modalSyncStatusMeta = useMemo(() => {
1234
- if (!workspaceCloudSyncEnabled) {
1235
- return {
1236
- label: 'Sync Off',
1237
- color: '#94a3b8',
1238
- border: 'rgba(148, 163, 184, 0.45)',
1239
- background: 'rgba(148, 163, 184, 0.12)'
1240
- };
1241
- }
1242
- switch (displaySyncState) {
1243
- case 'syncing':
1244
- return {
1245
- label: 'Syncing',
1246
- color: '#f59e0b',
1247
- border: 'rgba(245, 158, 11, 0.45)',
1248
- background: 'rgba(245, 158, 11, 0.16)'
1249
- };
1250
- case 'reconnecting':
1251
- return {
1252
- label: 'Reconnecting',
1253
- color: '#f59e0b',
1254
- border: 'rgba(245, 158, 11, 0.45)',
1255
- background: 'rgba(245, 158, 11, 0.16)'
1256
- };
1257
- case 'degraded-fallback':
1974
+ }, [
1975
+ showSyncStatusModal,
1976
+ loadRecentSyncEvents,
1977
+ syncRecentEvents.length,
1978
+ workspaceLastPushAt,
1979
+ workspaceLastPullAt,
1980
+ workspaceLastErrorAt,
1981
+ workspaceSyncStatus
1982
+ ]);
1983
+ const syncStatusMeta = useMemo(() => {
1984
+ switch (headerSyncStatus) {
1985
+ case 'off':
1258
1986
  return {
1259
- label: 'Fallback Pull',
1260
- color: '#fb923c',
1261
- border: 'rgba(251, 146, 60, 0.45)',
1262
- background: 'rgba(251, 146, 60, 0.16)'
1987
+ label: 'Off',
1988
+ icon: 'off',
1989
+ color: '#94a3b8',
1990
+ border: 'rgba(148, 163, 184, 0.35)',
1991
+ background: 'rgba(148, 163, 184, 0.12)'
1263
1992
  };
1264
- case 'error':
1993
+ case 'syncing':
1265
1994
  return {
1266
- label: 'Sync Error',
1267
- color: '#f87171',
1268
- border: 'rgba(248, 113, 113, 0.45)',
1269
- background: 'rgba(248, 113, 113, 0.14)'
1995
+ label: 'Syncing',
1996
+ icon: 'cloud',
1997
+ color: '#60a5fa',
1998
+ border: 'rgba(96, 165, 250, 0.35)',
1999
+ background: 'rgba(59, 130, 246, 0.12)'
1270
2000
  };
1271
- case 'disconnected':
2001
+ case 'attention':
1272
2002
  return {
1273
- label: 'Disconnected',
1274
- color: '#94a3b8',
1275
- border: 'rgba(148, 163, 184, 0.45)',
1276
- background: 'rgba(148, 163, 184, 0.12)'
2003
+ label: 'Needs Attention',
2004
+ icon: 'cloud',
2005
+ color: '#fda4af',
2006
+ border: 'rgba(253, 164, 175, 0.4)',
2007
+ background: 'rgba(239, 68, 68, 0.12)'
1277
2008
  };
1278
- case 'idle':
1279
2009
  default:
1280
2010
  return {
1281
- label: 'Synced',
1282
- color: '#34d399',
1283
- border: 'rgba(52, 211, 153, 0.45)',
1284
- background: 'rgba(52, 211, 153, 0.14)'
2011
+ label: 'Healthy',
2012
+ icon: 'cloud',
2013
+ color: '#86efac',
2014
+ border: 'rgba(134, 239, 172, 0.4)',
2015
+ background: 'rgba(34, 197, 94, 0.12)'
1285
2016
  };
1286
2017
  }
1287
- }, [displaySyncState, workspaceCloudSyncEnabled]);
1288
- const headerSyncStatusMeta = useMemo(() => {
1289
- if (!workspaceCloudSyncEnabled) {
1290
- return {
1291
- label: 'Sync Off',
1292
- color: '#94a3b8',
1293
- border: 'rgba(148, 163, 184, 0.45)',
1294
- background: 'rgba(148, 163, 184, 0.12)',
1295
- icon: 'off'
1296
- };
1297
- }
1298
- if (displaySyncState === 'error') {
1299
- return {
1300
- label: 'Sync Error',
1301
- color: '#f87171',
1302
- border: 'rgba(248, 113, 113, 0.45)',
1303
- background: 'rgba(248, 113, 113, 0.14)',
1304
- icon: 'off'
1305
- };
2018
+ }, [headerSyncStatus]);
2019
+ const handleCopySyncDetails = useCallback(async () => {
2020
+ const details = [
2021
+ 'Taskforce Sync Manager',
2022
+ `Workspace ID: ${currentWorkspaceId}`,
2023
+ `Status: ${syncStatusMeta.label}`,
2024
+ `Summary: ${headerSyncPresentation.summary}`,
2025
+ `Last successful sync: ${formattedLastSyncTime}`,
2026
+ `Last pull from cloud: ${formattedLastPullTime}`,
2027
+ `Last push to cloud: ${formattedLastPushTime}`,
2028
+ `Last error at: ${workspaceSyncDiagnostics.lastErrorAt ? new Date(workspaceSyncDiagnostics.lastErrorAt).toLocaleString() : 'Never'}`,
2029
+ `Pending local changes: ${workspaceSyncPendingChanges}`,
2030
+ `Last error: ${syncLastError}`,
2031
+ `Sync stage: ${syncStageLabel}`,
2032
+ `AI profile snapshot: ${workspaceSyncDiagnostics.aiProfileSnapshotCount}`,
2033
+ `AI profile raw response: ${workspaceSyncDiagnostics.aiProfileSnapshotRawCount}`,
2034
+ `Last pushed AI profiles tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileCount}`,
2035
+ `AI profile watermarks tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount}`,
2036
+ `AI snapshot last fetch: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt ? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString() : 'Never'}`,
2037
+ `AI snapshot fetch error: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'}`,
2038
+ `AI snapshot skip reason: ${workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'}`,
2039
+ `Document snapshot: ${workspaceSyncDiagnostics.documentSnapshotCount}`,
2040
+ `Asset snapshot: ${workspaceSyncDiagnostics.assetSnapshotCount}`,
2041
+ `Reference mismatches detected: ${referenceMismatchCount}`,
2042
+ `Queued full AI sync: ${workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'}`
2043
+ ];
2044
+ try {
2045
+ const events = syncRecentEvents.length > 0 ? syncRecentEvents : await loadRecentSyncEvents();
2046
+ const recentEventLines = events.map((event) => formatSyncEventLine(event));
2047
+ const copyText = [
2048
+ ...details,
2049
+ '',
2050
+ 'Recent sync events',
2051
+ ...(recentEventLines.length > 0 ? recentEventLines : ['No recent sync events recorded.'])
2052
+ ].join('\n');
2053
+ await navigator.clipboard.writeText(copyText);
2054
+ setWorkspaceSyncCopied(true);
2055
+ window.setTimeout(() => {
2056
+ setWorkspaceSyncCopied(false);
2057
+ }, 1800);
2058
+ pushNotice('Sync details copied to clipboard.', 'success');
1306
2059
  }
1307
- if (displaySyncState === 'degraded-fallback') {
1308
- return {
1309
- label: 'Fallback Pull',
1310
- color: '#fb923c',
1311
- border: 'rgba(251, 146, 60, 0.45)',
1312
- background: 'rgba(251, 146, 60, 0.16)',
1313
- icon: 'off'
1314
- };
2060
+ catch {
2061
+ setWorkspaceSyncCopied(false);
2062
+ pushNotice('Failed to copy sync details.', 'error');
1315
2063
  }
1316
- if (displaySyncState === 'reconnecting') {
1317
- return {
1318
- label: 'Reconnecting',
1319
- color: '#f59e0b',
1320
- border: 'rgba(245, 158, 11, 0.45)',
1321
- background: 'rgba(245, 158, 11, 0.16)',
1322
- icon: 'off'
1323
- };
2064
+ }, [
2065
+ currentWorkspaceId,
2066
+ syncStatusMeta.label,
2067
+ headerSyncPresentation.summary,
2068
+ formattedLastSyncTime,
2069
+ formattedLastPullTime,
2070
+ formattedLastPushTime,
2071
+ workspaceSyncDiagnostics,
2072
+ workspaceSyncPendingChanges,
2073
+ syncLastError,
2074
+ syncStageLabel,
2075
+ referenceMismatchCount,
2076
+ pushNotice,
2077
+ syncRecentEvents,
2078
+ loadRecentSyncEvents,
2079
+ formatSyncEventLine
2080
+ ]);
2081
+ const handleRepairSync = useCallback(async () => {
2082
+ setWorkspaceSyncRepairQueued(false);
2083
+ setWorkspaceSyncRepairBusy(true);
2084
+ try {
2085
+ await resetWorkspaceSyncCursorAndPull();
1324
2086
  }
1325
- if (displaySyncState === 'disconnected') {
1326
- return {
1327
- label: 'Disconnected',
1328
- color: '#94a3b8',
1329
- border: 'rgba(148, 163, 184, 0.45)',
1330
- background: 'rgba(148, 163, 184, 0.12)',
1331
- icon: 'off'
1332
- };
2087
+ finally {
2088
+ setWorkspaceSyncRepairBusy(false);
1333
2089
  }
1334
- return {
1335
- label: 'Synced',
1336
- color: '#34d399',
1337
- border: 'rgba(52, 211, 153, 0.45)',
1338
- background: 'rgba(52, 211, 153, 0.14)',
1339
- icon: 'cloud'
1340
- };
1341
- }, [displaySyncState, workspaceCloudSyncEnabled]);
1342
- const parentFilterLabel = hierarchyRootTaskId
1343
- ? 'Subtree'
1344
- : parentTaskFilterMode === 'parents'
1345
- ? t('standalone.linksParents')
1346
- : parentTaskFilterMode === 'linked'
1347
- ? t('standalone.linksLinked')
1348
- : parentTaskFilterMode === 'unlinked'
1349
- ? t('standalone.linksUnlinked')
1350
- : t('standalone.linksAll');
1351
- const selectedHierarchyRootTask = hierarchyRootTaskId ? hierarchyTaskById.get(hierarchyRootTaskId) : undefined;
1352
- const hierarchySubtreeStats = useMemo(() => {
1353
- if (!hierarchyDescendantIdSet || !selectedHierarchyRootTask)
1354
- return null;
1355
- const selectedTasks = hierarchyTasks.filter((task) => hierarchyDescendantIdSet.has(task.id));
1356
- let active = 0;
1357
- let terminal = 0;
1358
- let overdue = 0;
1359
- selectedTasks.forEach((task) => {
1360
- const isTerminal = task.status === 'done' || task.status === 'cancelled' || Boolean(task.isArchived);
1361
- if (isTerminal) {
1362
- terminal += 1;
1363
- }
1364
- else {
1365
- active += 1;
1366
- if (task.dueDate && task.dueDate < todayDateOnly)
1367
- overdue += 1;
1368
- }
1369
- });
1370
- let depth = 0;
1371
- const visited = new Set();
1372
- const stack = [{ id: selectedHierarchyRootTask.id, depth: 0 }];
1373
- while (stack.length > 0) {
1374
- const current = stack.pop();
1375
- if (visited.has(current.id))
1376
- continue;
1377
- visited.add(current.id);
1378
- if (current.depth > depth)
1379
- depth = current.depth;
1380
- const children = hierarchyChildrenByParent.get(current.id) || [];
1381
- children.forEach((child) => {
1382
- if (!visited.has(child.id) && hierarchyDescendantIdSet.has(child.id)) {
1383
- stack.push({ id: child.id, depth: current.depth + 1 });
1384
- }
1385
- });
2090
+ }, [resetWorkspaceSyncCursorAndPull]);
2091
+ const handleQueueOrRunRepairSync = useCallback(() => {
2092
+ if (workspaceSyncRepairBusy)
2093
+ return;
2094
+ if (workspaceSyncBusy) {
2095
+ setWorkspaceSyncRepairQueued(true);
2096
+ return;
1386
2097
  }
1387
- return { total: selectedTasks.length, active, terminal, overdue, depth };
1388
- }, [hierarchyDescendantIdSet, selectedHierarchyRootTask, hierarchyTasks, todayDateOnly, hierarchyChildrenByParent]);
1389
- const hierarchyVisibleLevelCounts = useMemo(() => {
1390
- const byLevel = new Map();
1391
- hierarchyDepthTasks.forEach((task) => {
1392
- const level = (hierarchyGlobalDepthMap.get(task.id) ?? 0) + 1;
1393
- byLevel.set(level, (byLevel.get(level) || 0) + 1);
1394
- });
1395
- return Array.from(byLevel.entries())
1396
- .sort((a, b) => a[0] - b[0])
1397
- .map(([level, count]) => ({ level, count }));
1398
- }, [hierarchyDepthTasks, hierarchyGlobalDepthMap]);
1399
- const hierarchyVisibleUnlinkedCount = useMemo(() => hierarchyVisibleTasks.filter((task) => !task.parentTaskId && !hierarchyParentIdSet.has(task.id)).length, [hierarchyVisibleTasks, hierarchyParentIdSet]);
1400
- const hierarchyVisibleRootCount = useMemo(() => hierarchyVisibleTasks.filter((task) => !task.parentTaskId && hierarchyParentIdSet.has(task.id)).length, [hierarchyVisibleTasks, hierarchyParentIdSet]);
1401
- const hierarchySubtreeTaskList = useMemo(() => {
1402
- if (!hierarchyRootTaskId || !hierarchyDescendantIdSet)
1403
- return [];
1404
- return hierarchyTasks
1405
- .filter((task) => hierarchyDescendantIdSet.has(task.id))
1406
- .sort((a, b) => {
1407
- const levelA = hierarchyGlobalDepthMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
1408
- const levelB = hierarchyGlobalDepthMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
1409
- if (levelA !== levelB)
1410
- return levelA - levelB;
1411
- return a.title.localeCompare(b.title);
1412
- });
1413
- }, [hierarchyRootTaskId, hierarchyDescendantIdSet, hierarchyTasks, hierarchyGlobalDepthMap]);
1414
- const applyHierarchySearchSelection = useCallback(() => {
1415
- const bestMatch = hierarchySearchMatches[0];
1416
- if (!bestMatch)
2098
+ void handleRepairSync();
2099
+ }, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy]);
2100
+ useEffect(() => {
2101
+ if (!workspaceSyncRepairQueued)
2102
+ return;
2103
+ if (workspaceSyncBusy || workspaceSyncRepairBusy)
1417
2104
  return;
1418
- setHierarchyRootTaskId(bestMatch.id);
1419
- setHierarchyRootSearch('');
1420
- if (parentTaskFilterMode === 'unlinked')
1421
- setParentTaskFilterMode('all');
1422
- }, [hierarchySearchMatches, parentTaskFilterMode, setParentTaskFilterMode]);
2105
+ void handleRepairSync();
2106
+ }, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy, workspaceSyncRepairQueued]);
1423
2107
  const selectedDateObj = useMemo(() => parseDateOnlyLocal(scheduleSelectedDate) || new Date(), [scheduleSelectedDate]);
1424
2108
  const scheduleWeekStart = useMemo(() => startOfWeek(selectedDateObj, globalWeekStartsOn), [selectedDateObj, globalWeekStartsOn]);
1425
2109
  const scheduleDates = useMemo(() => {
@@ -1490,99 +2174,7 @@ export function StandaloneLayout(props) {
1490
2174
  setScheduleBulkBusy(false);
1491
2175
  }
1492
2176
  }, [scheduleBulkBusy, expiredRecoveryCandidates, handleUpdateTask, fetchTasks]);
1493
- const handleScheduleParentDrop = useCallback((payload) => {
1494
- if (payload.targetDate < todayDateOnly) {
1495
- const byId = new Map(tasks.map((task) => [task.id, task]));
1496
- const hasNonTerminalLeaf = payload.toScheduleLeafTaskIds.some((taskId) => {
1497
- const task = byId.get(taskId);
1498
- if (!task)
1499
- return true;
1500
- return task.status !== 'done' && task.status !== 'cancelled';
1501
- });
1502
- if (hasNonTerminalLeaf) {
1503
- window.alert(t('schedule.completedOnlyPastSchedule'));
1504
- return;
1505
- }
1506
- }
1507
- if (payload.toScheduleLeafTaskIds.length === 0) {
1508
- window.alert(t('schedule.noUnscheduledLeafTasks', { parentTitle: payload.parentTitle }));
1509
- return;
1510
- }
1511
- setScheduleParentDropPrompt(payload);
1512
- }, [tasks, todayDateOnly]);
1513
- const confirmScheduleParentDrop = useCallback(async () => {
1514
- if (!scheduleParentDropPrompt || scheduleParentDropBusy)
1515
- return;
1516
- setScheduleParentDropBusy(true);
1517
- try {
1518
- const { targetDate, toScheduleLeafTaskIds } = scheduleParentDropPrompt;
1519
- const dayTasks = tasks
1520
- .filter((task) => task.scheduledDate === targetDate && !toScheduleLeafTaskIds.includes(task.id))
1521
- .sort((a, b) => {
1522
- const orderA = typeof a.orderInDay === 'number' ? a.orderInDay : Number.MAX_SAFE_INTEGER;
1523
- const orderB = typeof b.orderInDay === 'number' ? b.orderInDay : Number.MAX_SAFE_INTEGER;
1524
- if (orderA !== orderB)
1525
- return orderA - orderB;
1526
- return String(a.createdAt || '').localeCompare(String(b.createdAt || ''));
1527
- });
1528
- let nextOrder = dayTasks.length;
1529
- for (const taskId of toScheduleLeafTaskIds) {
1530
- await handleUpdateTask(taskId, {
1531
- scheduledDate: targetDate,
1532
- scheduledWeekKey: dateOnlyToIsoWeekKey(targetDate),
1533
- orderInDay: nextOrder++
1534
- });
1535
- }
1536
- setScheduleParentDropPrompt(null);
1537
- await fetchTasks(true);
1538
- }
1539
- finally {
1540
- setScheduleParentDropBusy(false);
1541
- }
1542
- }, [scheduleParentDropPrompt, scheduleParentDropBusy, tasks, handleUpdateTask, fetchTasks]);
1543
- const cancelScheduleParentDrop = useCallback(() => {
1544
- if (scheduleParentDropBusy)
1545
- return;
1546
- setScheduleParentDropPrompt(null);
1547
- }, [scheduleParentDropBusy]);
1548
- const scheduleMonthDate = useMemo(() => {
1549
- const [yearRaw, monthRaw] = scheduleCalendarMonth.split('-');
1550
- const year = Number(yearRaw);
1551
- const month = Number(monthRaw);
1552
- if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) {
1553
- return new Date(selectedDateObj.getFullYear(), selectedDateObj.getMonth(), 1);
1554
- }
1555
- return new Date(year, month - 1, 1);
1556
- }, [scheduleCalendarMonth, selectedDateObj]);
1557
- const scheduleCalendarMonthLabel = useMemo(() => formatDate(scheduleMonthDate, { month: 'long', year: 'numeric' }, resolvedLocale), [scheduleMonthDate, resolvedLocale]);
1558
2177
  const scheduleWeekLabel = useMemo(() => formatDate(scheduleWeekStart, { month: 'short', day: 'numeric', year: 'numeric' }, resolvedLocale), [scheduleWeekStart, resolvedLocale]);
1559
- const calendarCells = useMemo(() => {
1560
- const start = startOfWeek(scheduleMonthDate, globalWeekStartsOn);
1561
- return Array.from({ length: 42 }, (_, i) => addDays(start, i));
1562
- }, [scheduleMonthDate, globalWeekStartsOn]);
1563
- const calendarTaskSignals = useMemo(() => {
1564
- const byDate = new Map();
1565
- for (const task of scheduleBaseTasks) {
1566
- const dateOnly = task.scheduledDate || '';
1567
- if (!dateOnly)
1568
- continue;
1569
- const isTerminal = task.status === 'done' || task.status === 'cancelled';
1570
- const isExpired = !isTerminal && dateOnly < todayDateOnly;
1571
- const existing = byDate.get(dateOnly);
1572
- if (existing) {
1573
- existing.count += 1;
1574
- if (isExpired)
1575
- existing.hasExpired = true;
1576
- }
1577
- else {
1578
- byDate.set(dateOnly, { count: 1, hasExpired: isExpired });
1579
- }
1580
- }
1581
- return byDate;
1582
- }, [scheduleBaseTasks, todayDateOnly]);
1583
- const calendarWeekdayLabels = useMemo(() => (globalWeekStartsOn === 'sunday'
1584
- ? ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
1585
- : ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']), [globalWeekStartsOn]);
1586
2178
  // Calculate columns based on grouping
1587
2179
  const scheduleColumns = useMemo(() => {
1588
2180
  const orderedKeys = globalWeekStartsOn === 'sunday'
@@ -1620,8 +2212,8 @@ export function StandaloneLayout(props) {
1620
2212
  case 'type':
1621
2213
  return (activeTypes || []).map(t => ({
1622
2214
  ...t,
1623
- icon: TypeMeta[t.value]?.icon,
1624
- color: TypeMeta[t.value]?.color
2215
+ icon: t.icon || TypeMeta[t.value]?.icon,
2216
+ color: t.color || TypeMeta[t.value]?.color
1625
2217
  }));
1626
2218
  case 'priority':
1627
2219
  return priorities;
@@ -1636,159 +2228,718 @@ export function StandaloneLayout(props) {
1636
2228
  case 'approach':
1637
2229
  return approaches;
1638
2230
  case 'assignee':
1639
- return [
1640
- { value: 'agent', label: 'AI', icon: 'Bot', color: '#8b5cf6' },
1641
- { value: 'user', label: 'User', icon: 'User', color: '#22c55e' },
1642
- { value: 'unassigned', label: 'Unassigned', icon: 'Circle', color: '#64748b' }
1643
- ];
2231
+ return assigneeOptions.map((option) => ({
2232
+ value: option.value,
2233
+ label: option.label,
2234
+ icon: option.icon === 'HelpCircle' ? 'Circle' : option.icon,
2235
+ color: option.color,
2236
+ }));
1644
2237
  case 'status':
1645
2238
  return [
1646
- { value: 'task', label: 'To Do', icon: 'Circle', color: 'blue-200' },
1647
- { value: 'on-hold', label: 'Blocked', icon: 'Pause', color: 'amber-500' },
1648
- { value: 'in-progress', label: 'In Progress', icon: 'Play', color: 'blue-500' },
1649
- { value: 'review', label: 'Ready for Review', icon: 'Sparkles', color: 'violet-500' },
1650
- { value: 'completed', label: 'Completed', icon: 'Check', color: 'green-500' }
2239
+ ...STATUS_OPTIONS.filter((option) => option.value !== 'done' && option.value !== 'cancelled'),
2240
+ {
2241
+ value: 'completed',
2242
+ label: 'Completed',
2243
+ icon: getStatusDefinition('done').icon,
2244
+ color: getStatusDefinition('done').color
2245
+ }
1651
2246
  ];
1652
- case 'hierarchy': {
1653
- if (parentTaskFilterMode === 'unlinked') {
1654
- return [{
1655
- value: 'standalone',
1656
- label: 'Unlinked',
1657
- icon: 'Circle',
1658
- color: '#64748b'
1659
- }];
2247
+ case 'schedule':
2248
+ return scheduleColumns;
2249
+ default:
2250
+ return activeCategories;
2251
+ }
2252
+ }, [groupBy, activeCategories, activeTypes, priorities, approaches, assigneeOptions, scheduleColumns]);
2253
+ // === SECTION: AccountMenuControl ===
2254
+ // Not extracted: ~25 distinct props from local state would be needed.
2255
+ // References: isAuthenticated, authIdentityLabel, billingStatus, runtimeLabel,
2256
+ // connectedEnvironmentLabel, canManageCloudWorkspaces, workspaceActionBusy,
2257
+ // setWorkspacePanelOpen, canOpenTeamManagement, openTeamManagementModal, and more.
2258
+ const accountMenuControl = (_jsxs("div", { className: accountStyles.accountMenuWrap, ref: accountMenuRef, children: [_jsx("button", { className: `tf-control-icon ${accountStyles.avatarBtn}`, onClick: () => setShowAccountMenu(prev => !prev), title: isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-label": isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-haspopup": "menu", "aria-expanded": showAccountMenu, children: _jsx("span", { className: accountStyles.avatarBadge, "aria-hidden": "true", children: isAuthenticated ? accountBadgeInitial : '?' }) }), showAccountMenu && (_jsxs("div", { className: accountStyles.accountMenu, role: "menu", "aria-label": "Account menu", children: [_jsxs("div", { className: accountStyles.accountMenuSection, children: [_jsx("div", { className: accountStyles.accountMenuSectionLabel, children: "Account" }), _jsx("div", { className: accountStyles.accountMenuHint, children: isAuthBootstrapPending
2259
+ ? 'Checking sign-in status...'
2260
+ : isAuthenticated
2261
+ ? (_jsx(_Fragment, { children: hasAuthIdentity
2262
+ ? (_jsx(_Fragment, { children: _jsxs("span", { className: accountStyles.accountIdentityBlock, children: [_jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel }), hasAuthEmail && authDisplayName && (_jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Email" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: authEmailLabel })] })), hasSubscription && (_jsxs("button", { "aria-label": `Subscription ${subscriptionLabel}`, className: `${accountStyles.accountIdentityMetaLine} ${accountStyles.accountIdentityMetaAction}`, onClick: () => {
2263
+ setShowAccountMenu(false);
2264
+ openPlansScreen();
2265
+ }, type: "button", children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Subscription" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: subscriptionLabel })] })), _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Runtime" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: runtimeLabel })] }), _jsxs("span", { className: accountStyles.accountIdentityMetaLine, children: [_jsx("span", { className: accountStyles.accountIdentityMetaLabel, children: "Environment" }), _jsx("span", { className: accountStyles.accountIdentityMetaValue, children: connectedEnvironmentLabel })] })] }) }))
2266
+ : 'Signed in' }))
2267
+ : 'Not signed in' })] }), canManageCloudWorkspaces && (_jsxs("div", { className: accountStyles.accountMenuSection, children: [_jsx("div", { className: accountStyles.accountMenuSectionLabel, children: "Workspaces (Owned + Invited)" }), availableWorkspaces.length === 0 && (_jsx("div", { className: accountStyles.accountMenuHint, children: "No workspaces found for this account yet." })), availableWorkspaces.map((workspace) => (_jsxs("button", { className: `${accountStyles.accountMenuItem} ${workspace.id === currentWorkspaceId ? accountStyles.accountMenuItemActive : ''}`, onClick: () => handleSwitchWorkspace(workspace.id), role: "menuitem", disabled: workspaceActionBusy || workspace.id === currentWorkspaceId, title: workspace.description || workspace.name, children: [_jsx(Folder, { size: 15 }), _jsxs("span", { style: { display: 'flex', flexDirection: 'column', gap: '2px' }, children: [_jsx("span", { children: workspace.name }), _jsx("span", { className: accountStyles.accountMenuMeta, children: workspace.role })] })] }, workspace.id))), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2268
+ setWorkspaceActionError('');
2269
+ setShowCreateWorkspaceConfirm(true);
2270
+ setShowAccountMenu(false);
2271
+ }, role: "menuitem", disabled: workspaceActionBusy, children: [_jsx(Plus, { size: 15 }), "Create Workspace"] }), workspaceActionError && (_jsx("div", { className: accountStyles.accountMenuError, children: workspaceActionError }))] })), canOpenTeamManagement && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => { void openTeamManagementModal(); }, role: "menuitem", children: [_jsx(Users, { size: 15 }), "Team Management"] })), isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2272
+ setShowAccountMenu(false);
2273
+ setShowEditProfileModal(true);
2274
+ }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Edit Profile"] })), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2275
+ setShowAccountMenu(false);
2276
+ openPlansScreen();
2277
+ }, role: "menuitem", children: [_jsx(Layers, { size: 15 }), "Plans"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2278
+ setShowAccountMenu(false);
2279
+ setShowAccountHub(true);
2280
+ }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Account Hub"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2281
+ setShowAccountMenu(false);
2282
+ setShowHelpModal(true);
2283
+ }, role: "menuitem", children: [_jsx(HelpCircle, { size: 15 }), "Help & Tutorial"] }), runtimeMode === 'local' && !isAuthenticated && !isAuthBootstrapPending && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2284
+ openSharedLoginRoute('login');
2285
+ }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Sign In / Register"] })), isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: async () => {
2286
+ setShowAccountMenu(false);
2287
+ await logout();
2288
+ }, role: "menuitem", children: [_jsx(LogOut, { size: 15 }), "Sign Out"] }))] }))] }));
2289
+ const cycleEmptyColumnVisibility = useCallback(() => {
2290
+ setEmptyColumnMode((prev) => {
2291
+ if (prev === 'show')
2292
+ return 'collapse';
2293
+ if (prev === 'collapse')
2294
+ return 'hide';
2295
+ return 'show';
2296
+ });
2297
+ }, [setEmptyColumnMode]);
2298
+ const openAddTaskModal = useCallback(() => {
2299
+ resetForm();
2300
+ setActiveWorkspaceModule('tasks');
2301
+ setActiveTab('add');
2302
+ }, [resetForm, setActiveTab, setActiveWorkspaceModule]);
2303
+ const openAddTaskModalForWorkstream = useCallback((workstreamId) => {
2304
+ const workstream = props.workstreams.find((item) => item.id === workstreamId);
2305
+ resetForm();
2306
+ setWorkstreamInput(formatWorkstreamReference(workstream) || workstreamId);
2307
+ setActiveWorkspaceModule('tasks');
2308
+ setActiveTab('add');
2309
+ }, [props.workstreams, resetForm, setActiveTab, setActiveWorkspaceModule, setWorkstreamInput]);
2310
+ const openPlanningEditor = useCallback((editor) => {
2311
+ setPlanningEditor(editor);
2312
+ setPlanningPrimaryCollapsed(false);
2313
+ setPlanningSecondaryEditor(null);
2314
+ setPlanningSecondaryCollapsed(false);
2315
+ setPlanningDraftTitle('');
2316
+ setPlanningDraftDescription('');
2317
+ setPlanningDraftOwner('');
2318
+ const selectedInitiative = editor.entityType === 'workstream' && selectedPlanningInitiativeId
2319
+ ? planningStructure.initiativeById.get(selectedPlanningInitiativeId) || null
2320
+ : null;
2321
+ setPlanningDraftInitiativeId(selectedInitiative ? formatInitiativeReference(selectedInitiative) : '');
2322
+ setPlanningNestedWorkstreamDetailId(null);
2323
+ }, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
2324
+ const openPlanningSecondaryEditor = useCallback((editor, initiativeId) => {
2325
+ setPlanningSecondaryEditor(editor);
2326
+ setPlanningSecondaryCollapsed(false);
2327
+ setPlanningDraftTitle('');
2328
+ setPlanningDraftDescription('');
2329
+ setPlanningDraftOwner('');
2330
+ const selectedInitiative = editor.entityType === 'workstream'
2331
+ ? ((initiativeId ? planningStructure.initiativeById.get(initiativeId) : null)
2332
+ || (selectedPlanningInitiativeId ? planningStructure.initiativeById.get(selectedPlanningInitiativeId) || null : null))
2333
+ : null;
2334
+ setPlanningDraftInitiativeId(selectedInitiative ? formatInitiativeReference(selectedInitiative) : '');
2335
+ setPlanningNestedWorkstreamDetailId(null);
2336
+ }, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
2337
+ const closePlanningEditor = useCallback(() => {
2338
+ setPlanningEditor(null);
2339
+ setPlanningDraftTitle('');
2340
+ setPlanningDraftDescription('');
2341
+ setPlanningDraftOwner('');
2342
+ setPlanningDraftInitiativeId('');
2343
+ }, []);
2344
+ const closePlanningSecondaryEditor = useCallback(() => {
2345
+ setPlanningSecondaryEditor(null);
2346
+ setPlanningDraftTitle('');
2347
+ setPlanningDraftDescription('');
2348
+ setPlanningDraftOwner('');
2349
+ setPlanningDraftInitiativeId('');
2350
+ }, []);
2351
+ const submitPlanningEditor = useCallback(async () => {
2352
+ const activeEditor = planningSecondaryEditor || planningEditor;
2353
+ if (!activeEditor)
2354
+ return;
2355
+ const isSecondaryEditor = Boolean(planningSecondaryEditor);
2356
+ const ownerId = planningDraftOwner.trim() || null;
2357
+ try {
2358
+ if (activeEditor.entityType === 'initiative') {
2359
+ if (activeEditor.mode === 'edit') {
2360
+ await props.updateInitiative(activeEditor.targetId, {
2361
+ title: planningDraftTitle.trim() || 'Untitled Initiative',
2362
+ description: planningDraftDescription.trim() || null,
2363
+ ownerId,
2364
+ });
2365
+ setPlanningDrawerDetail({ type: 'initiative', id: activeEditor.targetId });
2366
+ setPlanningNestedWorkstreamDetailId(null);
2367
+ pushNotice('Initiative updated', 'success');
1660
2368
  }
1661
- const levels = Array.from({ length: hierarchyDepthTasks.length > 0 ? maxHierarchyDepth + 1 : 0 }, (_, index) => index + 1);
1662
- const hierarchyColumns = levels.map((level) => ({
1663
- value: level,
1664
- label: `Level ${level}`,
1665
- icon: 'Layers',
1666
- color: level === 1 ? '#3b82f6' : '#64748b'
1667
- }));
1668
- if (parentTaskFilterMode === 'all' && hierarchyShowUnlinkedColumn) {
1669
- hierarchyColumns.push({
1670
- value: 'standalone',
1671
- label: 'Unlinked',
1672
- icon: 'Circle',
1673
- color: '#64748b'
2369
+ else {
2370
+ const created = await props.createInitiative({
2371
+ title: planningDraftTitle.trim() || 'Untitled Initiative',
2372
+ description: planningDraftDescription.trim() || null,
2373
+ ownerId,
1674
2374
  });
2375
+ setPlanningDrawerDetail({ type: 'initiative', id: created.id });
2376
+ setPlanningNestedWorkstreamDetailId(null);
2377
+ pushNotice('Initiative created', 'success');
1675
2378
  }
1676
- return hierarchyColumns;
1677
2379
  }
1678
- case 'schedule':
1679
- return scheduleColumns;
2380
+ else {
2381
+ const nextInitiativeRaw = planningDraftInitiativeId.trim();
2382
+ const resolvedInitiative = nextInitiativeRaw.length === 0
2383
+ ? null
2384
+ : resolveInitiativeReference(planningStructure.initiatives, nextInitiativeRaw);
2385
+ if (nextInitiativeRaw.length > 0 && !resolvedInitiative) {
2386
+ pushNotice('Initiative not found by that reference.', 'error');
2387
+ return;
2388
+ }
2389
+ const patch = {
2390
+ title: planningDraftTitle.trim() || 'Untitled Workstream',
2391
+ description: planningDraftDescription.trim() || null,
2392
+ ownerId,
2393
+ initiativeId: resolvedInitiative?.id || null,
2394
+ };
2395
+ if (activeEditor.mode === 'edit') {
2396
+ await props.updateWorkstream(activeEditor.targetId, patch);
2397
+ if (isSecondaryEditor) {
2398
+ setPlanningNestedWorkstreamDetailId(activeEditor.targetId);
2399
+ }
2400
+ else {
2401
+ setPlanningDrawerDetail({ type: 'workstream', id: activeEditor.targetId });
2402
+ setPlanningNestedWorkstreamDetailId(null);
2403
+ }
2404
+ pushNotice('Workstream updated', 'success');
2405
+ }
2406
+ else {
2407
+ const created = await props.createWorkstream(patch);
2408
+ if (isSecondaryEditor) {
2409
+ setPlanningNestedWorkstreamDetailId(created.id);
2410
+ }
2411
+ else {
2412
+ setPlanningDrawerDetail({ type: 'workstream', id: created.id });
2413
+ setPlanningNestedWorkstreamDetailId(null);
2414
+ }
2415
+ pushNotice('Workstream created', 'success');
2416
+ }
2417
+ }
2418
+ if (isSecondaryEditor) {
2419
+ closePlanningSecondaryEditor();
2420
+ }
2421
+ else {
2422
+ closePlanningEditor();
2423
+ }
2424
+ }
2425
+ catch (error) {
2426
+ pushNotice(error instanceof Error ? error.message : 'Failed to save planning item.', 'error');
2427
+ }
2428
+ }, [
2429
+ closePlanningEditor,
2430
+ closePlanningSecondaryEditor,
2431
+ planningDraftDescription,
2432
+ planningDraftInitiativeId,
2433
+ planningDraftOwner,
2434
+ planningDraftTitle,
2435
+ planningEditor,
2436
+ planningSecondaryEditor,
2437
+ planningStructure.initiatives,
2438
+ props,
2439
+ pushNotice,
2440
+ ]);
2441
+ const togglePlanningInitiative = useCallback((initiativeId) => {
2442
+ setExpandedPlanningInitiativeIds((current) => {
2443
+ const next = new Set(current);
2444
+ if (next.has(initiativeId)) {
2445
+ next.delete(initiativeId);
2446
+ }
2447
+ else {
2448
+ next.add(initiativeId);
2449
+ }
2450
+ return next;
2451
+ });
2452
+ }, []);
2453
+ const selectPlanningInitiative = useCallback((initiativeId) => {
2454
+ setSelectedPlanningInitiativeId((current) => current === initiativeId ? '' : initiativeId);
2455
+ setSelectedPlanningWorkstreamId('');
2456
+ }, []);
2457
+ const selectPlanningWorkstream = useCallback((workstreamId, initiativeId) => {
2458
+ setSelectedPlanningWorkstreamId((current) => {
2459
+ const nextWorkstreamId = current === workstreamId ? '' : workstreamId;
2460
+ setSelectedPlanningInitiativeId(nextWorkstreamId ? (initiativeId || '') : '');
2461
+ return nextWorkstreamId;
2462
+ });
2463
+ }, []);
2464
+ const openInitiativeDetails = useCallback((initiativeId) => {
2465
+ setPlanningEditor(null);
2466
+ setPlanningSecondaryEditor(null);
2467
+ setPlanningPrimaryCollapsed(false);
2468
+ setPlanningSecondaryCollapsed(false);
2469
+ setPlanningDrawerDetail(initiativeId ? { type: 'initiative', id: initiativeId } : null);
2470
+ setPlanningNestedWorkstreamDetailId(null);
2471
+ }, []);
2472
+ const openWorkstreamDetails = useCallback((workstreamId) => {
2473
+ setPlanningEditor(null);
2474
+ setPlanningSecondaryEditor(null);
2475
+ setPlanningPrimaryCollapsed(false);
2476
+ setPlanningSecondaryCollapsed(false);
2477
+ setPlanningDrawerDetail(workstreamId ? { type: 'workstream', id: workstreamId } : null);
2478
+ setPlanningNestedWorkstreamDetailId(null);
2479
+ }, []);
2480
+ const openNestedWorkstreamDetails = useCallback((workstreamId) => {
2481
+ setPlanningSecondaryEditor(null);
2482
+ setPlanningSecondaryCollapsed(false);
2483
+ setPlanningNestedWorkstreamDetailId(workstreamId);
2484
+ }, []);
2485
+ const ownerValueByLabel = useMemo(() => {
2486
+ const map = new Map();
2487
+ assigneeOptions.forEach((option) => {
2488
+ map.set(option.label, String(option.value));
2489
+ });
2490
+ return map;
2491
+ }, [assigneeOptions]);
2492
+ const openPlanningCreateEditor = useCallback((type, initiativeId) => {
2493
+ if (type === 'workstream' && initiativeId) {
2494
+ openPlanningSecondaryEditor({ mode: 'create', entityType: 'workstream' }, initiativeId);
2495
+ return;
2496
+ }
2497
+ openPlanningEditor({ mode: 'create', entityType: type });
2498
+ if (type === 'workstream') {
2499
+ const initiative = (initiativeId ? planningStructure.initiativeById.get(initiativeId) : null)
2500
+ || (selectedPlanningInitiativeId ? planningStructure.initiativeById.get(selectedPlanningInitiativeId) : null)
2501
+ || null;
2502
+ setPlanningDraftInitiativeId(initiative ? formatInitiativeReference(initiative) : '');
2503
+ }
2504
+ }, [openPlanningEditor, openPlanningSecondaryEditor, planningStructure.initiativeById, selectedPlanningInitiativeId]);
2505
+ const openPlanningEditDialog = useCallback((type, id) => {
2506
+ if (type === 'initiative') {
2507
+ const item = planningStructure.initiativeById.get(id);
2508
+ if (!item)
2509
+ return;
2510
+ setPlanningEditor({ mode: 'edit', entityType: 'initiative', targetId: id });
2511
+ setPlanningDraftTitle(item.title);
2512
+ setPlanningDraftDescription(item.description || '');
2513
+ setPlanningDraftOwner(item.ownerLabel ? (ownerValueByLabel.get(item.ownerLabel) || '') : '');
2514
+ setPlanningDraftInitiativeId('');
2515
+ }
2516
+ else {
2517
+ const item = planningStructure.workstreamById.get(id)
2518
+ || planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === id);
2519
+ if (!item)
2520
+ return;
2521
+ setPlanningEditor({ mode: 'edit', entityType: 'workstream', targetId: id });
2522
+ setPlanningDraftTitle(item.title);
2523
+ setPlanningDraftDescription(item.description || '');
2524
+ setPlanningDraftOwner(item.ownerLabel ? (ownerValueByLabel.get(item.ownerLabel) || '') : '');
2525
+ const initiative = item.initiativeId ? planningStructure.initiativeById.get(item.initiativeId) || null : null;
2526
+ setPlanningDraftInitiativeId(initiative ? formatInitiativeReference(initiative) : '');
2527
+ }
2528
+ setPlanningNestedWorkstreamDetailId(null);
2529
+ }, [ownerValueByLabel, planningStructure.initiativeById, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
2530
+ const handleAssignInitiativeToWorkstream = useCallback(async (workstreamId, initiativeReferenceOverride) => {
2531
+ const workstream = props.workstreams.find((item) => item.id === workstreamId);
2532
+ if (!workstream) {
2533
+ pushNotice('Workstream not found.', 'error');
2534
+ return;
2535
+ }
2536
+ const nextInitiativeRaw = typeof initiativeReferenceOverride === 'string'
2537
+ ? initiativeReferenceOverride.trim()
2538
+ : initiativeReferenceOverride === null
2539
+ ? ''
2540
+ : planningDraftInitiativeId.trim();
2541
+ const resolvedInitiative = nextInitiativeRaw.length === 0
2542
+ ? null
2543
+ : resolveInitiativeReference(planningStructure.initiatives, nextInitiativeRaw);
2544
+ if (nextInitiativeRaw.length > 0 && !resolvedInitiative) {
2545
+ pushNotice('Initiative not found by that reference.', 'error');
2546
+ return;
2547
+ }
2548
+ const normalizedParentId = resolvedInitiative?.id || null;
2549
+ if ((workstream.initiativeId || null) === normalizedParentId) {
2550
+ pushNotice(normalizedParentId ? 'Workstream already belongs to that initiative.' : 'Workstream is already standalone.', 'info');
2551
+ return;
2552
+ }
2553
+ try {
2554
+ await props.updateWorkstream(workstreamId, { initiativeId: normalizedParentId });
2555
+ setPlanningDraftInitiativeId(resolvedInitiative ? formatInitiativeReference(resolvedInitiative) : '');
2556
+ pushNotice(normalizedParentId ? 'Initiative set' : 'Initiative removed', 'success');
2557
+ }
2558
+ catch {
2559
+ pushNotice('Failed to set initiative.', 'error');
2560
+ }
2561
+ }, [planningDraftInitiativeId, planningStructure.initiatives, props, pushNotice]);
2562
+ const handleAssignTaskToWorkstream = useCallback(async (taskId, workstreamId) => {
2563
+ const task = hierarchyTaskById.get(taskId);
2564
+ if (!task) {
2565
+ pushNotice('Task not found.', 'error');
2566
+ return;
2567
+ }
2568
+ const normalizedWorkstreamId = workstreamId || null;
2569
+ if (planningStructure.initiativeById.has(taskId) || planningStructure.workstreamById.has(taskId)) {
2570
+ pushNotice('Only execution tasks can be moved into workstreams.', 'error');
2571
+ return;
2572
+ }
2573
+ if (normalizedWorkstreamId && !planningStructure.workstreamById.has(normalizedWorkstreamId)) {
2574
+ pushNotice('Workstream not found.', 'error');
2575
+ return;
2576
+ }
2577
+ if ((task.workstreamId || null) === normalizedWorkstreamId) {
2578
+ pushNotice(normalizedWorkstreamId ? 'Task already belongs to that workstream.' : 'Task is already standalone.', 'info');
2579
+ return;
2580
+ }
2581
+ try {
2582
+ const res = await fetch(`/api/taskforce/task/${taskId}`, {
2583
+ method: 'PATCH',
2584
+ headers: { 'Content-Type': 'application/json' },
2585
+ body: JSON.stringify({ workstreamId: normalizedWorkstreamId })
2586
+ });
2587
+ if (!res.ok) {
2588
+ const data = await res.json().catch(() => ({}));
2589
+ pushNotice(data?.error || 'Failed to set workstream.', 'error');
2590
+ return;
2591
+ }
2592
+ await res.json().catch(() => null);
2593
+ await fetchTasks(true);
2594
+ pushNotice(normalizedWorkstreamId ? 'Workstream set' : 'Workstream removed', 'success');
2595
+ }
2596
+ catch {
2597
+ pushNotice('Failed to set workstream.', 'error');
2598
+ }
2599
+ }, [fetchTasks, hierarchyTaskById, planningStructure.initiativeById, planningStructure.workstreamById, pushNotice]);
2600
+ const handleAttachTaskToWorkstreamByReference = useCallback(async (workstreamId, taskReference) => {
2601
+ const needle = taskReference.trim();
2602
+ if (!needle) {
2603
+ pushNotice('Enter a task reference.', 'error');
2604
+ return;
2605
+ }
2606
+ const parsedReference = parseTaskReference(needle);
2607
+ const matchedTask = hierarchyTasks.find((task) => (task.id === needle
2608
+ || getTaskReferenceLabel(task) === needle
2609
+ || (parsedReference !== null && task.referenceNumber === parsedReference)));
2610
+ if (!matchedTask) {
2611
+ pushNotice('Task not found by that reference.', 'error');
2612
+ return;
2613
+ }
2614
+ await handleAssignTaskToWorkstream(matchedTask.id, workstreamId);
2615
+ }, [handleAssignTaskToWorkstream, hierarchyTasks, pushNotice]);
2616
+ const handleAttachWorkstreamToInitiativeByReference = useCallback(async (initiativeId, workstreamReference) => {
2617
+ const needle = workstreamReference.trim();
2618
+ if (!needle) {
2619
+ pushNotice('Enter a workstream reference.', 'error');
2620
+ return;
2621
+ }
2622
+ const initiative = planningStructure.initiativeById.get(initiativeId);
2623
+ if (!initiative) {
2624
+ pushNotice('Initiative not found.', 'error');
2625
+ return;
2626
+ }
2627
+ const parsedReference = parseWorkstreamReference(needle);
2628
+ const matchedWorkstream = props.workstreams.find((workstream) => (workstream.id === needle
2629
+ || formatWorkstreamReference(workstream) === needle
2630
+ || (parsedReference !== null && workstream.referenceNumber === parsedReference)));
2631
+ if (!matchedWorkstream) {
2632
+ pushNotice('Workstream not found by that reference.', 'error');
2633
+ return;
2634
+ }
2635
+ await handleAssignInitiativeToWorkstream(matchedWorkstream.id, formatInitiativeReference(initiative));
2636
+ }, [handleAssignInitiativeToWorkstream, planningStructure.initiativeById, props.workstreams, pushNotice]);
2637
+ const handleInitiativeFilterChange = useCallback((initiativeId) => {
2638
+ setSelectedPlanningInitiativeId(initiativeId);
2639
+ setSelectedPlanningWorkstreamId('');
2640
+ }, []);
2641
+ const handleWorkstreamFilterChange = useCallback((workstreamId) => {
2642
+ setSelectedPlanningWorkstreamId(workstreamId);
2643
+ setSelectedPlanningInitiativeId(workstreamId ? planningStructure.workstreamInitiativeIds.get(workstreamId) || '' : '');
2644
+ }, [planningStructure.workstreamInitiativeIds]);
2645
+ const handleModuleChange = useCallback((nextModuleId) => {
2646
+ const nextModule = resolveWorkspaceModule(nextModuleId, workspaceModules);
2647
+ if (nextModule !== nextModuleId) {
2648
+ const unavailableLabel = workspaceModules.find((module) => module.id === nextModuleId)?.label || 'That module';
2649
+ pushNotice(`${unavailableLabel} is not available in this build yet.`, 'error');
2650
+ return;
2651
+ }
2652
+ setActiveWorkspaceModule(nextModule);
2653
+ }, [
2654
+ pushNotice,
2655
+ setActiveWorkspaceModule,
2656
+ workspaceModules,
2657
+ ]);
2658
+ const handlePlanningToolToggle = useCallback(() => {
2659
+ if (resolvedWorkspaceModule === 'tasks' && planningDrawerOpen) {
2660
+ setPlanningDrawerOpen(false);
2661
+ setPlanningDrawerDetail(null);
2662
+ setPlanningNestedWorkstreamDetailId(null);
2663
+ setPlanningEditor(null);
2664
+ return;
2665
+ }
2666
+ if (resolvedWorkspaceModule !== 'tasks') {
2667
+ setActiveWorkspaceModule('tasks');
2668
+ setPlanningDrawerOpen(true);
2669
+ return;
2670
+ }
2671
+ setPlanningDrawerOpen((current) => !current);
2672
+ }, [planningDrawerOpen, resolvedWorkspaceModule, setActiveWorkspaceModule]);
2673
+ const handleBackToTaskFromAnnotatedAttachment = useCallback((taskId) => {
2674
+ setActiveWorkspaceModule('tasks');
2675
+ handleOpenTaskById(taskId);
2676
+ }, [handleOpenTaskById, setActiveWorkspaceModule]);
2677
+ const handleAnnotatedAttachmentOpenTarget = useCallback((target, options) => {
2678
+ setRequestedAnnotatedTarget(target);
2679
+ setRequestedAnnotatedSessionId(options?.sessionId ?? null);
2680
+ setRequestedAnnotatedOpenVersion((current) => current + 1);
2681
+ setActiveWorkspaceModule('annotate');
2682
+ }, [setActiveWorkspaceModule]);
2683
+ const handleAnnotatedAttachmentContextChange = useCallback(({ target, sessionId }) => {
2684
+ setRequestedAnnotatedTarget((current) => {
2685
+ if (!current && !target)
2686
+ return current;
2687
+ if (current
2688
+ && target
2689
+ && current.taskId === target.taskId
2690
+ && current.taskReferenceLabel === target.taskReferenceLabel
2691
+ && current.assetId === target.assetId
2692
+ && current.imageReferenceLabel === target.imageReferenceLabel
2693
+ && current.path === target.path
2694
+ && current.displayName === target.displayName) {
2695
+ return current;
2696
+ }
2697
+ return target;
2698
+ });
2699
+ setRequestedAnnotatedSessionId((current) => (current === sessionId ? current : sessionId));
2700
+ }, []);
2701
+ const sortOptions = useMemo(() => [
2702
+ { value: 'created', label: t('standalone.sortCreated') },
2703
+ { value: 'updated', label: t('standalone.sortUpdated') },
2704
+ { value: 'priority', label: t('standalone.sortPriority') },
2705
+ { value: 'complexity', label: t('standalone.sortComplexity') },
2706
+ ...buildCustomTaxonomySortOptions(taxonomies, [...props.searchAgnosticTasks, ...archivedScopeAllTasks])
2707
+ ], [archivedScopeAllTasks, props.searchAgnosticTasks, taxonomies]);
2708
+ const groupingOptions = useMemo(() => [
2709
+ { value: 'category', label: t('standalone.groupCategory') },
2710
+ { value: 'type', label: t('standalone.groupType') },
2711
+ { value: 'priority', label: t('standalone.groupPriority') },
2712
+ { value: 'complexity', label: t('standalone.groupComplexity') },
2713
+ { value: 'assignee', label: t('standalone.groupAssignee') },
2714
+ { value: 'status', label: t('standalone.groupStatus') },
2715
+ { value: 'schedule', label: t('standalone.groupSchedule') },
2716
+ ], []);
2717
+ const workspaceToolModules = useMemo(() => workspaceModules.filter((module) => module.enabled), [workspaceModules]);
2718
+ const taskDisplayActions = useMemo(() => {
2719
+ const actions = [
2720
+ {
2721
+ key: 'empty-columns',
2722
+ icon: emptyColumnMode === 'show'
2723
+ ? _jsx(Maximize2, { size: 16 })
2724
+ : emptyColumnMode === 'collapse'
2725
+ ? _jsx(Minimize2, { size: 16 })
2726
+ : _jsx(EyeOff, { size: 16 }),
2727
+ title: emptyColumnMode === 'show'
2728
+ ? t('standalone.showEmptyColumns')
2729
+ : emptyColumnMode === 'collapse'
2730
+ ? t('standalone.compressEmptyColumns')
2731
+ : t('standalone.hideEmptyColumns'),
2732
+ onClick: cycleEmptyColumnVisibility,
2733
+ active: emptyColumnMode !== 'show',
2734
+ },
2735
+ {
2736
+ key: 'compressed-cards',
2737
+ icon: compressedCards ? _jsx(ChevronsDown, { size: 16 }) : _jsx(ChevronsUp, { size: 16 }),
2738
+ title: compressedCards ? t('standalone.expandCards') : t('standalone.compressCards'),
2739
+ onClick: () => setCompressedCards(!compressedCards),
2740
+ active: compressedCards,
2741
+ },
2742
+ ];
2743
+ return actions;
2744
+ }, [
2745
+ compressedCards,
2746
+ cycleEmptyColumnVisibility,
2747
+ emptyColumnMode,
2748
+ ]);
2749
+ const universalFilterActions = useMemo(() => [{
2750
+ key: 'filter-toggle',
2751
+ icon: _jsx(Filter, { size: 16 }),
2752
+ title: t('standalone.toggleFilters'),
2753
+ onClick: () => setShowFilters(!showFilters),
2754
+ active: showFilters,
2755
+ className: hasActiveWorkspaceFilterControls ? styles.filterGlow : '',
2756
+ }], [hasActiveWorkspaceFilterControls, setShowFilters, showFilters]);
2757
+ const workspaceFilterBar = useMemo(() => {
2758
+ switch (workspaceChromeDefinition.filterBar.kind) {
2759
+ case 'task-filters':
2760
+ return (_jsx(FilterToolbar, { categoryFilterOptions: categoryFilterOptions, typeFilterOptions: typeFilterOptions, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, filterCategories: filterCategories, setFilterCategories: setFilterCategories, filterTypes: filterTypes, setFilterTypes: setFilterTypes, filterPriorities: filterPriorities, setFilterPriorities: setFilterPriorities, filterStatus: filterStatus, setFilterStatus: setFilterStatus, filterAssignees: filterAssignees, setFilterAssignees: setFilterAssignees, assigneeOptions: assigneeOptions, initiativeFilterOptions: initiativeFilterOptions, selectedInitiativeId: selectedPlanningInitiativeId, setSelectedInitiativeId: handleInitiativeFilterChange, workstreamFilterOptions: workstreamFilterOptions, selectedWorkstreamId: selectedPlanningWorkstreamId, setSelectedWorkstreamId: handleWorkstreamFilterChange, taskScope: taskScope, setTaskScope: setTaskScope, showArchive: showArchive, setShowArchive: setShowArchive, fetchArchive: fetchArchive, onDeleteAllDeleted: () => {
2761
+ const deletedCount = deletedScopeAllTasks.length;
2762
+ if (deletedCount === 0)
2763
+ return;
2764
+ const confirmed = window.confirm(`Permanently delete ${deletedCount} deleted task${deletedCount === 1 ? '' : 's'}? This cannot be undone.`);
2765
+ if (!confirmed)
2766
+ return;
2767
+ void handleEmptyDeletedTasks();
2768
+ }, clearFilters: () => {
2769
+ clearFilters();
2770
+ setSelectedPlanningInitiativeId('');
2771
+ setSelectedPlanningWorkstreamId('');
2772
+ } }));
2773
+ case 'document-filters':
2774
+ return (_jsx(DocumentFilterToolbar, { typeFilters: documentTypeFilters, setTypeFilters: setDocumentTypeFilters, attachmentFilters: documentAttachmentFilters, setAttachmentFilters: setDocumentAttachmentFilters, clearFilters: () => {
2775
+ setDocumentTypeFilters(DOCUMENT_TYPE_FILTER_OPTIONS.map((option) => option.value));
2776
+ setDocumentAttachmentFilters(DOCUMENT_ATTACHMENT_FILTER_OPTIONS.map((option) => option.value));
2777
+ } }));
2778
+ case 'empty':
1680
2779
  default:
1681
- return activeCategories;
2780
+ return _jsx(EmptyWorkspaceFilterBar, {});
1682
2781
  }
1683
- }, [groupBy, activeCategories, activeTypes, priorities, approaches, maxHierarchyDepth, parentTaskFilterMode, scheduleColumns, hierarchyDepthTasks.length, hierarchyShowUnlinkedColumn]);
1684
- return (_jsxs("div", { className: `${styles.standaloneWrapper} ${zenMode ? styles.zenModeEnabled : ''}`, "data-theme": currentTheme, children: [_jsxs("div", { className: `${styles.header} ${styles.standaloneHeader} `, children: [_jsxs("div", { className: `${styles.headerTitle} ${styles.standaloneTitle}`, children: [_jsx("img", { src: logo, alt: "Taskforce Logo", className: styles.brandIcon, onError: (e) => {
1685
- const target = e.currentTarget;
1686
- if (target.src !== favicon) {
1687
- target.src = favicon;
1688
- }
1689
- else {
1690
- target.style.display = 'none';
1691
- }
1692
- } }), _jsx("span", { children: "TaskForce" }), projectName && (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.projectSlash, children: "/" }), _jsx("span", { className: styles.projectName, title: `Workspace ID: ${currentWorkspaceId}`, children: projectName })] })), _jsx("span", { className: styles.taskCountBadge, title: activeCountTitle, children: activeCountLabel }), shouldShowSyncStatus && (_jsx("button", { className: styles.headerActionBtn, onClick: () => setShowSyncStatusModal(true), title: `Cloud sync status: ${headerSyncStatusMeta.label} | Pull: ${formattedLastPullTime} | Push: ${formattedLastPushTime}${realtimeSyncEnabled ? ` | Realtime: ${realtimeConnectionState} (${realtimeSyncFlagSource}) dup=${realtimeTelemetry.duplicateDiscarded} ooo=${realtimeTelemetry.outOfOrderDiscarded} invalid=${realtimeTelemetry.invalidDiscarded}` : ''}`, style: {
1693
- marginLeft: '6px',
1694
- height: '24px',
1695
- width: '24px',
1696
- padding: 0,
1697
- borderRadius: '999px',
1698
- border: `1px solid ${headerSyncStatusMeta.border}`,
1699
- background: headerSyncStatusMeta.background,
1700
- color: headerSyncStatusMeta.color,
1701
- display: 'inline-flex',
1702
- alignItems: 'center',
1703
- justifyContent: 'center'
1704
- }, children: headerSyncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 12 })) : (_jsx(Cloud, { size: 12 })) })), props.loadingTasks && (_jsx("div", { style: { marginLeft: '8px', display: 'flex', alignItems: 'center' }, children: _jsx(Loader2, { size: 14, className: styles.spinner }) })), successBanner && (_jsxs("div", { className: styles.headerSuccess, children: [_jsx(Check, { size: 14 }), _jsx("span", { children: successBanner.message })] }))] }), _jsxs("div", { className: styles.headerActions, style: { gap: '16px' }, children: [_jsxs("div", { className: styles.searchContainer, style: { width: '240px' }, children: [_jsx(Search, { size: 16, className: styles.searchIcon }), _jsx("input", { type: "text", className: `${styles.searchInput} ${hasSearchFilter ? styles.searchActive : ''}`, placeholder: t('standalone.searchPlaceholder'), value: searchQuery, onChange: (e) => setSearchQuery(e.target.value) }), searchQuery && (_jsx("button", { className: styles.clearSearchBtn, onClick: () => setSearchQuery(''), title: t('standalone.clearSearchTitle'), children: _jsx(X, { size: 14 }) }))] }), _jsxs("div", { className: styles.groupByContainer, children: [_jsxs("span", { className: styles.groupByLabel, children: [_jsx(ArrowUpDown, { size: 12 }), " ", t('standalone.sortLabel')] }), _jsxs("select", { className: `${styles.select} ${styles.groupBySelect} `, value: sortBy, onChange: (e) => setSortBy(e.target.value), children: [_jsx("option", { value: "created", children: t('standalone.sortCreated') }), _jsx("option", { value: "updated", children: t('standalone.sortUpdated') }), _jsx("option", { value: "priority", children: t('standalone.sortPriority') }), _jsx("option", { value: "complexity", children: t('standalone.sortComplexity') })] }), _jsx("button", { className: `${styles.headerActionBtn} ${styles.sortDirectionBtn}`, onClick: () => props.toggleSortOrder(), title: props.sortOrder === 'desc' ? t('standalone.sortDirectionDescTitle') : t('standalone.sortDirectionAscTitle'), children: props.sortOrder === 'desc' ? _jsx(ArrowDown, { size: 14 }) : _jsx(ArrowUp, { size: 14 }) })] }), _jsx("div", { className: styles.headerDivider }), groupBy !== 'docs' && (_jsxs("div", { className: styles.groupByContainer, children: [_jsxs("span", { className: styles.groupByLabel, children: [_jsx(Layers, { size: 12 }), " ", t('standalone.groupLabel')] }), _jsxs("select", { className: `${styles.select} ${styles.groupBySelect} `, value: groupBy, onChange: (e) => setGroupBy(e.target.value), children: [_jsx("option", { value: "category", children: t('standalone.groupCategory') }), _jsx("option", { value: "type", children: t('standalone.groupType') }), _jsx("option", { value: "priority", children: t('standalone.groupPriority') }), _jsx("option", { value: "complexity", children: t('standalone.groupComplexity') }), _jsx("option", { value: "approach", children: t('standalone.groupApproach') }), _jsx("option", { value: "assignee", children: t('standalone.groupAssignee') }), _jsx("option", { value: "status", children: t('standalone.groupStatus') }), _jsx("option", { value: "hierarchy", children: t('standalone.groupHierarchy') }), _jsx("option", { value: "schedule", children: t('standalone.groupSchedule') })] })] })), _jsx("button", { className: `${styles.headerActionBtn} ${groupBy === 'docs' ? styles.headerActionBtnActive : ''}`, onClick: () => setGroupBy(groupBy === 'docs' ? lastNonDocsGroupByRef.current : 'docs'), title: groupBy === 'docs' ? 'Exit Docs view' : 'Open Docs view', children: _jsx(FileText, { size: 15 }) }), _jsxs("button", { className: `${styles.headerActionBtn} ${emptyColumnMode !== 'show' ? styles.headerActionBtnActive : ''} `, onClick: () => {
1705
- setEmptyColumnMode(prev => {
1706
- if (prev === 'show')
1707
- return 'collapse';
1708
- if (prev === 'collapse')
1709
- return 'hide';
1710
- return 'show';
1711
- });
1712
- }, title: emptyColumnMode === 'show' ? t('standalone.showEmptyColumns') :
1713
- emptyColumnMode === 'collapse' ? t('standalone.compressEmptyColumns') :
1714
- t('standalone.hideEmptyColumns'), children: [emptyColumnMode === 'show' && _jsx(Maximize2, { size: 16 }), emptyColumnMode === 'collapse' && _jsx(Minimize2, { size: 16 }), emptyColumnMode === 'hide' && _jsx(EyeOff, { size: 16 })] }), _jsx("button", { className: `${styles.headerActionBtn} ${compressedCards ? styles.headerActionBtnActive : ''}`, onClick: () => setCompressedCards(!compressedCards), title: compressedCards ? t('standalone.expandCards') : t('standalone.compressCards'), children: compressedCards ? _jsx(ChevronsDown, { size: 16 }) : _jsx(ChevronsUp, { size: 16 }) }), _jsx("button", { className: `${styles.headerActionBtn} ${zenMode ? styles.headerActionBtnActive : ''}`, onClick: () => setZenMode(), title: zenMode ? t('standalone.exitZenMode') : t('standalone.enterZenMode'), children: _jsx(Zap, { size: 16, fill: zenMode ? "currentColor" : "none" }) }), _jsx("div", { className: styles.headerDivider }), _jsx("button", { className: `${styles.headerActionBtn} ${showFilters ? styles.headerActionBtnActive : ''} ${hasDropdownFilters ? styles.filterGlow : ''}`, onClick: () => setShowFilters(!showFilters), title: t('standalone.toggleFilters'), children: _jsx(Filter, { size: 16 }) }), _jsxs("button", { className: styles.primaryUpdateBtn, onClick: () => {
1715
- resetForm();
1716
- setActiveTab('add');
1717
- }, children: [_jsx(Plus, { size: 16 }), " ", t('standalone.addTask')] }), _jsxs("div", { className: styles.accountMenuWrap, ref: accountMenuRef, children: [_jsx("button", { className: `${styles.headerActionBtn} ${styles.avatarBtn}`, onClick: () => setShowAccountMenu(prev => !prev), title: isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-label": isAuthenticated ? 'Account' : 'Account (Not signed in)', "aria-haspopup": "menu", "aria-expanded": showAccountMenu, children: _jsx("span", { className: styles.avatarBadge, "aria-hidden": "true", children: isAuthenticated ? 'U' : '?' }) }), showAccountMenu && (_jsxs("div", { className: styles.accountMenu, role: "menu", "aria-label": "Account menu", children: [_jsxs("div", { className: styles.accountMenuSection, children: [_jsx("div", { className: styles.accountMenuSectionLabel, children: "Account" }), _jsx("div", { className: styles.accountMenuHint, children: isAuthBootstrapPending
1718
- ? 'Checking sign-in status...'
1719
- : isAuthenticated
1720
- ? (_jsx(_Fragment, { children: hasAuthIdentity
1721
- ? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: styles.accountIdentityEmail, children: authIdentityLabel })] })
1722
- : 'Signed in' }))
1723
- : 'Not signed in' })] }), canManageCloudWorkspaces && (_jsxs("div", { className: styles.accountMenuSection, children: [_jsx("div", { className: styles.accountMenuSectionLabel, children: "Workspaces (Owned + Invited)" }), availableWorkspaces.length === 0 && (_jsx("div", { className: styles.accountMenuHint, children: "No workspaces found for this account yet." })), availableWorkspaces.map((workspace) => (_jsxs("button", { className: `${styles.accountMenuItem} ${workspace.id === currentWorkspaceId ? styles.accountMenuItemActive : ''}`, onClick: () => handleSwitchWorkspace(workspace.id), role: "menuitem", disabled: workspaceActionBusy || workspace.id === currentWorkspaceId, title: workspace.description || workspace.name, children: [_jsx(Folder, { size: 15 }), _jsxs("span", { style: { display: 'flex', flexDirection: 'column', gap: '2px' }, children: [_jsx("span", { children: workspace.name }), _jsx("span", { className: styles.accountMenuMeta, children: workspace.role })] })] }, workspace.id))), _jsxs("button", { className: styles.accountMenuItem, onClick: () => {
1724
- setWorkspaceActionError('');
1725
- setWorkspacePanelOpen((prev) => !prev);
1726
- }, role: "menuitem", disabled: workspaceActionBusy, children: [_jsx(Plus, { size: 15 }), workspacePanelOpen ? 'Close Workspace Form' : 'Create Workspace'] }), workspacePanelOpen && (_jsxs("div", { className: styles.accountMenuWorkspaceForm, children: [_jsx("input", { className: styles.input, value: workspaceFormName, onChange: (event) => setWorkspaceFormName(event.target.value), placeholder: "Workspace name", disabled: workspaceActionBusy }), _jsx("input", { className: styles.input, value: workspaceFormDescription, onChange: (event) => setWorkspaceFormDescription(event.target.value), placeholder: "Workspace description (optional)", disabled: workspaceActionBusy }), _jsx("button", { className: styles.primaryUpdateBtn, onClick: handleCreateWorkspace, disabled: workspaceActionBusy, children: workspaceActionBusy ? 'Saving...' : 'Save Workspace' })] })), workspaceActionError && (_jsx("div", { className: styles.accountMenuError, children: workspaceActionError }))] })), canOpenTeamManagement && (_jsxs("button", { className: styles.accountMenuItem, onClick: () => { void openTeamManagementModal(); }, role: "menuitem", children: [_jsx(Users, { size: 15 }), "Team Management"] })), _jsxs("button", { className: styles.accountMenuItem, onClick: () => {
1727
- setShowAccountMenu(false);
1728
- setActiveTab('settings');
1729
- setSettingsSection('general');
1730
- }, role: "menuitem", children: [_jsx(Settings, { size: 15 }), "Workspace Settings"] }), _jsxs("button", { className: styles.accountMenuItem, onClick: () => {
1731
- setShowAccountMenu(false);
1732
- setShowAccountHub(true);
1733
- }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Account Hub"] }), _jsxs("button", { className: styles.accountMenuItem, onClick: () => {
1734
- setShowAccountMenu(false);
1735
- setShowHelpModal(true);
1736
- }, role: "menuitem", children: [_jsx(HelpCircle, { size: 15 }), "Help & Tutorial"] }), runtimeMode === 'local' && !isAuthenticated && !isAuthBootstrapPending && (_jsxs("button", { className: styles.accountMenuItem, onClick: () => {
1737
- openSharedLoginRoute('login');
1738
- }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Sign In / Register"] })), isAuthenticated && (_jsxs("button", { className: styles.accountMenuItem, onClick: async () => {
1739
- setShowAccountMenu(false);
1740
- await logout();
1741
- }, role: "menuitem", children: [_jsx(LogOut, { size: 15 }), "Sign Out"] }))] }))] })] })] }), authBlocked && (_jsx("div", { className: styles.authBlockedBanner, children: "Authentication required for this environment. Use the Account menu to sign in." })), showFilters && (_jsxs("div", { className: styles.filterToolbar, style: {
1742
- padding: '8px 24px',
1743
- borderBottom: '1px solid var(--border-color)',
1744
- background: 'var(--bg-secondary)',
1745
- display: 'flex',
1746
- alignItems: 'center',
1747
- gap: '12px',
1748
- flexWrap: 'wrap',
1749
- position: 'relative',
1750
- zIndex: 50
1751
- }, children: [_jsx("span", { style: { fontSize: '12px', fontWeight: 600, color: 'var(--text-secondary)', marginRight: '4px' }, children: t('standalone.filtersCaps') }), _jsx(MultiSelectFilter, { label: t('standalone.categoryLabel'), options: activeCategories.filter(c => !c.disabled), selected: filterCategories, onChange: (vals) => setFilterCategories(vals), variant: "value" }), _jsx(MultiSelectFilter, { label: t('standalone.typeLabel'), options: activeTypes.map(t => ({ value: t.value, label: t.label })), selected: filterTypes, onChange: (vals) => setFilterTypes(vals), variant: "value" }), _jsx(MultiSelectFilter, { label: t('standalone.priorityLabel'), options: priorities, selected: filterPriorities, onChange: (vals) => setFilterPriorities(vals), variant: "value" }), _jsx(MultiSelectFilter, { label: t('standalone.statusLabel'), options: STATUS_OPTIONS, selected: filterStatus, onChange: (vals) => setFilterStatus(vals), variant: "value" }), assigneeFilterEnabled && (_jsx(MultiSelectFilter, { label: t('standalone.assigneeLabel'), options: [
1752
- { value: 'agent', label: 'AI' },
1753
- { value: 'user', label: 'User' },
1754
- { value: 'unassigned', label: 'Unassigned' }
1755
- ], selected: filterAssignees, onChange: (vals) => setFilterAssignees(vals), variant: "value" })), _jsx("div", { className: styles.headerDivider, style: { height: '16px', margin: '0 8px' } }), _jsxs("div", { className: styles.groupByContainer, children: [_jsxs("span", { className: styles.groupByLabel, children: [_jsx(Layers, { size: 12 }), " ", t('standalone.linksLabel')] }), _jsxs("select", { className: `${styles.select} ${styles.groupBySelect}`, value: parentTaskFilterMode, onChange: (e) => setParentTaskFilterMode(e.target.value), title: t('standalone.chooseLinksTitle'), children: [_jsx("option", { value: "all", children: t('standalone.linksAll') }), _jsx("option", { value: "parents", children: t('standalone.linksParents') }), _jsx("option", { value: "linked", children: t('standalone.linksLinked') }), _jsx("option", { value: "unlinked", children: t('standalone.linksUnlinked') })] })] }), _jsxs("label", { className: styles.archiveToggle, title: t('standalone.includeArchiveTitle'), children: [_jsx("input", { type: "checkbox", checked: showArchive, onChange: (e) => {
1756
- setShowArchive(e.target.checked);
1757
- if (e.target.checked)
1758
- fetchArchive();
1759
- } }), _jsx("span", { children: t('standalone.includeArchive') })] }), _jsx("div", { style: { flex: 1 } }), _jsxs("button", { onClick: clearFilters, style: {
1760
- background: 'none',
1761
- border: 'none',
1762
- color: 'var(--text-secondary)',
1763
- cursor: 'pointer',
1764
- fontSize: '12px',
1765
- display: 'flex',
1766
- alignItems: 'center',
1767
- gap: '4px'
1768
- }, children: [_jsx(X, { size: 12 }), " ", t('standalone.clearFilters')] })] })), groupBy === 'docs' ? (_jsx("div", { className: `${styles.standalonePage} ${styles.standaloneContent}`, style: { position: 'relative', display: 'flex', flex: 1, minHeight: 0, overflow: 'hidden' }, children: _jsx(DocumentWorkspace, { tasks: tasks, apiBaseUrl: props.config?.apiBaseUrl || '', requestedDocPath: requestedDocPath, onRequestedDocHandled: () => setRequestedDocPath(null) }) })) : (_jsxs("div", { className: `${styles.standalonePage} ${styles.standaloneContent} `, style: {
2782
+ }, [
2783
+ assigneeOptions,
2784
+ categoryFilterOptions,
2785
+ clearFilters,
2786
+ deletedScopeAllTasks.length,
2787
+ documentAttachmentFilters,
2788
+ documentTypeFilters,
2789
+ filterAssignees,
2790
+ filterCategories,
2791
+ filterPriorities,
2792
+ filterStatus,
2793
+ filterTypes,
2794
+ handleInitiativeFilterChange,
2795
+ handleWorkstreamFilterChange,
2796
+ initiativeFilterOptions,
2797
+ priorities,
2798
+ selectedPlanningInitiativeId,
2799
+ selectedPlanningWorkstreamId,
2800
+ setDocumentAttachmentFilters,
2801
+ setDocumentTypeFilters,
2802
+ setFilterAssignees,
2803
+ setFilterCategories,
2804
+ setFilterPriorities,
2805
+ setFilterStatus,
2806
+ setFilterTypes,
2807
+ setSelectedPlanningInitiativeId,
2808
+ setSelectedPlanningWorkstreamId,
2809
+ setShowArchive,
2810
+ setTaskScope,
2811
+ showArchive,
2812
+ taskScope,
2813
+ taxonomyDisplayLabels,
2814
+ typeFilterOptions,
2815
+ workstreamFilterOptions,
2816
+ workspaceChromeDefinition.filterBar.kind,
2817
+ fetchArchive,
2818
+ handleEmptyDeletedTasks,
2819
+ ]);
2820
+ const universalDisplayActions = useMemo(() => {
2821
+ const actions = [{
2822
+ key: 'zen-toggle',
2823
+ icon: _jsx(Zap, { size: 16, fill: zenMode ? 'currentColor' : 'none' }),
2824
+ title: zenMode ? t('standalone.exitZenMode') : t('standalone.enterZenMode'),
2825
+ onClick: () => setZenMode(),
2826
+ active: zenMode,
2827
+ }];
2828
+ return actions;
2829
+ }, [
2830
+ zenMode,
2831
+ setZenMode,
2832
+ ]);
2833
+ const headerSectionRegistry = useMemo(() => ({
2834
+ search: (_jsx(WorkspaceHeaderSearch, { value: searchQuery, placeholder: t('standalone.searchPlaceholder'), active: hasSearchFilter, onChange: setSearchQuery, onClear: () => setSearchQuery(''), clearTitle: t('standalone.clearSearchTitle') })),
2835
+ sort: (_jsx(WorkspaceHeaderSelect, { label: _jsxs(_Fragment, { children: [_jsx(ArrowUpDown, { size: 12 }), " ", t('standalone.sortLabel')] }), value: sortBy, options: sortOptions, onChange: (value) => setSortBy(value), trailingAction: (_jsx("button", { className: `tf-control-icon ${styles.sortDirectionBtn}`, onClick: () => props.toggleSortOrder(), title: props.sortOrder === 'desc' ? t('standalone.sortDirectionDescTitle') : t('standalone.sortDirectionAscTitle'), children: props.sortOrder === 'desc' ? _jsx(ArrowDown, { size: 14 }) : _jsx(ArrowUp, { size: 14 }) })) })),
2836
+ 'divider-primary': _jsx(WorkspaceHeaderDivider, {}),
2837
+ grouping: (_jsx(WorkspaceHeaderSelect, { label: _jsxs(_Fragment, { children: [_jsx(Layers, { size: 12 }), " ", t('standalone.groupLabel')] }), value: groupBy, options: groupingOptions, onChange: (value) => setGroupBy(value) })),
2838
+ 'divider-secondary': _jsx(WorkspaceHeaderDivider, {}),
2839
+ 'task-display-actions': (_jsx(WorkspaceHeaderActions, { actions: taskDisplayActions })),
2840
+ }), [
2841
+ groupBy,
2842
+ groupingOptions,
2843
+ handleModuleChange,
2844
+ hasSearchFilter,
2845
+ props,
2846
+ searchQuery,
2847
+ setGroupBy,
2848
+ setSearchQuery,
2849
+ setSortBy,
2850
+ sortBy,
2851
+ sortOptions,
2852
+ taskDisplayActions,
2853
+ workspaceChromeDefinition.moduleId,
2854
+ ]);
2855
+ const primaryActionRegistry = useMemo(() => ({
2856
+ 'add-task': (_jsx(WorkspacePrimaryAction, { icon: _jsx(Plus, { size: 16 }), label: t('standalone.addTask'), onClick: openAddTaskModal })),
2857
+ 'add-document': null,
2858
+ }), [openAddTaskModal]);
2859
+ const workspaceToolRail = useMemo(() => {
2860
+ const moduleIcons = {
2861
+ tasks: _jsx(ClipboardCheck, { size: 18 }),
2862
+ docs: _jsx(FileText, { size: 18 }),
2863
+ annotate: _jsx(Image, { size: 18 }),
2864
+ workflows: _jsx(Workflow, { size: 18 }),
2865
+ agents: _jsx(Bot, { size: 18 }),
2866
+ };
2867
+ return (_jsxs("aside", { className: shellStyles.workspaceToolRail, "aria-label": "Workspace tools", children: [_jsxs("div", { className: shellStyles.workspaceToolRailMain, children: [_jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${(resolvedWorkspaceModule === 'tasks' && planningDrawerOpen) ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: handlePlanningToolToggle, title: resolvedWorkspaceModule === 'tasks' && planningDrawerOpen ? 'Collapse planning trays' : 'Planning', "aria-label": resolvedWorkspaceModule === 'tasks' && planningDrawerOpen ? 'Collapse planning trays' : 'Planning', children: resolvedWorkspaceModule === 'tasks' && planningDrawerOpen ? _jsx(ChevronLeft, { size: 18 }) : _jsx(NotebookTabs, { size: 18 }) }), _jsx("div", { className: shellStyles.workspaceToolRailDivider }), workspaceToolModules.map((module) => (_jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${resolvedWorkspaceModule === module.id ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: () => handleModuleChange(module.id), title: module.label, "aria-label": module.label, children: moduleIcons[module.id] }, module.id)))] }), _jsxs("div", { className: shellStyles.workspaceToolRailBottom, children: [_jsx("div", { className: shellStyles.workspaceToolRailDivider }), _jsx("button", { type: "button", className: `${shellStyles.workspaceToolButton} ${activeTab === 'settings' ? shellStyles.workspaceToolButtonActive : ''}`.trim(), onClick: () => {
2868
+ setSettingsSection('general');
2869
+ setActiveTab('settings');
2870
+ }, title: "Workspace Settings", "aria-label": "Workspace Settings", children: _jsx(Settings, { size: 18 }) })] })] }));
2871
+ }, [activeTab, handleModuleChange, handlePlanningToolToggle, planningDrawerOpen, resolvedWorkspaceModule, setSettingsSection, setActiveTab, workspaceToolModules]);
2872
+ const planningDrawerPortalRef = useRef(null);
2873
+ 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: () => {
2874
+ setPlanningSecondaryEditor(null);
2875
+ setPlanningNestedWorkstreamDetailId(null);
2876
+ }, 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) => {
2877
+ void props.archiveInitiative(initiativeId).then(() => {
2878
+ pushNotice('Initiative archived', 'success');
2879
+ }).catch((error) => {
2880
+ pushNotice(error instanceof Error ? error.message : 'Failed to archive initiative.', 'error');
2881
+ });
2882
+ }, onUnarchiveInitiative: (initiativeId) => {
2883
+ void props.unarchiveInitiative(initiativeId).then(() => {
2884
+ pushNotice('Initiative unarchived', 'success');
2885
+ }).catch((error) => {
2886
+ pushNotice(error instanceof Error ? error.message : 'Failed to unarchive initiative.', 'error');
2887
+ });
2888
+ }, onArchiveWorkstream: (workstreamId) => {
2889
+ void props.archiveWorkstream(workstreamId).then(() => {
2890
+ pushNotice('Workstream archived', 'success');
2891
+ }).catch((error) => {
2892
+ pushNotice(error instanceof Error ? error.message : 'Failed to archive workstream.', 'error');
2893
+ });
2894
+ }, onUnarchiveWorkstream: (workstreamId) => {
2895
+ void props.unarchiveWorkstream(workstreamId).then(() => {
2896
+ pushNotice('Workstream unarchived', 'success');
2897
+ }).catch((error) => {
2898
+ pushNotice(error instanceof Error ? error.message : 'Failed to unarchive workstream.', 'error');
2899
+ });
2900
+ } }));
2901
+ const effectiveHeaderSectionIds = workspaceChromeDefinition.headerSections;
2902
+ const workspaceHeaderSections = effectiveHeaderSectionIds
2903
+ .map((sectionId) => {
2904
+ const section = headerSectionRegistry[sectionId];
2905
+ return section ? _jsx(React.Fragment, { children: section }, sectionId) : null;
2906
+ })
2907
+ .filter(Boolean);
2908
+ const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
2909
+ ? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
2910
+ : null;
2911
+ 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 }), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: () => setShowSyncStatusModal(true), title: `Sync manager: ${syncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
2912
+ marginLeft: '6px',
2913
+ height: '24px',
2914
+ width: '24px',
2915
+ padding: 0,
2916
+ borderRadius: '999px',
2917
+ border: `1px solid ${syncStatusMeta.border}`,
2918
+ background: syncStatusMeta.background,
2919
+ color: syncStatusMeta.color,
2920
+ display: 'inline-flex',
2921
+ alignItems: 'center',
2922
+ justifyContent: 'center'
2923
+ }, children: syncStatusMeta.icon === 'off' ? (_jsx(CloudOff, { size: 16 })) : (_jsx(Cloud, { size: 16 })) }))] })), actions: (_jsxs(_Fragment, { children: [isPlansScreen && (_jsx("button", { className: "tf-control-icon", onClick: handlePlansBack, title: "Back", children: "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, apiBaseUrl: billingApiBaseUrl, connectedEnvironmentSource: props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '', embedded: true, shellOwnsScroll: true }) }) })) : 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: () => {
2924
+ setRequestedDocPath(null);
2925
+ setRequestedDocAssetId(null);
2926
+ }, 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: {
1769
2927
  position: 'relative',
1770
2928
  opacity: props.loadingTasks ? 0.7 : 1,
1771
- transition: 'opacity 0.2s ease, padding-right 220ms cubic-bezier(0.4, 0, 0.2, 1)',
2929
+ transition: 'opacity 0.2s ease, padding 220ms cubic-bezier(0.4, 0, 0.2, 1)',
1772
2930
  display: 'flex',
1773
2931
  flex: 1,
1774
2932
  minHeight: 0,
1775
- paddingRight: (groupBy === 'schedule' && scheduleSidebarOpen) || (groupBy === 'hierarchy' && hierarchySidebarOpen) ? '332px' : 0
1776
- }, children: [_jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [_jsx("div", { style: {
1777
- position: 'absolute',
1778
- top: 0,
1779
- right: 0,
1780
- width: '100%',
1781
- height: '100%',
1782
- backgroundImage: `url(${taskforceBg})`,
1783
- backgroundRepeat: 'no-repeat',
1784
- backgroundPosition: 'top right',
1785
- backgroundSize: '60%',
1786
- opacity: 0.2,
1787
- pointerEvents: 'none',
1788
- zIndex: 0
1789
- } }), boardVisibleTasks.length === 0 && groupBy !== 'schedule' ? (_jsxs("div", { className: styles.emptyState, children: [_jsx(Layers, { size: 48 }), _jsx("p", { children: groupBy === 'hierarchy' && hierarchyRootTaskId ? 'No tasks match this subtree with current filters.' : t('standalone.noTasksMatchFilters') }), _jsx("p", { style: { fontSize: '12px', color: 'var(--text-secondary)' }, children: groupBy === 'hierarchy' && hierarchyRootTaskId
1790
- ? `Focused on ${selectedHierarchyRootTask?.title || hierarchyRootTaskId}. Try clearing subtree focus or broadening filters.`
1791
- : t('standalone.showingWithLinksHint', { parentFilterLabel }) })] })) : (_jsx(TaskKanban, { tasks: boardVisibleTasks, allTasks: kanbanAllTasks, hierarchyTasks: hierarchyTasks, columns: kanbanColumns, groupBy: groupBy, scheduleDates: scheduleDates, searchQuery: searchQuery, filterCategories: filterCategories, filterTypes: filterTypes, filterPriorities: filterPriorities, filterStatus: filterStatus, filterAssignees: filterAssignees, assigneeFilterEnabled: assigneeFilterEnabled, scheduleFilteredTaskIds: Array.from(scheduleFilteredTaskIds), copiedId: copiedId, availableSpecialists: availableSpecialists, taxonomies: taxonomies, priorities: priorities, approaches: approaches, onUpdateTask: handleUpdateTask, onTaskClick: (task) => handleEdit(task), onOpenTaskById: handleOpenTaskById, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onArchiveTask: handleArchiveTask, onUnarchive: handleUnarchive, onDelete: handleDelete, onUnlinkChildTask: handleUnlinkChildTask, onClearParentTask: handleClearParentTask, onAddTaskToColumn: handleAddTaskToColumn, onScheduleParentDrop: handleScheduleParentDrop, onScheduleDaySelected: (dateOnly) => {
2933
+ paddingLeft: planningDrawerOpen
2934
+ ? `${WORKSPACE_TOOL_RAIL_WIDTH + planningDrawerWidthPx + WORKSPACE_CONTENT_GUTTER}px`
2935
+ : `${WORKSPACE_TOOL_RAIL_WIDTH + WORKSPACE_CONTENT_GUTTER}px`,
2936
+ paddingRight: (groupBy === 'schedule' && scheduleSidebarOpen) ? `${SCHEDULE_SIDEBAR_WIDTH_PX}px` : 0
2937
+ }, children: [workspaceToolRail, _jsx("div", { ref: planningDrawerPortalRef, style: {
2938
+ position: 'absolute',
2939
+ inset: 0,
2940
+ zIndex: 35,
2941
+ pointerEvents: 'none',
2942
+ } }), _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: [], 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) => {
1792
2943
  setScheduleSelectedDate(dateOnly);
1793
2944
  const parsed = parseDateOnlyLocal(dateOnly);
1794
2945
  if (parsed) {
@@ -1798,291 +2949,154 @@ export function StandaloneLayout(props) {
1798
2949
  if (groupBy !== 'schedule')
1799
2950
  return;
1800
2951
  setScheduleScrollLeft(scrollLeft);
1801
- }, emptyColumnMode: emptyColumnMode, categories: activeCategories, compressed: compressedCards, recentlyChangedTaskIds: props.recentlyChangedTaskIds, hierarchyChildOrderMode: hierarchyChildOrderMode, sortBy: sortBy, sortOrder: props.sortOrder }, `${groupBy}-${kanbanColumns.map(c => String(c.value)).join('|')}-${scheduleDates.mon}`)), groupBy === 'schedule' && !scheduleSidebarOpen && (_jsx("button", { type: "button", className: styles.headerActionBtn, onClick: () => setScheduleSidebarOpen(true), title: "Show Schedule Sidebar", style: {
1802
- position: 'absolute',
1803
- top: '10px',
1804
- right: '10px',
1805
- zIndex: 20,
1806
- border: '1px solid var(--border-color)',
1807
- background: 'var(--bg-secondary)'
1808
- }, children: _jsx(PanelRightOpen, { size: 16 }) })), groupBy === 'hierarchy' && !hierarchySidebarOpen && (_jsx("button", { type: "button", className: styles.headerActionBtn, onClick: () => setHierarchySidebarOpen(true), title: "Show Hierarchy Sidebar", style: {
2952
+ }, emptyColumnMode: emptyColumnMode, categories: activeCategories, compressed: compressedCards, readOnlyMode: taskScope === 'deleted' ? 'deleted' : taskScope === 'archived' ? 'archived' : null, recentlyChangedTaskIds: props.recentlyChangedTaskIds, sortBy: sortBy, sortOrder: props.sortOrder, planningDropTargets: planningDrawerPortalRef.current
2953
+ ? createPortal(planningDrawerNode, planningDrawerPortalRef.current)
2954
+ : null, onAssignTaskToWorkstream: handleAssignTaskToWorkstream, onAssignWorkstreamToInitiative: handleAssignInitiativeToWorkstream, workstreams: props.workstreams, initiatives: props.initiatives, onUnarchive: (taskId) => {
2955
+ if (taskScope === 'deleted') {
2956
+ const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
2957
+ if (!deletedRecord)
2958
+ return;
2959
+ void handleRestoreDeletedTask(deletedRecord.id);
2960
+ return;
2961
+ }
2962
+ void handleUnarchive(taskId);
2963
+ }, onDelete: (taskId) => {
2964
+ if (taskScope === 'deleted') {
2965
+ const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
2966
+ if (!deletedRecord)
2967
+ return;
2968
+ const confirmed = window.confirm('Permanently delete this task? This cannot be undone.');
2969
+ if (!confirmed)
2970
+ return;
2971
+ void handlePermanentlyDeleteDeletedTask(deletedRecord.id);
2972
+ return;
2973
+ }
2974
+ void handleDelete(taskId);
2975
+ } }, `${groupBy}-${kanbanColumns.map(c => String(c.value)).join('|')}-${scheduleDates.mon}`), groupBy === 'schedule' && !scheduleSidebarOpen && (_jsx("button", { type: "button", className: "tf-control-icon", onClick: () => {
2976
+ setPlanningDrawerDetail(null);
2977
+ setScheduleSidebarOpen(true);
2978
+ }, title: "Show Schedule Sidebar", style: {
1809
2979
  position: 'absolute',
1810
2980
  top: '10px',
1811
2981
  right: '10px',
1812
2982
  zIndex: 20,
1813
- border: '1px solid var(--border-color)',
1814
- background: 'var(--bg-secondary)'
1815
- }, children: _jsx(PanelRightOpen, { size: 16 }) }))] }), groupBy === 'hierarchy' && (_jsxs("aside", { className: styles.appScrollbar, style: {
1816
- position: 'absolute',
1817
- top: 0,
1818
- right: 0,
1819
- bottom: 0,
1820
- width: '320px',
1821
- minWidth: '320px',
1822
- borderLeft: '1px solid var(--border-color)',
1823
- background: 'var(--bg-secondary)',
1824
- padding: '12px',
1825
- display: 'flex',
1826
- flexDirection: 'column',
1827
- gap: '12px',
1828
- zIndex: 40,
1829
- overflowY: 'auto',
1830
- transform: hierarchySidebarOpen ? 'translateX(0)' : 'translateX(108%)',
1831
- pointerEvents: hierarchySidebarOpen ? 'auto' : 'none',
1832
- transition: 'transform 220ms cubic-bezier(0.4, 0, 0.2, 1)',
1833
- willChange: 'transform'
1834
- }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px', fontWeight: 600 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx(Layers, { size: 16 }), _jsx("span", { children: "Hierarchy Controls" })] }), _jsx("button", { type: "button", className: styles.headerActionBtn, onClick: () => setHierarchySidebarOpen(false), title: "Hide Hierarchy Sidebar", children: _jsx(PanelRightClose, { size: 16 }) })] }), _jsxs("div", { style: {
1835
- border: '1px solid var(--border-color)',
1836
- borderRadius: '10px',
1837
- padding: '10px',
1838
- display: 'flex',
1839
- flexDirection: 'column',
1840
- gap: '8px'
1841
- }, children: [_jsx("div", { style: { fontSize: '12px', fontWeight: 600 }, children: "Focus Subtree" }), _jsx("input", { type: "text", className: styles.input, placeholder: "Search parent tasks...", value: hierarchyRootSearch, onChange: (e) => setHierarchyRootSearch(e.target.value), onKeyDown: (e) => {
1842
- if (e.key === 'Enter') {
1843
- e.preventDefault();
1844
- applyHierarchySearchSelection();
1845
- }
1846
- } }), _jsx("div", { style: {
1847
- border: '1px solid var(--border-color)',
1848
- borderRadius: '8px',
1849
- padding: '8px',
1850
- display: 'flex',
1851
- flexDirection: 'column',
1852
- gap: '6px',
1853
- minHeight: '190px'
1854
- }, children: hierarchyRootSearch.trim().length > 0 ? (_jsxs("div", { className: styles.appScrollbar, style: {
1855
- maxHeight: '180px',
1856
- minHeight: '180px',
1857
- overflowY: 'auto',
1858
- border: '1px solid var(--border-color)',
1859
- borderRadius: '8px',
1860
- padding: '6px',
1861
- display: 'flex',
1862
- flexDirection: 'column',
1863
- gap: '6px'
1864
- }, children: [hierarchySearchMatches.slice(0, 24).map((task) => (_jsxs("button", { type: "button", className: styles.headerActionBtn, onClick: () => {
1865
- setHierarchyRootTaskId(task.id);
1866
- setHierarchyRootSearch('');
1867
- if (parentTaskFilterMode === 'unlinked') {
1868
- setParentTaskFilterMode('all');
1869
- }
1870
- }, style: { justifyContent: 'space-between', border: '1px solid var(--border-color)' }, title: task.title, children: [_jsx("span", { style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, children: task.title }), _jsx("span", { style: { opacity: 0.75 }, children: task.id.split('-').slice(-1)[0] })] }, task.id))), hierarchySearchMatches.length === 0 && (_jsxs("div", { style: { fontSize: '12px', color: 'var(--text-secondary)', padding: '4px 2px' }, children: ["No matches for \"", hierarchyRootSearch, "\"."] }))] })) : hierarchyRootTaskId ? (_jsxs(_Fragment, { children: [_jsx("div", { style: { fontSize: '13px', fontWeight: 600, color: 'var(--text-primary)' }, title: selectedHierarchyRootTask?.title || hierarchyRootTaskId, children: selectedHierarchyRootTask?.title || hierarchyRootTaskId }), hierarchySubtreeStats && (_jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', fontSize: '12px', color: 'var(--text-secondary)' }, children: [_jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchySubtreeStats.total }), " total"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchySubtreeStats.active }), " active"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchySubtreeStats.terminal }), " done/cancelled"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: hierarchySubtreeStats.overdue > 0 ? 'var(--color-error, #ef4444)' : 'var(--text-primary)' }, children: hierarchySubtreeStats.overdue }), " overdue"] }), _jsxs("div", { style: { gridColumn: '1 / -1', border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchySubtreeStats.depth }), " levels deep"] })] })), _jsx("button", { className: styles.headerActionBtn, onClick: () => {
1871
- setHierarchyRootTaskId('');
1872
- setHierarchyRootSearch('');
1873
- }, style: { justifyContent: 'center', border: '1px solid var(--border-color)', background: 'rgba(59,130,246,0.12)' }, children: "Clear subtree focus" })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { style: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', fontSize: '12px', color: 'var(--text-secondary)' }, children: [_jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchyVisibleTasks.length }), " visible"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchyVisibleRootCount }), " root parents"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchyVisibleUnlinkedCount }), " unlinked"] }), _jsxs("div", { style: { border: '1px solid var(--border-color)', borderRadius: '8px', padding: '6px' }, children: [_jsx("strong", { style: { color: 'var(--text-primary)' }, children: hierarchyDepthTasks.length > 0 ? maxHierarchyDepth + 1 : 0 }), " levels"] })] }), _jsx("div", { style: { fontSize: '12px', color: 'var(--text-secondary)' }, children: hierarchyVisibleLevelCounts.length > 0
1874
- ? hierarchyVisibleLevelCounts.map((entry) => `L${entry.level}: ${entry.count}`).join(' • ')
1875
- : 'No linked hierarchy tasks in current filters.' })] })) })] }), _jsxs("div", { style: {
1876
- border: '1px solid var(--border-color)',
1877
- borderRadius: '10px',
1878
- padding: '10px',
1879
- display: 'flex',
1880
- flexDirection: 'column',
1881
- gap: '8px'
1882
- }, children: [_jsx("div", { style: { fontSize: '12px', fontWeight: 600 }, children: "Display" }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }, children: [_jsx("input", { type: "checkbox", checked: hierarchyShowUnlinkedColumn, onChange: (e) => {
1883
- const next = e.target.checked;
1884
- setHierarchyShowUnlinkedColumn(next);
1885
- if (!next && parentTaskFilterMode === 'unlinked') {
1886
- setParentTaskFilterMode('all');
1887
- }
1888
- } }), "Show unlinked column"] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }, children: [_jsx("input", { type: "checkbox", checked: hierarchyChildOrderMode === 'manual', onChange: (e) => setHierarchyChildOrderMode(e.target.checked ? 'manual' : 'auto') }), "Sort by parent task order"] }), _jsx("div", { style: { fontSize: '11px', color: 'var(--text-tertiary)' }, children: "Keeps siblings in the order defined on the parent task." })] })] })), groupBy === 'schedule' && (_jsxs("aside", { className: styles.appScrollbar, style: {
1889
- position: 'absolute',
1890
- top: 0,
1891
- right: 0,
1892
- bottom: 0,
1893
- width: '320px',
1894
- minWidth: '320px',
1895
- borderLeft: '1px solid var(--border-color)',
1896
- background: 'var(--bg-secondary)',
1897
- padding: '12px',
1898
- display: 'flex',
1899
- flexDirection: 'column',
1900
- gap: '12px',
1901
- zIndex: 40,
1902
- overflowY: 'auto',
1903
- transform: scheduleSidebarOpen ? 'translateX(0)' : 'translateX(108%)',
1904
- pointerEvents: scheduleSidebarOpen ? 'auto' : 'none',
1905
- transition: 'transform 220ms cubic-bezier(0.4, 0, 0.2, 1)',
1906
- willChange: 'transform'
1907
- }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px', fontWeight: 600 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx(CalendarDays, { size: 16 }), _jsx("span", { children: "Schedule Controls" })] }), _jsx("button", { type: "button", className: styles.headerActionBtn, onClick: () => setScheduleSidebarOpen(false), title: "Hide Schedule Sidebar", children: _jsx(PanelRightClose, { size: 16 }) })] }), _jsxs("div", { style: { fontSize: '12px', color: 'var(--text-secondary)' }, children: ["Week of ", scheduleWeekLabel] }), _jsxs("div", { style: {
1908
- border: '1px solid var(--border-color)',
1909
- borderRadius: '10px',
1910
- padding: '10px'
1911
- }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }, children: [_jsx("button", { className: styles.headerActionBtn, onClick: () => {
1912
- const prev = new Date(scheduleMonthDate);
1913
- prev.setMonth(scheduleMonthDate.getMonth() - 1);
1914
- setScheduleCalendarMonth(`${prev.getFullYear()}-${String(prev.getMonth() + 1).padStart(2, '0')}`);
1915
- }, title: "Previous month", children: _jsx(ChevronLeft, { size: 14 }) }), _jsx("div", { style: { fontSize: '12px', fontWeight: 600 }, children: scheduleCalendarMonthLabel }), _jsx("button", { className: styles.headerActionBtn, onClick: () => {
1916
- const next = new Date(scheduleMonthDate);
1917
- next.setMonth(scheduleMonthDate.getMonth() + 1);
1918
- setScheduleCalendarMonth(`${next.getFullYear()}-${String(next.getMonth() + 1).padStart(2, '0')}`);
1919
- }, title: "Next month", children: _jsx(ChevronRight, { size: 14 }) })] }), _jsx("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(7, minmax(0, 1fr))', gap: '4px', marginBottom: '4px' }, children: calendarWeekdayLabels.map((label) => (_jsx("div", { style: { fontSize: '11px', color: 'var(--text-secondary)', textAlign: 'center' }, children: label }, label))) }), _jsx("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(7, minmax(0, 1fr))', gap: '4px' }, children: calendarCells.map((date) => {
1920
- const dateOnly = toDateOnlyLocal(date);
1921
- const isOutsideMonth = date.getMonth() !== scheduleMonthDate.getMonth();
1922
- const weekStartForCell = startOfWeek(date, globalWeekStartsOn);
1923
- const isSelectedWeek = toDateOnlyLocal(weekStartForCell) === toDateOnlyLocal(scheduleWeekStart);
1924
- const isSelectedDate = dateOnly === scheduleSelectedDate;
1925
- const isToday = dateOnly === todayDateOnly;
1926
- const isPast = dateOnly < todayDateOnly;
1927
- const signal = calendarTaskSignals.get(dateOnly);
1928
- const hasScheduledTasks = !!signal;
1929
- const hasExpiredScheduledTasks = !!signal?.hasExpired;
1930
- return (_jsxs("button", { onClick: () => {
1931
- setScheduleSelectedDate(dateOnly);
1932
- setScheduleCalendarMonth(`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`);
1933
- }, style: {
1934
- border: isSelectedDate
1935
- ? '1px solid #2563eb'
1936
- : isToday
1937
- ? '1px solid rgba(245, 158, 11, 0.95)'
1938
- : '1px solid transparent',
1939
- borderRadius: '8px',
1940
- padding: '6px 0',
1941
- fontSize: '12px',
1942
- position: 'relative',
1943
- background: isSelectedDate
1944
- ? 'rgba(59,130,246,0.22)'
1945
- : isToday
1946
- ? 'rgba(245,158,11,0.16)'
1947
- : isSelectedWeek
1948
- ? 'rgba(59,130,246,0.18)'
1949
- : 'transparent',
1950
- color: isOutsideMonth ? 'var(--text-tertiary)' : 'var(--text-primary)',
1951
- cursor: 'pointer',
1952
- fontWeight: isSelectedDate || isToday ? 700 : 500,
1953
- opacity: isPast ? 0.42 : 1
1954
- }, title: `${isToday ? `${dateOnly} (Today)` : dateOnly}`
1955
- + (hasScheduledTasks ? ` • ${signal?.count} scheduled` : '')
1956
- + (hasExpiredScheduledTasks ? ' • includes expired' : ''), children: [date.getDate(), hasScheduledTasks && (_jsx("span", { style: {
1957
- position: 'absolute',
1958
- left: '50%',
1959
- bottom: '3px',
1960
- transform: 'translateX(-50%)',
1961
- width: '5px',
1962
- height: '5px',
1963
- borderRadius: '999px',
1964
- background: hasExpiredScheduledTasks ? '#ef4444' : '#3b82f6',
1965
- opacity: isOutsideMonth ? 0.6 : 0.95
1966
- } }))] }, dateOnly));
1967
- }) }), _jsx("div", { style: { display: 'flex', justifyContent: 'space-between', marginTop: '10px' }, children: _jsx("button", { className: styles.headerActionBtn, onClick: () => {
1968
- const now = new Date();
1969
- setScheduleSelectedDate(toDateOnlyLocal(now));
1970
- setScheduleCalendarMonth(`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`);
1971
- }, title: "Jump to current week", children: "Today" }) })] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }, children: [_jsx("input", { type: "checkbox", checked: scheduleShowWeekends, onChange: (e) => setScheduleShowWeekends(e.target.checked) }), "Show weekends"] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }, children: [_jsx("input", { type: "checkbox", checked: scheduleShowBacklog, onChange: (e) => setScheduleShowBacklog(e.target.checked) }), "Show backlog"] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '13px' }, children: [_jsx("input", { type: "checkbox", checked: scheduleOnlyExpired, onChange: (e) => setScheduleOnlyExpired(e.target.checked) }), "Only expired/overdue"] }), expiredScheduledCount > 0 && (_jsxs("div", { style: {
1972
- border: '1px solid var(--border-color)',
1973
- borderRadius: '10px',
1974
- padding: '10px',
1975
- display: 'flex',
1976
- flexDirection: 'column',
1977
- gap: '8px'
1978
- }, children: [_jsx("div", { style: { fontSize: '12px', fontWeight: 600 }, children: "Expired Tasks" }), _jsxs("div", { style: { fontSize: '12px', color: 'var(--text-secondary)', display: 'flex', flexDirection: 'column', gap: '2px' }, children: [_jsxs("span", { children: [expiredScheduledCount, " expired"] }), _jsxs("span", { children: [overdueDueCount, " overdue"] })] }), _jsx("button", { className: styles.headerActionBtn, onClick: handleMoveExpiredToSelectedWeek, disabled: scheduleBulkBusy || expiredLeafCandidates.length === 0, title: "Move expired leaf tasks to the selected week while keeping weekday alignment", style: { justifyContent: 'flex-start', border: '1px solid var(--border-color)' }, children: scheduleBulkBusy ? 'Working...' : 'Schedule to selected week' }), _jsx("button", { className: styles.headerActionBtn, onClick: handleMoveExpiredToBacklog, disabled: scheduleBulkBusy || expiredRecoveryCandidates.length === 0, title: "Unschedule expired/overdue tasks back to backlog", style: { justifyContent: 'flex-start', border: '1px solid var(--border-color)' }, children: scheduleBulkBusy ? 'Working...' : 'Unschedule to backlog' })] }))] }))] })), _jsxs(Modal, { isOpen: activeTab === 'add', onClose: handleCloseModal, title: editingTaskId ? 'Edit Task' : 'New Task', size: "md", theme: currentTheme, draggable: true, closeOnOverlayClick: false, children: [_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, title: title, currentTask: currentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleArchiveTask: handleArchiveTask }), _jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, parentTaskIdInput: parentTaskIdInput, addChildTaskIdInput: addChildTaskIdInput, formTaxonomies: formTaxonomies, onTaxonomyChange: (id, val) => setFormTaxonomies(prev => ({ ...prev, [id]: val })), onParentTaskIdInputChange: setParentTaskIdInput, onAddChildTaskIdInputChange: setAddChildTaskIdInput, taxonomies: taxonomies, selectedSpecialists: selectedSpecialists, comments: comments, newCommentText: newCommentText, contextFiles: screenshots, contextLinkBrowsePath: contextLinkBrowsePath, descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showSpecialists: showSpecialists, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showTaskRelationships: showTaskRelationships, showChildTasks: showChildTasks, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, approaches: approaches, availableSpecialists: availableSpecialists, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onSelectedSpecialistsChange: setSelectedSpecialists, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowSpecialistsChange: setShowSpecialists, onShowChecklistChange: setShowChecklist, onShowTaskRelationshipsChange: setShowTaskRelationships, onShowChildTasksChange: setShowChildTasks, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
2983
+ }, children: _jsx(ChevronLeft, { size: 16 }) }))] }), groupBy === 'schedule' && (_jsx(ScheduleSidebar, { open: scheduleSidebarOpen, onClose: () => setScheduleSidebarOpen(false), scheduleSelectedDate: scheduleSelectedDate, setScheduleSelectedDate: setScheduleSelectedDate, scheduleCalendarMonth: scheduleCalendarMonth, setScheduleCalendarMonth: setScheduleCalendarMonth, scheduleShowWeekends: scheduleShowWeekends, setScheduleShowWeekends: setScheduleShowWeekends, scheduleShowBacklog: scheduleShowBacklog, setScheduleShowBacklog: setScheduleShowBacklog, scheduleOnlyExpired: scheduleOnlyExpired, setScheduleOnlyExpired: setScheduleOnlyExpired, expiredScheduledCount: expiredScheduledCount, overdueDueCount: overdueDueCount, expiredLeafCandidates: expiredLeafCandidates, expiredRecoveryCandidates: expiredRecoveryCandidates, onMoveExpiredToSelectedWeek: handleMoveExpiredToSelectedWeek, onMoveExpiredToBacklog: handleMoveExpiredToBacklog, scheduleBulkBusy: scheduleBulkBusy, globalWeekStartsOn: globalWeekStartsOn, resolvedLocale: resolvedLocale, todayDateOnly: todayDateOnly, scheduleBaseTasks: scheduleBaseTasks, scheduleWeekStart: scheduleWeekStart, scheduleWeekLabel: scheduleWeekLabel }))] })), _jsxs(Modal, { isOpen: activeTab === 'add', onClose: handleCloseModal, title: editingTaskId ? (currentTask?.isDeleted ? 'Deleted Task' : 'Edit Task') : 'New Task', size: taskModalFullscreen ? 'full' : 'xl', theme: currentTheme, headerActions: (_jsx("button", { className: "tf-control-icon", onClick: () => setTaskModalFullscreen((value) => !value), title: taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', "aria-label": taskModalFullscreen ? 'Exit full screen' : 'Enter full screen', children: taskModalFullscreen ? _jsx(Minimize2, { size: 18 }) : _jsx(Maximize2, { size: 18 }) })), draggable: !taskModalFullscreen, closeOnOverlayClick: false, children: [_jsx(TaskActionHeader, { editingTaskId: editingTaskId, loading: loading, autoSaveState: autoSaveState, title: title, currentTask: currentTask, currentTaskWorkstream: currentTaskWorkstream, currentTaskInitiative: currentTaskInitiative, workstreamInput: workstreamInput, onWorkstreamInputChange: setWorkstreamInput, onSetWorkstreamForCurrentTask: handleSetWorkstreamForCurrentTask, handleSubmit: handleSubmit, resetForm: resetForm, handleCopyId: handleCopyId, copiedId: copiedId, tasks: tasks, handleToggleInProgress: handleToggleInProgress, handleToggleReview: handleToggleReview, handleToggleComplete: handleToggleComplete, handleToggleCancel: handleToggleCancel, handleSetStatus: handleSetStatus, handleArchiveTask: handleArchiveTask, handleRestoreDeletedTask: currentTask?.deletedRecordId
2984
+ ? (deletedRecordId) => {
2985
+ void handleRestoreDeletedTask(deletedRecordId);
2986
+ }
2987
+ : undefined, handlePermanentlyDeleteDeletedTask: currentTask?.deletedRecordId
2988
+ ? (deletedRecordId) => {
2989
+ const confirmed = window.confirm('Permanently delete this task? This cannot be undone.');
2990
+ if (!confirmed)
2991
+ return;
2992
+ void handlePermanentlyDeleteDeletedTask(deletedRecordId);
2993
+ resetForm();
2994
+ setActiveTab('tasks');
2995
+ }
2996
+ : undefined }), _jsx(TaskForm, { editingTaskId: editingTaskId, error: error, title: title, description: description, checklistItems: checklistItems, category: category, type: type, priority: priority, complexity: complexity, manualComplexityEnabled: manualComplexityEnabled, approach: approach, assignee: assignee, scheduledDate: scheduledDate, dueDate: dueDate, workstreamInput: workstreamInput, formTaxonomies: formTaxonomies, onTaxonomyChange: (id, val) => setFormTaxonomies(prev => ({ ...prev, [id]: val })), taxonomies: taxonomies, comments: comments, newCommentText: newCommentText, contextFiles: attachments, currentWorkspaceId: currentWorkspaceId, apiBaseUrl: apiBaseUrl, descriptionFocused: descriptionFocused, showMarkdownHelp: showMarkdownHelp, showChecklist: showChecklist, checklistEnabled: checklistDropdownEnabled, showComments: showComments, categories: activeCategories, types: activeTypes, priorities: priorities, taxonomyDisplayLabels: taxonomyDisplayLabels, assigneeOptions: assigneeOptions, workstreams: props.workstreams, copiedId: copiedId, onTitleChange: setTitle, onDescriptionChange: setDescription, onChecklistItemsChange: setChecklistItems, onCategoryChange: setCategory, onTypeChange: setType, onPriorityChange: setPriority, onComplexityChange: setComplexity, onApproachChange: (val) => { setApproach(val); setFormTaxonomies(prev => ({ ...prev, approach: val })); }, onAssigneeChange: setAssignee, onScheduledDateChange: setScheduledDate, onDueDateChange: setDueDate, onWorkstreamInputChange: setWorkstreamInput, onNewCommentTextChange: setNewCommentText, onDescriptionFocusedChange: setDescriptionFocused, onShowMarkdownHelpChange: setShowMarkdownHelp, onShowChecklistChange: setShowChecklist, onShowCommentsChange: setShowComments, onOpenSettings: (section) => {
1979
2997
  setSettingsSection(section);
1980
2998
  setActiveTab('settings');
1981
- }, onSubmit: handleSubmit, commentsEndRef: commentsEndRef, onAddComment: () => handleAddComment(newCommentText), onSetParentForCurrentTask: handleSetParentForCurrentTask, onAddChildToCurrentTask: handleAddChildToCurrentTask, onCreateChildForCurrentTask: handleCreateChildForCurrentTask, onUnlinkChildTask: handleUnlinkChildTask, onReorderChildTasks: handleReorderChildTasks, onOpenTaskById: handleOpenTaskById, onAddContextFile: (file) => {
1982
- setScreenshots(prev => [...prev, file]);
1983
- }, onBrowseContextLinkPath: () => {
1984
- setBrowserTarget({ type: 'context-link' });
1985
- fetchFolders('');
1986
- setShowFolderBrowser(true);
2999
+ }, onSubmit: handleSubmit, commentsEndRef: commentsEndRef, onAddComment: () => handleAddComment(newCommentText), onOpenTaskById: handleOpenTaskById, onAddContextFile: (file) => {
3000
+ let nextAttachments = attachments;
3001
+ setAttachments((prev) => {
3002
+ nextAttachments = [...prev, file];
3003
+ return nextAttachments;
3004
+ });
3005
+ setAttachmentsDirty(true);
3006
+ if (editingTaskId) {
3007
+ void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
3008
+ }
1987
3009
  }, onRemoveContextFile: async (index) => {
1988
- const screenshot = screenshots[index];
1989
- const path = typeof screenshot === 'string' ? screenshot : screenshot.path;
1990
- setScreenshots(prev => prev.filter((_, i) => i !== index));
1991
- if (typeof path === 'string' && path.startsWith('/api/taskforce/context/')) {
1992
- try {
1993
- await fetch(path, { method: 'DELETE' });
1994
- }
1995
- catch { }
3010
+ let nextAttachments = attachments;
3011
+ setAttachments((prev) => {
3012
+ nextAttachments = prev.filter((_, i) => i !== index);
3013
+ return nextAttachments;
3014
+ });
3015
+ setAttachmentsDirty(true);
3016
+ if (editingTaskId) {
3017
+ void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
1996
3018
  }
1997
3019
  }, onUpdateContextCaption: (index, caption) => {
1998
- setScreenshots(prev => prev.map((s, i) => {
1999
- if (i !== index)
2000
- return s;
2001
- if (typeof s === 'string')
2002
- return { path: s, caption, timestamp: new Date().toISOString() };
2003
- return { ...s, caption };
2004
- }));
2005
- }, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onArchiveTask: handleArchiveTask, onUnarchive: handleUnarchive, currentTask: currentTask, currentTaskParent: currentTaskParent, currentTaskChildren: currentTaskChildren })] }), _jsx(Modal, { isOpen: activeTab === 'settings', onClose: handleCloseModal, title: "Settings", size: "md", theme: currentTheme, draggable: true, isSettings: true, closeOnOverlayClick: false, children: _jsx(TaskSettings, { settingsModel: settingsModel, onSectionChange: setSettingsSection }) }), _jsx(Modal, { isOpen: !!scheduleWarningPrompt, onClose: cancelScheduleWarning, title: "Date Warning", size: "sm", theme: currentTheme, draggable: false, closeOnOverlayClick: false, children: _jsxs("div", { className: styles.form, style: { padding: '16px', gap: '12px' }, children: [_jsx("p", { style: { margin: 0, fontSize: '13px', color: 'var(--text-secondary)' }, children: "Due date is before scheduled date." }), scheduleWarningPrompt && (_jsxs("p", { style: { margin: 0, fontSize: '12px', color: 'var(--text-muted)' }, children: ["Due: ", scheduleWarningPrompt.dueDate, " \u00B7 Scheduled: ", scheduleWarningPrompt.scheduledDate] })), _jsxs("div", { className: styles.formActions, children: [_jsx("button", { className: styles.cancelBtn, onClick: cancelScheduleWarning, children: "Go Back" }), _jsx("button", { className: styles.submitBtn, onClick: confirmScheduleWarning, children: "Save Anyway" })] })] }) }), _jsx(Modal, { isOpen: !!scheduleParentDropPrompt, onClose: cancelScheduleParentDrop, title: "Schedule Descendants", size: "sm", theme: currentTheme, draggable: false, closeOnOverlayClick: false, children: _jsxs("div", { className: styles.form, style: { padding: '16px', gap: '12px' }, children: [scheduleParentDropPrompt && (_jsxs(_Fragment, { children: [_jsxs("p", { style: { margin: 0, fontSize: '13px', color: 'var(--text-secondary)' }, children: ["Schedule unscheduled leaf tasks under ", _jsx("strong", { children: scheduleParentDropPrompt.parentTitle }), " to ", _jsx("strong", { children: scheduleParentDropPrompt.targetDate }), "?"] }), _jsxs("p", { style: { margin: 0, fontSize: '12px', color: 'var(--text-muted)' }, children: [scheduleParentDropPrompt.toScheduleLeafTaskIds.length, " leaf task", scheduleParentDropPrompt.toScheduleLeafTaskIds.length === 1 ? '' : 's', " will be scheduled.", scheduleParentDropPrompt.alreadyScheduledLeafCount > 0 ? ` ${scheduleParentDropPrompt.alreadyScheduledLeafCount} already scheduled leaf task${scheduleParentDropPrompt.alreadyScheduledLeafCount === 1 ? '' : 's'} will be skipped.` : ''] })] })), _jsxs("div", { className: styles.formActions, children: [_jsx("button", { className: styles.cancelBtn, onClick: cancelScheduleParentDrop, disabled: scheduleParentDropBusy, children: "Cancel" }), _jsx("button", { className: styles.submitBtn, onClick: confirmScheduleParentDrop, disabled: scheduleParentDropBusy, children: scheduleParentDropBusy ? 'Scheduling...' : 'Schedule' })] })] }) }), _jsx(Modal, { isOpen: showHelpModal, onClose: () => setShowHelpModal(false), title: "Quick Start", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: modalStyles.form, style: { paddingTop: '18px' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--text-secondary)' }, children: "Use this quick guide to get productive in Taskforce dashboard mode." }), _jsxs("div", { children: [_jsx("strong", { children: "1. Capture work" }), _jsxs("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: ["Click ", _jsx("code", { children: "Add Task" }), ", write a short title, and save."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "2. Plan the board" }), _jsxs("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: ["Use ", _jsx("code", { children: "Sort" }), ", ", _jsx("code", { children: "Group" }), ", and ", _jsx("code", { children: "Filters" }), " in the header."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "3. Execute and stage" }), _jsxs("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: ["Move tasks through ", _jsx("code", { children: "Task" }), ", ", _jsx("code", { children: "In Progress" }), ", ", _jsx("code", { children: "Review" }), ", and ", _jsx("code", { children: "Done" }), "."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "4. Archive finished work" }), _jsx("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: "Archive completed/cancelled tasks to keep active board signal high." })] }), _jsxs("div", { className: modalStyles.formActions, style: { display: 'flex', justifyContent: 'flex-end', gap: '10px' }, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => {
3020
+ let nextAttachments = attachments;
3021
+ setAttachments((prev) => {
3022
+ nextAttachments = prev.map((s, i) => {
3023
+ if (i !== index)
3024
+ return s;
3025
+ if (typeof s === 'string')
3026
+ return { path: s, caption, timestamp: new Date().toISOString() };
3027
+ return { ...s, caption };
3028
+ });
3029
+ return nextAttachments;
3030
+ });
3031
+ setAttachmentsDirty(true);
3032
+ if (editingTaskId) {
3033
+ void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
3034
+ }
3035
+ }, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onUnarchive: (taskId) => {
3036
+ if (currentTask?.isDeleted) {
3037
+ const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
3038
+ if (!deletedRecord)
3039
+ return;
3040
+ void handleRestoreDeletedTask(deletedRecord.id);
3041
+ return;
3042
+ }
3043
+ void handleUnarchive(taskId);
3044
+ }, 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(Modal, { isOpen: !!scheduleWarningPrompt, onClose: cancelScheduleWarning, title: "Date Warning", size: "sm", theme: currentTheme, draggable: false, closeOnOverlayClick: false, children: _jsxs("div", { className: `${styles.form} tf-inline-stack-sm`, style: { padding: '16px', gap: '12px' }, children: [_jsx("p", { className: "tf-text-body", children: "Due date is before scheduled date." }), scheduleWarningPrompt && (_jsxs("p", { className: "tf-text-helper", children: ["Due: ", scheduleWarningPrompt.dueDate, " \u00B7 Scheduled: ", scheduleWarningPrompt.scheduledDate] })), _jsxs("div", { className: styles.formActions, children: [_jsx("button", { className: styles.cancelBtn, onClick: cancelScheduleWarning, children: "Go Back" }), _jsx("button", { className: styles.submitBtn, onClick: confirmScheduleWarning, children: "Save Anyway" })] })] }) }), _jsx(Modal, { isOpen: showHelpModal, onClose: () => setShowHelpModal(false), title: "Quick Start", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.mutedText, children: "Use this quick guide to get productive in Taskforce dashboard mode." }), _jsxs("div", { children: [_jsx("strong", { children: "1. Capture work" }), _jsxs("p", { className: managementStyles.sectionText, children: ["Click ", _jsx("code", { children: "Add Task" }), ", write a short title, and save."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "2. Plan the board" }), _jsxs("p", { className: managementStyles.sectionText, children: ["Use ", _jsx("code", { children: "Sort" }), ", ", _jsx("code", { children: "Group" }), ", and ", _jsx("code", { children: "Filters" }), " in the header."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "3. Execute and stage" }), _jsxs("p", { className: managementStyles.sectionText, children: ["Move tasks through ", _jsx("code", { children: "Task" }), ", ", _jsx("code", { children: "In Progress" }), ", ", _jsx("code", { children: "Review" }), ", and ", _jsx("code", { children: "Done" }), "."] })] }), _jsxs("div", { children: [_jsx("strong", { children: "4. Archive finished work" }), _jsx("p", { className: managementStyles.sectionText, children: "Archive completed/cancelled tasks to keep active board signal high." })] }), _jsxs("div", { className: `${modalStyles.formActions} ${managementStyles.modalActionsEnd}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => {
2006
3045
  setShowHelpModal(false);
2007
3046
  setActiveTab('settings');
2008
3047
  setSettingsSection('general');
2009
- }, children: "Open Settings" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowHelpModal(false), children: "Close Tutorial" })] })] }) }), _jsx(Modal, { isOpen: showAccountHub, onClose: () => setShowAccountHub(false), title: "Account Hub", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: modalStyles.form, style: { paddingTop: '18px' }, children: [_jsx("p", { style: { margin: 0, color: 'var(--text-secondary)' }, children: "Manage sign in, profile, and workspace controls from one place." }), _jsxs("div", { className: styles.accountHubCard, children: [_jsx("strong", { children: "Authentication" }), _jsx("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: runtimeMode === 'cloud'
3048
+ }, children: "Open Settings" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowHelpModal(false), children: "Close Tutorial" })] })] }) }), _jsx(Modal, { isOpen: showEditProfileModal, onClose: () => setShowEditProfileModal(false), title: "Edit Profile", size: "sm", theme: currentTheme, draggable: true, footer: (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: styles.secondaryButton, onClick: () => setShowEditProfileModal(false), disabled: profileSaveBusy, children: "Cancel" }), _jsx("button", { type: "button", className: styles.primaryUpdateBtn, onClick: () => { void handleSaveProfile(); }, disabled: profileSaveBusy || !profileDisplayNameDraft.trim(), children: profileSaveBusy ? 'Saving...' : 'Save Profile' })] })), children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.sectionText, children: "Update the name shown around your account and shared workspace surfaces." }), _jsxs("label", { className: managementStyles.inlineFieldLabel, children: ["Display name", _jsx("input", { className: styles.input, type: "text", value: profileDisplayNameDraft, onChange: (event) => setProfileDisplayNameDraft(event.target.value), placeholder: "Display name", disabled: profileSaveBusy })] }), _jsxs("label", { className: managementStyles.inlineFieldLabel, children: ["Email", _jsx("input", { className: styles.input, type: "email", value: authEmailLabel, readOnly: true, disabled: true })] }), profileSaveError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: profileSaveError })), profileSaveNotice && !profileSaveError && (_jsx("p", { className: managementStyles.errorText, children: profileSaveNotice }))] }) }), _jsx(Modal, { isOpen: showAccountHub, onClose: () => setShowAccountHub(false), title: "Account Hub", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [_jsx("p", { className: managementStyles.mutedText, children: "Manage sign in, profile, and workspace controls from one place." }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Authentication" }), _jsx("p", { className: managementStyles.sectionText, children: runtimeMode === 'cloud'
2010
3049
  ? (authRequiredForApi
2011
3050
  ? (isAuthenticated ? 'Signed in. API access is enabled.' : 'Cloud mode requires sign in before app usage.')
2012
3051
  : 'Cloud runtime with optional authentication.')
2013
- : 'Local runtime allows guest usage without sign in.' }), _jsx("p", { style: { margin: '6px 0 0 0', color: 'var(--text-secondary)' }, children: isAuthenticated
3052
+ : 'Local runtime allows guest usage without sign in.' }), _jsx("p", { className: managementStyles.sectionTextSpaced, children: isAuthenticated
2014
3053
  ? (hasAuthIdentity
2015
- ? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: styles.accountIdentityEmail, children: authIdentityLabel })] })
3054
+ ? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel })] })
2016
3055
  : 'Signed in')
2017
- : 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", { style: { marginTop: '10px', color: 'var(--text-secondary)' }, children: "Use the `/login` screen to sign in." }))] }), _jsxs("div", { className: styles.accountHubCard, children: [_jsx("strong", { children: "Profile & Account" }), _jsx("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: "Profile editing and account preferences will live here." })] }), _jsxs("div", { className: styles.accountHubCard, children: [_jsx("strong", { children: "Billing" }), runtimeMode !== 'cloud' ? (_jsx("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: "Billing actions are available in cloud runtime only." })) : !isAuthenticated ? (_jsx("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: "Sign in to manage subscription billing." })) : (_jsxs(_Fragment, { children: [billingLoading && (_jsx("p", { style: { margin: '6px 0 0 0', color: 'var(--text-secondary)' }, children: "Loading billing status..." })), billingError && (_jsx("p", { className: styles.loginError, style: { marginTop: '8px' }, children: billingError })), billingActionError && (_jsx("p", { className: styles.loginError, style: { marginTop: '8px' }, children: billingActionError })), billingNotice && (_jsx("p", { style: { margin: '8px 0 0 0', color: 'var(--text-secondary)' }, children: billingNotice })), _jsxs("p", { style: { margin: '6px 0 0 0', color: 'var(--text-secondary)' }, children: ["Plan: ", _jsx("code", { children: String(billingStatus?.planId || 'free') }), ' · ', "Entitlement: ", _jsx("code", { children: String(billingStatus?.entitlementState || 'free') })] }), _jsxs("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: ["Stripe status: ", _jsx("code", { children: String(billingStatus?.stripeStatus || 'n/a') }), billingStatus?.effectiveUntil ? ` · Effective until ${new Date(billingStatus.effectiveUntil).toLocaleString()}` : ''] }), _jsx("div", { style: { marginTop: '10px', display: 'flex', gap: '8px', flexWrap: 'wrap' }, children: _jsxs("label", { className: styles.accountMenuMeta, style: { display: 'inline-flex', alignItems: 'center', gap: '8px' }, children: ["Interval", _jsxs("select", { className: styles.input, value: billingIntervalChoice, onChange: (event) => setBillingIntervalChoice(event.target.value === 'year' ? 'year' : 'month'), disabled: billingActionBusy, children: [_jsx("option", { value: "month", children: "Monthly" }), _jsx("option", { value: "year", children: "Yearly" })] })] }) }), _jsxs("div", { style: { marginTop: '10px', display: 'flex', gap: '10px', justifyContent: 'flex-end', flexWrap: 'wrap' }, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => { void loadBillingStatus(); }, disabled: billingActionBusy || billingLoading, children: "Refresh Billing" }), _jsx("button", { className: styles.cancelBtn, onClick: () => { void handleSwitchSubscriptionInterval(); }, disabled: billingActionBusy || !billingStatus?.stripeSubscriptionId, children: "Update Interval" }), _jsx("button", { className: styles.cancelBtn, onClick: () => { void handleOpenBillingPortal(); }, disabled: billingActionBusy || !billingStatus?.stripeCustomerId, children: "Manage Billing" }), _jsx("button", { className: styles.submitBtn, onClick: () => { void handleStartCheckout(); }, disabled: billingActionBusy, children: billingActionBusy ? 'Working...' : 'Start Checkout' })] })] }))] }), _jsxs("div", { className: styles.accountHubCard, children: [_jsx("strong", { children: "Workspace Audit Log" }), _jsx("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: "Audit entries are workspace-scoped for the active workspace, not global account history." }), _jsxs("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: ["Active workspace: ", _jsx("code", { children: currentWorkspaceId })] }), _jsx("div", { style: { marginTop: '10px', display: 'flex', gap: '10px', justifyContent: 'flex-end' }, children: _jsx("button", { className: styles.cancelBtn, onClick: () => {
3056
+ : 'Signed in as: not signed in' }), runtimeMode === 'cloud' && authRequiredForApi && !isAuthenticated && (_jsx("p", { className: managementStyles.sectionTextTop, children: "Use the `/login` screen to sign in." }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Profile & Account" }), _jsx("p", { className: managementStyles.sectionText, children: "Profile editing and account preferences will live here." })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Billing" }), !isAuthenticated ? (_jsx("p", { className: managementStyles.sectionText, children: "Sign in to manage subscription billing." })) : runtimeMode !== 'cloud' ? (_jsx("p", { className: managementStyles.sectionText, children: "Billing remains cloud-backed in local runtime and uses your connected cloud account." })) : (_jsxs(_Fragment, { children: [billingLoading && (_jsx("p", { className: managementStyles.sectionTextSpaced, children: "Loading billing status..." })), billingError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingError })), billingActionError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: billingActionError })), billingNotice && (_jsx("p", { className: managementStyles.errorText, children: billingNotice })), _jsxs("p", { className: managementStyles.sectionTextSpaced, children: ["Plan: ", _jsx("code", { children: String(billingStatus?.planId || 'n/a') }), ' · ', "Entitlement: ", _jsx("code", { children: String(billingStatus?.entitlementState || 'n/a') })] }), _jsxs("p", { className: managementStyles.sectionText, children: ["Stripe status: ", _jsx("code", { children: String(billingStatus?.stripeStatus || 'n/a') }), billingStatus?.effectiveUntil ? ` · Effective until ${new Date(billingStatus.effectiveUntil).toLocaleString()}` : ''] }), _jsx("div", { className: managementStyles.wrapRow, children: _jsxs("label", { className: `${accountStyles.accountMenuMeta} ${managementStyles.inlineFieldLabel}`, children: ["Interval", _jsxs("select", { className: styles.input, value: billingIntervalChoice, onChange: (event) => setBillingIntervalChoice(event.target.value === 'year' ? 'year' : 'month'), disabled: billingActionBusy, children: [_jsx("option", { value: "month", children: "Monthly" }), _jsx("option", { value: "year", children: "Yearly" })] })] }) }), _jsxs("div", { className: managementStyles.actionRowEnd, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => { void loadBillingStatus(); }, disabled: billingActionBusy || billingLoading, children: "Refresh Billing" }), _jsx("button", { className: styles.cancelBtn, onClick: () => { void handleSwitchSubscriptionInterval(); }, disabled: billingActionBusy || !billingStatus?.stripeSubscriptionId, children: "Update Interval" }), _jsx("button", { className: styles.cancelBtn, onClick: () => { void handleOpenBillingPortal(); }, disabled: billingActionBusy || !billingStatus?.stripeCustomerId, children: "Manage Billing" }), _jsx("button", { className: styles.submitBtn, onClick: () => { void handleStartCheckout(); }, disabled: billingActionBusy, children: billingActionBusy ? 'Working...' : 'Start Checkout' })] })] }))] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Audit Log" }), _jsx("p", { className: managementStyles.sectionText, children: "Audit entries are workspace-scoped for the active workspace, not global account history." }), _jsxs("p", { className: managementStyles.sectionText, children: ["Active workspace: ", _jsx("code", { children: currentWorkspaceId })] }), _jsx("div", { className: managementStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, onClick: () => {
2018
3057
  setShowAccountHub(false);
2019
3058
  void openTeamManagementModal();
2020
3059
  setTeamManagementTab('audit');
2021
- }, disabled: !canOpenTeamManagement, children: "Open Workspace Audit" }) })] }), _jsxs("div", { className: styles.accountHubCard, children: [_jsx("strong", { children: "Workspace Cloud Sync" }), _jsx("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: canManageWorkspaceSync
3060
+ }, disabled: !canOpenTeamManagement, children: "Open Workspace Audit" }) })] }), _jsxs("div", { className: accountStyles.accountHubCard, children: [_jsx("strong", { children: "Workspace Cloud Sync" }), _jsx("p", { className: managementStyles.sectionText, children: canManageWorkspaceSync
2022
3061
  ? (workspaceCloudSyncEnabled
2023
- ? `Enabled${workspaceSyncSourceOfTruth ? ` · Source: ${workspaceSyncSourceOfTruth}` : ''}`
3062
+ ? `Enabled · ${syncStatusMeta.label}`
2024
3063
  : 'Disabled')
2025
- : 'Sign in to cloud account to enable workspace sync.' }), workspaceSyncError && (_jsx("p", { className: styles.loginError, style: { marginTop: '8px' }, children: workspaceSyncError })), _jsx("div", { style: { marginTop: '10px', display: 'flex', gap: '10px', justifyContent: 'flex-end' }, children: _jsx("button", { className: styles.cancelBtn, disabled: !canManageWorkspaceSync || workspaceSyncBusy, onClick: async () => {
2026
- if (!canManageWorkspaceSync)
2027
- return;
2028
- setWorkspaceSyncError(null);
2029
- if (!workspaceCloudSyncEnabled) {
2030
- setWorkspaceSyncBusy(true);
2031
- const result = await saveWorkspaceCloudSyncSettings({
2032
- enabled: true,
2033
- // I6: start false; the orchestrator marks it true after first successful pull.
2034
- onboardingCompleted: false
2035
- });
2036
- if (!result.success)
2037
- setWorkspaceSyncError(result.error || 'Failed to enable sync.');
2038
- setWorkspaceSyncBusy(false);
2039
- return;
2040
- }
2041
- setWorkspaceSyncBusy(true);
2042
- const result = await saveWorkspaceCloudSyncSettings({
2043
- enabled: false,
2044
- sourceOfTruth: workspaceSyncSourceOfTruth,
2045
- onboardingCompleted: workspaceSyncOnboardingCompleted
2046
- });
2047
- if (!result.success)
2048
- setWorkspaceSyncError(result.error || 'Failed to disable sync.');
2049
- setWorkspaceSyncBusy(false);
2050
- }, children: workspaceCloudSyncEnabled ? 'Disable Sync' : 'Enable Sync' }) })] }), _jsxs("div", { className: modalStyles.formActions, style: { display: 'flex', justifyContent: 'flex-end', gap: '10px' }, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => {
3064
+ : 'Sign in to cloud account to enable workspace sync.' }), workspaceSyncError && (_jsx("p", { className: `${authStyles.loginError} ${managementStyles.errorText}`, children: workspaceSyncError })), _jsx("div", { className: managementStyles.actionRowEnd, children: _jsx("button", { className: styles.cancelBtn, disabled: !canManageWorkspaceSync || syncControlBusy, onClick: () => { void handleWorkspaceSyncToggle(!workspaceCloudSyncEnabled); }, children: workspaceCloudSyncEnabled ? 'Disable Sync' : 'Enable Sync' }) })] }), _jsxs("div", { className: `${modalStyles.formActions} ${managementStyles.modalActionsEnd}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => {
2051
3065
  setShowAccountHub(false);
2052
3066
  setShowHelpModal(true);
2053
- }, children: "Help & Tutorial" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowAccountHub(false), children: "Close" })] })] }) }), _jsx(Modal, { isOpen: showTeamManagementModal, onClose: () => setShowTeamManagementModal(false), title: "Team Management", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: modalStyles.form, style: { paddingTop: '18px' }, children: [teamPlanMode !== 'team' && (_jsx("p", { className: styles.loginError, children: "Team management is only available in TEAM accounts." })), teamMgmtError && (_jsx("p", { className: styles.loginError, children: teamMgmtError })), _jsxs("div", { className: styles.settingsTabs, children: [_jsx("button", { className: `${styles.settingsTabBtn} ${teamManagementTab === 'members' ? styles.settingsTabBtnActive : ''}`, onClick: () => {
3067
+ }, children: "Help & Tutorial" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowAccountHub(false), children: "Close" })] })] }) }), _jsx(Modal, { isOpen: showTeamManagementModal, onClose: () => setShowTeamManagementModal(false), title: "Team Management", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: [teamPlanMode === 'personal' && (_jsx("p", { className: authStyles.loginError, children: "Team management is only available in TEAM accounts." })), teamMgmtError && (_jsx("p", { className: authStyles.loginError, children: teamMgmtError })), _jsxs("div", { className: `${styles.settingsTabs} tf-scrollbar tf-scrollbar--track-transparent tf-scrollbar--compact`, children: [_jsx("button", { className: `${styles.settingsTabBtn} ${teamManagementTab === 'members' ? styles.settingsTabBtnActive : ''}`, onClick: () => {
2054
3068
  setTeamManagementTab('members');
2055
3069
  void loadTeamUsers();
2056
3070
  }, children: "Members" }), _jsx("button", { className: `${styles.settingsTabBtn} ${teamManagementTab === 'invites' ? styles.settingsTabBtnActive : ''}`, onClick: () => setTeamManagementTab('invites'), children: "Invites" }), _jsx("button", { className: `${styles.settingsTabBtn} ${teamManagementTab === 'audit' ? styles.settingsTabBtnActive : ''}`, onClick: () => {
2057
3071
  setTeamManagementTab('audit');
2058
3072
  void loadTeamAuditPage(teamAuditPage);
2059
- }, children: "Audit Log" })] }), teamManagementTab === 'members' && (_jsxs("div", { className: styles.accountHubCard, children: [teamUsersLoading && _jsx("p", { style: { margin: 0, color: 'var(--text-secondary)' }, children: "Loading members..." }), !teamUsersLoading && teamUsers.length === 0 && (_jsx("p", { style: { margin: 0, color: 'var(--text-secondary)' }, children: "No users found." })), !teamUsersLoading && teamUsers.map((member) => (_jsxs("div", { style: { borderTop: '1px solid var(--border-color)', paddingTop: '8px', marginTop: '8px' }, children: [_jsx("div", { style: { fontWeight: 600 }, children: member.displayName || member.email }), _jsxs("div", { className: styles.accountMenuMeta, children: [member.email, " \u00B7 ", member.status, member.disabled ? ' · deactivated' : ''] }), _jsxs("div", { style: { marginTop: '6px', display: 'flex', gap: '8px', flexWrap: 'wrap' }, children: [_jsxs("select", { className: styles.input, value: member.role, onChange: (event) => {
3073
+ }, children: "Audit Log" })] }), teamManagementTab === 'members' && (_jsxs("div", { className: accountStyles.accountHubCard, children: [teamUsersLoading && _jsx("p", { className: managementStyles.mutedText, children: "Loading members..." }), !teamUsersLoading && teamUsers.length === 0 && (_jsx("p", { className: managementStyles.mutedText, children: "No users found." })), !teamUsersLoading && teamUsers.map((member) => (_jsxs("div", { className: managementStyles.memberRow, children: [_jsx("div", { className: managementStyles.memberTitle, children: member.displayName || member.email }), _jsxs("div", { className: accountStyles.accountMenuMeta, children: [member.email, " \u00B7 ", member.status, member.disabled ? ' · deactivated' : ''] }), _jsxs("div", { className: managementStyles.memberActions, children: [_jsxs("select", { className: `${styles.input} ${managementStyles.selectRole}`, value: member.role, onChange: (event) => {
2060
3074
  void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/role`, {
2061
3075
  method: 'PATCH',
2062
3076
  headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
2063
3077
  credentials: 'include',
2064
3078
  body: JSON.stringify({
2065
- workspaceId: currentWorkspaceId,
3079
+ workspaceId: teamManagementWorkspaceId,
2066
3080
  role: event.target.value
2067
3081
  })
2068
3082
  }));
2069
- }, disabled: teamActionBusyUserId === member.userId, style: { maxWidth: '160px' }, children: [_jsx("option", { value: "owner", children: "Owner" }), _jsx("option", { value: "admin", children: "Admin" }), _jsx("option", { value: "member", children: "Member" })] }), member.role === 'member' && (_jsxs("select", { className: styles.input, value: member.permissionMode, onChange: (event) => {
3083
+ }, disabled: teamActionBusyUserId === member.userId, children: [_jsx("option", { value: "owner", children: "Owner" }), _jsx("option", { value: "admin", children: "Admin" }), _jsx("option", { value: "member", children: "Member" })] }), member.role === 'member' && (_jsxs("select", { className: `${styles.input} ${managementStyles.selectPermission}`, value: member.permissionMode, onChange: (event) => {
2070
3084
  void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/permission-mode`, {
2071
3085
  method: 'PATCH',
2072
3086
  headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
2073
3087
  credentials: 'include',
2074
3088
  body: JSON.stringify({
2075
- workspaceId: currentWorkspaceId,
3089
+ workspaceId: teamManagementWorkspaceId,
2076
3090
  mode: event.target.value
2077
3091
  })
2078
3092
  }));
2079
- }, disabled: teamActionBusyUserId === member.userId, style: { maxWidth: '170px' }, children: [_jsx("option", { value: "read-write", children: "Read / Write" }), _jsx("option", { value: "read-only", children: "Read Only" })] })), _jsx("button", { className: styles.cancelBtn, disabled: teamActionBusyUserId === member.userId, onClick: () => {
3093
+ }, disabled: teamActionBusyUserId === member.userId, children: [_jsx("option", { value: "read-write", children: "Read / Write" }), _jsx("option", { value: "read-only", children: "Read Only" })] })), _jsx("button", { className: styles.cancelBtn, disabled: teamActionBusyUserId === member.userId, onClick: () => {
2080
3094
  void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/disable`, {
2081
3095
  method: 'PATCH',
2082
3096
  headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
2083
3097
  credentials: 'include',
2084
3098
  body: JSON.stringify({
2085
- workspaceId: currentWorkspaceId,
3099
+ workspaceId: teamManagementWorkspaceId,
2086
3100
  disabled: !member.disabled
2087
3101
  })
2088
3102
  }));
@@ -2091,27 +3105,52 @@ export function StandaloneLayout(props) {
2091
3105
  method: 'POST',
2092
3106
  headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
2093
3107
  credentials: 'include',
2094
- body: JSON.stringify({ workspaceId: currentWorkspaceId })
3108
+ body: JSON.stringify({ workspaceId: teamManagementWorkspaceId })
2095
3109
  }));
2096
3110
  }, children: "Revoke Invite" })), _jsx("button", { className: styles.cancelBtn, disabled: teamActionBusyUserId === member.userId, onClick: () => {
2097
- void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}?workspaceId=${encodeURIComponent(currentWorkspaceId)}`, {
3111
+ void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}?workspaceId=${encodeURIComponent(teamManagementWorkspaceId)}`, {
2098
3112
  method: 'DELETE',
2099
3113
  headers: teamAdminHeaders(),
2100
3114
  credentials: 'include'
2101
3115
  }));
2102
- }, children: "Remove" })] })] }, member.userId)))] })), teamManagementTab === 'invites' && (_jsxs("div", { className: styles.accountHubCard, children: [teamInviteFeedback && (_jsx("p", { style: { margin: '0 0 8px 0', color: 'var(--text-secondary)' }, children: teamInviteFeedback })), _jsxs("div", { className: styles.pathInputGroup, children: [_jsx("label", { className: styles.label, style: { minWidth: '90px' }, children: "Email" }), _jsx("input", { className: styles.input, value: teamInviteEmail, onChange: (event) => setTeamInviteEmail(event.target.value), placeholder: "name@company.com" })] }), _jsxs("div", { className: styles.pathInputGroup, children: [_jsx("label", { className: styles.label, style: { minWidth: '90px' }, children: "Role" }), _jsxs("select", { className: styles.input, value: teamInviteRole, onChange: (event) => setTeamInviteRole(event.target.value === 'admin' ? 'admin' : 'member'), style: { maxWidth: '150px' }, children: [_jsx("option", { value: "member", children: "Member" }), _jsx("option", { value: "admin", children: "Admin" })] }), teamInviteRole === 'member' && (_jsxs("select", { className: styles.input, value: teamInvitePermissionMode, onChange: (event) => setTeamInvitePermissionMode(event.target.value === 'read-only' ? 'read-only' : 'read-write'), style: { maxWidth: '170px' }, children: [_jsx("option", { value: "read-write", children: "Read / Write" }), _jsx("option", { value: "read-only", children: "Read Only" })] })), _jsx("button", { className: styles.submitBtn, disabled: teamInviteBusy || !teamInviteEmail.trim() || teamPlanMode !== 'team', onClick: () => { void submitTeamInvite(); }, children: teamInviteBusy ? 'Sending...' : 'Send Invite' })] }), _jsxs("div", { style: { marginTop: '12px' }, children: [_jsx("div", { style: { fontWeight: 600, marginBottom: '6px' }, children: "Pending Invites" }), pendingInvites.length === 0 && (_jsx("p", { style: { margin: 0, color: 'var(--text-secondary)' }, children: "No pending invites." })), pendingInvites.map((invite) => (_jsxs("div", { style: { borderTop: '1px solid var(--border-color)', paddingTop: '8px', marginTop: '8px' }, children: [_jsx("div", { style: { fontWeight: 600 }, children: invite.displayName || invite.email }), _jsxs("div", { className: styles.accountMenuMeta, children: [invite.email, " \u00B7 ", invite.role] })] }, invite.userId)))] })] })), teamManagementTab === 'audit' && (_jsxs("div", { className: styles.accountHubCard, children: [teamAuditLoading && _jsx("p", { style: { margin: 0, color: 'var(--text-secondary)' }, children: "Loading audit entries..." }), !teamAuditLoading && teamAuditEvents.length === 0 && (_jsx("p", { style: { margin: 0, color: 'var(--text-secondary)' }, children: "No audit events for this workspace." })), !teamAuditLoading && (_jsx("div", { style: { maxHeight: '320px', overflowY: 'auto', paddingRight: '4px' }, children: teamAuditEvents.map((event) => (_jsxs("div", { style: { borderTop: '1px solid var(--border-color)', paddingTop: '8px', marginTop: '8px' }, children: [_jsx("div", { className: styles.accountMenuMeta, children: new Date(event.createdAt).toLocaleString() }), _jsx("div", { style: { fontWeight: 600 }, children: event.action }), _jsxs("div", { className: styles.accountMenuMeta, children: [event.actorUserId, " (", event.actorRole, ")"] })] }, event.id))) })), _jsxs("div", { style: { marginTop: '10px', display: 'flex', justifyContent: 'space-between', gap: '8px' }, children: [_jsx("button", { className: styles.cancelBtn, disabled: teamAuditLoading || teamAuditPage === 0, onClick: () => { void loadTeamAuditPage(teamAuditPage - 1); }, children: "Previous" }), _jsxs("span", { className: styles.accountMenuMeta, children: ["Page ", teamAuditPage + 1, " of ", teamAuditPages] }), _jsx("button", { className: styles.cancelBtn, disabled: teamAuditLoading || !teamAuditHasMore || teamAuditPage + 1 >= teamAuditPages, onClick: () => { void loadTeamAuditPage(teamAuditPage + 1); }, children: "Next" })] })] })), _jsx("div", { className: modalStyles.formActions, style: { display: 'flex', justifyContent: 'flex-end', gap: '10px' }, children: _jsx("button", { className: styles.cancelBtn, onClick: () => setShowTeamManagementModal(false), children: "Close" }) })] }) }), _jsx(Modal, { isOpen: showSyncStatusModal, onClose: () => {
3116
+ }, children: "Remove" })] })] }, member.userId)))] })), teamManagementTab === 'invites' && (_jsxs("div", { className: accountStyles.accountHubCard, children: [teamInviteFeedback && (_jsx("p", { className: managementStyles.sectionText, children: teamInviteFeedback })), _jsxs("div", { className: styles.pathInputGroup, children: [_jsx("label", { className: `${styles.label} ${managementStyles.labelFixed}`, children: "Email" }), _jsx("input", { className: styles.input, value: teamInviteEmail, onChange: (event) => setTeamInviteEmail(event.target.value), placeholder: "name@company.com" })] }), _jsxs("div", { className: styles.pathInputGroup, children: [_jsx("label", { className: `${styles.label} ${managementStyles.labelFixed}`, children: "Role" }), _jsxs("select", { className: `${styles.input} ${managementStyles.selectRole}`, value: teamInviteRole, onChange: (event) => setTeamInviteRole(event.target.value === 'admin' ? 'admin' : 'member'), children: [_jsx("option", { value: "member", children: "Member" }), _jsx("option", { value: "admin", children: "Admin" })] }), teamInviteRole === 'member' && (_jsxs("select", { className: `${styles.input} ${managementStyles.selectPermission}`, value: teamInvitePermissionMode, onChange: (event) => setTeamInvitePermissionMode(event.target.value === 'read-only' ? 'read-only' : 'read-write'), children: [_jsx("option", { value: "read-write", children: "Read / Write" }), _jsx("option", { value: "read-only", children: "Read Only" })] })), _jsx("button", { className: styles.submitBtn, disabled: teamInviteBusy || !teamInviteEmail.trim() || teamPlanMode !== 'team', onClick: () => { void submitTeamInvite(); }, children: teamInviteBusy ? 'Sending...' : 'Send Invite' })] }), _jsxs("div", { className: managementStyles.inviteRow, children: [_jsx("div", { className: managementStyles.listHeading, children: "Pending Invites" }), pendingInvites.length === 0 && (_jsx("p", { className: managementStyles.mutedText, children: "No pending invites." })), pendingInvites.map((invite) => (_jsxs("div", { className: managementStyles.memberRow, children: [_jsx("div", { className: managementStyles.memberTitle, children: invite.displayName || invite.email }), _jsxs("div", { className: accountStyles.accountMenuMeta, children: [invite.email, " \u00B7 ", invite.role] })] }, invite.userId)))] })] })), teamManagementTab === 'audit' && (_jsxs("div", { className: accountStyles.accountHubCard, children: [teamAuditLoading && _jsx("p", { className: managementStyles.mutedText, children: "Loading audit entries..." }), !teamAuditLoading && teamAuditEvents.length === 0 && (_jsx("p", { className: managementStyles.mutedText, children: "No audit events for this workspace." })), !teamAuditLoading && (_jsx("div", { className: managementStyles.auditList, children: teamAuditEvents.map((event) => (_jsxs("div", { className: managementStyles.memberRow, children: [_jsx("div", { className: accountStyles.accountMenuMeta, children: new Date(event.createdAt).toLocaleString() }), _jsx("div", { className: managementStyles.memberTitle, children: event.action }), _jsxs("div", { className: accountStyles.accountMenuMeta, children: [event.actorUserId, " (", event.actorRole, ")"] })] }, event.id))) })), _jsxs("div", { className: managementStyles.auditPager, children: [_jsx("button", { className: styles.cancelBtn, disabled: teamAuditLoading || teamAuditPage === 0, onClick: () => { void loadTeamAuditPage(teamAuditPage - 1); }, children: "Previous" }), _jsxs("span", { className: accountStyles.accountMenuMeta, children: ["Page ", teamAuditPage + 1, " of ", teamAuditPages] }), _jsx("button", { className: styles.cancelBtn, disabled: teamAuditLoading || !teamAuditHasMore || teamAuditPage + 1 >= teamAuditPages, onClick: () => { void loadTeamAuditPage(teamAuditPage + 1); }, children: "Next" })] })] })), _jsx("div", { className: `${modalStyles.formActions} ${managementStyles.modalActionsEnd}`, children: _jsx("button", { className: styles.cancelBtn, onClick: () => setShowTeamManagementModal(false), children: "Close" }) })] }) }), _jsx(Modal, { isOpen: showCreateWorkspaceConfirm, onClose: () => setShowCreateWorkspaceConfirm(false), title: "Create Workspace", size: "sm", theme: currentTheme, draggable: true, footer: (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: styles.secondaryButton, onClick: () => setShowCreateWorkspaceConfirm(false), children: "Cancel" }), _jsx("button", { type: "button", className: styles.primaryUpdateBtn, onClick: () => openWorkspaceSetup({ intent: 'create-workspace' }), children: "Continue to Setup" })] })), children: _jsx("div", { className: `${modalStyles.form} ${managementStyles.modalBody}`, children: _jsx("p", { className: managementStyles.sectionText, children: "Start a new workspace with the guided setup flow. You will choose the workspace name, description, and starter taxonomy library before anything is created." }) }) }), _jsx(Modal, { isOpen: showSyncStatusModal, onClose: () => {
2103
3117
  setShowSyncStatusModal(false);
2104
- }, title: "Cloud Sync Status", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${styles.syncStatusModal}`, children: [_jsxs("div", { className: styles.syncStatusTop, children: [_jsxs("div", { children: [_jsx("div", { className: styles.syncStatusHeading, children: "Current Status" }), _jsx("div", { className: styles.syncStatusSubtext, children: _jsx("code", { children: currentWorkspaceId }) }), workspaceBootstrapSyncInProgress && (_jsxs("div", { className: styles.syncStatusPausedHint, children: ["Initial sync in progress: ", workspaceBootstrapSyncAppliedChanges, " changes across ", workspaceBootstrapSyncPages, " page", workspaceBootstrapSyncPages === 1 ? '' : 's', "."] })), runtimeMode === 'local' && !isAuthenticated && (_jsx("div", { className: styles.syncStatusPausedHint, children: "Sync is paused. Sign in on cloud to resume." }))] }), _jsx("div", { className: styles.syncStatusBadge, style: {
2105
- borderColor: modalSyncStatusMeta.border,
2106
- background: modalSyncStatusMeta.background,
2107
- color: modalSyncStatusMeta.color
2108
- }, children: modalSyncStatusMeta.label })] }), _jsxs("div", { className: styles.syncStatusGrid, children: [_jsxs("div", { className: styles.syncStatusItem, children: [_jsx("span", { className: styles.syncStatusLabel, children: "Last successful sync" }), _jsx("span", { className: styles.syncStatusValue, children: formattedLastSyncTime })] }), _jsxs("div", { className: styles.syncStatusItem, children: [_jsx("span", { className: styles.syncStatusLabel, children: "Pending local changes" }), _jsx("span", { className: styles.syncStatusValue, children: userGlobalSyncPendingChanges })] }), _jsxs("div", { className: styles.syncStatusItem, children: [_jsx("span", { className: styles.syncStatusLabel, children: "Last pull from cloud" }), _jsx("span", { className: styles.syncStatusValue, children: formattedLastPullTime })] }), _jsxs("div", { className: styles.syncStatusItem, children: [_jsx("span", { className: styles.syncStatusLabel, children: "Last push to cloud" }), _jsx("span", { className: styles.syncStatusValue, children: formattedLastPushTime })] }), _jsxs("div", { className: styles.syncStatusItem, children: [_jsx("span", { className: styles.syncStatusLabel, children: "Scheduled retry" }), _jsx("span", { className: styles.syncStatusValue, children: workspaceRetryAt ? formattedRetryTime : 'None' })] }), _jsxs("div", { className: styles.syncStatusItem, children: [_jsx("span", { className: styles.syncStatusLabel, children: "Last error time" }), _jsx("span", { className: styles.syncStatusValue, children: workspaceLastErrorAt ? formattedLastErrorTime : 'None' })] })] }), _jsxs("div", { className: styles.syncStatusDetailsGrid, children: [_jsx("div", { className: styles.syncStatusTableWrap, children: _jsx("table", { className: styles.syncStatusTable, children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("th", { children: "Runtime mode" }), _jsx("td", { children: runtimeMode })] }), _jsxs("tr", { children: [_jsx("th", { children: "Sync source of truth" }), _jsx("td", { children: workspaceSyncSourceOfTruth || 'unset' })] }), _jsxs("tr", { children: [_jsx("th", { children: "Ready" }), _jsx("td", { children: workspaceSyncReady ? 'yes' : 'no' })] }), _jsxs("tr", { children: [_jsx("th", { children: "Hydrated" }), _jsx("td", { children: workspaceCloudHydrated ? 'yes' : 'no' })] }), _jsxs("tr", { children: [_jsx("th", { children: "Failure count" }), _jsx("td", { children: workspaceSyncFailureCount })] })] }) }) }), _jsx("div", { className: styles.syncStatusTableWrap, children: _jsx("table", { className: styles.syncStatusTable, children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("th", { children: "Auth failures" }), _jsx("td", { children: workspaceSyncAuthFailureCount })] }), _jsxs("tr", { children: [_jsx("th", { children: "Last failure source" }), _jsx("td", { children: workspaceSyncLastFailureSource || 'none' })] }), _jsxs("tr", { children: [_jsx("th", { children: "Last failure status" }), _jsx("td", { children: workspaceSyncLastFailureStatusCode ?? 'none' })] }), _jsxs("tr", { children: [_jsx("th", { children: "Retries scheduled" }), _jsx("td", { children: workspaceSyncRetryScheduledCount })] })] }) }) })] }), _jsx("div", { className: styles.syncStatusTableWrap, children: _jsx("table", { className: styles.syncStatusTable, children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("th", { className: styles.syncStatusTableNarrowLabel, children: "Last error" }), _jsx("td", { className: styles.syncStatusTableError, style: { color: (workspaceLastErrorMessage || userGlobalSyncError) ? '#fda4af' : 'var(--text-secondary)' }, children: workspaceLastErrorMessage || userGlobalSyncError || 'None' })] }), _jsxs("tr", { children: [_jsx("th", { className: styles.syncStatusTableNarrowLabel, children: "Last warning" }), _jsx("td", { style: { color: workspaceLastWarningMessage ? '#fcd34d' : 'var(--text-secondary)' }, children: workspaceLastWarningMessage || 'None' })] })] }) }) }), _jsxs("div", { className: `${modalStyles.formActions} ${styles.syncStatusActions}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => void resetWorkspaceSyncCursorAndPull(), disabled: userGlobalSyncStatus === 'syncing', title: "Clear pull cursor and fetch full cloud state into local workspace", children: "Reset Cursor + Full Pull" }), runtimeMode === 'local' && !isAuthenticated && (_jsx("button", { className: styles.submitBtn, onClick: () => {
2109
- openSharedLoginRoute('login');
2110
- }, children: "Sign In / Register" })), _jsx("button", { className: styles.cancelBtn, onClick: () => {
2111
- setShowSyncStatusModal(false);
2112
- }, children: "Close" }), _jsx("button", { className: styles.submitBtn, onClick: () => void retryWorkspaceCloudSync(), disabled: userGlobalSyncStatus === 'syncing', children: userGlobalSyncStatus === 'syncing' ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 14, className: styles.spinner }), "Syncing..."] })) : (_jsxs(_Fragment, { children: [_jsx(RefreshCw, { size: 14 }), "Retry Now"] })) })] })] }) }), unsavedModalOpen && createPortal(_jsx("div", { className: `${modalStyles.overlay} ${modalStyles.unsavedOverlay}`, style: { zIndex: 2000 }, children: _jsxs("div", { className: `${modalStyles.modal} ${modalStyles.unsavedModal}`, "data-theme": currentTheme, children: [_jsx("div", { className: `${modalStyles.header} ${modalStyles.unsavedHeader}`, children: _jsxs("div", { className: `${modalStyles.headerTitle} ${modalStyles.unsavedTitle}`, children: [_jsx(AlertTriangle, { size: 20 }), "Unsaved Changes"] }) }), _jsx("div", { className: `${modalStyles.form} ${modalStyles.unsavedContent}`, children: _jsx("p", { className: modalStyles.unsavedText, children: "You have unsaved changes. Would you like to save them?" }) }), _jsxs("div", { className: `${modalStyles.formActions} ${modalStyles.unsavedActions}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => setUnsavedModalOpen(false), title: "Close dialog and continue editing", children: "Keep Editing" }), _jsx("button", { className: styles.destructiveBtn, onClick: handleDiscard, title: "Discard unsaved changes and leave", children: "Discard" }), _jsxs("button", { className: styles.submitBtn, onClick: handleSaveAndNavigate, disabled: loading, title: "Save changes and leave", children: [loading ? _jsx(Loader2, { size: 16, className: styles.spinner }) : _jsx(Save, { size: 16 }), "Save"] })] })] }) }), document.body), showFolderBrowser && createPortal(_jsx("div", { className: styles.folderBrowserOverlay, children: _jsxs("div", { className: styles.folderBrowser, children: [_jsxs("div", { className: styles.browserHeader, children: [_jsxs("div", { className: styles.browserPathInfo, children: [_jsx(Folder, { size: 14 }), _jsx("span", { children: currentBrowsePath || 'Project Root' })] }), _jsxs("div", { className: styles.browserActions, children: [currentBrowsePath && (_jsx("button", { className: styles.helpLink, onClick: () => {
3118
+ }, title: "Sync Manager", size: "md", theme: currentTheme, draggable: true, children: _jsxs("div", { className: `${modalStyles.form} ${syncStatusStyles.syncStatusModal}`, style: { gap: '10px' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusTop, children: [_jsx("div", { className: syncStatusStyles.syncStatusHeaderInfo, children: _jsxs("div", { className: syncStatusStyles.syncStatusMetaHeader, children: [_jsx("div", { className: syncStatusStyles.syncStatusBadge, style: {
3119
+ borderColor: syncStatusMeta.border,
3120
+ background: syncStatusMeta.background,
3121
+ color: syncStatusMeta.color
3122
+ }, children: syncStatusMeta.label }), _jsxs("span", { className: syncStatusStyles.syncStatusLabel, children: ["Workspace: ", _jsx("code", { children: currentWorkspaceLabel })] })] }) }), _jsx("label", { className: syncStatusStyles.syncToggle, children: _jsxs("span", { className: [
3123
+ syncStatusStyles.syncToggleControl,
3124
+ workspaceCloudSyncEnabled ? syncStatusStyles.syncToggleControlEnabled : syncStatusStyles.syncToggleControlDisabled,
3125
+ syncControlBusy ? syncStatusStyles.syncToggleControlBusy : '',
3126
+ !canManageWorkspaceSync ? syncStatusStyles.syncToggleControlBlocked : ''
3127
+ ].filter(Boolean).join(' '), children: [_jsx("input", { className: syncStatusStyles.syncToggleInput, type: "checkbox", role: "switch", "aria-label": "Enable Sync", checked: workspaceCloudSyncEnabled, disabled: !canManageWorkspaceSync || syncControlBusy, onChange: (event) => {
3128
+ void handleWorkspaceSyncToggle(event.target.checked);
3129
+ } }), _jsx("span", { className: `${syncStatusStyles.syncToggleState} ${syncStatusStyles.syncToggleStateOn}`, children: "On" }), _jsx("span", { className: `${syncStatusStyles.syncToggleState} ${syncStatusStyles.syncToggleStateOff}`, children: "Off" }), _jsx("span", { className: syncStatusStyles.syncToggleThumb })] }) })] }), _jsxs("div", { className: syncStatusStyles.syncStatusSummaryCompact, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Health" }), _jsx("span", { className: syncStatusStyles.syncStatusSummaryValue, children: workspaceSyncSummary })] }), workspaceSyncRepairBusy && (_jsxs("div", { className: syncStatusStyles.syncStatusRepairBanner, children: [_jsxs("div", { className: syncStatusStyles.syncStatusRepairHeader, children: [_jsxs("div", { className: syncStatusStyles.syncStatusRepairBadge, children: [_jsx(Loader2, { size: 13, className: styles.spinner }), _jsx("span", { children: "Repair In Progress" })] }), _jsx("span", { className: syncStatusStyles.syncStatusRepairMeta, children: "Advanced recovery mode" })] }), _jsx("span", { className: syncStatusStyles.syncStatusRepairText, children: referenceMismatchCount > 0
3130
+ ? `Taskforce is clearing the saved sync cursor and reconciling workspace state from the cloud again, including ${referenceMismatchCount} detected reference mismatch${referenceMismatchCount === 1 ? '' : 'es'}. The panel may stay busy for a while during large repairs.`
3131
+ : 'Taskforce is clearing the saved sync cursor and reconciling workspace state from the cloud again. The panel may stay busy for a while during large repairs.' })] })), _jsx("div", { className: syncStatusStyles.syncStatusSectionHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Current Session" }) }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactGrid, children: [_jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Stage" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: syncStageLabel })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Local Changes" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: workspaceSyncPendingChanges })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Successful" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastSyncTime })] })] }), _jsx("div", { className: syncStatusStyles.syncStatusSectionHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Activity" }) }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactGrid, children: [_jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Pull" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastPullTime })] }), _jsxs("div", { className: syncStatusStyles.syncStatusCompactItem, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Last Push" }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: formattedLastPushTime })] })] }), syncLastError !== 'None' && (_jsxs("div", { className: syncStatusStyles.syncStatusPanel, style: { background: 'rgba(239, 68, 68, 0.05)', borderColor: 'rgba(239, 68, 68, 0.2)' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusSectionHeader, style: { marginTop: 0 }, children: [_jsx(AlertTriangle, { size: 12, color: "#fda4af" }), _jsxs("span", { className: syncStatusStyles.syncStatusLabel, style: { color: '#fda4af' }, children: ["Last Error (", workspaceSyncDiagnostics.lastErrorAt ? new Date(workspaceSyncDiagnostics.lastErrorAt).toLocaleTimeString() : 'Recent', ")"] })] }), _jsx("span", { className: `${syncStatusStyles.syncStatusValue} ${syncStatusStyles.syncStatusValueError}`, children: syncLastError })] })), referenceMismatchCount > 0 && (_jsxs("div", { className: syncStatusStyles.syncStatusPanel, style: { background: 'rgba(250, 204, 21, 0.08)', borderColor: 'rgba(250, 204, 21, 0.22)' }, children: [_jsxs("div", { className: syncStatusStyles.syncStatusPanelHeader, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, style: { color: '#fde68a' }, children: "Identifier Integrity" }), _jsxs("span", { className: syncStatusStyles.syncStatusPanelMeta, children: [referenceMismatchCount, " mismatch", referenceMismatchCount === 1 ? '' : 'es', " detected"] })] }), _jsx("span", { className: syncStatusStyles.syncStatusValue, children: "Document or image reference numbers disagreed during normal sync. Standard sync now preserves existing identifiers; use Repair to reconcile mismatched references deliberately." }), activeReferenceMismatchSummaries.length > 0 && (_jsx("div", { style: { marginTop: '10px', display: 'flex', flexDirection: 'column', gap: '8px' }, children: activeReferenceMismatchSummaries.map((item) => (_jsxs("div", { style: {
3132
+ display: 'flex',
3133
+ flexDirection: 'column',
3134
+ gap: '2px',
3135
+ padding: '8px 10px',
3136
+ borderRadius: '8px',
3137
+ background: 'rgba(15, 23, 42, 0.18)',
3138
+ border: '1px solid rgba(250, 204, 21, 0.16)'
3139
+ }, children: [_jsx("span", { className: syncStatusStyles.syncStatusValue, style: { fontSize: '12px', wordBreak: 'break-all' }, children: item.pathLabel }), item.refsLabel ? (_jsx("span", { className: syncStatusStyles.syncStatusPanelMeta, children: item.refsLabel })) : null] }, item.key))) }))] })), _jsxs("div", { className: syncStatusStyles.syncStatusPanel, children: [_jsxs("div", { className: syncStatusStyles.syncStatusPanelHeader, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Local Sync Diagnostics" }), _jsx("span", { className: syncStatusStyles.syncStatusPanelMeta, style: { fontSize: '10px' }, children: "AI profiles & metadata" })] }), _jsx("div", { className: syncStatusStyles.syncStatusCompactGrid, style: { gridTemplateColumns: 'repeat(2, 1fr)' }, children: syncDiagnosticsSummary.map((item) => (_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: '8px' }, children: [_jsx("span", { className: syncStatusStyles.syncStatusLabel, style: { fontSize: '9px', textTransform: 'capitalize' }, children: item.label }), _jsx("span", { className: syncStatusStyles.syncStatusValue, style: { fontSize: '11px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: item.value })] }, item.label))) })] }), _jsxs("div", { className: syncStatusStyles.syncStatusPanel, children: [_jsx("div", { className: syncStatusStyles.syncStatusPanelHeader, children: _jsx("span", { className: syncStatusStyles.syncStatusLabel, children: "Recent Events" }) }), _jsx("div", { ref: syncEventsListRef, className: syncStatusStyles.syncStatusEventsList, style: { height: '120px' }, children: syncEventRows.map((row) => (_jsx("div", { className: [
3140
+ syncStatusStyles.syncStatusEventItem,
3141
+ row.tone === 'error' ? syncStatusStyles.syncStatusEventItemError : '',
3142
+ row.tone === 'muted' ? syncStatusStyles.syncStatusEventItemMuted : ''
3143
+ ].filter(Boolean).join(' '), style: { padding: '6px 8px', borderRadius: '6px' }, children: _jsx("span", { className: syncStatusStyles.syncStatusEventLine, style: { fontSize: '11px' }, children: row.text }) }, row.key))) })] }), _jsxs("div", { className: `${modalStyles.formActions} ${syncStatusStyles.syncStatusActions}`, children: [_jsxs("div", { className: syncStatusStyles.syncStatusSecondaryActions, children: [_jsx("button", { className: styles.cancelBtn, onClick: handleQueueOrRunRepairSync, disabled: workspaceSyncRepairBusy, title: workspaceSyncRepairBusy
3144
+ ? 'Repair is running now.'
3145
+ : workspaceSyncRepairQueued
3146
+ ? 'Repair is queued and will start when the current sync finishes.'
3147
+ : workspaceSyncBusy
3148
+ ? 'Queue a repair to start automatically when the current sync finishes.'
3149
+ : 'Advanced recovery: clear the saved pull cursor and re-fetch cloud state from the beginning.', children: workspaceSyncRepairBusy ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 14, className: styles.spinner }), _jsx("span", { style: { marginLeft: '6px' }, children: "Repairing..." })] })) : workspaceSyncRepairQueued ? ('Repair Queued') : ('Repair') }), _jsxs("button", { className: styles.cancelBtn, onClick: () => { void handleCopySyncDetails(); }, title: "Copy the current sync manager report for support or AI troubleshooting.", children: [_jsx(Copy, { size: 14 }), _jsx("span", { style: { marginLeft: '6px' }, children: workspaceSyncCopied ? 'Copied' : 'Copy Report' })] })] }), _jsxs("div", { className: syncStatusStyles.syncStatusPrimaryActions, children: [_jsx("div", { className: syncStatusStyles.syncStatusPrimaryActionSlot, children: runtimeMode === 'local' && !isAuthenticated ? (_jsx("button", { className: styles.submitBtn, onClick: () => {
3150
+ openSharedLoginRoute('login');
3151
+ }, children: "Sign In / Register" })) : (_jsx("div", { className: syncStatusStyles.syncStatusActionPlaceholder, "aria-hidden": "true" })) }), _jsx("div", { className: syncStatusStyles.syncStatusPrimaryActionSlot, children: _jsx("button", { className: styles.submitBtn, onClick: () => void retryWorkspaceCloudSync(), disabled: workspaceSyncBusy, children: workspaceSyncBusy ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 14, className: styles.spinner }), "Syncing..."] })) : (_jsxs(_Fragment, { children: [_jsx(RefreshCw, { size: 14 }), "Sync"] })) }) })] })] })] }) }), unsavedModalOpen && createPortal(_jsx("div", { className: `${modalStyles.overlay} ${modalStyles.unsavedOverlay}`, style: { zIndex: 2000 }, children: _jsxs("div", { className: `tf-surface-modal tf-modal-shell ${modalStyles.modal} ${modalStyles.unsavedModal}`, "data-theme": currentTheme, children: [_jsx("div", { className: `tf-modal-header ${modalStyles.unsavedHeader}`, children: _jsxs("div", { className: `tf-modal-title ${modalStyles.unsavedTitle}`, children: [_jsx(AlertTriangle, { size: 20 }), "Unsaved Changes"] }) }), _jsx("div", { className: `${modalStyles.form} ${modalStyles.unsavedContent}`, children: _jsx("p", { className: modalStyles.unsavedText, children: "You have unsaved changes. Would you like to save them?" }) }), _jsxs("div", { className: `${modalStyles.formActions} ${modalStyles.unsavedActions}`, children: [_jsx("button", { className: styles.cancelBtn, onClick: () => setUnsavedModalOpen(false), title: "Close dialog and continue editing", children: "Keep Editing" }), _jsx("button", { className: styles.destructiveBtn, onClick: handleDiscard, title: "Discard unsaved changes and leave", children: "Discard" }), _jsxs("button", { className: styles.submitBtn, onClick: handleSaveAndNavigate, disabled: loading, title: "Save changes and leave", children: [loading ? _jsx(Loader2, { size: 16, className: styles.spinner }) : _jsx(Save, { size: 16 }), "Save"] })] })] }) }), document.body), showFolderBrowser && createPortal(_jsx("div", { className: folderBrowserStyles.overlay, children: _jsxs("div", { className: folderBrowserStyles.browser, children: [_jsxs("div", { className: folderBrowserStyles.header, children: [_jsxs("div", { className: folderBrowserStyles.pathInfo, children: [_jsx(Folder, { size: 14 }), _jsx("span", { children: currentBrowsePath || 'Project Root' })] }), _jsxs("div", { className: folderBrowserStyles.actions, children: [currentBrowsePath && (_jsx("button", { className: styles.helpLink, onClick: () => {
2113
3152
  const parts = currentBrowsePath.split('/').filter(Boolean);
2114
3153
  parts.pop();
2115
3154
  fetchFolders(parts.length ? parts.join('/') + '/' : '');
2116
- }, children: "Back" })), _jsx("button", { className: styles.helpLink, onClick: () => setShowFolderBrowser(false), children: "Close" })] })] }), _jsxs("div", { className: styles.browserList, children: [browserTarget && typeof browserTarget === 'object' && browserTarget.type !== 'context-link' && (_jsxs("div", { className: `${styles.browserItem} ${styles.browserItemFile} ${styles.browserSelectCurrent}`, onClick: () => handleSelectPath(currentBrowsePath), children: [_jsx(Check, { size: 14 }), " Select Current: ./", currentBrowsePath || '(root)'] })), folders.map(folder => (_jsxs("div", { className: styles.browserItem, onClick: () => fetchFolders(currentBrowsePath + folder + '/'), children: [_jsx(Folder, { size: 14 }), " ", folder, "/"] }, folder))), files.map(file => (_jsxs("div", { className: `${styles.browserItem} ${styles.browserItemFile}`, onClick: () => handleSelectPath(currentBrowsePath + file), children: [_jsx(FileText, { size: 14 }), " ", file] }, file))), folders.length === 0 && files.length === 0 && (_jsx("div", { className: `${styles.browserItem} ${styles.browserEmpty}`, children: "No items found" }))] })] }) }), document.body), _jsx(Modal, { isOpen: !!cancellationPromptTask, onClose: handleCancelCancellation, title: "Cancel Task", size: "sm", theme: currentTheme, draggable: true, footer: _jsxs(_Fragment, { children: [_jsx("button", { className: styles.cancelBtn, onClick: handleCancelCancellation, title: "Close and continue working on this task", children: "Back" }), _jsxs("button", { className: `${styles.warningBtn} ${styles.cancelTaskConfirmBtn}`, onClick: handleConfirmCancellation, title: "Keep the task, set status to cancelled, and save a cancellation reason", style: { gap: '8px' }, disabled: cancellationOpenDescendantIds.length > 0 && !cancellationMode, children: [_jsx(Ban, { size: 16 }), " Confirm Cancellation"] }), _jsxs("button", { className: `${styles.destructiveBtn} ${styles.cancelTaskDeleteBtn}`, onClick: handleDeleteFromCancellation, title: "Permanently delete this task and all of its data", style: { gap: '8px' }, children: [_jsx(Trash2, { size: 16 }), " Delete Permanently"] })] }), children: _jsxs("div", { className: styles.form, style: { padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: '8px', overflow: 'hidden' }, children: [cancellationOpenDescendantIds.length > 0 && (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: '8px' }, children: [_jsxs("p", { style: { margin: 0, fontSize: '14px', color: 'var(--text-secondary)' }, children: ["This parent has ", cancellationOpenDescendantIds.length, " open descendant task", cancellationOpenDescendantIds.length === 1 ? '' : 's', "."] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("input", { type: "radio", name: "cancel-mode", checked: cancellationMode === 'cascade', onChange: () => setCancellationMode('cascade') }), "Cancel parent and cancel open descendants"] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: '8px' }, children: [_jsx("input", { type: "radio", name: "cancel-mode", checked: cancellationMode === 'unlink', onChange: () => setCancellationMode('unlink') }), "Cancel parent and unlink open descendants"] })] })), _jsx("p", { style: { margin: 0, fontSize: '14px', color: 'var(--text-secondary)' }, children: "Why is this task being cancelled?" }), _jsx("textarea", { className: styles.textarea, placeholder: "Reason for record (e.g., No longer relevant, duplicate, etc.)", value: localCancellationReason, onChange: (e) => setLocalCancellationReason(e.target.value), autoFocus: true, style: { minHeight: '60px', width: '100%', resize: 'none', boxSizing: 'border-box' }, rows: 2 })] }) })] }));
3155
+ }, children: "Back" })), _jsx("button", { className: styles.helpLink, onClick: () => setShowFolderBrowser(false), children: "Close" })] })] }), _jsxs("div", { className: folderBrowserStyles.list, children: [browserTarget && typeof browserTarget === 'object' && (_jsxs("div", { className: `${folderBrowserStyles.item} ${folderBrowserStyles.itemFile} ${folderBrowserStyles.itemCurrent}`, onClick: () => handleSelectPath(currentBrowsePath), children: [_jsx(Check, { size: 14 }), " Select Current: ./", currentBrowsePath || '(root)'] })), folders.map(folder => (_jsxs("div", { className: folderBrowserStyles.item, onClick: () => fetchFolders(currentBrowsePath + folder + '/'), children: [_jsx(Folder, { size: 14 }), " ", folder, "/"] }, folder))), files.map(file => (_jsxs("div", { className: `${folderBrowserStyles.item} ${folderBrowserStyles.itemFile}`, onClick: () => handleSelectPath(currentBrowsePath + file), children: [_jsx(FileText, { size: 14 }), " ", file] }, file))), folders.length === 0 && files.length === 0 && (_jsx("div", { className: `${folderBrowserStyles.item} ${folderBrowserStyles.empty}`, children: "No items found" }))] })] }) }), document.body)] }));
2117
3156
  }