@taskforcehq/taskforce 0.3.169 → 0.3.300

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 (696) hide show
  1. package/README.md +52 -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 +2212 -1194
  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 +66 -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 +46 -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 +239 -0
  269. package/dist/hooks/useTaskData.test.d.ts +1 -0
  270. package/dist/hooks/useTaskData.test.js +119 -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 +3293 -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-TVmCgizS.js +1 -0
  489. package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +1 -0
  490. package/dist/ui/assets/AnnotatedAttachmentWorkspace-CAyhnPeR.js +3 -0
  491. package/dist/ui/assets/DocumentWorkspace-BOCw5e4D.js +250 -0
  492. package/dist/ui/assets/DocumentWorkspace-C_8T8oz-.css +1 -0
  493. package/dist/ui/assets/InitiativesModule-CN0r02Oi.js +1 -0
  494. package/dist/ui/assets/PlansPage-BlVC_lRq.css +1 -0
  495. package/dist/ui/assets/PlansPage-FcQSNLZd.js +1 -0
  496. package/dist/ui/assets/TaskSettings-BwB9NtmJ.css +1 -0
  497. package/dist/ui/assets/TaskSettings-Cw5zYD1L.js +8 -0
  498. package/dist/ui/assets/WorkflowsModule-CY9R2zPB.js +5 -0
  499. package/dist/ui/assets/index-Bzqy5QGm.js +6 -0
  500. package/dist/ui/assets/index-DxRiI3tY.css +1 -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 +28 -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/migrate-planning-model.ts +233 -0
  610. package/scripts/migrate-task-data-assets.mjs +408 -0
  611. package/scripts/playwright-auth.sh +11 -0
  612. package/scripts/run-dev-proxy.sh +2 -0
  613. package/scripts/update-homebrew-formula.sh +20 -3
  614. package/scripts/with-esbuild.sh +11 -0
  615. package/src/resources/templates/WORKFLOW_V2_POLICY.md +1 -1
  616. package/src/resources/templates/workflow-sources/workflowDocs.mjs +62 -52
  617. package/src/resources/templates/workflows/collaborate.yaml +11 -5
  618. package/src/resources/templates/workflows/do.yaml +8 -2
  619. package/src/resources/templates/workflows/evaluate.yaml +13 -7
  620. package/src/resources/templates/workflows/execute.yaml +40 -23
  621. package/src/resources/templates/workflows/plan.yaml +15 -8
  622. package/src/resources/templates/workflows/review.yaml +14 -8
  623. package/dist/TaskforceScreenshot.d.ts +0 -11
  624. package/dist/TaskforceScreenshot.js +0 -113
  625. package/dist/compat/priorityCompat.d.ts +0 -2
  626. package/dist/compat/priorityCompat.js +0 -24
  627. package/dist/components/features/settings/TaxonomyManager 2.d.ts +0 -52
  628. package/dist/components/features/settings/TaxonomyManager 2.js +0 -75
  629. package/dist/components/task/TaskKanban 2.d.ts +0 -76
  630. package/dist/components/task/TaskKanban 2.js +0 -1004
  631. package/dist/components/task/TaskScreenshotSection.d.ts +0 -15
  632. package/dist/components/task/TaskScreenshotSection.js +0 -22
  633. package/dist/components/views/StandaloneLayout 2.d.ts +0 -8
  634. package/dist/components/views/StandaloneLayout 2.js +0 -1660
  635. package/dist/core/InitiativeTemplates.test.js +0 -50
  636. package/dist/core/ParentLifecycle.test.js +0 -98
  637. package/dist/migrations/clientMigrations.d.ts +0 -9
  638. package/dist/migrations/clientMigrations.js +0 -74
  639. package/dist/migrations/flags.d.ts +0 -6
  640. package/dist/migrations/flags.js +0 -8
  641. package/dist/migrations/taskMigrations.d.ts +0 -71
  642. package/dist/migrations/taskMigrations.js +0 -236
  643. package/dist/migrations/taskMigrations.test.js +0 -34
  644. package/dist/server/routes.billing.js +0 -991
  645. package/dist/server/routes.billing.test.js +0 -387
  646. package/dist/server/routes.sync.integration.test.js +0 -470
  647. package/dist/server/routes.sync.js +0 -1141
  648. package/dist/soak/syncSoakHarness.d.ts +0 -6
  649. package/dist/soak/syncSoakHarness.js +0 -10
  650. package/dist/ui/assets/index-0W5vyCpn.css +0 -1
  651. package/dist/ui/assets/index-MCZuNNfn.js +0 -23
  652. package/dist/ui/assets/logo_white_01-0Vx-iIIv.png +0 -0
  653. package/dist/ui/assets/vendor-dnd-BI1K77C1.js +0 -5
  654. package/dist/ui/assets/vendor-react-DdIDsh7f.js +0 -9
  655. package/dist/ui/assets/vendor-router-BtiNU7Dl.js +0 -3
  656. package/dist/utils/taskHierarchy.d.ts +0 -4
  657. package/dist/utils/taskHierarchy.js +0 -69
  658. package/dist/utils/taskHierarchy.test.js +0 -38
  659. package/dist/utils/taskParenting.d.ts +0 -3
  660. package/dist/utils/taskParenting.js +0 -24
  661. package/dist/utils/taskParenting.test.js +0 -21
  662. package/dist/utils/taskRelations.d.ts +0 -6
  663. package/dist/utils/taskRelations.js +0 -24
  664. package/dist/utils/taskRelations.test.js +0 -34
  665. package/scripts/check-compat-boundaries.js +0 -48
  666. /package/dist/{__tests__/planParser.test.d.ts → TaskforceCore.filter-persistence.test.d.ts} +0 -0
  667. /package/dist/{__tests__/releaseVersion.test.d.ts → TaskforceCore.routing.test.d.ts} +0 -0
  668. /package/dist/{__tests__/structuredCommentValidation.test.d.ts → annotatedAttachments/AnnotatedAttachmentSessionStore.test.d.ts} +0 -0
  669. /package/dist/{__tests__/structuredDocValidation.test.d.ts → annotatedAttachments/payload.test.d.ts} +0 -0
  670. /package/dist/{__tests__/templateEngine.test.d.ts → annotatedAttachments/types.js} +0 -0
  671. /package/dist/{__tests__/templates.test.d.ts → compat/workspaceSyncCompat.test.d.ts} +0 -0
  672. /package/dist/{__tests__/workflowDocumentFixtures.test.d.ts → components/features/AgentsModule.test.d.ts} +0 -0
  673. /package/dist/{__tests__/workflowExportMatrix.test.d.ts → components/features/AnnotatedAttachmentWorkspace.test.d.ts} +0 -0
  674. /package/dist/{__tests__/workflowExportSnapshots.test.d.ts → components/features/DocumentWorkspace.test.d.ts} +0 -0
  675. /package/dist/{__tests__/workflowGeneration.test.d.ts → components/features/settings/TaxonomyLibraryManager.test.d.ts} +0 -0
  676. /package/dist/{__tests__/workflowV2Policy.test.d.ts → components/task/TaskActionHeader.test.d.ts} +0 -0
  677. /package/dist/{core/InitiativeTemplates.test.d.ts → components/task/TaskKanban.planning-drop.test.d.ts} +0 -0
  678. /package/dist/{core/ParentLifecycle.test.d.ts → components/task/TaskStatusActions.test.d.ts} +0 -0
  679. /package/dist/{migrations/taskMigrations.test.d.ts → components/ui/TopNoticeLayer.test.d.ts} +0 -0
  680. /package/dist/{server/routes.billing.test.d.ts → components/views/AppShellHeader.test.d.ts} +0 -0
  681. /package/dist/{server/routes.sync.integration.test.d.ts → components/views/PlanComparisonPage.test.d.ts} +0 -0
  682. /package/dist/{utils/taskHierarchy.test.d.ts → components/views/chrome/WorkspaceChromeSections.test.d.ts} +0 -0
  683. /package/dist/{utils/taskParenting.test.d.ts → components/views/chrome/workspaceChrome.test.d.ts} +0 -0
  684. /package/dist/{utils/taskRelations.test.d.ts → components/views/panels/DocumentFilterToolbar.test.d.ts} +0 -0
  685. /package/dist/public/{android-chrome-192x192.png → archive/phoenix/android-chrome-192x192.png} +0 -0
  686. /package/dist/public/{android-chrome-512x512.png → archive/phoenix/android-chrome-512x512.png} +0 -0
  687. /package/dist/public/{favicon-16x16.png → archive/phoenix/favicon-16x16.png} +0 -0
  688. /package/dist/public/{favicon-32x32.png → archive/phoenix/favicon-32x32.png} +0 -0
  689. /package/dist/public/{favicon.png → archive/phoenix/favicon.png} +0 -0
  690. /package/dist/{__tests__ → services}/workflowExportSnapshots.test.js +0 -0
  691. /package/dist/{__tests__ → services}/workflowGeneration.test.js +0 -0
  692. /package/dist/ui/favicon/{android-chrome-192x192.png → archive/phoenix/android-chrome-192x192.png} +0 -0
  693. /package/dist/ui/favicon/{android-chrome-512x512.png → archive/phoenix/android-chrome-512x512.png} +0 -0
  694. /package/dist/ui/favicon/{favicon-16x16.png → archive/phoenix/favicon-16x16.png} +0 -0
  695. /package/dist/ui/favicon/{favicon-32x32.png → archive/phoenix/favicon-32x32.png} +0 -0
  696. /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,469 @@ 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) => ({ value: initiative.id, label: initiative.title })), [planningStructure.initiatives]);
895
+ const workstreamFilterOptions = useMemo(() => [
896
+ ...planningStructure.initiatives.flatMap((initiative) => initiative.workstreams.map((workstream) => ({ value: workstream.id, label: workstream.title }))),
897
+ ...planningStructure.standaloneWorkstreams.map((workstream) => ({ value: workstream.id, label: workstream.title })),
898
+ ], [planningStructure.initiatives, planningStructure.standaloneWorkstreams]);
899
+ const selectedPlanningTaskIds = useMemo(() => {
900
+ if (selectedPlanningWorkstreamId) {
901
+ return new Set(planningStructure.workstreamTaskIds.get(selectedPlanningWorkstreamId) || []);
902
+ }
903
+ if (selectedPlanningInitiativeId) {
904
+ return new Set(planningStructure.initiativeTaskIds.get(selectedPlanningInitiativeId) || []);
905
+ }
906
+ return null;
907
+ }, [planningStructure.initiativeTaskIds, planningStructure.workstreamTaskIds, selectedPlanningInitiativeId, selectedPlanningWorkstreamId]);
908
+ const planningDrawerItem = useMemo(() => {
909
+ if (!planningDrawerDetail)
421
910
  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));
911
+ if (planningDrawerDetail.type === 'initiative') {
912
+ const item = planningStructure.initiativeById.get(planningDrawerDetail.id);
913
+ return item ? { type: 'initiative', item } : null;
914
+ }
915
+ const item = planningStructure.workstreamById.get(planningDrawerDetail.id)
916
+ || planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === planningDrawerDetail.id);
917
+ return item ? { type: 'workstream', item } : null;
918
+ }, [planningDrawerDetail, planningStructure.initiativeById, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
919
+ const planningNestedWorkstreamItem = useMemo(() => {
920
+ if (!planningNestedWorkstreamDetailId)
921
+ return null;
922
+ const item = planningStructure.workstreamById.get(planningNestedWorkstreamDetailId)
923
+ || planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === planningNestedWorkstreamDetailId);
924
+ return item ? { type: 'workstream', item } : null;
925
+ }, [planningNestedWorkstreamDetailId, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
926
+ const planningSecondaryPane = useMemo(() => {
927
+ if (planningSecondaryEditor) {
928
+ return { kind: 'editor', editor: planningSecondaryEditor };
929
+ }
930
+ if (planningNestedWorkstreamItem) {
931
+ return { kind: 'detail', detail: planningNestedWorkstreamItem };
932
+ }
933
+ return null;
934
+ }, [planningNestedWorkstreamItem, planningSecondaryEditor]);
935
+ const planningDrawerWidthPx = getPlanningDrawerWidthPx(planningTreeCollapsed, Boolean(planningDrawerItem || planningEditor), planningPrimaryCollapsed, Boolean(planningSecondaryPane), planningSecondaryCollapsed);
936
+ const planningDraftInitiativeSummary = useMemo(() => {
937
+ if (!planningDraftInitiativeId.trim())
938
+ return null;
939
+ const exactInitiative = planningStructure.initiativeById.get(planningDraftInitiativeId);
940
+ if (exactInitiative)
941
+ return exactInitiative;
942
+ return resolveInitiativeReference(planningStructure.initiatives, planningDraftInitiativeId);
943
+ }, [planningDraftInitiativeId, planningStructure.initiativeById, planningStructure.initiatives]);
944
+ useEffect(() => {
945
+ if (!planningDrawerOpen)
946
+ return;
947
+ setPlanningTreeCollapsed(false);
948
+ setPlanningPrimaryCollapsed(false);
949
+ setPlanningSecondaryCollapsed(false);
950
+ }, [planningDrawerOpen]);
951
+ useEffect(() => {
952
+ if (selectedPlanningInitiativeId && !planningStructure.initiativeById.has(selectedPlanningInitiativeId)) {
953
+ setSelectedPlanningInitiativeId('');
433
954
  }
434
- else if (parentTaskFilterMode === 'linked') {
435
- base = kanbanTasks.filter(task => !!task.parentTaskId || hierarchyParentIdSet.has(task.id));
955
+ if (selectedPlanningWorkstreamId && !planningStructure.workstreamById.has(selectedPlanningWorkstreamId)) {
956
+ setSelectedPlanningWorkstreamId('');
436
957
  }
437
- else if (parentTaskFilterMode === 'unlinked') {
438
- base = kanbanTasks.filter(task => isUnlinkedTask(task));
958
+ if (planningDrawerDetail?.type === 'initiative' && !planningStructure.initiativeById.has(planningDrawerDetail.id)) {
959
+ setPlanningDrawerDetail(null);
439
960
  }
440
- if (!hierarchyShowUnlinkedColumn && parentTaskFilterMode !== 'unlinked') {
441
- base = base.filter((task) => !isUnlinkedTask(task));
961
+ if (planningDrawerDetail?.type === 'workstream' && !planningStructure.workstreamById.has(planningDrawerDetail.id)) {
962
+ setPlanningDrawerDetail(null);
442
963
  }
443
- if (hierarchyDescendantIdSet) {
444
- base = base.filter(task => hierarchyDescendantIdSet.has(task.id)
445
- || (hierarchyShowUnlinkedColumn && (parentTaskFilterMode === 'all' || parentTaskFilterMode === 'unlinked') && isUnlinkedTask(task)));
964
+ if (planningNestedWorkstreamDetailId && !planningStructure.workstreamById.has(planningNestedWorkstreamDetailId)) {
965
+ setPlanningNestedWorkstreamDetailId(null);
446
966
  }
447
- return base;
448
- }, [groupBy, parentTaskFilterMode, kanbanTasks, hierarchyParentIdSet, scheduleBaseTasks, hierarchyDescendantIdSet, hierarchyShowUnlinkedColumn]);
967
+ if (planningSecondaryEditor?.mode === 'edit'
968
+ && !planningStructure.workstreamById.has(planningSecondaryEditor.targetId)) {
969
+ setPlanningSecondaryEditor(null);
970
+ }
971
+ }, [
972
+ planningDrawerDetail,
973
+ planningNestedWorkstreamDetailId,
974
+ planningSecondaryEditor,
975
+ planningStructure.initiativeById,
976
+ planningStructure.workstreamById,
977
+ selectedPlanningInitiativeId,
978
+ selectedPlanningWorkstreamId,
979
+ ]);
980
+ const hierarchyVisibleTasks = useMemo(() => {
981
+ if (groupBy === 'schedule')
982
+ return scheduleBaseTasks;
983
+ return kanbanTasks;
984
+ }, [groupBy, kanbanTasks, scheduleBaseTasks]);
449
985
  const todayDateOnly = useMemo(() => {
450
986
  const now = new Date();
451
987
  const y = now.getFullYear();
@@ -465,6 +1001,11 @@ export function StandaloneLayout(props) {
465
1001
  return expiredScheduled || overdueDue;
466
1002
  });
467
1003
  }, [groupBy, scheduleOnlyExpired, hierarchyVisibleTasks, todayDateOnly]);
1004
+ const scopedBoardVisibleTasks = useMemo(() => {
1005
+ if (!selectedPlanningTaskIds || selectedPlanningTaskIds.size === 0)
1006
+ return boardVisibleTasks;
1007
+ return boardVisibleTasks.filter((task) => selectedPlanningTaskIds.has(task.id));
1008
+ }, [boardVisibleTasks, selectedPlanningTaskIds]);
468
1009
  const expiredRecoveryCandidates = useMemo(() => {
469
1010
  if (groupBy !== 'schedule')
470
1011
  return [];
@@ -507,108 +1048,78 @@ export function StandaloneLayout(props) {
507
1048
  return !!task.scheduledDate && task.scheduledDate < todayDateOnly;
508
1049
  });
509
1050
  }, [groupBy, hierarchyVisibleTasks, todayDateOnly]);
510
- const expiredLeafCandidates = useMemo(() => expiredRecoveryCandidates.filter(task => !hierarchyParentIdSet.has(task.id)), [expiredRecoveryCandidates, hierarchyParentIdSet]);
1051
+ const expiredLeafCandidates = expiredRecoveryCandidates;
511
1052
  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
1053
  const handleCloseModal = () => {
535
1054
  handleNavigation(() => {
536
- if (tryReturnToParent())
1055
+ if (returnToPreviousTask())
537
1056
  return;
1057
+ if (activeTab === 'add' && taskReturnTrail.length > 0) {
1058
+ clearReturnToParentTask();
1059
+ }
1060
+ setTaskModalFullscreen(false);
538
1061
  if (activeTab === 'add')
539
1062
  resetForm();
540
1063
  setActiveTab('tasks');
541
1064
  });
542
1065
  };
543
- const [localCancellationReason, setLocalCancellationReason] = useState('');
544
- const handleConfirmCancellation = async () => {
545
- const success = await confirmCancellation(localCancellationReason, cancellationMode);
546
- if (success) {
547
- setLocalCancellationReason('');
1066
+ useEffect(() => {
1067
+ if (activeTab !== 'add') {
1068
+ setTaskModalFullscreen(false);
548
1069
  }
549
- };
550
- const handleCancelCancellation = () => {
551
- cancelCancellationPrompt();
552
- setLocalCancellationReason('');
553
- };
554
- const handleDeleteFromCancellation = async () => {
555
- if (!cancellationPromptTask)
1070
+ }, [activeTab]);
1071
+ const handleSetStatus = useCallback((task, status) => {
1072
+ if (task.status === status)
556
1073
  return;
557
- const confirmed = window.confirm('Delete this task permanently? This cannot be undone.');
558
- if (!confirmed)
1074
+ if (status === 'in-progress') {
1075
+ void handleToggleInProgress(task);
559
1076
  return;
560
- const deleted = await handleDelete(cancellationPromptTask.id, false, { skipConfirm: true });
561
- if (deleted) {
562
- handleCancelCancellation();
563
1077
  }
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
- }
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
- }
1078
+ if (status === 'review') {
1079
+ void handleToggleReview(task);
1080
+ return;
600
1081
  }
601
- else if (groupField === 'complexity') {
602
- const direct = Number(value);
603
- if (Number.isFinite(direct)) {
604
- setComplexity(direct);
605
- }
1082
+ if (status === 'done') {
1083
+ void handleToggleComplete(task);
1084
+ return;
606
1085
  }
607
- else if (groupField === 'hierarchy') {
608
- // Hierarchy columns are depth-based and do not map to form defaults.
1086
+ if (status === 'cancelled') {
1087
+ void handleToggleCancel(task);
1088
+ return;
609
1089
  }
1090
+ void handleUpdateTask(task.id, {
1091
+ status,
1092
+ completedAt: null
1093
+ });
1094
+ }, [handleToggleInProgress, handleToggleReview, handleToggleComplete, handleToggleCancel, handleUpdateTask]);
1095
+ const handleAddTaskToColumn = useCallback((groupField, value) => {
1096
+ resetForm();
1097
+ const defaults = resolveDraftDefaultsForGrouping(groupField, value, {
1098
+ categories: activeCategories,
1099
+ approaches
1100
+ });
1101
+ if (defaults.category)
1102
+ setCategory(defaults.category);
1103
+ if (defaults.type)
1104
+ setType(defaults.type);
1105
+ if (typeof defaults.priority === 'number')
1106
+ setPriority(defaults.priority);
1107
+ if (typeof defaults.complexity === 'number')
1108
+ setComplexity(defaults.complexity);
1109
+ if (defaults.approach)
1110
+ setApproach(defaults.approach);
1111
+ const taxonomyApproach = defaults.taxonomyApproach;
1112
+ if (typeof taxonomyApproach === 'string' && taxonomyApproach.length > 0) {
1113
+ setFormTaxonomies(prev => ({ ...prev, approach: taxonomyApproach }));
1114
+ }
1115
+ if (defaults.assignee)
1116
+ setAssignee(defaults.assignee);
1117
+ if (defaults.status)
1118
+ setStatus(defaults.status);
1119
+ if (defaults.scheduledDate)
1120
+ setScheduledDate(defaults.scheduledDate);
610
1121
  setActiveTab('add');
611
- }, [resetForm, activeCategories, approaches, setCategory, setType, setPriority, setComplexity, setApproach, setFormTaxonomies, setActiveTab]);
1122
+ }, [resetForm, activeCategories, approaches, setCategory, setType, setPriority, setComplexity, setStatus, setApproach, setAssignee, setScheduledDate, setFormTaxonomies, setActiveTab]);
612
1123
  // Ensure 'done' status is shown by default as requested
613
1124
  React.useEffect(() => {
614
1125
  if (filterStatus && !filterStatus.includes('done')) {
@@ -619,14 +1130,21 @@ export function StandaloneLayout(props) {
619
1130
  const [showHelpModal, setShowHelpModal] = useState(false);
620
1131
  const [showAccountMenu, setShowAccountMenu] = useState(false);
621
1132
  const [showAccountHub, setShowAccountHub] = useState(false);
1133
+ const [showEditProfileModal, setShowEditProfileModal] = useState(false);
622
1134
  const [showSyncStatusModal, setShowSyncStatusModal] = useState(false);
623
- const [workspaceSyncBusy, setWorkspaceSyncBusy] = useState(false);
1135
+ const [workspaceSyncToggleBusy, setWorkspaceSyncToggleBusy] = useState(false);
624
1136
  const [workspaceSyncError, setWorkspaceSyncError] = useState(null);
625
- const [workspacePanelOpen, setWorkspacePanelOpen] = useState(false);
626
- const [workspaceFormName, setWorkspaceFormName] = useState('');
627
- const [workspaceFormDescription, setWorkspaceFormDescription] = useState('');
1137
+ const [workspaceSyncRepairBusy, setWorkspaceSyncRepairBusy] = useState(false);
1138
+ const [workspaceSyncRepairQueued, setWorkspaceSyncRepairQueued] = useState(false);
1139
+ const [workspaceSyncCopied, setWorkspaceSyncCopied] = useState(false);
1140
+ const [syncRecentEvents, setSyncRecentEvents] = useState([]);
1141
+ const [syncRecentEventsLoading, setSyncRecentEventsLoading] = useState(false);
1142
+ const [syncRecentEventsError, setSyncRecentEventsError] = useState(null);
1143
+ const syncEventsListRef = useRef(null);
1144
+ const pendingSyncEventsScrollRestoreRef = useRef(null);
628
1145
  const [workspaceActionBusy, setWorkspaceActionBusy] = useState(false);
629
1146
  const [workspaceActionError, setWorkspaceActionError] = useState('');
1147
+ const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
630
1148
  const [showTeamManagementModal, setShowTeamManagementModal] = useState(false);
631
1149
  const [teamManagementTab, setTeamManagementTab] = useState('members');
632
1150
  const [teamPlanMode, setTeamPlanMode] = useState('unknown');
@@ -651,9 +1169,12 @@ export function StandaloneLayout(props) {
651
1169
  const [billingActionError, setBillingActionError] = useState(null);
652
1170
  const [billingNotice, setBillingNotice] = useState(null);
653
1171
  const [billingIntervalChoice, setBillingIntervalChoice] = useState('month');
1172
+ const [profileDisplayNameDraft, setProfileDisplayNameDraft] = useState('');
1173
+ const [profileSaveBusy, setProfileSaveBusy] = useState(false);
1174
+ const [profileSaveError, setProfileSaveError] = useState(null);
1175
+ const [profileSaveNotice, setProfileSaveNotice] = useState(null);
654
1176
  const TEAM_AUDIT_PAGE_SIZE = 25;
655
1177
  const accountMenuRef = useRef(null);
656
- const billingSuccessPollRef = useRef(null);
657
1178
  // showSearch state removed - search always visible in actions area
658
1179
  useEffect(() => {
659
1180
  if (!showAccountMenu)
@@ -666,45 +1187,119 @@ export function StandaloneLayout(props) {
666
1187
  document.addEventListener('mousedown', handlePointerDown);
667
1188
  return () => document.removeEventListener('mousedown', handlePointerDown);
668
1189
  }, [showAccountMenu]);
669
- useEffect(() => () => {
670
- if (billingSuccessPollRef.current) {
671
- window.clearInterval(billingSuccessPollRef.current);
672
- billingSuccessPollRef.current = null;
673
- }
674
- }, []);
675
1190
  const canManageCloudWorkspaces = runtimeMode === 'cloud' && workspaceSwitchingEnabled && isAuthenticated;
1191
+ const canAccessCloudTeamManagement = isAuthenticated && (runtimeMode === 'cloud' || cloudAuthConfigured);
676
1192
  const canManageWorkspaceSync = runtimeMode === 'local' && isAuthenticated;
677
- const currentWorkspaceRole = useMemo(() => {
1193
+ const handleWorkspaceSyncToggle = useCallback(async (enabled) => {
1194
+ if (!canManageWorkspaceSync)
1195
+ return;
1196
+ setWorkspaceSyncError(null);
1197
+ setWorkspaceSyncToggleBusy(true);
1198
+ const result = await saveWorkspaceCloudSyncSettings({ enabled });
1199
+ if (!result.success) {
1200
+ setWorkspaceSyncError(result.error || (enabled ? 'Failed to enable sync.' : 'Failed to disable sync.'));
1201
+ }
1202
+ setWorkspaceSyncToggleBusy(false);
1203
+ }, [
1204
+ canManageWorkspaceSync,
1205
+ saveWorkspaceCloudSyncSettings
1206
+ ]);
1207
+ const syncControlBusy = workspaceSyncToggleBusy;
1208
+ const currentWorkspaceLabel = useMemo(() => {
678
1209
  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();
1210
+ const namedWorkspace = String(current?.name || '').trim();
1211
+ if (namedWorkspace)
1212
+ return namedWorkspace;
1213
+ if (runtimeMode === 'local') {
1214
+ const localProjectName = String(projectName || '').trim();
1215
+ if (localProjectName)
1216
+ return localProjectName;
1217
+ }
1218
+ const fallbackId = String(currentWorkspaceId || '').trim();
1219
+ return fallbackId || 'Workspace';
1220
+ }, [availableWorkspaces, currentWorkspaceId, projectName, runtimeMode]);
1221
+ const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement, currentWorkspaceRole);
1222
+ const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement, currentWorkspaceRole, teamPlanMode);
1223
+ const authDisplayName = String(authUserDisplayName || '').trim();
1224
+ const authEmailLabel = String(authUserEmail || '').trim();
1225
+ const authIdentityLabel = authDisplayName || authEmailLabel;
690
1226
  const hasAuthIdentity = authIdentityLabel.length > 0;
1227
+ const hasAuthEmail = authEmailLabel.length > 0;
1228
+ const accountBadgeLabel = (authIdentityLabel || authEmailLabel || 'U').trim();
1229
+ const accountBadgeInitial = accountBadgeLabel.charAt(0).toUpperCase() || 'U';
1230
+ const subscriptionLabel = String(billingStatus?.planName || billingStatus?.planId || '').trim();
1231
+ const hasSubscription = subscriptionLabel.length > 0;
1232
+ const teamManagementWorkspaceId = String(authWorkspaceId || currentWorkspaceId || '').trim();
1233
+ const runtimeLabel = runtimeMode === 'cloud' ? 'CLOUD' : 'LOCAL';
1234
+ const apiBaseUrl = props.config?.apiBaseUrl || '';
1235
+ const billingApiBaseUrl = useMemo(() => {
1236
+ const preferredBase = runtimeMode === 'local'
1237
+ ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1238
+ : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1239
+ return String(preferredBase || '').trim().replace(/\/+$/, '');
1240
+ }, [props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
1241
+ const buildBillingApiUrl = useCallback((path) => {
1242
+ const normalizedPath = String(path || '').trim();
1243
+ if (!billingApiBaseUrl)
1244
+ return normalizedPath;
1245
+ return `${billingApiBaseUrl}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`;
1246
+ }, [billingApiBaseUrl]);
1247
+ const connectedEnvironmentLabel = useMemo(() => resolveConnectedEnvironmentLabel(props.config?.cloudEnvironment, [
1248
+ String(props.config?.cloudBaseUrl || ''),
1249
+ String(props.config?.cloudAuthBaseUrl || ''),
1250
+ String(props.config?.apiBaseUrl || ''),
1251
+ String(props.config?.baseUrl || '')
1252
+ ]), [
1253
+ props.config?.cloudEnvironment,
1254
+ props.config?.cloudBaseUrl,
1255
+ props.config?.cloudAuthBaseUrl,
1256
+ props.config?.apiBaseUrl,
1257
+ props.config?.baseUrl
1258
+ ]);
691
1259
  const isAuthBootstrapPending = cloudAuthConfigured && !authSessionResolved;
1260
+ useEffect(() => {
1261
+ if (!showEditProfileModal)
1262
+ return;
1263
+ setProfileDisplayNameDraft(authDisplayName);
1264
+ setProfileSaveError(null);
1265
+ setProfileSaveNotice(null);
1266
+ }, [authDisplayName, showEditProfileModal]);
1267
+ const handleSaveProfile = useCallback(async () => {
1268
+ const trimmedDisplayName = profileDisplayNameDraft.trim();
1269
+ if (!trimmedDisplayName) {
1270
+ setProfileSaveError('Display name is required.');
1271
+ setProfileSaveNotice(null);
1272
+ return;
1273
+ }
1274
+ setProfileSaveBusy(true);
1275
+ setProfileSaveError(null);
1276
+ setProfileSaveNotice(null);
1277
+ const result = await updateCurrentUserProfile({ displayName: trimmedDisplayName });
1278
+ if (!result.success) {
1279
+ setProfileSaveError(result.error || 'Failed to update profile.');
1280
+ setProfileSaveBusy(false);
1281
+ return;
1282
+ }
1283
+ setProfileSaveNotice('Profile updated.');
1284
+ setProfileSaveBusy(false);
1285
+ setShowEditProfileModal(false);
1286
+ }, [profileDisplayNameDraft, updateCurrentUserProfile]);
692
1287
  const teamAdminHeaders = useCallback(() => {
693
1288
  const headers = {};
694
- const workspaceId = String(currentWorkspaceId || '').trim();
1289
+ const workspaceId = String(teamManagementWorkspaceId || '').trim();
695
1290
  if (workspaceId)
696
1291
  headers['x-taskforce-workspace-id'] = workspaceId;
697
1292
  return headers;
698
- }, [currentWorkspaceId]);
1293
+ }, [teamManagementWorkspaceId]);
699
1294
  const loadBillingStatus = useCallback(async () => {
700
- if (runtimeMode !== 'cloud' || !isAuthenticated) {
1295
+ if (!isAuthenticated) {
701
1296
  setBillingStatus(null);
702
1297
  return null;
703
1298
  }
704
1299
  setBillingLoading(true);
705
1300
  setBillingError(null);
706
1301
  try {
707
- const res = await fetch('/api/taskforce/billing/status', {
1302
+ const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/status'), {
708
1303
  method: 'GET',
709
1304
  credentials: 'include'
710
1305
  });
@@ -725,46 +1320,19 @@ export function StandaloneLayout(props) {
725
1320
  finally {
726
1321
  setBillingLoading(false);
727
1322
  }
728
- }, [isAuthenticated, runtimeMode]);
1323
+ }, [buildBillingApiUrl, isAuthenticated]);
729
1324
  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]);
1325
+ const params = new URLSearchParams();
1326
+ params.set('screen', 'plans');
1327
+ params.set('interval', billingIntervalChoice);
1328
+ navigate(`/?${params.toString()}`);
1329
+ }, [billingIntervalChoice, navigate]);
762
1330
  const handleOpenBillingPortal = useCallback(async () => {
763
1331
  setBillingActionError(null);
764
1332
  setBillingNotice(null);
765
1333
  setBillingActionBusy(true);
766
1334
  try {
767
- const res = await fetch('/api/taskforce/billing/portal-session', {
1335
+ const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/portal-session'), {
768
1336
  method: 'POST',
769
1337
  credentials: 'include'
770
1338
  });
@@ -786,18 +1354,23 @@ export function StandaloneLayout(props) {
786
1354
  finally {
787
1355
  setBillingActionBusy(false);
788
1356
  }
789
- }, []);
1357
+ }, [buildBillingApiUrl]);
790
1358
  const handleSwitchSubscriptionInterval = useCallback(async () => {
791
1359
  setBillingActionError(null);
792
1360
  setBillingNotice(null);
793
1361
  setBillingActionBusy(true);
794
1362
  try {
795
- const res = await fetch('/api/taskforce/billing/subscription', {
1363
+ const currentPlanVersionId = String(billingStatus?.planVersionId || '').trim();
1364
+ if (!currentPlanVersionId) {
1365
+ setBillingActionError('No active plan version is linked to this account.');
1366
+ return;
1367
+ }
1368
+ const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/subscription'), {
796
1369
  method: 'POST',
797
1370
  credentials: 'include',
798
1371
  headers: { 'Content-Type': 'application/json' },
799
1372
  body: JSON.stringify({
800
- tier: 'team',
1373
+ planVersionId: currentPlanVersionId,
801
1374
  interval: billingIntervalChoice
802
1375
  })
803
1376
  });
@@ -815,11 +1388,11 @@ export function StandaloneLayout(props) {
815
1388
  finally {
816
1389
  setBillingActionBusy(false);
817
1390
  }
818
- }, [billingIntervalChoice, loadBillingStatus]);
1391
+ }, [billingIntervalChoice, billingStatus?.planVersionId, buildBillingApiUrl, loadBillingStatus]);
819
1392
  const loadTeamPlanMode = useCallback(async () => {
820
- if (!canManageCloudWorkspaces || !(currentWorkspaceRole === 'owner' || currentWorkspaceRole === 'admin')) {
1393
+ if (!canManageTeamWorkspace) {
821
1394
  setTeamPlanMode('unknown');
822
- return;
1395
+ return 'unknown';
823
1396
  }
824
1397
  try {
825
1398
  const res = await fetch('/api/taskforce/account/entitlements', {
@@ -830,80 +1403,38 @@ export function StandaloneLayout(props) {
830
1403
  const payload = await res.json().catch(() => ({}));
831
1404
  if (!res.ok) {
832
1405
  setTeamPlanMode('unknown');
833
- return;
1406
+ return 'unknown';
834
1407
  }
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');
1408
+ const nextMode = resolveTeamPlanModeFromEntitlements(payload);
1409
+ setTeamPlanMode(nextMode);
1410
+ return nextMode;
840
1411
  }
841
1412
  catch {
842
1413
  setTeamPlanMode('unknown');
1414
+ return 'unknown';
843
1415
  }
844
- }, [canManageCloudWorkspaces, currentWorkspaceRole, teamAdminHeaders]);
1416
+ }, [canManageTeamWorkspace, teamAdminHeaders]);
845
1417
  useEffect(() => {
846
1418
  if (!showAccountMenu && !showTeamManagementModal && !showAccountHub)
847
1419
  return;
848
1420
  void loadTeamPlanMode();
849
1421
  }, [showAccountHub, showAccountMenu, showTeamManagementModal, loadTeamPlanMode]);
850
1422
  useEffect(() => {
851
- if (!showAccountHub)
852
- return;
853
- void loadBillingStatus();
854
- }, [loadBillingStatus, showAccountHub]);
855
- useEffect(() => {
856
- if (!isAuthenticated)
1423
+ if (!isAuthenticated || !authSessionResolved)
857
1424
  return;
858
- if (location.pathname !== '/billing/success' && location.pathname !== '/billing/cancel')
859
- 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 });
1425
+ if (!canManageTeamWorkspace) {
1426
+ setTeamPlanMode('unknown');
867
1427
  return;
868
1428
  }
869
- setBillingNotice('Checkout complete. Waiting for billing confirmation...');
1429
+ void loadTeamPlanMode();
1430
+ }, [authSessionResolved, canManageTeamWorkspace, isAuthenticated, loadTeamPlanMode]);
1431
+ useEffect(() => {
1432
+ if (!showAccountHub && !showAccountMenu)
1433
+ return;
870
1434
  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]);
1435
+ }, [loadBillingStatus, showAccountHub, showAccountMenu]);
905
1436
  const loadTeamUsers = useCallback(async () => {
906
- if (!canOpenTeamManagement)
1437
+ if (!canManageTeamWorkspace)
907
1438
  return;
908
1439
  setTeamUsersLoading(true);
909
1440
  setTeamMgmtError(null);
@@ -945,16 +1476,16 @@ export function StandaloneLayout(props) {
945
1476
  finally {
946
1477
  setTeamUsersLoading(false);
947
1478
  }
948
- }, [canOpenTeamManagement, teamAdminHeaders]);
1479
+ }, [canManageTeamWorkspace, teamAdminHeaders]);
949
1480
  const loadTeamAuditPage = useCallback(async (page) => {
950
- if (!canOpenTeamManagement)
1481
+ if (!canManageTeamWorkspace)
951
1482
  return;
952
1483
  const safePage = Math.max(0, Math.floor(page));
953
1484
  setTeamAuditLoading(true);
954
1485
  setTeamMgmtError(null);
955
1486
  try {
956
1487
  const offset = safePage * TEAM_AUDIT_PAGE_SIZE;
957
- const res = await fetch(`/api/taskforce/admin/audit-logs?limit=${TEAM_AUDIT_PAGE_SIZE}&offset=${offset}`, {
1488
+ const res = await fetch(`/api/taskforce/admin/workspace-audit-logs?limit=${TEAM_AUDIT_PAGE_SIZE}&offset=${offset}`, {
958
1489
  method: 'GET',
959
1490
  credentials: 'include',
960
1491
  headers: teamAdminHeaders()
@@ -983,7 +1514,7 @@ export function StandaloneLayout(props) {
983
1514
  finally {
984
1515
  setTeamAuditLoading(false);
985
1516
  }
986
- }, [canOpenTeamManagement, teamAdminHeaders]);
1517
+ }, [canManageTeamWorkspace, teamAdminHeaders]);
987
1518
  const reloadTeamManagementData = useCallback(async () => {
988
1519
  await Promise.all([loadTeamUsers(), loadTeamAuditPage(teamAuditPage)]);
989
1520
  }, [loadTeamUsers, loadTeamAuditPage, teamAuditPage]);
@@ -995,10 +1526,14 @@ export function StandaloneLayout(props) {
995
1526
  setShowSyncStatusModal(false);
996
1527
  navigate(`/login?mode=${mode}&next=${encodeURIComponent(target)}`);
997
1528
  }, [location.hash, location.pathname, location.search, navigate]);
998
- const openWorkspaceSetup = useCallback(() => {
1529
+ const openWorkspaceSetup = useCallback((options) => {
999
1530
  setShowAccountMenu(false);
1000
- setWorkspacePanelOpen(false);
1001
- navigate('/setup?step=workspace', { replace: true });
1531
+ setShowCreateWorkspaceConfirm(false);
1532
+ const params = new URLSearchParams({ step: 'workspace' });
1533
+ if (options?.intent === 'create-workspace') {
1534
+ params.set('intent', 'create-workspace');
1535
+ }
1536
+ navigate(`/setup?${params.toString()}`, { replace: true });
1002
1537
  }, [navigate]);
1003
1538
  const handleSwitchWorkspace = useCallback(async (workspaceId) => {
1004
1539
  if (!workspaceId || workspaceActionBusy)
@@ -1017,53 +1552,24 @@ export function StandaloneLayout(props) {
1017
1552
  }
1018
1553
  await fetchTasks(true);
1019
1554
  setShowAccountMenu(false);
1020
- setWorkspacePanelOpen(false);
1021
1555
  }
1022
1556
  finally {
1023
1557
  setWorkspaceActionBusy(false);
1024
1558
  }
1025
1559
  }, [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.');
1560
+ const openTeamManagementModal = useCallback(async () => {
1561
+ setShowAccountMenu(false);
1562
+ setShowTeamManagementModal(true);
1563
+ setTeamManagementTab('members');
1564
+ const resolvedMode = await loadTeamPlanMode();
1565
+ if (resolvedMode !== 'team')
1032
1566
  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
- await loadTeamUsers();
1062
- await loadTeamAuditPage(0);
1063
- }, [loadTeamAuditPage, loadTeamPlanMode, loadTeamUsers]);
1064
- const executeTeamUserAction = useCallback(async (userId, fn) => {
1065
- setTeamMgmtError(null);
1066
- setTeamActionBusyUserId(userId);
1567
+ await loadTeamUsers();
1568
+ await loadTeamAuditPage(0);
1569
+ }, [loadTeamAuditPage, loadTeamPlanMode, loadTeamUsers]);
1570
+ const executeTeamUserAction = useCallback(async (userId, fn) => {
1571
+ setTeamMgmtError(null);
1572
+ setTeamActionBusyUserId(userId);
1067
1573
  try {
1068
1574
  const res = await fn();
1069
1575
  const payload = await res.json().catch(() => ({}));
@@ -1096,7 +1602,7 @@ export function StandaloneLayout(props) {
1096
1602
  },
1097
1603
  credentials: 'include',
1098
1604
  body: JSON.stringify({
1099
- workspaceId: currentWorkspaceId,
1605
+ workspaceId: teamManagementWorkspaceId,
1100
1606
  email,
1101
1607
  role: teamInviteRole,
1102
1608
  permissionMode: teamInviteRole === 'member' ? teamInvitePermissionMode : 'read-write'
@@ -1138,27 +1644,64 @@ export function StandaloneLayout(props) {
1138
1644
  const hasTypeFilter = filterTypes.length !== activeTypes.length;
1139
1645
  const hasPriorityFilter = filterPriorities.length !== priorities.length;
1140
1646
  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]);
1647
+ const hasAssigneeFilter = filterAssignees.length !== assigneeOptions.length;
1648
+ const hasInitiativeScopeFilter = selectedPlanningInitiativeId.length > 0;
1649
+ const hasWorkstreamScopeFilter = selectedPlanningWorkstreamId.length > 0;
1650
+ return hasCategoryFilter
1651
+ || hasTypeFilter
1652
+ || hasPriorityFilter
1653
+ || hasStatusFilter
1654
+ || hasAssigneeFilter
1655
+ || hasInitiativeScopeFilter
1656
+ || hasWorkstreamScopeFilter;
1657
+ }, [
1658
+ activeCategories.length,
1659
+ activeTypes.length,
1660
+ assigneeOptions.length,
1661
+ filterAssignees.length,
1662
+ filterCategories.length,
1663
+ filterPriorities.length,
1664
+ filterStatus.length,
1665
+ filterTypes.length,
1666
+ priorities.length,
1667
+ selectedPlanningInitiativeId,
1668
+ selectedPlanningWorkstreamId,
1669
+ ]);
1670
+ const hasDocumentDropdownFilters = useMemo(() => (documentTypeFilters.length !== DOCUMENT_TYPE_FILTER_OPTIONS.length
1671
+ || documentAttachmentFilters.length !== DOCUMENT_ATTACHMENT_FILTER_OPTIONS.length), [documentAttachmentFilters.length, documentTypeFilters.length]);
1672
+ const workspaceChromeMode = resolvedWorkspaceModule;
1673
+ const workspaceChromeDefinition = useMemo(() => getWorkspaceChromeDefinition(workspaceChromeMode), [workspaceChromeMode]);
1674
+ const hasActiveWorkspaceFilterControls = useMemo(() => {
1675
+ switch (workspaceChromeDefinition.filterBar.kind) {
1676
+ case 'task-filters':
1677
+ return hasDropdownFilters;
1678
+ case 'document-filters':
1679
+ return hasDocumentDropdownFilters;
1680
+ default:
1681
+ return false;
1682
+ }
1683
+ }, [
1684
+ hasDocumentDropdownFilters,
1685
+ hasDropdownFilters,
1686
+ workspaceChromeDefinition.filterBar.kind,
1687
+ ]);
1145
1688
  // Check if any filters are set (search + dropdown filters)
1146
1689
  const isFiltered = hasSearchFilter || hasDropdownFilters;
1147
- const totalActiveCount = tasks.length;
1148
- const visibleActiveCount = props.filteredTasks.length;
1690
+ const totalActiveCount = taskScope === 'archived'
1691
+ ? archivedScopeAllTasks.length
1692
+ : taskScope === 'deleted'
1693
+ ? deletedScopeAllTasks.length
1694
+ : tasks.length;
1695
+ const visibleActiveCount = taskScope === 'archived'
1696
+ ? archivedScopeTasks.length
1697
+ : taskScope === 'deleted'
1698
+ ? filteredDeletedTasks.length
1699
+ : props.filteredTasks.length;
1149
1700
  const activeCountLabel = isFiltered ? `${visibleActiveCount}/${totalActiveCount}` : `${totalActiveCount}`;
1150
1701
  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]);
1702
+ ? `${taskScope.charAt(0).toUpperCase() + taskScope.slice(1)} tasks matching current filters`
1703
+ : `${taskScope.charAt(0).toUpperCase() + taskScope.slice(1)} tasks`;
1704
+ const shouldShowSyncStatus = runtimeMode === 'local' && isAuthenticated;
1162
1705
  const formatSyncTimestamp = useCallback((value) => {
1163
1706
  if (!value)
1164
1707
  return 'Never';
@@ -1167,259 +1710,379 @@ export function StandaloneLayout(props) {
1167
1710
  return 'Never';
1168
1711
  return parsed.toLocaleString();
1169
1712
  }, []);
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]);
1713
+ const formattedLastSyncTime = useMemo(() => formatSyncTimestamp(workspaceLastSuccessfulSyncAt), [formatSyncTimestamp, workspaceLastSuccessfulSyncAt]);
1714
+ const formattedLastPullTime = useMemo(() => formatSyncTimestamp(workspaceLastPullAt), [formatSyncTimestamp, workspaceLastPullAt]);
1715
+ const formattedLastPushTime = useMemo(() => formatSyncTimestamp(workspaceLastPushAt), [formatSyncTimestamp, workspaceLastPushAt]);
1716
+ const rawSyncLastError = workspaceSyncError || workspaceLastErrorMessage || userGlobalSyncError || 'None';
1717
+ const headerSyncPresentation = useMemo(() => resolveHeaderWorkspaceSyncPresentation({
1718
+ runtimeMode,
1719
+ isAuthenticated,
1720
+ workspaceSyncStatus,
1721
+ workspaceCloudSyncEnabled,
1722
+ workspaceSyncSummary,
1723
+ workspaceSyncRecommendedAction,
1724
+ workspaceSyncError: rawSyncLastError
1725
+ }), [
1726
+ runtimeMode,
1727
+ isAuthenticated,
1728
+ workspaceSyncStatus,
1729
+ workspaceCloudSyncEnabled,
1730
+ workspaceSyncSummary,
1731
+ workspaceSyncRecommendedAction,
1732
+ rawSyncLastError
1733
+ ]);
1734
+ const syncLastError = headerSyncPresentation.lastError;
1735
+ const [headerSyncStatus, setHeaderSyncStatus] = useState(headerSyncPresentation.status);
1736
+ const syncStageLabel = useMemo(() => {
1737
+ if (workspaceSyncRepairBusy) {
1738
+ return 'Repairing';
1739
+ }
1740
+ if (workspaceSyncBusy && workspaceSyncPhase === 'error') {
1741
+ return 'Recovering';
1742
+ }
1743
+ switch (workspaceSyncPhase) {
1744
+ case 'provision-local':
1745
+ return 'Initial cloud upload';
1746
+ case 'attach-cloud':
1747
+ return 'Initial cloud pull';
1748
+ case 'active':
1749
+ return 'Active';
1750
+ case 'error':
1751
+ return 'Error';
1752
+ default:
1753
+ return 'Idle';
1754
+ }
1755
+ }, [workspaceSyncPhase, workspaceSyncRepairBusy]);
1756
+ const workspaceSyncDiagnostics = getWorkspaceSyncDiagnostics();
1757
+ const syncDiagnosticsSummary = useMemo(() => ([
1758
+ {
1759
+ label: 'AI profile snapshot',
1760
+ value: `${workspaceSyncDiagnostics.aiProfileSnapshotCount} local profile${workspaceSyncDiagnostics.aiProfileSnapshotCount === 1 ? '' : 's'}`
1761
+ },
1762
+ {
1763
+ label: 'AI profile raw response',
1764
+ value: `${workspaceSyncDiagnostics.aiProfileSnapshotRawCount} from route`
1765
+ },
1766
+ {
1767
+ label: 'Last pushed AI profiles',
1768
+ value: `${workspaceSyncDiagnostics.lastPushedAiProfileCount} tracked`
1769
+ },
1770
+ {
1771
+ label: 'AI watermark map',
1772
+ value: `${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount} tracked`
1773
+ },
1774
+ {
1775
+ label: 'Document snapshot',
1776
+ value: `${workspaceSyncDiagnostics.documentSnapshotCount} local doc${workspaceSyncDiagnostics.documentSnapshotCount === 1 ? '' : 's'}`
1777
+ },
1778
+ {
1779
+ label: 'Asset snapshot',
1780
+ value: `${workspaceSyncDiagnostics.assetSnapshotCount} local asset${workspaceSyncDiagnostics.assetSnapshotCount === 1 ? '' : 's'}`
1781
+ },
1782
+ {
1783
+ label: 'Queued full AI sync',
1784
+ value: workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'
1785
+ },
1786
+ {
1787
+ label: 'AI snapshot last fetch',
1788
+ value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt
1789
+ ? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString()
1790
+ : 'Never'
1791
+ },
1792
+ {
1793
+ label: 'AI snapshot fetch error',
1794
+ value: workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'
1795
+ },
1796
+ {
1797
+ label: 'AI snapshot skip reason',
1798
+ value: workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'
1799
+ }
1800
+ ]), [workspaceSyncDiagnostics]);
1801
+ const shouldDelayHeaderSyncing = headerSyncPresentation.actionable
1802
+ && workspaceSyncStatus === 'syncing'
1803
+ && workspaceSyncPhase === 'active'
1804
+ && workspaceSyncPendingChanges === 0;
1188
1805
  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);
1806
+ if (!shouldDelayHeaderSyncing) {
1807
+ setHeaderSyncStatus(headerSyncPresentation.status);
1204
1808
  return;
1205
1809
  }
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);
1810
+ const timeoutId = window.setTimeout(() => {
1811
+ setHeaderSyncStatus(headerSyncPresentation.status);
1812
+ }, 1200);
1813
+ return () => window.clearTimeout(timeoutId);
1814
+ }, [headerSyncPresentation.status, shouldDelayHeaderSyncing]);
1815
+ const formatSyncEventLine = useCallback((event) => {
1816
+ const occurredAt = typeof event.occurredAt === 'string' && event.occurredAt.trim().length > 0
1817
+ ? event.occurredAt
1818
+ : 'unknown-time';
1819
+ const eventType = typeof event.eventType === 'string' && event.eventType.trim().length > 0
1820
+ ? event.eventType.trim()
1821
+ : 'sync';
1822
+ const status = event.status === 'error' ? 'failed' : 'succeeded';
1823
+ const changeCountValue = Number(event.changeCount);
1824
+ const changeCount = Number.isFinite(changeCountValue)
1825
+ ? ` (${Math.max(0, Math.floor(changeCountValue))} change${Math.floor(changeCountValue) === 1 ? '' : 's'})`
1826
+ : '';
1827
+ const requestMsValue = Number(event.requestMs);
1828
+ const requestMs = Number.isFinite(requestMsValue)
1829
+ ? ` in ${Math.max(0, Math.floor(requestMsValue))}ms`
1830
+ : '';
1831
+ const statusCodeValue = Number(event.statusCode);
1832
+ const statusCode = Number.isFinite(statusCodeValue)
1833
+ ? ` [${Math.floor(statusCodeValue)}]`
1834
+ : '';
1835
+ const errorMessage = typeof event.errorMessage === 'string' && event.errorMessage.trim().length > 0
1836
+ ? `: ${event.errorMessage.trim()}`
1837
+ : '';
1838
+ return `${occurredAt} ${eventType} ${status}${changeCount}${requestMs}${statusCode}${errorMessage}`;
1839
+ }, []);
1840
+ const syncEventRows = useMemo(() => {
1841
+ if (syncRecentEventsLoading && syncRecentEvents.length === 0) {
1842
+ return [{
1843
+ key: 'loading',
1844
+ text: 'Loading recent sync events...',
1845
+ tone: 'muted'
1846
+ }];
1847
+ }
1848
+ if (syncRecentEventsError) {
1849
+ return [{
1850
+ key: 'error',
1851
+ text: syncRecentEventsError,
1852
+ tone: 'error'
1853
+ }];
1854
+ }
1855
+ if (syncRecentEvents.length === 0) {
1856
+ return [{
1857
+ key: 'empty',
1858
+ text: 'No recent sync events recorded.',
1859
+ tone: 'muted'
1860
+ }];
1861
+ }
1862
+ return syncRecentEvents.map((event, index) => ({
1863
+ key: getSyncEventStableKey(event, index),
1864
+ text: formatSyncEventLine(event),
1865
+ tone: event.status === 'error' ? 'error' : 'default'
1866
+ }));
1867
+ }, [formatSyncEventLine, syncRecentEvents, syncRecentEventsError, syncRecentEventsLoading]);
1868
+ const referenceMismatchCount = useMemo(() => getActiveReferenceMismatchCount(syncRecentEvents), [syncRecentEvents]);
1869
+ const activeReferenceMismatchEvents = useMemo(() => getActiveReferenceMismatchEvents(syncRecentEvents), [syncRecentEvents]);
1870
+ const activeReferenceMismatchSummaries = useMemo(() => (activeReferenceMismatchEvents.map((event, index) => {
1871
+ const details = event.details && typeof event.details === 'object' ? event.details : null;
1872
+ const pathValue = String(details?.path || '').trim();
1873
+ const existingRefValue = Number(details?.existingReferenceNumber);
1874
+ const incomingRefValue = Number(details?.incomingReferenceNumber);
1875
+ const pathLabel = pathValue || `Mismatch ${index + 1}`;
1876
+ const refsLabel = (Number.isFinite(existingRefValue) || Number.isFinite(incomingRefValue))
1877
+ ? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
1878
+ : null;
1879
+ return {
1880
+ key: getSyncEventStableKey(event, index),
1881
+ pathLabel,
1882
+ refsLabel,
1883
+ };
1884
+ })), [activeReferenceMismatchEvents]);
1885
+ useLayoutEffect(() => {
1886
+ const pendingRestore = pendingSyncEventsScrollRestoreRef.current;
1887
+ const listEl = syncEventsListRef.current;
1888
+ if (!pendingRestore || !listEl)
1213
1889
  return;
1890
+ const delta = listEl.scrollHeight - pendingRestore.scrollHeight;
1891
+ listEl.scrollTop = pendingRestore.scrollTop + Math.max(0, delta);
1892
+ pendingSyncEventsScrollRestoreRef.current = null;
1893
+ }, [syncRecentEvents]);
1894
+ const loadRecentSyncEvents = useCallback(async () => {
1895
+ const res = await fetch(`/api/taskforce/sync/events?workspace_id=${encodeURIComponent(currentWorkspaceId)}&limit=15`, {
1896
+ method: 'GET',
1897
+ credentials: 'include'
1898
+ });
1899
+ if (!res.ok) {
1900
+ throw new Error(`Failed to load sync events (${res.status})`);
1214
1901
  }
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);
1902
+ const data = await res.json().catch(() => ({}));
1903
+ return Array.isArray(data?.events)
1904
+ ? data.events.filter((event) => event && typeof event === 'object')
1905
+ : [];
1906
+ }, [currentWorkspaceId]);
1907
+ useEffect(() => {
1908
+ if (!showSyncStatusModal)
1221
1909
  return;
1910
+ let cancelled = false;
1911
+ const isInitialLoad = syncRecentEvents.length === 0;
1912
+ if (isInitialLoad) {
1913
+ setSyncRecentEventsLoading(true);
1222
1914
  }
1223
- applyState(rawSyncState);
1224
- }, [rawSyncState, displaySyncState]);
1225
- useEffect(() => {
1226
- return () => {
1227
- if (syncStateTimerRef.current !== null) {
1228
- window.clearTimeout(syncStateTimerRef.current);
1229
- syncStateTimerRef.current = null;
1915
+ void loadRecentSyncEvents()
1916
+ .then((events) => {
1917
+ if (cancelled)
1918
+ return;
1919
+ const listEl = syncEventsListRef.current;
1920
+ pendingSyncEventsScrollRestoreRef.current = listEl && listEl.scrollTop > 8
1921
+ ? {
1922
+ scrollTop: listEl.scrollTop,
1923
+ scrollHeight: listEl.scrollHeight
1924
+ }
1925
+ : null;
1926
+ setSyncRecentEvents((current) => {
1927
+ const next = current.length === 0 ? events : mergeRecentSyncEvents(current, events, 15);
1928
+ return areSyncEventListsEqual(current, next) ? current : next;
1929
+ });
1930
+ setSyncRecentEventsError(null);
1931
+ })
1932
+ .catch((error) => {
1933
+ if (cancelled)
1934
+ return;
1935
+ const message = error instanceof Error && error.message.trim().length > 0
1936
+ ? error.message.trim()
1937
+ : 'Unable to load recent sync events.';
1938
+ setSyncRecentEventsError(message);
1939
+ if (isInitialLoad) {
1940
+ setSyncRecentEvents([]);
1230
1941
  }
1942
+ })
1943
+ .finally(() => {
1944
+ if (cancelled)
1945
+ return;
1946
+ if (isInitialLoad) {
1947
+ setSyncRecentEventsLoading(false);
1948
+ }
1949
+ });
1950
+ return () => {
1951
+ cancelled = true;
1231
1952
  };
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':
1953
+ }, [
1954
+ showSyncStatusModal,
1955
+ loadRecentSyncEvents,
1956
+ syncRecentEvents.length,
1957
+ workspaceLastPushAt,
1958
+ workspaceLastPullAt,
1959
+ workspaceLastErrorAt,
1960
+ workspaceSyncStatus
1961
+ ]);
1962
+ const syncStatusMeta = useMemo(() => {
1963
+ switch (headerSyncStatus) {
1964
+ case 'off':
1258
1965
  return {
1259
- label: 'Fallback Pull',
1260
- color: '#fb923c',
1261
- border: 'rgba(251, 146, 60, 0.45)',
1262
- background: 'rgba(251, 146, 60, 0.16)'
1966
+ label: 'Off',
1967
+ icon: 'off',
1968
+ color: '#94a3b8',
1969
+ border: 'rgba(148, 163, 184, 0.35)',
1970
+ background: 'rgba(148, 163, 184, 0.12)'
1263
1971
  };
1264
- case 'error':
1972
+ case 'syncing':
1265
1973
  return {
1266
- label: 'Sync Error',
1267
- color: '#f87171',
1268
- border: 'rgba(248, 113, 113, 0.45)',
1269
- background: 'rgba(248, 113, 113, 0.14)'
1974
+ label: 'Syncing',
1975
+ icon: 'cloud',
1976
+ color: '#60a5fa',
1977
+ border: 'rgba(96, 165, 250, 0.35)',
1978
+ background: 'rgba(59, 130, 246, 0.12)'
1270
1979
  };
1271
- case 'disconnected':
1980
+ case 'attention':
1272
1981
  return {
1273
- label: 'Disconnected',
1274
- color: '#94a3b8',
1275
- border: 'rgba(148, 163, 184, 0.45)',
1276
- background: 'rgba(148, 163, 184, 0.12)'
1982
+ label: 'Needs Attention',
1983
+ icon: 'cloud',
1984
+ color: '#fda4af',
1985
+ border: 'rgba(253, 164, 175, 0.4)',
1986
+ background: 'rgba(239, 68, 68, 0.12)'
1277
1987
  };
1278
- case 'idle':
1279
1988
  default:
1280
1989
  return {
1281
- label: 'Synced',
1282
- color: '#34d399',
1283
- border: 'rgba(52, 211, 153, 0.45)',
1284
- background: 'rgba(52, 211, 153, 0.14)'
1990
+ label: 'Healthy',
1991
+ icon: 'cloud',
1992
+ color: '#86efac',
1993
+ border: 'rgba(134, 239, 172, 0.4)',
1994
+ background: 'rgba(34, 197, 94, 0.12)'
1285
1995
  };
1286
1996
  }
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
- };
1997
+ }, [headerSyncStatus]);
1998
+ const handleCopySyncDetails = useCallback(async () => {
1999
+ const details = [
2000
+ 'Taskforce Sync Manager',
2001
+ `Workspace ID: ${currentWorkspaceId}`,
2002
+ `Status: ${syncStatusMeta.label}`,
2003
+ `Summary: ${headerSyncPresentation.summary}`,
2004
+ `Last successful sync: ${formattedLastSyncTime}`,
2005
+ `Last pull from cloud: ${formattedLastPullTime}`,
2006
+ `Last push to cloud: ${formattedLastPushTime}`,
2007
+ `Last error at: ${workspaceSyncDiagnostics.lastErrorAt ? new Date(workspaceSyncDiagnostics.lastErrorAt).toLocaleString() : 'Never'}`,
2008
+ `Pending local changes: ${workspaceSyncPendingChanges}`,
2009
+ `Last error: ${syncLastError}`,
2010
+ `Sync stage: ${syncStageLabel}`,
2011
+ `AI profile snapshot: ${workspaceSyncDiagnostics.aiProfileSnapshotCount}`,
2012
+ `AI profile raw response: ${workspaceSyncDiagnostics.aiProfileSnapshotRawCount}`,
2013
+ `Last pushed AI profiles tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileCount}`,
2014
+ `AI profile watermarks tracked: ${workspaceSyncDiagnostics.lastPushedAiProfileWatermarkCount}`,
2015
+ `AI snapshot last fetch: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt ? new Date(workspaceSyncDiagnostics.aiProfileSnapshotLastFetchAt).toLocaleString() : 'Never'}`,
2016
+ `AI snapshot fetch error: ${workspaceSyncDiagnostics.aiProfileSnapshotLastFetchError || 'None'}`,
2017
+ `AI snapshot skip reason: ${workspaceSyncDiagnostics.aiProfileSnapshotLastSkipReason || 'None'}`,
2018
+ `Document snapshot: ${workspaceSyncDiagnostics.documentSnapshotCount}`,
2019
+ `Asset snapshot: ${workspaceSyncDiagnostics.assetSnapshotCount}`,
2020
+ `Reference mismatches detected: ${referenceMismatchCount}`,
2021
+ `Queued full AI sync: ${workspaceSyncDiagnostics.forceFullAiProfilePushQueued ? 'Yes' : 'No'}`
2022
+ ];
2023
+ try {
2024
+ const events = syncRecentEvents.length > 0 ? syncRecentEvents : await loadRecentSyncEvents();
2025
+ const recentEventLines = events.map((event) => formatSyncEventLine(event));
2026
+ const copyText = [
2027
+ ...details,
2028
+ '',
2029
+ 'Recent sync events',
2030
+ ...(recentEventLines.length > 0 ? recentEventLines : ['No recent sync events recorded.'])
2031
+ ].join('\n');
2032
+ await navigator.clipboard.writeText(copyText);
2033
+ setWorkspaceSyncCopied(true);
2034
+ window.setTimeout(() => {
2035
+ setWorkspaceSyncCopied(false);
2036
+ }, 1800);
2037
+ pushNotice('Sync details copied to clipboard.', 'success');
1306
2038
  }
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
- };
2039
+ catch {
2040
+ setWorkspaceSyncCopied(false);
2041
+ pushNotice('Failed to copy sync details.', 'error');
1315
2042
  }
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
- };
2043
+ }, [
2044
+ currentWorkspaceId,
2045
+ syncStatusMeta.label,
2046
+ headerSyncPresentation.summary,
2047
+ formattedLastSyncTime,
2048
+ formattedLastPullTime,
2049
+ formattedLastPushTime,
2050
+ workspaceSyncDiagnostics,
2051
+ workspaceSyncPendingChanges,
2052
+ syncLastError,
2053
+ syncStageLabel,
2054
+ referenceMismatchCount,
2055
+ pushNotice,
2056
+ syncRecentEvents,
2057
+ loadRecentSyncEvents,
2058
+ formatSyncEventLine
2059
+ ]);
2060
+ const handleRepairSync = useCallback(async () => {
2061
+ setWorkspaceSyncRepairQueued(false);
2062
+ setWorkspaceSyncRepairBusy(true);
2063
+ try {
2064
+ await resetWorkspaceSyncCursorAndPull();
1324
2065
  }
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
- };
2066
+ finally {
2067
+ setWorkspaceSyncRepairBusy(false);
1333
2068
  }
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
- });
2069
+ }, [resetWorkspaceSyncCursorAndPull]);
2070
+ const handleQueueOrRunRepairSync = useCallback(() => {
2071
+ if (workspaceSyncRepairBusy)
2072
+ return;
2073
+ if (workspaceSyncBusy) {
2074
+ setWorkspaceSyncRepairQueued(true);
2075
+ return;
1386
2076
  }
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)
2077
+ void handleRepairSync();
2078
+ }, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy]);
2079
+ useEffect(() => {
2080
+ if (!workspaceSyncRepairQueued)
1417
2081
  return;
1418
- setHierarchyRootTaskId(bestMatch.id);
1419
- setHierarchyRootSearch('');
1420
- if (parentTaskFilterMode === 'unlinked')
1421
- setParentTaskFilterMode('all');
1422
- }, [hierarchySearchMatches, parentTaskFilterMode, setParentTaskFilterMode]);
2082
+ if (workspaceSyncBusy || workspaceSyncRepairBusy)
2083
+ return;
2084
+ void handleRepairSync();
2085
+ }, [handleRepairSync, workspaceSyncBusy, workspaceSyncRepairBusy, workspaceSyncRepairQueued]);
1423
2086
  const selectedDateObj = useMemo(() => parseDateOnlyLocal(scheduleSelectedDate) || new Date(), [scheduleSelectedDate]);
1424
2087
  const scheduleWeekStart = useMemo(() => startOfWeek(selectedDateObj, globalWeekStartsOn), [selectedDateObj, globalWeekStartsOn]);
1425
2088
  const scheduleDates = useMemo(() => {
@@ -1490,99 +2153,7 @@ export function StandaloneLayout(props) {
1490
2153
  setScheduleBulkBusy(false);
1491
2154
  }
1492
2155
  }, [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
2156
  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
2157
  // Calculate columns based on grouping
1587
2158
  const scheduleColumns = useMemo(() => {
1588
2159
  const orderedKeys = globalWeekStartsOn === 'sunday'
@@ -1620,8 +2191,8 @@ export function StandaloneLayout(props) {
1620
2191
  case 'type':
1621
2192
  return (activeTypes || []).map(t => ({
1622
2193
  ...t,
1623
- icon: TypeMeta[t.value]?.icon,
1624
- color: TypeMeta[t.value]?.color
2194
+ icon: t.icon || TypeMeta[t.value]?.icon,
2195
+ color: t.color || TypeMeta[t.value]?.color
1625
2196
  }));
1626
2197
  case 'priority':
1627
2198
  return priorities;
@@ -1636,159 +2207,718 @@ export function StandaloneLayout(props) {
1636
2207
  case 'approach':
1637
2208
  return approaches;
1638
2209
  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
- ];
2210
+ return assigneeOptions.map((option) => ({
2211
+ value: option.value,
2212
+ label: option.label,
2213
+ icon: option.icon === 'HelpCircle' ? 'Circle' : option.icon,
2214
+ color: option.color,
2215
+ }));
1644
2216
  case 'status':
1645
2217
  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' }
2218
+ ...STATUS_OPTIONS.filter((option) => option.value !== 'done' && option.value !== 'cancelled'),
2219
+ {
2220
+ value: 'completed',
2221
+ label: 'Completed',
2222
+ icon: getStatusDefinition('done').icon,
2223
+ color: getStatusDefinition('done').color
2224
+ }
1651
2225
  ];
1652
- case 'hierarchy': {
1653
- if (parentTaskFilterMode === 'unlinked') {
1654
- return [{
1655
- value: 'standalone',
1656
- label: 'Unlinked',
1657
- icon: 'Circle',
1658
- color: '#64748b'
1659
- }];
2226
+ case 'schedule':
2227
+ return scheduleColumns;
2228
+ default:
2229
+ return activeCategories;
2230
+ }
2231
+ }, [groupBy, activeCategories, activeTypes, priorities, approaches, assigneeOptions, scheduleColumns]);
2232
+ // === SECTION: AccountMenuControl ===
2233
+ // Not extracted: ~25 distinct props from local state would be needed.
2234
+ // References: isAuthenticated, authIdentityLabel, billingStatus, runtimeLabel,
2235
+ // connectedEnvironmentLabel, canManageCloudWorkspaces, workspaceActionBusy,
2236
+ // setWorkspacePanelOpen, canOpenTeamManagement, openTeamManagementModal, and more.
2237
+ 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
2238
+ ? 'Checking sign-in status...'
2239
+ : isAuthenticated
2240
+ ? (_jsx(_Fragment, { children: hasAuthIdentity
2241
+ ? (_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: () => {
2242
+ setShowAccountMenu(false);
2243
+ openPlansScreen();
2244
+ }, 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 })] })] }) }))
2245
+ : 'Signed in' }))
2246
+ : '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: () => {
2247
+ setWorkspaceActionError('');
2248
+ setShowCreateWorkspaceConfirm(true);
2249
+ setShowAccountMenu(false);
2250
+ }, 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: () => {
2251
+ setShowAccountMenu(false);
2252
+ setShowEditProfileModal(true);
2253
+ }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Edit Profile"] })), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2254
+ setShowAccountMenu(false);
2255
+ openPlansScreen();
2256
+ }, role: "menuitem", children: [_jsx(Layers, { size: 15 }), "Plans"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2257
+ setShowAccountMenu(false);
2258
+ setShowAccountHub(true);
2259
+ }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Account Hub"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2260
+ setShowAccountMenu(false);
2261
+ setShowHelpModal(true);
2262
+ }, role: "menuitem", children: [_jsx(HelpCircle, { size: 15 }), "Help & Tutorial"] }), runtimeMode === 'local' && !isAuthenticated && !isAuthBootstrapPending && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2263
+ openSharedLoginRoute('login');
2264
+ }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Sign In / Register"] })), isAuthenticated && (_jsxs("button", { className: accountStyles.accountMenuItem, onClick: async () => {
2265
+ setShowAccountMenu(false);
2266
+ await logout();
2267
+ }, role: "menuitem", children: [_jsx(LogOut, { size: 15 }), "Sign Out"] }))] }))] }));
2268
+ const cycleEmptyColumnVisibility = useCallback(() => {
2269
+ setEmptyColumnMode((prev) => {
2270
+ if (prev === 'show')
2271
+ return 'collapse';
2272
+ if (prev === 'collapse')
2273
+ return 'hide';
2274
+ return 'show';
2275
+ });
2276
+ }, [setEmptyColumnMode]);
2277
+ const openAddTaskModal = useCallback(() => {
2278
+ resetForm();
2279
+ setActiveWorkspaceModule('tasks');
2280
+ setActiveTab('add');
2281
+ }, [resetForm, setActiveTab, setActiveWorkspaceModule]);
2282
+ const openAddTaskModalForWorkstream = useCallback((workstreamId) => {
2283
+ const workstream = props.workstreams.find((item) => item.id === workstreamId);
2284
+ resetForm();
2285
+ setWorkstreamInput(formatWorkstreamReference(workstream) || workstreamId);
2286
+ setActiveWorkspaceModule('tasks');
2287
+ setActiveTab('add');
2288
+ }, [props.workstreams, resetForm, setActiveTab, setActiveWorkspaceModule, setWorkstreamInput]);
2289
+ const openPlanningEditor = useCallback((editor) => {
2290
+ setPlanningEditor(editor);
2291
+ setPlanningPrimaryCollapsed(false);
2292
+ setPlanningSecondaryEditor(null);
2293
+ setPlanningSecondaryCollapsed(false);
2294
+ setPlanningDraftTitle('');
2295
+ setPlanningDraftDescription('');
2296
+ setPlanningDraftOwner('');
2297
+ const selectedInitiative = editor.entityType === 'workstream' && selectedPlanningInitiativeId
2298
+ ? planningStructure.initiativeById.get(selectedPlanningInitiativeId) || null
2299
+ : null;
2300
+ setPlanningDraftInitiativeId(selectedInitiative ? formatInitiativeReference(selectedInitiative) : '');
2301
+ setPlanningNestedWorkstreamDetailId(null);
2302
+ }, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
2303
+ const openPlanningSecondaryEditor = useCallback((editor, initiativeId) => {
2304
+ setPlanningSecondaryEditor(editor);
2305
+ setPlanningSecondaryCollapsed(false);
2306
+ setPlanningDraftTitle('');
2307
+ setPlanningDraftDescription('');
2308
+ setPlanningDraftOwner('');
2309
+ const selectedInitiative = editor.entityType === 'workstream'
2310
+ ? ((initiativeId ? planningStructure.initiativeById.get(initiativeId) : null)
2311
+ || (selectedPlanningInitiativeId ? planningStructure.initiativeById.get(selectedPlanningInitiativeId) || null : null))
2312
+ : null;
2313
+ setPlanningDraftInitiativeId(selectedInitiative ? formatInitiativeReference(selectedInitiative) : '');
2314
+ setPlanningNestedWorkstreamDetailId(null);
2315
+ }, [planningStructure.initiativeById, selectedPlanningInitiativeId]);
2316
+ const closePlanningEditor = useCallback(() => {
2317
+ setPlanningEditor(null);
2318
+ setPlanningDraftTitle('');
2319
+ setPlanningDraftDescription('');
2320
+ setPlanningDraftOwner('');
2321
+ setPlanningDraftInitiativeId('');
2322
+ }, []);
2323
+ const closePlanningSecondaryEditor = useCallback(() => {
2324
+ setPlanningSecondaryEditor(null);
2325
+ setPlanningDraftTitle('');
2326
+ setPlanningDraftDescription('');
2327
+ setPlanningDraftOwner('');
2328
+ setPlanningDraftInitiativeId('');
2329
+ }, []);
2330
+ const submitPlanningEditor = useCallback(async () => {
2331
+ const activeEditor = planningSecondaryEditor || planningEditor;
2332
+ if (!activeEditor)
2333
+ return;
2334
+ const isSecondaryEditor = Boolean(planningSecondaryEditor);
2335
+ const ownerId = planningDraftOwner.trim() || null;
2336
+ try {
2337
+ if (activeEditor.entityType === 'initiative') {
2338
+ if (activeEditor.mode === 'edit') {
2339
+ await props.updateInitiative(activeEditor.targetId, {
2340
+ title: planningDraftTitle.trim() || 'Untitled Initiative',
2341
+ description: planningDraftDescription.trim() || null,
2342
+ ownerId,
2343
+ });
2344
+ setPlanningDrawerDetail({ type: 'initiative', id: activeEditor.targetId });
2345
+ setPlanningNestedWorkstreamDetailId(null);
2346
+ pushNotice('Initiative updated', 'success');
1660
2347
  }
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'
2348
+ else {
2349
+ const created = await props.createInitiative({
2350
+ title: planningDraftTitle.trim() || 'Untitled Initiative',
2351
+ description: planningDraftDescription.trim() || null,
2352
+ ownerId,
1674
2353
  });
2354
+ setPlanningDrawerDetail({ type: 'initiative', id: created.id });
2355
+ setPlanningNestedWorkstreamDetailId(null);
2356
+ pushNotice('Initiative created', 'success');
1675
2357
  }
1676
- return hierarchyColumns;
1677
2358
  }
1678
- case 'schedule':
1679
- return scheduleColumns;
2359
+ else {
2360
+ const nextInitiativeRaw = planningDraftInitiativeId.trim();
2361
+ const resolvedInitiative = nextInitiativeRaw.length === 0
2362
+ ? null
2363
+ : resolveInitiativeReference(planningStructure.initiatives, nextInitiativeRaw);
2364
+ if (nextInitiativeRaw.length > 0 && !resolvedInitiative) {
2365
+ pushNotice('Initiative not found by that reference.', 'error');
2366
+ return;
2367
+ }
2368
+ const patch = {
2369
+ title: planningDraftTitle.trim() || 'Untitled Workstream',
2370
+ description: planningDraftDescription.trim() || null,
2371
+ ownerId,
2372
+ initiativeId: resolvedInitiative?.id || null,
2373
+ };
2374
+ if (activeEditor.mode === 'edit') {
2375
+ await props.updateWorkstream(activeEditor.targetId, patch);
2376
+ if (isSecondaryEditor) {
2377
+ setPlanningNestedWorkstreamDetailId(activeEditor.targetId);
2378
+ }
2379
+ else {
2380
+ setPlanningDrawerDetail({ type: 'workstream', id: activeEditor.targetId });
2381
+ setPlanningNestedWorkstreamDetailId(null);
2382
+ }
2383
+ pushNotice('Workstream updated', 'success');
2384
+ }
2385
+ else {
2386
+ const created = await props.createWorkstream(patch);
2387
+ if (isSecondaryEditor) {
2388
+ setPlanningNestedWorkstreamDetailId(created.id);
2389
+ }
2390
+ else {
2391
+ setPlanningDrawerDetail({ type: 'workstream', id: created.id });
2392
+ setPlanningNestedWorkstreamDetailId(null);
2393
+ }
2394
+ pushNotice('Workstream created', 'success');
2395
+ }
2396
+ }
2397
+ if (isSecondaryEditor) {
2398
+ closePlanningSecondaryEditor();
2399
+ }
2400
+ else {
2401
+ closePlanningEditor();
2402
+ }
2403
+ }
2404
+ catch (error) {
2405
+ pushNotice(error instanceof Error ? error.message : 'Failed to save planning item.', 'error');
2406
+ }
2407
+ }, [
2408
+ closePlanningEditor,
2409
+ closePlanningSecondaryEditor,
2410
+ planningDraftDescription,
2411
+ planningDraftInitiativeId,
2412
+ planningDraftOwner,
2413
+ planningDraftTitle,
2414
+ planningEditor,
2415
+ planningSecondaryEditor,
2416
+ planningStructure.initiatives,
2417
+ props,
2418
+ pushNotice,
2419
+ ]);
2420
+ const togglePlanningInitiative = useCallback((initiativeId) => {
2421
+ setExpandedPlanningInitiativeIds((current) => {
2422
+ const next = new Set(current);
2423
+ if (next.has(initiativeId)) {
2424
+ next.delete(initiativeId);
2425
+ }
2426
+ else {
2427
+ next.add(initiativeId);
2428
+ }
2429
+ return next;
2430
+ });
2431
+ }, []);
2432
+ const selectPlanningInitiative = useCallback((initiativeId) => {
2433
+ setSelectedPlanningInitiativeId((current) => current === initiativeId ? '' : initiativeId);
2434
+ setSelectedPlanningWorkstreamId('');
2435
+ }, []);
2436
+ const selectPlanningWorkstream = useCallback((workstreamId, initiativeId) => {
2437
+ setSelectedPlanningWorkstreamId((current) => {
2438
+ const nextWorkstreamId = current === workstreamId ? '' : workstreamId;
2439
+ setSelectedPlanningInitiativeId(nextWorkstreamId ? (initiativeId || '') : '');
2440
+ return nextWorkstreamId;
2441
+ });
2442
+ }, []);
2443
+ const openInitiativeDetails = useCallback((initiativeId) => {
2444
+ setPlanningEditor(null);
2445
+ setPlanningSecondaryEditor(null);
2446
+ setPlanningPrimaryCollapsed(false);
2447
+ setPlanningSecondaryCollapsed(false);
2448
+ setPlanningDrawerDetail(initiativeId ? { type: 'initiative', id: initiativeId } : null);
2449
+ setPlanningNestedWorkstreamDetailId(null);
2450
+ }, []);
2451
+ const openWorkstreamDetails = useCallback((workstreamId) => {
2452
+ setPlanningEditor(null);
2453
+ setPlanningSecondaryEditor(null);
2454
+ setPlanningPrimaryCollapsed(false);
2455
+ setPlanningSecondaryCollapsed(false);
2456
+ setPlanningDrawerDetail(workstreamId ? { type: 'workstream', id: workstreamId } : null);
2457
+ setPlanningNestedWorkstreamDetailId(null);
2458
+ }, []);
2459
+ const openNestedWorkstreamDetails = useCallback((workstreamId) => {
2460
+ setPlanningSecondaryEditor(null);
2461
+ setPlanningSecondaryCollapsed(false);
2462
+ setPlanningNestedWorkstreamDetailId(workstreamId);
2463
+ }, []);
2464
+ const ownerValueByLabel = useMemo(() => {
2465
+ const map = new Map();
2466
+ assigneeOptions.forEach((option) => {
2467
+ map.set(option.label, String(option.value));
2468
+ });
2469
+ return map;
2470
+ }, [assigneeOptions]);
2471
+ const openPlanningCreateEditor = useCallback((type, initiativeId) => {
2472
+ if (type === 'workstream' && initiativeId) {
2473
+ openPlanningSecondaryEditor({ mode: 'create', entityType: 'workstream' }, initiativeId);
2474
+ return;
2475
+ }
2476
+ openPlanningEditor({ mode: 'create', entityType: type });
2477
+ if (type === 'workstream') {
2478
+ const initiative = (initiativeId ? planningStructure.initiativeById.get(initiativeId) : null)
2479
+ || (selectedPlanningInitiativeId ? planningStructure.initiativeById.get(selectedPlanningInitiativeId) : null)
2480
+ || null;
2481
+ setPlanningDraftInitiativeId(initiative ? formatInitiativeReference(initiative) : '');
2482
+ }
2483
+ }, [openPlanningEditor, openPlanningSecondaryEditor, planningStructure.initiativeById, selectedPlanningInitiativeId]);
2484
+ const openPlanningEditDialog = useCallback((type, id) => {
2485
+ if (type === 'initiative') {
2486
+ const item = planningStructure.initiativeById.get(id);
2487
+ if (!item)
2488
+ return;
2489
+ setPlanningEditor({ mode: 'edit', entityType: 'initiative', targetId: id });
2490
+ setPlanningDraftTitle(item.title);
2491
+ setPlanningDraftDescription(item.description || '');
2492
+ setPlanningDraftOwner(item.ownerLabel ? (ownerValueByLabel.get(item.ownerLabel) || '') : '');
2493
+ setPlanningDraftInitiativeId('');
2494
+ }
2495
+ else {
2496
+ const item = planningStructure.workstreamById.get(id)
2497
+ || planningStructure.standaloneWorkstreams.find((workstream) => workstream.id === id);
2498
+ if (!item)
2499
+ return;
2500
+ setPlanningEditor({ mode: 'edit', entityType: 'workstream', targetId: id });
2501
+ setPlanningDraftTitle(item.title);
2502
+ setPlanningDraftDescription(item.description || '');
2503
+ setPlanningDraftOwner(item.ownerLabel ? (ownerValueByLabel.get(item.ownerLabel) || '') : '');
2504
+ const initiative = item.initiativeId ? planningStructure.initiativeById.get(item.initiativeId) || null : null;
2505
+ setPlanningDraftInitiativeId(initiative ? formatInitiativeReference(initiative) : '');
2506
+ }
2507
+ setPlanningNestedWorkstreamDetailId(null);
2508
+ }, [ownerValueByLabel, planningStructure.initiativeById, planningStructure.standaloneWorkstreams, planningStructure.workstreamById]);
2509
+ const handleAssignInitiativeToWorkstream = useCallback(async (workstreamId, initiativeReferenceOverride) => {
2510
+ const workstream = props.workstreams.find((item) => item.id === workstreamId);
2511
+ if (!workstream) {
2512
+ pushNotice('Workstream not found.', 'error');
2513
+ return;
2514
+ }
2515
+ const nextInitiativeRaw = typeof initiativeReferenceOverride === 'string'
2516
+ ? initiativeReferenceOverride.trim()
2517
+ : initiativeReferenceOverride === null
2518
+ ? ''
2519
+ : planningDraftInitiativeId.trim();
2520
+ const resolvedInitiative = nextInitiativeRaw.length === 0
2521
+ ? null
2522
+ : resolveInitiativeReference(planningStructure.initiatives, nextInitiativeRaw);
2523
+ if (nextInitiativeRaw.length > 0 && !resolvedInitiative) {
2524
+ pushNotice('Initiative not found by that reference.', 'error');
2525
+ return;
2526
+ }
2527
+ const normalizedParentId = resolvedInitiative?.id || null;
2528
+ if ((workstream.initiativeId || null) === normalizedParentId) {
2529
+ pushNotice(normalizedParentId ? 'Workstream already belongs to that initiative.' : 'Workstream is already standalone.', 'info');
2530
+ return;
2531
+ }
2532
+ try {
2533
+ await props.updateWorkstream(workstreamId, { initiativeId: normalizedParentId });
2534
+ setPlanningDraftInitiativeId(resolvedInitiative ? formatInitiativeReference(resolvedInitiative) : '');
2535
+ pushNotice(normalizedParentId ? 'Initiative set' : 'Initiative removed', 'success');
2536
+ }
2537
+ catch {
2538
+ pushNotice('Failed to set initiative.', 'error');
2539
+ }
2540
+ }, [planningDraftInitiativeId, planningStructure.initiatives, props, pushNotice]);
2541
+ const handleAssignTaskToWorkstream = useCallback(async (taskId, workstreamId) => {
2542
+ const task = hierarchyTaskById.get(taskId);
2543
+ if (!task) {
2544
+ pushNotice('Task not found.', 'error');
2545
+ return;
2546
+ }
2547
+ const normalizedWorkstreamId = workstreamId || null;
2548
+ if (planningStructure.initiativeById.has(taskId) || planningStructure.workstreamById.has(taskId)) {
2549
+ pushNotice('Only execution tasks can be moved into workstreams.', 'error');
2550
+ return;
2551
+ }
2552
+ if (normalizedWorkstreamId && !planningStructure.workstreamById.has(normalizedWorkstreamId)) {
2553
+ pushNotice('Workstream not found.', 'error');
2554
+ return;
2555
+ }
2556
+ if ((task.workstreamId || null) === normalizedWorkstreamId) {
2557
+ pushNotice(normalizedWorkstreamId ? 'Task already belongs to that workstream.' : 'Task is already standalone.', 'info');
2558
+ return;
2559
+ }
2560
+ try {
2561
+ const res = await fetch(`/api/taskforce/task/${taskId}`, {
2562
+ method: 'PATCH',
2563
+ headers: { 'Content-Type': 'application/json' },
2564
+ body: JSON.stringify({ workstreamId: normalizedWorkstreamId })
2565
+ });
2566
+ if (!res.ok) {
2567
+ const data = await res.json().catch(() => ({}));
2568
+ pushNotice(data?.error || 'Failed to set workstream.', 'error');
2569
+ return;
2570
+ }
2571
+ await res.json().catch(() => null);
2572
+ await fetchTasks(true);
2573
+ pushNotice(normalizedWorkstreamId ? 'Workstream set' : 'Workstream removed', 'success');
2574
+ }
2575
+ catch {
2576
+ pushNotice('Failed to set workstream.', 'error');
2577
+ }
2578
+ }, [fetchTasks, hierarchyTaskById, planningStructure.initiativeById, planningStructure.workstreamById, pushNotice]);
2579
+ const handleAttachTaskToWorkstreamByReference = useCallback(async (workstreamId, taskReference) => {
2580
+ const needle = taskReference.trim();
2581
+ if (!needle) {
2582
+ pushNotice('Enter a task reference.', 'error');
2583
+ return;
2584
+ }
2585
+ const parsedReference = parseTaskReference(needle);
2586
+ const matchedTask = hierarchyTasks.find((task) => (task.id === needle
2587
+ || getTaskReferenceLabel(task) === needle
2588
+ || (parsedReference !== null && task.referenceNumber === parsedReference)));
2589
+ if (!matchedTask) {
2590
+ pushNotice('Task not found by that reference.', 'error');
2591
+ return;
2592
+ }
2593
+ await handleAssignTaskToWorkstream(matchedTask.id, workstreamId);
2594
+ }, [handleAssignTaskToWorkstream, hierarchyTasks, pushNotice]);
2595
+ const handleAttachWorkstreamToInitiativeByReference = useCallback(async (initiativeId, workstreamReference) => {
2596
+ const needle = workstreamReference.trim();
2597
+ if (!needle) {
2598
+ pushNotice('Enter a workstream reference.', 'error');
2599
+ return;
2600
+ }
2601
+ const initiative = planningStructure.initiativeById.get(initiativeId);
2602
+ if (!initiative) {
2603
+ pushNotice('Initiative not found.', 'error');
2604
+ return;
2605
+ }
2606
+ const parsedReference = parseWorkstreamReference(needle);
2607
+ const matchedWorkstream = props.workstreams.find((workstream) => (workstream.id === needle
2608
+ || formatWorkstreamReference(workstream) === needle
2609
+ || (parsedReference !== null && workstream.referenceNumber === parsedReference)));
2610
+ if (!matchedWorkstream) {
2611
+ pushNotice('Workstream not found by that reference.', 'error');
2612
+ return;
2613
+ }
2614
+ await handleAssignInitiativeToWorkstream(matchedWorkstream.id, formatInitiativeReference(initiative));
2615
+ }, [handleAssignInitiativeToWorkstream, planningStructure.initiativeById, props.workstreams, pushNotice]);
2616
+ const handleInitiativeFilterChange = useCallback((initiativeId) => {
2617
+ setSelectedPlanningInitiativeId(initiativeId);
2618
+ setSelectedPlanningWorkstreamId('');
2619
+ }, []);
2620
+ const handleWorkstreamFilterChange = useCallback((workstreamId) => {
2621
+ setSelectedPlanningWorkstreamId(workstreamId);
2622
+ setSelectedPlanningInitiativeId(workstreamId ? planningStructure.workstreamInitiativeIds.get(workstreamId) || '' : '');
2623
+ }, [planningStructure.workstreamInitiativeIds]);
2624
+ const handleModuleChange = useCallback((nextModuleId) => {
2625
+ const nextModule = resolveWorkspaceModule(nextModuleId, workspaceModules);
2626
+ if (nextModule !== nextModuleId) {
2627
+ const unavailableLabel = workspaceModules.find((module) => module.id === nextModuleId)?.label || 'That module';
2628
+ pushNotice(`${unavailableLabel} is not available in this build yet.`, 'error');
2629
+ return;
2630
+ }
2631
+ setActiveWorkspaceModule(nextModule);
2632
+ }, [
2633
+ pushNotice,
2634
+ setActiveWorkspaceModule,
2635
+ workspaceModules,
2636
+ ]);
2637
+ const handlePlanningToolToggle = useCallback(() => {
2638
+ if (resolvedWorkspaceModule === 'tasks' && planningDrawerOpen) {
2639
+ setPlanningDrawerOpen(false);
2640
+ setPlanningDrawerDetail(null);
2641
+ setPlanningNestedWorkstreamDetailId(null);
2642
+ setPlanningEditor(null);
2643
+ return;
2644
+ }
2645
+ if (resolvedWorkspaceModule !== 'tasks') {
2646
+ setActiveWorkspaceModule('tasks');
2647
+ setPlanningDrawerOpen(true);
2648
+ return;
2649
+ }
2650
+ setPlanningDrawerOpen((current) => !current);
2651
+ }, [planningDrawerOpen, resolvedWorkspaceModule, setActiveWorkspaceModule]);
2652
+ const handleBackToTaskFromAnnotatedAttachment = useCallback((taskId) => {
2653
+ setActiveWorkspaceModule('tasks');
2654
+ handleOpenTaskById(taskId);
2655
+ }, [handleOpenTaskById, setActiveWorkspaceModule]);
2656
+ const handleAnnotatedAttachmentOpenTarget = useCallback((target, options) => {
2657
+ setRequestedAnnotatedTarget(target);
2658
+ setRequestedAnnotatedSessionId(options?.sessionId ?? null);
2659
+ setRequestedAnnotatedOpenVersion((current) => current + 1);
2660
+ setActiveWorkspaceModule('annotate');
2661
+ }, [setActiveWorkspaceModule]);
2662
+ const handleAnnotatedAttachmentContextChange = useCallback(({ target, sessionId }) => {
2663
+ setRequestedAnnotatedTarget((current) => {
2664
+ if (!current && !target)
2665
+ return current;
2666
+ if (current
2667
+ && target
2668
+ && current.taskId === target.taskId
2669
+ && current.taskReferenceLabel === target.taskReferenceLabel
2670
+ && current.assetId === target.assetId
2671
+ && current.imageReferenceLabel === target.imageReferenceLabel
2672
+ && current.path === target.path
2673
+ && current.displayName === target.displayName) {
2674
+ return current;
2675
+ }
2676
+ return target;
2677
+ });
2678
+ setRequestedAnnotatedSessionId((current) => (current === sessionId ? current : sessionId));
2679
+ }, []);
2680
+ const sortOptions = useMemo(() => [
2681
+ { value: 'created', label: t('standalone.sortCreated') },
2682
+ { value: 'updated', label: t('standalone.sortUpdated') },
2683
+ { value: 'priority', label: t('standalone.sortPriority') },
2684
+ { value: 'complexity', label: t('standalone.sortComplexity') },
2685
+ ...buildCustomTaxonomySortOptions(taxonomies, [...props.searchAgnosticTasks, ...archivedScopeAllTasks])
2686
+ ], [archivedScopeAllTasks, props.searchAgnosticTasks, taxonomies]);
2687
+ const groupingOptions = useMemo(() => [
2688
+ { value: 'category', label: t('standalone.groupCategory') },
2689
+ { value: 'type', label: t('standalone.groupType') },
2690
+ { value: 'priority', label: t('standalone.groupPriority') },
2691
+ { value: 'complexity', label: t('standalone.groupComplexity') },
2692
+ { value: 'assignee', label: t('standalone.groupAssignee') },
2693
+ { value: 'status', label: t('standalone.groupStatus') },
2694
+ { value: 'schedule', label: t('standalone.groupSchedule') },
2695
+ ], []);
2696
+ const workspaceToolModules = useMemo(() => workspaceModules.filter((module) => module.enabled), [workspaceModules]);
2697
+ const taskDisplayActions = useMemo(() => {
2698
+ const actions = [
2699
+ {
2700
+ key: 'empty-columns',
2701
+ icon: emptyColumnMode === 'show'
2702
+ ? _jsx(Maximize2, { size: 16 })
2703
+ : emptyColumnMode === 'collapse'
2704
+ ? _jsx(Minimize2, { size: 16 })
2705
+ : _jsx(EyeOff, { size: 16 }),
2706
+ title: emptyColumnMode === 'show'
2707
+ ? t('standalone.showEmptyColumns')
2708
+ : emptyColumnMode === 'collapse'
2709
+ ? t('standalone.compressEmptyColumns')
2710
+ : t('standalone.hideEmptyColumns'),
2711
+ onClick: cycleEmptyColumnVisibility,
2712
+ active: emptyColumnMode !== 'show',
2713
+ },
2714
+ {
2715
+ key: 'compressed-cards',
2716
+ icon: compressedCards ? _jsx(ChevronsDown, { size: 16 }) : _jsx(ChevronsUp, { size: 16 }),
2717
+ title: compressedCards ? t('standalone.expandCards') : t('standalone.compressCards'),
2718
+ onClick: () => setCompressedCards(!compressedCards),
2719
+ active: compressedCards,
2720
+ },
2721
+ ];
2722
+ return actions;
2723
+ }, [
2724
+ compressedCards,
2725
+ cycleEmptyColumnVisibility,
2726
+ emptyColumnMode,
2727
+ ]);
2728
+ const universalFilterActions = useMemo(() => [{
2729
+ key: 'filter-toggle',
2730
+ icon: _jsx(Filter, { size: 16 }),
2731
+ title: t('standalone.toggleFilters'),
2732
+ onClick: () => setShowFilters(!showFilters),
2733
+ active: showFilters,
2734
+ className: hasActiveWorkspaceFilterControls ? styles.filterGlow : '',
2735
+ }], [hasActiveWorkspaceFilterControls, setShowFilters, showFilters]);
2736
+ const workspaceFilterBar = useMemo(() => {
2737
+ switch (workspaceChromeDefinition.filterBar.kind) {
2738
+ case 'task-filters':
2739
+ 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: () => {
2740
+ const deletedCount = deletedScopeAllTasks.length;
2741
+ if (deletedCount === 0)
2742
+ return;
2743
+ const confirmed = window.confirm(`Permanently delete ${deletedCount} deleted task${deletedCount === 1 ? '' : 's'}? This cannot be undone.`);
2744
+ if (!confirmed)
2745
+ return;
2746
+ void handleEmptyDeletedTasks();
2747
+ }, clearFilters: () => {
2748
+ clearFilters();
2749
+ setSelectedPlanningInitiativeId('');
2750
+ setSelectedPlanningWorkstreamId('');
2751
+ } }));
2752
+ case 'document-filters':
2753
+ return (_jsx(DocumentFilterToolbar, { typeFilters: documentTypeFilters, setTypeFilters: setDocumentTypeFilters, attachmentFilters: documentAttachmentFilters, setAttachmentFilters: setDocumentAttachmentFilters, clearFilters: () => {
2754
+ setDocumentTypeFilters(DOCUMENT_TYPE_FILTER_OPTIONS.map((option) => option.value));
2755
+ setDocumentAttachmentFilters(DOCUMENT_ATTACHMENT_FILTER_OPTIONS.map((option) => option.value));
2756
+ } }));
2757
+ case 'empty':
1680
2758
  default:
1681
- return activeCategories;
2759
+ return _jsx(EmptyWorkspaceFilterBar, {});
1682
2760
  }
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: {
2761
+ }, [
2762
+ assigneeOptions,
2763
+ categoryFilterOptions,
2764
+ clearFilters,
2765
+ deletedScopeAllTasks.length,
2766
+ documentAttachmentFilters,
2767
+ documentTypeFilters,
2768
+ filterAssignees,
2769
+ filterCategories,
2770
+ filterPriorities,
2771
+ filterStatus,
2772
+ filterTypes,
2773
+ handleInitiativeFilterChange,
2774
+ handleWorkstreamFilterChange,
2775
+ initiativeFilterOptions,
2776
+ priorities,
2777
+ selectedPlanningInitiativeId,
2778
+ selectedPlanningWorkstreamId,
2779
+ setDocumentAttachmentFilters,
2780
+ setDocumentTypeFilters,
2781
+ setFilterAssignees,
2782
+ setFilterCategories,
2783
+ setFilterPriorities,
2784
+ setFilterStatus,
2785
+ setFilterTypes,
2786
+ setSelectedPlanningInitiativeId,
2787
+ setSelectedPlanningWorkstreamId,
2788
+ setShowArchive,
2789
+ setTaskScope,
2790
+ showArchive,
2791
+ taskScope,
2792
+ taxonomyDisplayLabels,
2793
+ typeFilterOptions,
2794
+ workstreamFilterOptions,
2795
+ workspaceChromeDefinition.filterBar.kind,
2796
+ fetchArchive,
2797
+ handleEmptyDeletedTasks,
2798
+ ]);
2799
+ const universalDisplayActions = useMemo(() => {
2800
+ const actions = [{
2801
+ key: 'zen-toggle',
2802
+ icon: _jsx(Zap, { size: 16, fill: zenMode ? 'currentColor' : 'none' }),
2803
+ title: zenMode ? t('standalone.exitZenMode') : t('standalone.enterZenMode'),
2804
+ onClick: () => setZenMode(),
2805
+ active: zenMode,
2806
+ }];
2807
+ return actions;
2808
+ }, [
2809
+ zenMode,
2810
+ setZenMode,
2811
+ ]);
2812
+ const headerSectionRegistry = useMemo(() => ({
2813
+ search: (_jsx(WorkspaceHeaderSearch, { value: searchQuery, placeholder: t('standalone.searchPlaceholder'), active: hasSearchFilter, onChange: setSearchQuery, onClear: () => setSearchQuery(''), clearTitle: t('standalone.clearSearchTitle') })),
2814
+ 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 }) })) })),
2815
+ 'divider-primary': _jsx(WorkspaceHeaderDivider, {}),
2816
+ grouping: (_jsx(WorkspaceHeaderSelect, { label: _jsxs(_Fragment, { children: [_jsx(Layers, { size: 12 }), " ", t('standalone.groupLabel')] }), value: groupBy, options: groupingOptions, onChange: (value) => setGroupBy(value) })),
2817
+ 'divider-secondary': _jsx(WorkspaceHeaderDivider, {}),
2818
+ 'task-display-actions': (_jsx(WorkspaceHeaderActions, { actions: taskDisplayActions })),
2819
+ }), [
2820
+ groupBy,
2821
+ groupingOptions,
2822
+ handleModuleChange,
2823
+ hasSearchFilter,
2824
+ props,
2825
+ searchQuery,
2826
+ setGroupBy,
2827
+ setSearchQuery,
2828
+ setSortBy,
2829
+ sortBy,
2830
+ sortOptions,
2831
+ taskDisplayActions,
2832
+ workspaceChromeDefinition.moduleId,
2833
+ ]);
2834
+ const primaryActionRegistry = useMemo(() => ({
2835
+ 'add-task': (_jsx(WorkspacePrimaryAction, { icon: _jsx(Plus, { size: 16 }), label: t('standalone.addTask'), onClick: openAddTaskModal })),
2836
+ 'add-document': null,
2837
+ }), [openAddTaskModal]);
2838
+ const workspaceToolRail = useMemo(() => {
2839
+ const moduleIcons = {
2840
+ tasks: _jsx(ClipboardCheck, { size: 18 }),
2841
+ docs: _jsx(FileText, { size: 18 }),
2842
+ annotate: _jsx(Image, { size: 18 }),
2843
+ workflows: _jsx(Workflow, { size: 18 }),
2844
+ agents: _jsx(Bot, { size: 18 }),
2845
+ };
2846
+ 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: () => {
2847
+ setSettingsSection('general');
2848
+ setActiveTab('settings');
2849
+ }, title: "Workspace Settings", "aria-label": "Workspace Settings", children: _jsx(Settings, { size: 18 }) })] })] }));
2850
+ }, [activeTab, handleModuleChange, handlePlanningToolToggle, planningDrawerOpen, resolvedWorkspaceModule, setSettingsSection, setActiveTab, workspaceToolModules]);
2851
+ const planningDrawerPortalRef = useRef(null);
2852
+ 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: () => {
2853
+ setPlanningSecondaryEditor(null);
2854
+ setPlanningNestedWorkstreamDetailId(null);
2855
+ }, 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) => {
2856
+ void props.archiveInitiative(initiativeId).then(() => {
2857
+ pushNotice('Initiative archived', 'success');
2858
+ }).catch((error) => {
2859
+ pushNotice(error instanceof Error ? error.message : 'Failed to archive initiative.', 'error');
2860
+ });
2861
+ }, onUnarchiveInitiative: (initiativeId) => {
2862
+ void props.unarchiveInitiative(initiativeId).then(() => {
2863
+ pushNotice('Initiative unarchived', 'success');
2864
+ }).catch((error) => {
2865
+ pushNotice(error instanceof Error ? error.message : 'Failed to unarchive initiative.', 'error');
2866
+ });
2867
+ }, onArchiveWorkstream: (workstreamId) => {
2868
+ void props.archiveWorkstream(workstreamId).then(() => {
2869
+ pushNotice('Workstream archived', 'success');
2870
+ }).catch((error) => {
2871
+ pushNotice(error instanceof Error ? error.message : 'Failed to archive workstream.', 'error');
2872
+ });
2873
+ }, onUnarchiveWorkstream: (workstreamId) => {
2874
+ void props.unarchiveWorkstream(workstreamId).then(() => {
2875
+ pushNotice('Workstream unarchived', 'success');
2876
+ }).catch((error) => {
2877
+ pushNotice(error instanceof Error ? error.message : 'Failed to unarchive workstream.', 'error');
2878
+ });
2879
+ } }));
2880
+ const effectiveHeaderSectionIds = workspaceChromeDefinition.headerSections;
2881
+ const workspaceHeaderSections = effectiveHeaderSectionIds
2882
+ .map((sectionId) => {
2883
+ const section = headerSectionRegistry[sectionId];
2884
+ return section ? _jsx(React.Fragment, { children: section }, sectionId) : null;
2885
+ })
2886
+ .filter(Boolean);
2887
+ const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
2888
+ ? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
2889
+ : null;
2890
+ 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: {
2891
+ marginLeft: '6px',
2892
+ height: '24px',
2893
+ width: '24px',
2894
+ padding: 0,
2895
+ borderRadius: '999px',
2896
+ border: `1px solid ${syncStatusMeta.border}`,
2897
+ background: syncStatusMeta.background,
2898
+ color: syncStatusMeta.color,
2899
+ display: 'inline-flex',
2900
+ alignItems: 'center',
2901
+ justifyContent: 'center'
2902
+ }, 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: () => {
2903
+ setRequestedDocPath(null);
2904
+ setRequestedDocAssetId(null);
2905
+ }, 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
2906
  position: 'relative',
1770
2907
  opacity: props.loadingTasks ? 0.7 : 1,
1771
- transition: 'opacity 0.2s ease, padding-right 220ms cubic-bezier(0.4, 0, 0.2, 1)',
2908
+ transition: 'opacity 0.2s ease, padding 220ms cubic-bezier(0.4, 0, 0.2, 1)',
1772
2909
  display: 'flex',
1773
2910
  flex: 1,
1774
2911
  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) => {
2912
+ paddingLeft: planningDrawerOpen
2913
+ ? `${WORKSPACE_TOOL_RAIL_WIDTH + planningDrawerWidthPx + WORKSPACE_CONTENT_GUTTER}px`
2914
+ : `${WORKSPACE_TOOL_RAIL_WIDTH + WORKSPACE_CONTENT_GUTTER}px`,
2915
+ paddingRight: (groupBy === 'schedule' && scheduleSidebarOpen) ? `${SCHEDULE_SIDEBAR_WIDTH_PX}px` : 0
2916
+ }, children: [workspaceToolRail, _jsx("div", { ref: planningDrawerPortalRef, style: {
2917
+ position: 'absolute',
2918
+ inset: 0,
2919
+ zIndex: 35,
2920
+ pointerEvents: 'none',
2921
+ } }), _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
2922
  setScheduleSelectedDate(dateOnly);
1793
2923
  const parsed = parseDateOnlyLocal(dateOnly);
1794
2924
  if (parsed) {
@@ -1798,291 +2928,154 @@ export function StandaloneLayout(props) {
1798
2928
  if (groupBy !== 'schedule')
1799
2929
  return;
1800
2930
  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: {
2931
+ }, 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
2932
+ ? createPortal(planningDrawerNode, planningDrawerPortalRef.current)
2933
+ : null, onAssignTaskToWorkstream: handleAssignTaskToWorkstream, onAssignWorkstreamToInitiative: handleAssignInitiativeToWorkstream, workstreams: props.workstreams, initiatives: props.initiatives, onUnarchive: (taskId) => {
2934
+ if (taskScope === 'deleted') {
2935
+ const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
2936
+ if (!deletedRecord)
2937
+ return;
2938
+ void handleRestoreDeletedTask(deletedRecord.id);
2939
+ return;
2940
+ }
2941
+ void handleUnarchive(taskId);
2942
+ }, onDelete: (taskId) => {
2943
+ if (taskScope === 'deleted') {
2944
+ const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
2945
+ if (!deletedRecord)
2946
+ return;
2947
+ const confirmed = window.confirm('Permanently delete this task? This cannot be undone.');
2948
+ if (!confirmed)
2949
+ return;
2950
+ void handlePermanentlyDeleteDeletedTask(deletedRecord.id);
2951
+ return;
2952
+ }
2953
+ void handleDelete(taskId);
2954
+ } }, `${groupBy}-${kanbanColumns.map(c => String(c.value)).join('|')}-${scheduleDates.mon}`), groupBy === 'schedule' && !scheduleSidebarOpen && (_jsx("button", { type: "button", className: "tf-control-icon", onClick: () => {
2955
+ setPlanningDrawerDetail(null);
2956
+ setScheduleSidebarOpen(true);
2957
+ }, title: "Show Schedule Sidebar", style: {
1809
2958
  position: 'absolute',
1810
2959
  top: '10px',
1811
2960
  right: '10px',
1812
2961
  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) => {
2962
+ }, 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
2963
+ ? (deletedRecordId) => {
2964
+ void handleRestoreDeletedTask(deletedRecordId);
2965
+ }
2966
+ : undefined, handlePermanentlyDeleteDeletedTask: currentTask?.deletedRecordId
2967
+ ? (deletedRecordId) => {
2968
+ const confirmed = window.confirm('Permanently delete this task? This cannot be undone.');
2969
+ if (!confirmed)
2970
+ return;
2971
+ void handlePermanentlyDeleteDeletedTask(deletedRecordId);
2972
+ resetForm();
2973
+ setActiveTab('tasks');
2974
+ }
2975
+ : 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
2976
  setSettingsSection(section);
1980
2977
  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);
2978
+ }, onSubmit: handleSubmit, commentsEndRef: commentsEndRef, onAddComment: () => handleAddComment(newCommentText), onOpenTaskById: handleOpenTaskById, onAddContextFile: (file) => {
2979
+ let nextAttachments = attachments;
2980
+ setAttachments((prev) => {
2981
+ nextAttachments = [...prev, file];
2982
+ return nextAttachments;
2983
+ });
2984
+ setAttachmentsDirty(true);
2985
+ if (editingTaskId) {
2986
+ void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
2987
+ }
1987
2988
  }, 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 { }
2989
+ let nextAttachments = attachments;
2990
+ setAttachments((prev) => {
2991
+ nextAttachments = prev.filter((_, i) => i !== index);
2992
+ return nextAttachments;
2993
+ });
2994
+ setAttachmentsDirty(true);
2995
+ if (editingTaskId) {
2996
+ void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
1996
2997
  }
1997
2998
  }, 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: () => {
2999
+ let nextAttachments = attachments;
3000
+ setAttachments((prev) => {
3001
+ nextAttachments = prev.map((s, i) => {
3002
+ if (i !== index)
3003
+ return s;
3004
+ if (typeof s === 'string')
3005
+ return { path: s, caption, timestamp: new Date().toISOString() };
3006
+ return { ...s, caption };
3007
+ });
3008
+ return nextAttachments;
3009
+ });
3010
+ setAttachmentsDirty(true);
3011
+ if (editingTaskId) {
3012
+ void handleUpdateTask(editingTaskId, { attachments: nextAttachments });
3013
+ }
3014
+ }, onCopyId: handleCopyId, onToggleInProgress: handleToggleInProgress, onToggleReview: handleToggleReview, onToggleComplete: handleToggleComplete, onToggleCancel: handleToggleCancel, onSetStatus: handleSetStatus, onArchiveTask: handleArchiveTask, onUnarchive: (taskId) => {
3015
+ if (currentTask?.isDeleted) {
3016
+ const deletedRecord = deletedTaskRecordByTaskId.get(taskId);
3017
+ if (!deletedRecord)
3018
+ return;
3019
+ void handleRestoreDeletedTask(deletedRecord.id);
3020
+ return;
3021
+ }
3022
+ void handleUnarchive(taskId);
3023
+ }, 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
3024
  setShowHelpModal(false);
2007
3025
  setActiveTab('settings');
2008
3026
  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'
3027
+ }, 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
3028
  ? (authRequiredForApi
2011
3029
  ? (isAuthenticated ? 'Signed in. API access is enabled.' : 'Cloud mode requires sign in before app usage.')
2012
3030
  : '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
3031
+ : 'Local runtime allows guest usage without sign in.' }), _jsx("p", { className: managementStyles.sectionTextSpaced, children: isAuthenticated
2014
3032
  ? (hasAuthIdentity
2015
- ? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: styles.accountIdentityEmail, children: authIdentityLabel })] })
3033
+ ? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel })] })
2016
3034
  : '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: () => {
3035
+ : '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
3036
  setShowAccountHub(false);
2019
3037
  void openTeamManagementModal();
2020
3038
  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
3039
+ }, 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
3040
  ? (workspaceCloudSyncEnabled
2023
- ? `Enabled${workspaceSyncSourceOfTruth ? ` · Source: ${workspaceSyncSourceOfTruth}` : ''}`
3041
+ ? `Enabled · ${syncStatusMeta.label}`
2024
3042
  : '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: () => {
3043
+ : '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
3044
  setShowAccountHub(false);
2052
3045
  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: () => {
3046
+ }, 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
3047
  setTeamManagementTab('members');
2055
3048
  void loadTeamUsers();
2056
3049
  }, 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
3050
  setTeamManagementTab('audit');
2058
3051
  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) => {
3052
+ }, 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
3053
  void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/role`, {
2061
3054
  method: 'PATCH',
2062
3055
  headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
2063
3056
  credentials: 'include',
2064
3057
  body: JSON.stringify({
2065
- workspaceId: currentWorkspaceId,
3058
+ workspaceId: teamManagementWorkspaceId,
2066
3059
  role: event.target.value
2067
3060
  })
2068
3061
  }));
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) => {
3062
+ }, 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
3063
  void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/permission-mode`, {
2071
3064
  method: 'PATCH',
2072
3065
  headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
2073
3066
  credentials: 'include',
2074
3067
  body: JSON.stringify({
2075
- workspaceId: currentWorkspaceId,
3068
+ workspaceId: teamManagementWorkspaceId,
2076
3069
  mode: event.target.value
2077
3070
  })
2078
3071
  }));
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: () => {
3072
+ }, 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
3073
  void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/disable`, {
2081
3074
  method: 'PATCH',
2082
3075
  headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
2083
3076
  credentials: 'include',
2084
3077
  body: JSON.stringify({
2085
- workspaceId: currentWorkspaceId,
3078
+ workspaceId: teamManagementWorkspaceId,
2086
3079
  disabled: !member.disabled
2087
3080
  })
2088
3081
  }));
@@ -2091,27 +3084,52 @@ export function StandaloneLayout(props) {
2091
3084
  method: 'POST',
2092
3085
  headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
2093
3086
  credentials: 'include',
2094
- body: JSON.stringify({ workspaceId: currentWorkspaceId })
3087
+ body: JSON.stringify({ workspaceId: teamManagementWorkspaceId })
2095
3088
  }));
2096
3089
  }, 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)}`, {
3090
+ void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}?workspaceId=${encodeURIComponent(teamManagementWorkspaceId)}`, {
2098
3091
  method: 'DELETE',
2099
3092
  headers: teamAdminHeaders(),
2100
3093
  credentials: 'include'
2101
3094
  }));
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: () => {
3095
+ }, 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
3096
  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: () => {
3097
+ }, 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: {
3098
+ borderColor: syncStatusMeta.border,
3099
+ background: syncStatusMeta.background,
3100
+ color: syncStatusMeta.color
3101
+ }, children: syncStatusMeta.label }), _jsxs("span", { className: syncStatusStyles.syncStatusLabel, children: ["Workspace: ", _jsx("code", { children: currentWorkspaceLabel })] })] }) }), _jsx("label", { className: syncStatusStyles.syncToggle, children: _jsxs("span", { className: [
3102
+ syncStatusStyles.syncToggleControl,
3103
+ workspaceCloudSyncEnabled ? syncStatusStyles.syncToggleControlEnabled : syncStatusStyles.syncToggleControlDisabled,
3104
+ syncControlBusy ? syncStatusStyles.syncToggleControlBusy : '',
3105
+ !canManageWorkspaceSync ? syncStatusStyles.syncToggleControlBlocked : ''
3106
+ ].filter(Boolean).join(' '), children: [_jsx("input", { className: syncStatusStyles.syncToggleInput, type: "checkbox", role: "switch", "aria-label": "Enable Sync", checked: workspaceCloudSyncEnabled, disabled: !canManageWorkspaceSync || syncControlBusy, onChange: (event) => {
3107
+ void handleWorkspaceSyncToggle(event.target.checked);
3108
+ } }), _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
3109
+ ? `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.`
3110
+ : '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: {
3111
+ display: 'flex',
3112
+ flexDirection: 'column',
3113
+ gap: '2px',
3114
+ padding: '8px 10px',
3115
+ borderRadius: '8px',
3116
+ background: 'rgba(15, 23, 42, 0.18)',
3117
+ border: '1px solid rgba(250, 204, 21, 0.16)'
3118
+ }, 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: [
3119
+ syncStatusStyles.syncStatusEventItem,
3120
+ row.tone === 'error' ? syncStatusStyles.syncStatusEventItemError : '',
3121
+ row.tone === 'muted' ? syncStatusStyles.syncStatusEventItemMuted : ''
3122
+ ].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
3123
+ ? 'Repair is running now.'
3124
+ : workspaceSyncRepairQueued
3125
+ ? 'Repair is queued and will start when the current sync finishes.'
3126
+ : workspaceSyncBusy
3127
+ ? 'Queue a repair to start automatically when the current sync finishes.'
3128
+ : '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: () => {
3129
+ openSharedLoginRoute('login');
3130
+ }, 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
3131
  const parts = currentBrowsePath.split('/').filter(Boolean);
2114
3132
  parts.pop();
2115
3133
  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 })] }) })] }));
3134
+ }, 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
3135
  }