@taskforcehq/taskforce 0.3.168

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 (492) hide show
  1. package/README.md +241 -0
  2. package/dist/Taskforce.d.ts +3 -0
  3. package/dist/Taskforce.js +3 -0
  4. package/dist/Taskforce.module.css +7587 -0
  5. package/dist/TaskforceCore.CategoryFallback.test.d.ts +1 -0
  6. package/dist/TaskforceCore.CategoryFallback.test.js +98 -0
  7. package/dist/TaskforceCore.d.ts +12 -0
  8. package/dist/TaskforceCore.js +729 -0
  9. package/dist/TaskforceCore.test.d.ts +1 -0
  10. package/dist/TaskforceCore.test.js +2132 -0
  11. package/dist/TaskforceScreenshot.d.ts +11 -0
  12. package/dist/TaskforceScreenshot.js +113 -0
  13. package/dist/TaskforceWidget.d.ts +6 -0
  14. package/dist/TaskforceWidget.js +101 -0
  15. package/dist/TaskforceWidget.test.d.ts +1 -0
  16. package/dist/TaskforceWidget.test.js +92 -0
  17. package/dist/UnsavedChangesRelaxed.test.d.ts +1 -0
  18. package/dist/UnsavedChangesRelaxed.test.js +16 -0
  19. package/dist/__tests__/localizationCatalogs.test.d.ts +1 -0
  20. package/dist/__tests__/localizationCatalogs.test.js +37 -0
  21. package/dist/__tests__/planParser.test.d.ts +1 -0
  22. package/dist/__tests__/planParser.test.js +183 -0
  23. package/dist/__tests__/releaseVersion.test.d.ts +1 -0
  24. package/dist/__tests__/releaseVersion.test.js +32 -0
  25. package/dist/__tests__/structuredCommentValidation.test.d.ts +1 -0
  26. package/dist/__tests__/structuredCommentValidation.test.js +31 -0
  27. package/dist/__tests__/structuredDocValidation.test.d.ts +1 -0
  28. package/dist/__tests__/structuredDocValidation.test.js +30 -0
  29. package/dist/__tests__/templateEngine.test.d.ts +1 -0
  30. package/dist/__tests__/templateEngine.test.js +150 -0
  31. package/dist/__tests__/templates.test.d.ts +1 -0
  32. package/dist/__tests__/templates.test.js +45 -0
  33. package/dist/__tests__/workflowDocumentFixtures.test.d.ts +1 -0
  34. package/dist/__tests__/workflowDocumentFixtures.test.js +78 -0
  35. package/dist/__tests__/workflowExportMatrix.test.d.ts +1 -0
  36. package/dist/__tests__/workflowExportMatrix.test.js +113 -0
  37. package/dist/__tests__/workflowExportSnapshots.test.d.ts +1 -0
  38. package/dist/__tests__/workflowExportSnapshots.test.js +28 -0
  39. package/dist/__tests__/workflowGeneration.test.d.ts +1 -0
  40. package/dist/__tests__/workflowGeneration.test.js +12 -0
  41. package/dist/__tests__/workflowSimulation.test.d.ts +1 -0
  42. package/dist/__tests__/workflowSimulation.test.js +113 -0
  43. package/dist/__tests__/workflowV2Policy.test.d.ts +1 -0
  44. package/dist/__tests__/workflowV2Policy.test.js +80 -0
  45. package/dist/api.d.ts +30 -0
  46. package/dist/api.js +85 -0
  47. package/dist/cli.d.ts +2 -0
  48. package/dist/cli.js +217 -0
  49. package/dist/compat/priorityCompat.d.ts +2 -0
  50. package/dist/compat/priorityCompat.js +24 -0
  51. package/dist/components/features/DocumentGenerateModal.d.ts +9 -0
  52. package/dist/components/features/DocumentGenerateModal.js +114 -0
  53. package/dist/components/features/DocumentIndex.d.ts +19 -0
  54. package/dist/components/features/DocumentIndex.js +51 -0
  55. package/dist/components/features/DocumentViewer.d.ts +9 -0
  56. package/dist/components/features/DocumentViewer.js +253 -0
  57. package/dist/components/features/DocumentWorkspace.d.ts +21 -0
  58. package/dist/components/features/DocumentWorkspace.js +88 -0
  59. package/dist/components/features/TaskSettings.d.ts +209 -0
  60. package/dist/components/features/TaskSettings.js +500 -0
  61. package/dist/components/features/TaskSettings.test.d.ts +1 -0
  62. package/dist/components/features/TaskSettings.test.js +308 -0
  63. package/dist/components/features/settings/CategoryManager.d.ts +19 -0
  64. package/dist/components/features/settings/CategoryManager.js +113 -0
  65. package/dist/components/features/settings/TaxonomyEditor.d.ts +8 -0
  66. package/dist/components/features/settings/TaxonomyEditor.js +98 -0
  67. package/dist/components/features/settings/TaxonomyManager 2.d.ts +52 -0
  68. package/dist/components/features/settings/TaxonomyManager 2.js +75 -0
  69. package/dist/components/features/settings/TaxonomyManager.d.ts +54 -0
  70. package/dist/components/features/settings/TaxonomyManager.js +77 -0
  71. package/dist/components/features/settings/TypeManager.d.ts +10 -0
  72. package/dist/components/features/settings/TypeManager.js +50 -0
  73. package/dist/components/features/settings/WorkflowEditorPrototype.d.ts +15 -0
  74. package/dist/components/features/settings/WorkflowEditorPrototype.js +245 -0
  75. package/dist/components/task/TaskActionHeader.d.ts +20 -0
  76. package/dist/components/task/TaskActionHeader.js +13 -0
  77. package/dist/components/task/TaskCard.d.ts +47 -0
  78. package/dist/components/task/TaskCard.js +326 -0
  79. package/dist/components/task/TaskCard.test.d.ts +1 -0
  80. package/dist/components/task/TaskCard.test.js +95 -0
  81. package/dist/components/task/TaskContextUpload.d.ts +12 -0
  82. package/dist/components/task/TaskContextUpload.js +393 -0
  83. package/dist/components/task/TaskContextUpload.test.d.ts +1 -0
  84. package/dist/components/task/TaskContextUpload.test.js +189 -0
  85. package/dist/components/task/TaskForm.d.ts +105 -0
  86. package/dist/components/task/TaskForm.js +241 -0
  87. package/dist/components/task/TaskForm.test.d.ts +1 -0
  88. package/dist/components/task/TaskForm.test.js +288 -0
  89. package/dist/components/task/TaskKanban 2.d.ts +76 -0
  90. package/dist/components/task/TaskKanban 2.js +1004 -0
  91. package/dist/components/task/TaskKanban.d.ts +76 -0
  92. package/dist/components/task/TaskKanban.empty-column-drop.test.d.ts +1 -0
  93. package/dist/components/task/TaskKanban.empty-column-drop.test.js +66 -0
  94. package/dist/components/task/TaskKanban.js +1026 -0
  95. package/dist/components/task/TaskKanban.test.d.ts +1 -0
  96. package/dist/components/task/TaskKanban.test.js +46 -0
  97. package/dist/components/task/TaskList.d.ts +74 -0
  98. package/dist/components/task/TaskList.js +38 -0
  99. package/dist/components/task/TaskList.test.d.ts +1 -0
  100. package/dist/components/task/TaskList.test.js +270 -0
  101. package/dist/components/task/TaskScreenshotSection.d.ts +15 -0
  102. package/dist/components/task/TaskScreenshotSection.js +22 -0
  103. package/dist/components/ui/LevelSelector.d.ts +13 -0
  104. package/dist/components/ui/LevelSelector.js +25 -0
  105. package/dist/components/ui/LevelSelector.test.d.ts +1 -0
  106. package/dist/components/ui/LevelSelector.test.js +55 -0
  107. package/dist/components/ui/Markdown.d.ts +6 -0
  108. package/dist/components/ui/Markdown.js +94 -0
  109. package/dist/components/ui/Markdown.test.d.ts +1 -0
  110. package/dist/components/ui/Markdown.test.js +38 -0
  111. package/dist/components/ui/Modal.d.ts +16 -0
  112. package/dist/components/ui/Modal.js +49 -0
  113. package/dist/components/ui/MultiSelectFilter.d.ts +10 -0
  114. package/dist/components/ui/MultiSelectFilter.js +71 -0
  115. package/dist/components/ui/MultiSelectFilter.test.d.ts +1 -0
  116. package/dist/components/ui/MultiSelectFilter.test.js +74 -0
  117. package/dist/components/ui/TaxonomyDropdown.d.ts +17 -0
  118. package/dist/components/ui/TaxonomyDropdown.js +145 -0
  119. package/dist/components/ui/TaxonomyDropdown.test.d.ts +1 -0
  120. package/dist/components/ui/TaxonomyDropdown.test.js +130 -0
  121. package/dist/components/views/StandaloneLayout 2.d.ts +8 -0
  122. package/dist/components/views/StandaloneLayout 2.js +1660 -0
  123. package/dist/components/views/StandaloneLayout.d.ts +8 -0
  124. package/dist/components/views/StandaloneLayout.js +2117 -0
  125. package/dist/components/views/WidgetView.d.ts +8 -0
  126. package/dist/components/views/WidgetView.js +221 -0
  127. package/dist/config/envSchema.d.ts +28 -0
  128. package/dist/config/envSchema.js +374 -0
  129. package/dist/config/envSchema.test.d.ts +1 -0
  130. package/dist/config/envSchema.test.js +97 -0
  131. package/dist/config/surfaceBase.d.ts +2 -0
  132. package/dist/config/surfaceBase.js +8 -0
  133. package/dist/core/BulkUpdateFieldsPreview.test.d.ts +1 -0
  134. package/dist/core/BulkUpdateFieldsPreview.test.js +62 -0
  135. package/dist/core/CompletedAt.test.d.ts +1 -0
  136. package/dist/core/CompletedAt.test.js +44 -0
  137. package/dist/core/CreatedBy.test.d.ts +1 -0
  138. package/dist/core/CreatedBy.test.js +60 -0
  139. package/dist/core/EntitlementsPolicy.test.d.ts +1 -0
  140. package/dist/core/EntitlementsPolicy.test.js +145 -0
  141. package/dist/core/InitiativeTemplates.test.d.ts +1 -0
  142. package/dist/core/InitiativeTemplates.test.js +50 -0
  143. package/dist/core/ParentLifecycle.test.d.ts +1 -0
  144. package/dist/core/ParentLifecycle.test.js +98 -0
  145. package/dist/core/ScheduleFields.test.d.ts +1 -0
  146. package/dist/core/ScheduleFields.test.js +197 -0
  147. package/dist/core/SetupState.test.d.ts +1 -0
  148. package/dist/core/SetupState.test.js +113 -0
  149. package/dist/core/SystemAdmin.test.d.ts +1 -0
  150. package/dist/core/SystemAdmin.test.js +295 -0
  151. package/dist/core/TaskTaxonomyValidation.test.d.ts +1 -0
  152. package/dist/core/TaskTaxonomyValidation.test.js +64 -0
  153. package/dist/core/Taskforce.d.ts +1063 -0
  154. package/dist/core/Taskforce.ids.test.d.ts +1 -0
  155. package/dist/core/Taskforce.ids.test.js +168 -0
  156. package/dist/core/Taskforce.js +5981 -0
  157. package/dist/core/Taskforce.realtimeMutation.test.d.ts +1 -0
  158. package/dist/core/Taskforce.realtimeMutation.test.js +76 -0
  159. package/dist/core/WorkspaceDelete.test.d.ts +1 -0
  160. package/dist/core/WorkspaceDelete.test.js +140 -0
  161. package/dist/core/WorkspacePermissions.test.d.ts +1 -0
  162. package/dist/core/WorkspacePermissions.test.js +151 -0
  163. package/dist/core/WorkspacePlanMode.test.d.ts +1 -0
  164. package/dist/core/WorkspacePlanMode.test.js +104 -0
  165. package/dist/hooks/authGuardPolicy.d.ts +26 -0
  166. package/dist/hooks/authGuardPolicy.js +32 -0
  167. package/dist/hooks/authGuardPolicy.test.d.ts +1 -0
  168. package/dist/hooks/authGuardPolicy.test.js +76 -0
  169. package/dist/hooks/useDraggable.d.ts +12 -0
  170. package/dist/hooks/useDraggable.js +65 -0
  171. package/dist/hooks/useRealtimeSync.d.ts +24 -0
  172. package/dist/hooks/useRealtimeSync.js +275 -0
  173. package/dist/hooks/useRealtimeSync.test.d.ts +1 -0
  174. package/dist/hooks/useRealtimeSync.test.js +284 -0
  175. package/dist/hooks/useSyncOrchestrator.d.ts +164 -0
  176. package/dist/hooks/useSyncOrchestrator.js +1427 -0
  177. package/dist/hooks/useSyncOrchestrator.retry-closure.test.d.ts +1 -0
  178. package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +367 -0
  179. package/dist/hooks/useTaskforce.d.ts +727 -0
  180. package/dist/hooks/useTaskforce.js +4396 -0
  181. package/dist/hooks/useTaskforce.priority-filters.test.d.ts +1 -0
  182. package/dist/hooks/useTaskforce.priority-filters.test.js +33 -0
  183. package/dist/hooks/useWorkspaceSyncController.d.ts +80 -0
  184. package/dist/hooks/useWorkspaceSyncController.js +598 -0
  185. package/dist/hooks/useWorkspaceSyncController.test.d.ts +1 -0
  186. package/dist/hooks/useWorkspaceSyncController.test.js +236 -0
  187. package/dist/index.d.ts +12 -0
  188. package/dist/index.js +11 -0
  189. package/dist/localization/index.d.ts +8 -0
  190. package/dist/localization/index.js +81 -0
  191. package/dist/localization/locales/en-US.d.ts +151 -0
  192. package/dist/localization/locales/en-US.js +150 -0
  193. package/dist/localization/locales/es-419.d.ts +2 -0
  194. package/dist/localization/locales/es-419.js +150 -0
  195. package/dist/localization/locales/pt-BR.d.ts +2 -0
  196. package/dist/localization/locales/pt-BR.js +150 -0
  197. package/dist/main.d.ts +1 -0
  198. package/dist/main.js +33 -0
  199. package/dist/mcp/optionResolution.d.ts +14 -0
  200. package/dist/mcp/optionResolution.js +126 -0
  201. package/dist/mcp/optionResolution.test.d.ts +1 -0
  202. package/dist/mcp/optionResolution.test.js +39 -0
  203. package/dist/mcp/server.d.ts +1 -0
  204. package/dist/mcp/server.js +1793 -0
  205. package/dist/mcp/structuredCommentValidation.d.ts +6 -0
  206. package/dist/mcp/structuredCommentValidation.js +56 -0
  207. package/dist/mcp/structuredDocValidation.d.ts +6 -0
  208. package/dist/mcp/structuredDocValidation.js +75 -0
  209. package/dist/migrations/billingSchemaParity.test.d.ts +1 -0
  210. package/dist/migrations/billingSchemaParity.test.js +46 -0
  211. package/dist/migrations/clientMigrations.d.ts +9 -0
  212. package/dist/migrations/clientMigrations.js +74 -0
  213. package/dist/migrations/flags.d.ts +6 -0
  214. package/dist/migrations/flags.js +8 -0
  215. package/dist/migrations/taskMigrations.d.ts +71 -0
  216. package/dist/migrations/taskMigrations.js +236 -0
  217. package/dist/migrations/taskMigrations.test.d.ts +1 -0
  218. package/dist/migrations/taskMigrations.test.js +34 -0
  219. package/dist/migrations/taskSchemaMigrations.d.ts +11 -0
  220. package/dist/migrations/taskSchemaMigrations.js +453 -0
  221. package/dist/plugins/index.d.ts +10 -0
  222. package/dist/plugins/index.js +10 -0
  223. package/dist/plugins/vite.d.ts +19 -0
  224. package/dist/plugins/vite.js +271 -0
  225. package/dist/public/android-chrome-192x192.png +0 -0
  226. package/dist/public/android-chrome-512x512.png +0 -0
  227. package/dist/public/apple-touch-icon.png +0 -0
  228. package/dist/public/favicon-16x16.png +0 -0
  229. package/dist/public/favicon-32x32.png +0 -0
  230. package/dist/public/favicon.ico +0 -0
  231. package/dist/public/favicon.png +0 -0
  232. package/dist/public/site.webmanifest +1 -0
  233. package/dist/public/taskforce.png +0 -0
  234. package/dist/resources/templates/WORKFLOW_V2_POLICY.md +55 -0
  235. package/dist/resources/templates/environments/aider.yaml +14 -0
  236. package/dist/resources/templates/environments/antigravity.yaml +10 -0
  237. package/dist/resources/templates/environments/claude.yaml +10 -0
  238. package/dist/resources/templates/environments/cline.yaml +10 -0
  239. package/dist/resources/templates/environments/codex.yaml +13 -0
  240. package/dist/resources/templates/environments/copilot.yaml +10 -0
  241. package/dist/resources/templates/environments/cursor.yaml +9 -0
  242. package/dist/resources/templates/environments/gemini.yaml +9 -0
  243. package/dist/resources/templates/environments/windsurf.yaml +10 -0
  244. package/dist/resources/templates/workflow-sources/README.md +9 -0
  245. package/dist/resources/templates/workflow-sources/workflowDocs.mjs +520 -0
  246. package/dist/resources/templates/workflow-sources/workflowEditorOverrides.json +1 -0
  247. package/dist/resources/templates/workflows/collaborate.yaml +104 -0
  248. package/dist/resources/templates/workflows/do.yaml +82 -0
  249. package/dist/resources/templates/workflows/evaluate.yaml +111 -0
  250. package/dist/resources/templates/workflows/execute.yaml +142 -0
  251. package/dist/resources/templates/workflows/plan.yaml +112 -0
  252. package/dist/resources/templates/workflows/review.yaml +109 -0
  253. package/dist/runtime/capabilities.d.ts +8 -0
  254. package/dist/runtime/capabilities.js +16 -0
  255. package/dist/security/virusScan.d.ts +16 -0
  256. package/dist/security/virusScan.js +83 -0
  257. package/dist/server/auth.d.ts +51 -0
  258. package/dist/server/auth.js +266 -0
  259. package/dist/server/auth.test.d.ts +1 -0
  260. package/dist/server/auth.test.js +111 -0
  261. package/dist/server/buildInfo.d.ts +7 -0
  262. package/dist/server/buildInfo.js +100 -0
  263. package/dist/server/buildInfo.test.d.ts +1 -0
  264. package/dist/server/buildInfo.test.js +29 -0
  265. package/dist/server/cors.d.ts +7 -0
  266. package/dist/server/cors.js +76 -0
  267. package/dist/server/cors.test.d.ts +1 -0
  268. package/dist/server/cors.test.js +71 -0
  269. package/dist/server/email.d.ts +28 -0
  270. package/dist/server/email.js +138 -0
  271. package/dist/server/email.test.d.ts +1 -0
  272. package/dist/server/email.test.js +113 -0
  273. package/dist/server/index.cookieProxy.test.d.ts +1 -0
  274. package/dist/server/index.cookieProxy.test.js +19 -0
  275. package/dist/server/index.d.ts +17 -0
  276. package/dist/server/index.js +757 -0
  277. package/dist/server/index.rateLimit.test.d.ts +1 -0
  278. package/dist/server/index.rateLimit.test.js +13 -0
  279. package/dist/server/index.test.d.ts +1 -0
  280. package/dist/server/index.test.js +19 -0
  281. package/dist/server/rateLimit.d.ts +29 -0
  282. package/dist/server/rateLimit.js +99 -0
  283. package/dist/server/rateLimit.test.d.ts +1 -0
  284. package/dist/server/rateLimit.test.js +107 -0
  285. package/dist/server/realtimeSync.auth.test.d.ts +1 -0
  286. package/dist/server/realtimeSync.auth.test.js +62 -0
  287. package/dist/server/realtimeSync.replay.test.d.ts +1 -0
  288. package/dist/server/realtimeSync.replay.test.js +192 -0
  289. package/dist/server/realtimeSync.workspaceIsolation.test.d.ts +1 -0
  290. package/dist/server/realtimeSync.workspaceIsolation.test.js +101 -0
  291. package/dist/server/realtimeSyncWs.d.ts +30 -0
  292. package/dist/server/realtimeSyncWs.js +257 -0
  293. package/dist/server/routes.billing.d.ts +15 -0
  294. package/dist/server/routes.billing.js +991 -0
  295. package/dist/server/routes.billing.test.d.ts +1 -0
  296. package/dist/server/routes.billing.test.js +387 -0
  297. package/dist/server/routes.d.ts +72 -0
  298. package/dist/server/routes.js +4820 -0
  299. package/dist/server/routes.sync.d.ts +17 -0
  300. package/dist/server/routes.sync.integration.test.d.ts +1 -0
  301. package/dist/server/routes.sync.integration.test.js +470 -0
  302. package/dist/server/routes.sync.js +1141 -0
  303. package/dist/server/routes.test.d.ts +1 -0
  304. package/dist/server/routes.test.js +4945 -0
  305. package/dist/server/runtimeMode.d.ts +4 -0
  306. package/dist/server/runtimeMode.js +25 -0
  307. package/dist/server/runtimeMode.test.d.ts +1 -0
  308. package/dist/server/runtimeMode.test.js +26 -0
  309. package/dist/server/securityHeaders.d.ts +3 -0
  310. package/dist/server/securityHeaders.js +25 -0
  311. package/dist/server/securityHeaders.test.d.ts +1 -0
  312. package/dist/server/securityHeaders.test.js +32 -0
  313. package/dist/server/smtpTransport.d.ts +21 -0
  314. package/dist/server/smtpTransport.js +11 -0
  315. package/dist/server/start.d.ts +1 -0
  316. package/dist/server/start.js +17 -0
  317. package/dist/services/formatTransformers.d.ts +29 -0
  318. package/dist/services/formatTransformers.js +56 -0
  319. package/dist/services/templateEngine.d.ts +46 -0
  320. package/dist/services/templateEngine.js +148 -0
  321. package/dist/shared/runtimeContract.d.ts +12 -0
  322. package/dist/shared/runtimeContract.js +23 -0
  323. package/dist/shared/runtimeContract.test.d.ts +1 -0
  324. package/dist/shared/runtimeContract.test.js +23 -0
  325. package/dist/soak/preflight.cli.d.ts +1 -0
  326. package/dist/soak/preflight.cli.js +28 -0
  327. package/dist/soak/preflight.d.ts +26 -0
  328. package/dist/soak/preflight.js +113 -0
  329. package/dist/soak/preflight.test.d.ts +1 -0
  330. package/dist/soak/preflight.test.js +39 -0
  331. package/dist/soak/syncSoakHarness.d.ts +6 -0
  332. package/dist/soak/syncSoakHarness.js +10 -0
  333. package/dist/storage/createDatabaseAdapter.d.ts +2 -0
  334. package/dist/storage/createDatabaseAdapter.js +9 -0
  335. package/dist/storage/databaseAdapter.d.ts +22 -0
  336. package/dist/storage/databaseAdapter.js +1 -0
  337. package/dist/storage/documentIntegrity.d.ts +28 -0
  338. package/dist/storage/documentIntegrity.js +103 -0
  339. package/dist/storage/documentPurge.d.ts +14 -0
  340. package/dist/storage/documentPurge.js +46 -0
  341. package/dist/storage/documentRegistry.d.ts +39 -0
  342. package/dist/storage/documentRegistry.js +115 -0
  343. package/dist/storage/integritySchedule.d.ts +17 -0
  344. package/dist/storage/integritySchedule.js +62 -0
  345. package/dist/storage/objectStorage.d.ts +20 -0
  346. package/dist/storage/objectStorage.js +15 -0
  347. package/dist/storage/objectStorageClient.d.ts +18 -0
  348. package/dist/storage/objectStorageClient.js +301 -0
  349. package/dist/storage/objectStorageConfig.d.ts +2 -0
  350. package/dist/storage/objectStorageConfig.js +45 -0
  351. package/dist/storage/postgresAdapter.d.ts +20 -0
  352. package/dist/storage/postgresAdapter.js +204 -0
  353. package/dist/storage/postgresWorker.d.ts +1 -0
  354. package/dist/storage/postgresWorker.js +110 -0
  355. package/dist/storage/providerConfig.d.ts +6 -0
  356. package/dist/storage/providerConfig.js +11 -0
  357. package/dist/storage/sqliteAdapter.d.ts +14 -0
  358. package/dist/storage/sqliteAdapter.js +28 -0
  359. package/dist/storage/taskAssetStore.d.ts +56 -0
  360. package/dist/storage/taskAssetStore.js +297 -0
  361. package/dist/sync/cloudSyncApi.d.ts +47 -0
  362. package/dist/sync/cloudSyncApi.js +138 -0
  363. package/dist/sync/cloudSyncApi.test.d.ts +1 -0
  364. package/dist/sync/cloudSyncApi.test.js +54 -0
  365. package/dist/sync/encryptionService.d.ts +11 -0
  366. package/dist/sync/encryptionService.js +61 -0
  367. package/dist/sync/encryptionService.test.d.ts +1 -0
  368. package/dist/sync/encryptionService.test.js +52 -0
  369. package/dist/sync/realtimeFeature.d.ts +6 -0
  370. package/dist/sync/realtimeFeature.js +20 -0
  371. package/dist/sync/realtimeFeature.test.d.ts +1 -0
  372. package/dist/sync/realtimeFeature.test.js +30 -0
  373. package/dist/sync/realtimeReconcile.d.ts +30 -0
  374. package/dist/sync/realtimeReconcile.js +90 -0
  375. package/dist/sync/realtimeReconcile.test.d.ts +1 -0
  376. package/dist/sync/realtimeReconcile.test.js +43 -0
  377. package/dist/sync/syncService.d.ts +96 -0
  378. package/dist/sync/syncService.js +423 -0
  379. package/dist/sync/syncService.test.d.ts +1 -0
  380. package/dist/sync/syncService.test.js +484 -0
  381. package/dist/sync/workspaceSyncModel.d.ts +61 -0
  382. package/dist/sync/workspaceSyncModel.js +136 -0
  383. package/dist/sync/workspaceSyncModel.test.d.ts +1 -0
  384. package/dist/sync/workspaceSyncModel.test.js +73 -0
  385. package/dist/test/setup.d.ts +1 -0
  386. package/dist/test/setup.js +15 -0
  387. package/dist/types.d.ts +157 -0
  388. package/dist/types.js +41 -0
  389. package/dist/ui/assets/RussoOne-Regular-C3BxZIj7.ttf +0 -0
  390. package/dist/ui/assets/index-0W5vyCpn.css +1 -0
  391. package/dist/ui/assets/index-MCZuNNfn.js +23 -0
  392. package/dist/ui/assets/logo_white_01-0Vx-iIIv.png +0 -0
  393. package/dist/ui/assets/taskforce-BxLPokNB.png +0 -0
  394. package/dist/ui/assets/vendor-dnd-BI1K77C1.js +5 -0
  395. package/dist/ui/assets/vendor-icons-ZFG3SGwo.js +1 -0
  396. package/dist/ui/assets/vendor-markdown-CKYkQHfj.js +29 -0
  397. package/dist/ui/assets/vendor-react-DdIDsh7f.js +9 -0
  398. package/dist/ui/assets/vendor-router-BtiNU7Dl.js +3 -0
  399. package/dist/ui/branding/logos/logo_black_01.png +0 -0
  400. package/dist/ui/branding/logos/logo_black_02.png +0 -0
  401. package/dist/ui/branding/logos/logo_white_01.png +0 -0
  402. package/dist/ui/branding/logos/logo_white_02.png +0 -0
  403. package/dist/ui/favicon/android-chrome-192x192.png +0 -0
  404. package/dist/ui/favicon/android-chrome-512x512.png +0 -0
  405. package/dist/ui/favicon/apple-touch-icon.png +0 -0
  406. package/dist/ui/favicon/favicon-16x16.png +0 -0
  407. package/dist/ui/favicon/favicon-32x32.png +0 -0
  408. package/dist/ui/favicon/favicon.ico +0 -0
  409. package/dist/ui/favicon/favicon.png +0 -0
  410. package/dist/ui/favicon/site.webmanifest +1 -0
  411. package/dist/ui/fonts/RussoOne-Regular.ttf +0 -0
  412. package/dist/ui/images/taskforce.png +0 -0
  413. package/dist/ui/index.html +19 -0
  414. package/dist/utils/checklistMarkdown.d.ts +9 -0
  415. package/dist/utils/checklistMarkdown.js +46 -0
  416. package/dist/utils/constants.d.ts +21 -0
  417. package/dist/utils/constants.icons.test.d.ts +1 -0
  418. package/dist/utils/constants.icons.test.js +40 -0
  419. package/dist/utils/constants.js +100 -0
  420. package/dist/utils/contextFiles.d.ts +8 -0
  421. package/dist/utils/contextFiles.js +54 -0
  422. package/dist/utils/formatting.d.ts +5 -0
  423. package/dist/utils/formatting.js +30 -0
  424. package/dist/utils/planParser.d.ts +22 -0
  425. package/dist/utils/planParser.js +151 -0
  426. package/dist/utils/taskDragZones.d.ts +8 -0
  427. package/dist/utils/taskDragZones.js +14 -0
  428. package/dist/utils/taskDragZones.test.d.ts +1 -0
  429. package/dist/utils/taskDragZones.test.js +14 -0
  430. package/dist/utils/taskHierarchy.d.ts +4 -0
  431. package/dist/utils/taskHierarchy.js +69 -0
  432. package/dist/utils/taskHierarchy.test.d.ts +1 -0
  433. package/dist/utils/taskHierarchy.test.js +38 -0
  434. package/dist/utils/taskParenting.d.ts +3 -0
  435. package/dist/utils/taskParenting.js +24 -0
  436. package/dist/utils/taskParenting.test.d.ts +1 -0
  437. package/dist/utils/taskParenting.test.js +21 -0
  438. package/dist/utils/taskRelations.d.ts +6 -0
  439. package/dist/utils/taskRelations.js +24 -0
  440. package/dist/utils/taskRelations.test.d.ts +1 -0
  441. package/dist/utils/taskRelations.test.js +34 -0
  442. package/dist/utils/taskUtils.d.ts +2 -0
  443. package/dist/utils/taskUtils.js +20 -0
  444. package/dist/utils/taskforceApiClient.d.ts +7 -0
  445. package/dist/utils/taskforceApiClient.js +42 -0
  446. package/dist/utils/workspaceIdentity.d.ts +8 -0
  447. package/dist/utils/workspaceIdentity.js +47 -0
  448. package/dist/utils/workspaceIdentity.test.d.ts +1 -0
  449. package/dist/utils/workspaceIdentity.test.js +32 -0
  450. package/dist/utils/workspaceLifecycle.d.ts +26 -0
  451. package/dist/utils/workspaceLifecycle.js +69 -0
  452. package/dist/utils/workspaceLifecycle.test.d.ts +1 -0
  453. package/dist/utils/workspaceLifecycle.test.js +64 -0
  454. package/package.json +133 -0
  455. package/scripts/check-compat-boundaries.js +48 -0
  456. package/scripts/check-env.ts +24 -0
  457. package/scripts/check-localization-strings.mjs +49 -0
  458. package/scripts/check-runtime-boundaries.mjs +44 -0
  459. package/scripts/debug-global.mjs +34 -0
  460. package/scripts/debug-server.mjs +36 -0
  461. package/scripts/export-sqlite-to-postgres.mjs +125 -0
  462. package/scripts/generate-env-example.ts +8 -0
  463. package/scripts/generate-workflow-templates.mjs +134 -0
  464. package/scripts/mcp-stdio-adapter.mjs +160 -0
  465. package/scripts/postinstall.js +60 -0
  466. package/scripts/release-version-lib.mjs +88 -0
  467. package/scripts/release-version.mjs +72 -0
  468. package/scripts/run-dev-proxy.sh +49 -0
  469. package/scripts/run-e2e-realtime.sh +16 -0
  470. package/scripts/run-smoke.sh +23 -0
  471. package/scripts/run-soak.sh +29 -0
  472. package/scripts/test-global.js +143 -0
  473. package/scripts/update-homebrew-formula.sh +38 -0
  474. package/src/resources/templates/WORKFLOW_V2_POLICY.md +55 -0
  475. package/src/resources/templates/environments/aider.yaml +14 -0
  476. package/src/resources/templates/environments/antigravity.yaml +10 -0
  477. package/src/resources/templates/environments/claude.yaml +10 -0
  478. package/src/resources/templates/environments/cline.yaml +10 -0
  479. package/src/resources/templates/environments/codex.yaml +13 -0
  480. package/src/resources/templates/environments/copilot.yaml +10 -0
  481. package/src/resources/templates/environments/cursor.yaml +9 -0
  482. package/src/resources/templates/environments/gemini.yaml +9 -0
  483. package/src/resources/templates/environments/windsurf.yaml +10 -0
  484. package/src/resources/templates/workflow-sources/README.md +9 -0
  485. package/src/resources/templates/workflow-sources/workflowDocs.mjs +520 -0
  486. package/src/resources/templates/workflow-sources/workflowEditorOverrides.json +1 -0
  487. package/src/resources/templates/workflows/collaborate.yaml +104 -0
  488. package/src/resources/templates/workflows/do.yaml +82 -0
  489. package/src/resources/templates/workflows/evaluate.yaml +111 -0
  490. package/src/resources/templates/workflows/execute.yaml +142 -0
  491. package/src/resources/templates/workflows/plan.yaml +112 -0
  492. package/src/resources/templates/workflows/review.yaml +109 -0
@@ -0,0 +1,1660 @@
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';
3
+ import { createPortal } from 'react-dom';
4
+ import { 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, ChevronsUp, ChevronsDown, EyeOff, Zap, HelpCircle, CalendarDays, PanelRightClose, PanelRightOpen } from 'lucide-react';
6
+ import { STATUS_OPTIONS } from '../../utils/constants';
7
+ import { MultiSelectFilter } from '../ui/MultiSelectFilter';
8
+ import styles from '../../Taskforce.module.css';
9
+ import modalStyles from '../ui/Modal.module.css';
10
+ import { Modal } from '../ui/Modal';
11
+ import { TaskForm } from '../task/TaskForm';
12
+ import { TaskSettings } from '../features/TaskSettings';
13
+ import { DocumentWorkspace } from '../features/DocumentWorkspace';
14
+ import { TaskKanban } from '../task/TaskKanban';
15
+ import { TaskActionHeader } from '../task/TaskActionHeader';
16
+ import { TypeMeta } from '../../utils/constants';
17
+ import { buildHierarchyDepthMap, collectSubtreeTaskIds } from '../../utils/taskHierarchy';
18
+ import { formatDate } from '../../utils/formatting';
19
+ 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}`;
41
+ }
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;
50
+ }
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;
60
+ }
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];
73
+ }
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];
84
+ }
85
+ function addDays(base, days) {
86
+ const next = new Date(base);
87
+ next.setDate(base.getDate() + days);
88
+ return next;
89
+ }
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')}`;
100
+ }
101
+ export function StandaloneLayout(props) {
102
+ const navigate = useNavigate();
103
+ const {
104
+ // State
105
+ activeTab, setActiveTab, activeCategories, groupBy, setGroupBy, emptyColumnMode, setEmptyColumnMode, zenMode, setZenMode, filterStatus, setFilterStatus, parentTaskFilterMode, setParentTaskFilterMode,
106
+ // Data & Actions
107
+ tasks, handleEdit, handleSubmit, resetForm, handleUpdateTask,
108
+ // Form State (destructure all needed for TaskForm)
109
+ 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,
110
+ // Form Actions
111
+ handleAddComment, handleSetParentForCurrentTask, handleAddChildToCurrentTask, handleCreateChildForCurrentTask, handleUnlinkChildTask, handleReorderChildTasks, handleClearParentTask, handleOpenTaskById,
112
+ // Other Props for Form
113
+ 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,
114
+ // filterStatus, setFilterStatus, // This was duplicated, removed one instance
115
+ sortBy, setSortBy, clearFilters, // setSearchQuery, // This was duplicated, removed one instance
116
+ // filterCategories, setFilterCategories, // This was duplicated, removed one instance
117
+ // filterTypes, setFilterTypes, // This was duplicated, removed one instance
118
+ // filterPriorities, setFilterPriorities, // This was duplicated, removed one instance
119
+ // filterStatus, setFilterStatus, // This was duplicated, removed one instance
120
+ // sortBy, setSortBy, // This was duplicated, removed one instance
121
+ // clearFilters, // This was duplicated, removed one instance
122
+ // activeTypes, // This was duplicated, removed one instance
123
+ // Settings State & Props
124
+ settingsModel, currentTheme, setCurrentTheme, pathSaved, saveSettings, keyShortcut, setKeyShortcut, globalWeekStartsOn, locale, jsonBackupEnabled, setJsonBackupEnabled, assigneeFilterEnabled, mcpHostRoot, setMcpHostRoot, settingsSection, setSettingsSection, runtimeMode, cloudAuthConfigured, authRequiredForApi, authBlocked, isAuthenticated, workspaceCloudSyncEnabled, 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, loginWithCredentials, currentWorkspaceId, availableWorkspaces, fetchWorkspaces, createWorkspace, switchWorkspace, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
125
+ // Browser Props
126
+ showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget, contextLinkBrowsePath, handleSelectPath, handleAddPath, handleRemovePath,
127
+ // Settings Managers
128
+ handleUpdateCategory, handleRemoveCategory, handleSaveCategory, handleUpdateCategoryIcon, handleUpdateCategoryColor, handleSaveType, handleRemoveType, handleUpdateTaxonomies, handleUpdatePriorities, handleUpdateApproaches, pathValidation,
129
+ // Exports
130
+ exportEnvironment, setExportEnvironment, exportWorkflowsPath, setExportWorkflowsPath, exportSpecialistsPath, setExportSpecialistsPath, exportResult, handleExportResources, exportingResource, availableWorkflows, onExportWorkflows, fetchWorkflows, availableEnvironments,
131
+ // Misc
132
+ successBanner, returnToParentTaskId, returnFromChildTaskId, clearReturnToParentTask, tasksScrollRef, setTasksScrollPos,
133
+ // Refs
134
+ commentsEndRef,
135
+ // Modals - Unsaved Changes
136
+ unsavedModalOpen, setUnsavedModalOpen, pendingNavigation, handleNavigation, currentTask, currentTaskParent, currentTaskChildren,
137
+ // Modals - Cancellation
138
+ cancellationPromptTask, confirmCancellation, cancelCancellationPrompt, scheduleWarningPrompt, confirmScheduleWarning, cancelScheduleWarning } = props;
139
+ const resolvedLocale = locale || getLocale();
140
+ useEffect(() => {
141
+ const normalizedProject = String(projectName || '').trim();
142
+ document.title = normalizedProject ? `Taskforce - ${normalizedProject}` : 'Taskforce';
143
+ }, [projectName]);
144
+ // Handlers
145
+ const handleDiscard = () => {
146
+ setUnsavedModalOpen(false);
147
+ if (pendingNavigation) {
148
+ if (activeTab === 'add')
149
+ resetForm();
150
+ pendingNavigation();
151
+ }
152
+ else {
153
+ if (activeTab === 'add')
154
+ resetForm();
155
+ setActiveTab('tasks');
156
+ }
157
+ };
158
+ const handleSaveAndNavigate = async () => {
159
+ // Fake event for handleSubmit
160
+ const fakeEvent = { preventDefault: () => { } };
161
+ await handleSubmit(fakeEvent);
162
+ setUnsavedModalOpen(false);
163
+ if (pendingNavigation) {
164
+ pendingNavigation();
165
+ }
166
+ };
167
+ // State for Card Compression
168
+ const [compressedCards, setCompressedCards] = useState(false);
169
+ const [scheduleShowWeekends, setScheduleShowWeekends] = useState(false);
170
+ const [scheduleShowBacklog, setScheduleShowBacklog] = useState(true);
171
+ const [scheduleOnlyExpired, setScheduleOnlyExpired] = useState(false);
172
+ const [scheduleSidebarOpen, setScheduleSidebarOpen] = useState(true);
173
+ const [hierarchySidebarOpen, setHierarchySidebarOpen] = useState(true);
174
+ const [hierarchyRootTaskId, setHierarchyRootTaskId] = useState('');
175
+ const [hierarchyShowUnlinkedColumn, setHierarchyShowUnlinkedColumn] = useState(true);
176
+ const [hierarchyChildOrderMode, setHierarchyChildOrderMode] = useState('manual');
177
+ const [hierarchyRootSearch, setHierarchyRootSearch] = useState('');
178
+ const [scheduleSelectedDate, setScheduleSelectedDate] = useState(toDateOnlyLocal(new Date()));
179
+ const [scheduleCalendarMonth, setScheduleCalendarMonth] = useState(() => {
180
+ const now = new Date();
181
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
182
+ });
183
+ const [scheduleScrollLeft, setScheduleScrollLeft] = useState(0);
184
+ const [showFilters, setShowFilters] = useState(false);
185
+ const [requestedDocPath, setRequestedDocPath] = useState(null);
186
+ const [layoutStateReady, setLayoutStateReady] = useState(false);
187
+ const loadedLayoutStateWorkspaceRef = useRef('');
188
+ const workspaceUiStateHeaders = useMemo(() => {
189
+ const headers = {};
190
+ const workspaceIdHeader = String(currentWorkspaceId || '').trim();
191
+ if (runtimeMode === 'cloud' && workspaceIdHeader && workspaceIdHeader !== 'default') {
192
+ headers['x-taskforce-workspace-id'] = workspaceIdHeader;
193
+ }
194
+ return headers;
195
+ }, [runtimeMode, currentWorkspaceId]);
196
+ const layoutStateWorkspaceKey = useMemo(() => `${runtimeMode}:${String(currentWorkspaceId || 'default').trim() || 'default'}`, [runtimeMode, currentWorkspaceId]);
197
+ useEffect(() => {
198
+ const handleOpenMarkdownDocument = (event) => {
199
+ const customEvent = event;
200
+ const fsPath = customEvent.detail?.fsPath;
201
+ if (!fsPath)
202
+ return;
203
+ customEvent.preventDefault();
204
+ setRequestedDocPath(fsPath);
205
+ if (groupBy !== 'docs') {
206
+ setGroupBy('docs');
207
+ }
208
+ };
209
+ window.addEventListener('taskforce:open-markdown-document', handleOpenMarkdownDocument);
210
+ return () => {
211
+ window.removeEventListener('taskforce:open-markdown-document', handleOpenMarkdownDocument);
212
+ };
213
+ }, [groupBy, setGroupBy]);
214
+ useEffect(() => {
215
+ if (loadedLayoutStateWorkspaceRef.current === layoutStateWorkspaceKey)
216
+ return;
217
+ loadedLayoutStateWorkspaceRef.current = layoutStateWorkspaceKey;
218
+ setLayoutStateReady(false);
219
+ let cancelled = false;
220
+ const loadLayoutUiState = async () => {
221
+ try {
222
+ const res = await fetch('/api/taskforce/ui-state?key=layout', {
223
+ method: 'GET',
224
+ credentials: 'include',
225
+ headers: workspaceUiStateHeaders
226
+ });
227
+ if (!res.ok)
228
+ return;
229
+ const data = await res.json().catch(() => ({}));
230
+ const state = (data?.state && typeof data.state === 'object')
231
+ ? data.state
232
+ : null;
233
+ if (!state || cancelled)
234
+ return;
235
+ if (typeof state.scheduleShowWeekends === 'boolean')
236
+ setScheduleShowWeekends(state.scheduleShowWeekends);
237
+ if (typeof state.scheduleShowBacklog === 'boolean')
238
+ setScheduleShowBacklog(state.scheduleShowBacklog);
239
+ if (typeof state.scheduleOnlyExpired === 'boolean')
240
+ setScheduleOnlyExpired(state.scheduleOnlyExpired);
241
+ if (typeof state.scheduleSidebarOpen === 'boolean')
242
+ setScheduleSidebarOpen(state.scheduleSidebarOpen);
243
+ if (typeof state.hierarchySidebarOpen === 'boolean')
244
+ setHierarchySidebarOpen(state.hierarchySidebarOpen);
245
+ if (typeof state.hierarchyRootTaskId === 'string')
246
+ setHierarchyRootTaskId(state.hierarchyRootTaskId);
247
+ if (typeof state.hierarchyShowUnlinkedColumn === 'boolean')
248
+ setHierarchyShowUnlinkedColumn(state.hierarchyShowUnlinkedColumn);
249
+ if (state.hierarchyChildOrderMode === 'manual' || state.hierarchyChildOrderMode === 'auto') {
250
+ setHierarchyChildOrderMode(state.hierarchyChildOrderMode);
251
+ }
252
+ if (typeof state.scheduleSelectedDate === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(state.scheduleSelectedDate)) {
253
+ setScheduleSelectedDate(state.scheduleSelectedDate);
254
+ }
255
+ if (typeof state.scheduleCalendarMonth === 'string' && /^\d{4}-\d{2}$/.test(state.scheduleCalendarMonth)) {
256
+ setScheduleCalendarMonth(state.scheduleCalendarMonth);
257
+ }
258
+ if (typeof state.scheduleScrollLeft === 'number' && Number.isFinite(state.scheduleScrollLeft) && state.scheduleScrollLeft >= 0) {
259
+ setScheduleScrollLeft(state.scheduleScrollLeft);
260
+ }
261
+ if (typeof state.showFilters === 'boolean')
262
+ setShowFilters(state.showFilters);
263
+ }
264
+ catch {
265
+ // ignore read failure
266
+ }
267
+ finally {
268
+ if (!cancelled)
269
+ setLayoutStateReady(true);
270
+ }
271
+ };
272
+ loadLayoutUiState();
273
+ return () => {
274
+ cancelled = true;
275
+ };
276
+ }, [layoutStateWorkspaceKey, workspaceUiStateHeaders]);
277
+ useEffect(() => {
278
+ if (!layoutStateReady)
279
+ return;
280
+ const timeoutId = window.setTimeout(async () => {
281
+ try {
282
+ await fetch('/api/taskforce/ui-state', {
283
+ method: 'POST',
284
+ headers: { 'Content-Type': 'application/json', ...workspaceUiStateHeaders },
285
+ credentials: 'include',
286
+ body: JSON.stringify({
287
+ stateKey: 'layout',
288
+ patch: {
289
+ scheduleShowWeekends,
290
+ scheduleShowBacklog,
291
+ scheduleOnlyExpired,
292
+ scheduleSidebarOpen,
293
+ hierarchySidebarOpen,
294
+ hierarchyRootTaskId,
295
+ hierarchyShowUnlinkedColumn,
296
+ hierarchyChildOrderMode,
297
+ scheduleSelectedDate,
298
+ scheduleCalendarMonth,
299
+ scheduleScrollLeft,
300
+ showFilters
301
+ }
302
+ })
303
+ });
304
+ }
305
+ catch {
306
+ // ignore write failure
307
+ }
308
+ }, 250);
309
+ return () => window.clearTimeout(timeoutId);
310
+ }, [
311
+ layoutStateReady,
312
+ scheduleShowWeekends,
313
+ scheduleShowBacklog,
314
+ scheduleOnlyExpired,
315
+ scheduleSidebarOpen,
316
+ hierarchySidebarOpen,
317
+ hierarchyRootTaskId,
318
+ hierarchyShowUnlinkedColumn,
319
+ hierarchyChildOrderMode,
320
+ scheduleSelectedDate,
321
+ scheduleCalendarMonth,
322
+ scheduleScrollLeft,
323
+ showFilters,
324
+ workspaceUiStateHeaders
325
+ ]);
326
+ useEffect(() => {
327
+ if (groupBy === 'schedule') {
328
+ setScheduleSidebarOpen(true);
329
+ }
330
+ if (groupBy === 'hierarchy') {
331
+ setHierarchySidebarOpen(true);
332
+ }
333
+ }, [groupBy]);
334
+ const { showArchive, setShowArchive, fetchArchive, filteredArchive } = props;
335
+ const kanbanTasks = useMemo(() => {
336
+ if (!showArchive)
337
+ return props.filteredTasks;
338
+ const archivedWithFlag = filteredArchive.map(t => ({ ...t, isArchived: true }));
339
+ return [...props.filteredTasks, ...archivedWithFlag];
340
+ }, [props.filteredTasks, filteredArchive, showArchive]);
341
+ const scheduleBaseTasks = useMemo(() => props.tasks.filter(task => !task.isArchived), [props.tasks]);
342
+ const scheduleFilteredTaskIds = useMemo(() => new Set(props.filteredTasks.map(task => task.id)), [props.filteredTasks]);
343
+ const kanbanAllTasks = useMemo(() => {
344
+ if (!showArchive)
345
+ return props.searchAgnosticTasks;
346
+ return [...props.searchAgnosticTasks, ...filteredArchive];
347
+ }, [props.searchAgnosticTasks, filteredArchive, showArchive]);
348
+ const hierarchyTasks = useMemo(() => {
349
+ const byId = new Map();
350
+ for (const task of props.archivedTasks)
351
+ byId.set(task.id, task);
352
+ for (const task of props.tasks)
353
+ byId.set(task.id, task);
354
+ return Array.from(byId.values());
355
+ }, [props.tasks, props.archivedTasks]);
356
+ const hierarchyParentIdSet = useMemo(() => {
357
+ const set = new Set();
358
+ for (const task of hierarchyTasks) {
359
+ if (task.parentTaskId)
360
+ set.add(task.parentTaskId);
361
+ }
362
+ return set;
363
+ }, [hierarchyTasks]);
364
+ const hierarchyTaskById = useMemo(() => {
365
+ const map = new Map();
366
+ hierarchyTasks.forEach((task) => map.set(task.id, task));
367
+ return map;
368
+ }, [hierarchyTasks]);
369
+ const hierarchyChildrenByParent = useMemo(() => {
370
+ const map = new Map();
371
+ hierarchyTasks.forEach((task) => {
372
+ if (!task.parentTaskId)
373
+ return;
374
+ const list = map.get(task.parentTaskId) || [];
375
+ list.push(task);
376
+ map.set(task.parentTaskId, list);
377
+ });
378
+ return map;
379
+ }, [hierarchyTasks]);
380
+ const hierarchySearchMatches = useMemo(() => {
381
+ const needle = hierarchyRootSearch.trim().toLowerCase();
382
+ if (!needle)
383
+ return [];
384
+ const rankForTask = (task) => {
385
+ const id = task.id.toLowerCase();
386
+ const shortId = (task.id.split('-').slice(-1)[0] || task.id).toLowerCase();
387
+ const title = task.title.toLowerCase();
388
+ if (id === needle)
389
+ return 0;
390
+ if (shortId === needle)
391
+ return 1;
392
+ if (id.startsWith(needle) || shortId.startsWith(needle))
393
+ return 2;
394
+ if (title.startsWith(needle))
395
+ return 3;
396
+ if (title.includes(needle))
397
+ return 4;
398
+ if (id.includes(needle) || shortId.includes(needle))
399
+ return 5;
400
+ return null;
401
+ };
402
+ return hierarchyTasks
403
+ .map((task) => ({ task, rank: rankForTask(task) }))
404
+ .filter((entry) => entry.rank !== null)
405
+ .sort((a, b) => {
406
+ if (a.rank !== b.rank)
407
+ return a.rank - b.rank;
408
+ return a.task.title.localeCompare(b.task.title);
409
+ })
410
+ .map((entry) => entry.task);
411
+ }, [hierarchyTasks, hierarchyRootSearch]);
412
+ const hierarchyDescendantIdSet = useMemo(() => {
413
+ if (!hierarchyRootTaskId)
414
+ return null;
415
+ return collectSubtreeTaskIds(hierarchyTasks, hierarchyRootTaskId);
416
+ }, [hierarchyRootTaskId, hierarchyTasks]);
417
+ const hierarchyVisibleTasks = useMemo(() => {
418
+ if (groupBy === 'schedule')
419
+ return scheduleBaseTasks;
420
+ if (groupBy !== 'hierarchy')
421
+ return kanbanTasks;
422
+ const isUnlinkedTask = (task) => !task.parentTaskId && !hierarchyParentIdSet.has(task.id);
423
+ let base = kanbanTasks;
424
+ if (parentTaskFilterMode === 'parents') {
425
+ base = kanbanTasks.filter(task => hierarchyParentIdSet.has(task.id));
426
+ }
427
+ else if (parentTaskFilterMode === 'linked') {
428
+ base = kanbanTasks.filter(task => !!task.parentTaskId || hierarchyParentIdSet.has(task.id));
429
+ }
430
+ else if (parentTaskFilterMode === 'unlinked') {
431
+ base = kanbanTasks.filter(task => isUnlinkedTask(task));
432
+ }
433
+ if (!hierarchyShowUnlinkedColumn && parentTaskFilterMode !== 'unlinked') {
434
+ base = base.filter((task) => !isUnlinkedTask(task));
435
+ }
436
+ if (hierarchyDescendantIdSet) {
437
+ base = base.filter(task => hierarchyDescendantIdSet.has(task.id)
438
+ || (hierarchyShowUnlinkedColumn && (parentTaskFilterMode === 'all' || parentTaskFilterMode === 'unlinked') && isUnlinkedTask(task)));
439
+ }
440
+ return base;
441
+ }, [groupBy, parentTaskFilterMode, kanbanTasks, hierarchyParentIdSet, scheduleBaseTasks, hierarchyDescendantIdSet, hierarchyShowUnlinkedColumn]);
442
+ const todayDateOnly = useMemo(() => {
443
+ const now = new Date();
444
+ const y = now.getFullYear();
445
+ const m = String(now.getMonth() + 1).padStart(2, '0');
446
+ const d = String(now.getDate()).padStart(2, '0');
447
+ return `${y}-${m}-${d}`;
448
+ }, []);
449
+ const boardVisibleTasks = useMemo(() => {
450
+ if (groupBy !== 'schedule' || !scheduleOnlyExpired)
451
+ return hierarchyVisibleTasks;
452
+ return hierarchyVisibleTasks.filter((task) => {
453
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
454
+ if (isTerminal)
455
+ return false;
456
+ const expiredScheduled = !!task.scheduledDate && task.scheduledDate < todayDateOnly;
457
+ const overdueDue = !!task.dueDate && task.dueDate < todayDateOnly;
458
+ return expiredScheduled || overdueDue;
459
+ });
460
+ }, [groupBy, scheduleOnlyExpired, hierarchyVisibleTasks, todayDateOnly]);
461
+ const expiredRecoveryCandidates = useMemo(() => {
462
+ if (groupBy !== 'schedule')
463
+ return [];
464
+ return hierarchyVisibleTasks.filter((task) => {
465
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
466
+ if (isTerminal)
467
+ return false;
468
+ const expiredScheduled = !!task.scheduledDate && task.scheduledDate < todayDateOnly;
469
+ const overdueDue = !!task.dueDate && task.dueDate < todayDateOnly;
470
+ return expiredScheduled || overdueDue;
471
+ });
472
+ }, [groupBy, hierarchyVisibleTasks, todayDateOnly]);
473
+ const expiredScheduledCount = useMemo(() => {
474
+ if (groupBy !== 'schedule')
475
+ return 0;
476
+ return hierarchyVisibleTasks.filter((task) => {
477
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
478
+ if (isTerminal)
479
+ return false;
480
+ return !!task.scheduledDate && task.scheduledDate < todayDateOnly;
481
+ }).length;
482
+ }, [groupBy, hierarchyVisibleTasks, todayDateOnly]);
483
+ const overdueDueCount = useMemo(() => {
484
+ if (groupBy !== 'schedule')
485
+ return 0;
486
+ return hierarchyVisibleTasks.filter((task) => {
487
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
488
+ if (isTerminal)
489
+ return false;
490
+ return !!task.dueDate && task.dueDate < todayDateOnly;
491
+ }).length;
492
+ }, [groupBy, hierarchyVisibleTasks, todayDateOnly]);
493
+ const expiredColumnCandidates = useMemo(() => {
494
+ if (groupBy !== 'schedule')
495
+ return [];
496
+ return hierarchyVisibleTasks.filter((task) => {
497
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
498
+ if (isTerminal)
499
+ return false;
500
+ return !!task.scheduledDate && task.scheduledDate < todayDateOnly;
501
+ });
502
+ }, [groupBy, hierarchyVisibleTasks, todayDateOnly]);
503
+ const filteredExpiredColumnCandidates = useMemo(() => {
504
+ if (groupBy !== 'schedule')
505
+ return [];
506
+ return expiredColumnCandidates.filter((task) => scheduleFilteredTaskIds.has(task.id));
507
+ }, [groupBy, expiredColumnCandidates, scheduleFilteredTaskIds]);
508
+ const expiredLeafCandidates = useMemo(() => expiredRecoveryCandidates.filter(task => !hierarchyParentIdSet.has(task.id)), [expiredRecoveryCandidates, hierarchyParentIdSet]);
509
+ const [scheduleBulkBusy, setScheduleBulkBusy] = useState(false);
510
+ const [scheduleParentDropBusy, setScheduleParentDropBusy] = useState(false);
511
+ const [scheduleParentDropPrompt, setScheduleParentDropPrompt] = useState(null);
512
+ const hierarchyDepthTasks = useMemo(() => hierarchyVisibleTasks.filter(task => !!task.parentTaskId || hierarchyParentIdSet.has(task.id)), [hierarchyVisibleTasks, hierarchyParentIdSet]);
513
+ const hierarchyGlobalDepthMap = useMemo(() => buildHierarchyDepthMap(hierarchyTasks), [hierarchyTasks]);
514
+ const maxHierarchyDepth = useMemo(() => hierarchyDepthTasks.reduce((maxDepth, task) => {
515
+ const depth = hierarchyGlobalDepthMap.get(task.id) ?? 0;
516
+ return depth > maxDepth ? depth : maxDepth;
517
+ }, 0), [hierarchyDepthTasks, hierarchyGlobalDepthMap]);
518
+ const tryReturnToParent = () => {
519
+ if (activeTab === 'add'
520
+ && editingTaskId
521
+ && returnToParentTaskId
522
+ && returnFromChildTaskId === editingTaskId) {
523
+ const parentTask = tasks.find(t => t.id === returnToParentTaskId);
524
+ clearReturnToParentTask();
525
+ if (parentTask) {
526
+ handleEdit(parentTask);
527
+ return true;
528
+ }
529
+ }
530
+ return false;
531
+ };
532
+ const handleCloseModal = () => {
533
+ handleNavigation(() => {
534
+ if (tryReturnToParent())
535
+ return;
536
+ if (activeTab === 'add')
537
+ resetForm();
538
+ setActiveTab('tasks');
539
+ });
540
+ };
541
+ const [localCancellationReason, setLocalCancellationReason] = useState('');
542
+ const handleConfirmCancellation = () => {
543
+ confirmCancellation(localCancellationReason);
544
+ setLocalCancellationReason('');
545
+ };
546
+ const handleCancelCancellation = () => {
547
+ cancelCancellationPrompt();
548
+ setLocalCancellationReason('');
549
+ };
550
+ const handleDeleteFromCancellation = async () => {
551
+ if (!cancellationPromptTask)
552
+ return;
553
+ const confirmed = window.confirm('Delete this task permanently? This cannot be undone.');
554
+ if (!confirmed)
555
+ return;
556
+ const deleted = await handleDelete(cancellationPromptTask.id, false, { skipConfirm: true });
557
+ if (deleted) {
558
+ handleCancelCancellation();
559
+ }
560
+ };
561
+ const handleAddTaskToColumn = useCallback((groupField, value) => {
562
+ resetForm();
563
+ // Handle different grouping fields
564
+ if (groupField === 'category') {
565
+ const cat = activeCategories.find(c => String(c.value) === String(value) || c.label === String(value));
566
+ if (cat)
567
+ setCategory(cat.label);
568
+ }
569
+ else if (groupField === 'type') {
570
+ setType(String(value));
571
+ }
572
+ else if (groupField === 'priority') {
573
+ setPriority(Number(value));
574
+ }
575
+ else if (groupField === 'approach') {
576
+ const app = approaches.find(a => String(a.value) === String(value) || a.label === String(value));
577
+ if (app) {
578
+ setApproach(app.value);
579
+ setFormTaxonomies(prev => ({ ...prev, approach: app.value }));
580
+ }
581
+ }
582
+ else if (groupField === 'status') {
583
+ // When adding to a status column, we just open the form.
584
+ // But we can preset the status flags if we want, or just let users set them.
585
+ // Currently, the TaskForm doesn't expose a simple 'status' dropdown to preset.
586
+ // So for now, we'll just open the form.
587
+ // Ideally, we would preset the flags (inProgress, readyForReview, etc.)
588
+ // implementing basic presetting:
589
+ if (value === 'inProgress') {
590
+ handleToggleInProgress({}); // Hacky: assumes handleToggle works on new task? No.
591
+ // Better: we can't easily preset status flags in the current form setup without modifying resetForm/TaskForm props deeply.
592
+ // For now, simple open.
593
+ // Actually, let's try to pass initial state if possible, but useTaskforce API is limited.
594
+ // We'll leave it as default (Task) for now, or users can toggle after adding.
595
+ }
596
+ }
597
+ else if (groupField === 'complexity') {
598
+ const direct = Number(value);
599
+ if (Number.isFinite(direct)) {
600
+ setComplexity(direct);
601
+ }
602
+ }
603
+ else if (groupField === 'hierarchy') {
604
+ // Hierarchy columns are depth-based and do not map to form defaults.
605
+ }
606
+ setActiveTab('add');
607
+ }, [resetForm, activeCategories, approaches, setCategory, setType, setPriority, setComplexity, setApproach, setFormTaxonomies, setActiveTab]);
608
+ // Ensure 'done' status is shown by default as requested
609
+ React.useEffect(() => {
610
+ if (filterStatus && !filterStatus.includes('done')) {
611
+ setFilterStatus(prev => [...prev, 'done']);
612
+ }
613
+ }, []);
614
+ // Local state for filter toolbar
615
+ const [showHelpModal, setShowHelpModal] = useState(false);
616
+ const [showAccountMenu, setShowAccountMenu] = useState(false);
617
+ const [showAccountHub, setShowAccountHub] = useState(false);
618
+ const [showSyncStatusModal, setShowSyncStatusModal] = useState(false);
619
+ const [workspaceSyncBusy, setWorkspaceSyncBusy] = useState(false);
620
+ const [workspaceSyncError, setWorkspaceSyncError] = useState(null);
621
+ const [showCloudSignInModal, setShowCloudSignInModal] = useState(false);
622
+ const [cloudSignInEmail, setCloudSignInEmail] = useState('');
623
+ const [cloudSignInPassword, setCloudSignInPassword] = useState('');
624
+ const [cloudSignInBusy, setCloudSignInBusy] = useState(false);
625
+ const [cloudSignInError, setCloudSignInError] = useState(null);
626
+ const [workspacePanelOpen, setWorkspacePanelOpen] = useState(false);
627
+ const [workspaceFormName, setWorkspaceFormName] = useState('');
628
+ const [workspaceFormDescription, setWorkspaceFormDescription] = useState('');
629
+ const [workspaceActionBusy, setWorkspaceActionBusy] = useState(false);
630
+ const [workspaceActionError, setWorkspaceActionError] = useState('');
631
+ const accountMenuRef = useRef(null);
632
+ // showSearch state removed - search always visible in actions area
633
+ useEffect(() => {
634
+ if (!showAccountMenu)
635
+ return;
636
+ const handlePointerDown = (event) => {
637
+ if (!accountMenuRef.current?.contains(event.target)) {
638
+ setShowAccountMenu(false);
639
+ }
640
+ };
641
+ document.addEventListener('mousedown', handlePointerDown);
642
+ return () => document.removeEventListener('mousedown', handlePointerDown);
643
+ }, [showAccountMenu]);
644
+ const canManageCloudWorkspaces = runtimeMode === 'cloud' && isAuthenticated;
645
+ const canManageWorkspaceSync = runtimeMode === 'local' && isAuthenticated;
646
+ const openCloudSignIn = useCallback(() => {
647
+ setCloudSignInError(null);
648
+ setShowCloudSignInModal(true);
649
+ }, []);
650
+ const closeCloudSignInModal = useCallback(() => {
651
+ if (cloudSignInBusy)
652
+ return;
653
+ setShowCloudSignInModal(false);
654
+ setCloudSignInError(null);
655
+ }, [cloudSignInBusy]);
656
+ const openWorkspaceSetup = useCallback(() => {
657
+ setShowAccountMenu(false);
658
+ setWorkspacePanelOpen(false);
659
+ navigate('/setup?step=workspace', { replace: true });
660
+ }, [navigate]);
661
+ const routeToWorkspaceSetupIfNeeded = useCallback(async (hintWorkspaceSetupRequired) => {
662
+ const workspacesResult = await fetchWorkspaces();
663
+ if (workspacesResult.success) {
664
+ const workspaceCount = Array.isArray(workspacesResult.workspaces)
665
+ ? workspacesResult.workspaces.length
666
+ : 0;
667
+ if (workspaceCount === 0) {
668
+ openWorkspaceSetup();
669
+ return true;
670
+ }
671
+ return false;
672
+ }
673
+ if (hintWorkspaceSetupRequired) {
674
+ openWorkspaceSetup();
675
+ return true;
676
+ }
677
+ return false;
678
+ }, [fetchWorkspaces, openWorkspaceSetup]);
679
+ const handleSwitchWorkspace = useCallback(async (workspaceId) => {
680
+ if (!workspaceId || workspaceActionBusy)
681
+ return;
682
+ setWorkspaceActionError('');
683
+ setWorkspaceActionBusy(true);
684
+ try {
685
+ const result = await switchWorkspace(workspaceId);
686
+ if (!result.success) {
687
+ if (result.code === 'WORKSPACE_NOT_FOUND' || result.code === 'WORKSPACE_ID_REQUIRED') {
688
+ openWorkspaceSetup();
689
+ return;
690
+ }
691
+ setWorkspaceActionError(result.error || 'Failed to switch workspace.');
692
+ return;
693
+ }
694
+ await fetchTasks(true);
695
+ setShowAccountMenu(false);
696
+ setWorkspacePanelOpen(false);
697
+ }
698
+ finally {
699
+ setWorkspaceActionBusy(false);
700
+ }
701
+ }, [switchWorkspace, workspaceActionBusy, fetchTasks, openWorkspaceSetup]);
702
+ const handleCreateWorkspace = useCallback(async () => {
703
+ if (workspaceActionBusy)
704
+ return;
705
+ const name = workspaceFormName.trim();
706
+ if (!name) {
707
+ setWorkspaceActionError('Workspace name is required.');
708
+ return;
709
+ }
710
+ setWorkspaceActionError('');
711
+ setWorkspaceActionBusy(true);
712
+ try {
713
+ const result = await createWorkspace(name, workspaceFormDescription.trim() || undefined);
714
+ if (!result.success) {
715
+ if (result.code === 'WORKSPACE_NOT_FOUND' || result.code === 'WORKSPACE_ID_REQUIRED') {
716
+ openWorkspaceSetup();
717
+ return;
718
+ }
719
+ setWorkspaceActionError(result.error || 'Failed to create workspace.');
720
+ return;
721
+ }
722
+ await fetchTasks(true);
723
+ setWorkspaceFormName('');
724
+ setWorkspaceFormDescription('');
725
+ setWorkspacePanelOpen(false);
726
+ setShowAccountMenu(false);
727
+ }
728
+ finally {
729
+ setWorkspaceActionBusy(false);
730
+ }
731
+ }, [workspaceActionBusy, workspaceFormName, workspaceFormDescription, createWorkspace, fetchTasks, openWorkspaceSetup]);
732
+ // Check if any filters are set
733
+ const isFiltered = useMemo(() => {
734
+ const hasSearch = searchQuery.trim().length > 0;
735
+ const hasCategoryFilter = filterCategories.length !== activeCategories.length;
736
+ const hasTypeFilter = filterTypes.length !== activeTypes.length;
737
+ const hasPriorityFilter = filterPriorities.length !== priorities.length;
738
+ const hasStatusFilter = filterStatus.length !== STATUS_OPTIONS.length;
739
+ const hasAssigneeFilter = assigneeFilterEnabled && filterAssignees.length !== 3;
740
+ const hasParentsOnlyFilter = parentTaskFilterMode !== 'all';
741
+ return hasSearch || hasCategoryFilter || hasTypeFilter || hasPriorityFilter || hasStatusFilter || hasAssigneeFilter || hasParentsOnlyFilter;
742
+ }, [searchQuery, filterCategories, activeCategories, filterTypes, activeTypes, filterPriorities, priorities, filterStatus, assigneeFilterEnabled, filterAssignees, parentTaskFilterMode]);
743
+ const totalActiveCount = tasks.length;
744
+ const visibleActiveCount = props.filteredTasks.length;
745
+ const activeCountLabel = isFiltered ? `${visibleActiveCount}/${totalActiveCount}` : `${totalActiveCount}`;
746
+ const activeCountTitle = isFiltered
747
+ ? t('standalone.visibleActiveTasksTitle')
748
+ : t('standalone.totalActiveTasksTitle');
749
+ const shouldShowSyncStatus = runtimeMode === 'local';
750
+ const formattedLastSyncTime = useMemo(() => {
751
+ if (!userGlobalLastSyncedAt)
752
+ return 'Never';
753
+ const parsed = new Date(userGlobalLastSyncedAt);
754
+ if (Number.isNaN(parsed.getTime()))
755
+ return 'Never';
756
+ return parsed.toLocaleString();
757
+ }, [userGlobalLastSyncedAt]);
758
+ const formatSyncTimestamp = useCallback((value) => {
759
+ if (!value)
760
+ return 'Never';
761
+ const parsed = new Date(value);
762
+ if (Number.isNaN(parsed.getTime()))
763
+ return 'Never';
764
+ return parsed.toLocaleString();
765
+ }, []);
766
+ const formattedLastPullTime = useMemo(() => formatSyncTimestamp(workspaceLastPullAt), [workspaceLastPullAt, formatSyncTimestamp]);
767
+ const formattedLastPushTime = useMemo(() => formatSyncTimestamp(workspaceLastPushAt), [workspaceLastPushAt, formatSyncTimestamp]);
768
+ const formattedLastErrorTime = useMemo(() => formatSyncTimestamp(workspaceLastErrorAt), [workspaceLastErrorAt, formatSyncTimestamp]);
769
+ const formattedRetryTime = useMemo(() => formatSyncTimestamp(workspaceRetryAt), [workspaceRetryAt, formatSyncTimestamp]);
770
+ const [displaySyncState, setDisplaySyncState] = useState(() => (workspaceCloudSyncEnabled ? userGlobalSyncStatus : 'disconnected'));
771
+ const syncStateSinceRef = useRef(Date.now());
772
+ const syncStateTimerRef = useRef(null);
773
+ const rawSyncState = workspaceCloudSyncEnabled
774
+ ? userGlobalSyncStatus
775
+ : 'disconnected';
776
+ useEffect(() => {
777
+ if (syncStateTimerRef.current !== null) {
778
+ window.clearTimeout(syncStateTimerRef.current);
779
+ syncStateTimerRef.current = null;
780
+ }
781
+ const applyState = (next) => {
782
+ setDisplaySyncState(next);
783
+ syncStateSinceRef.current = Date.now();
784
+ };
785
+ if (rawSyncState === displaySyncState)
786
+ return;
787
+ // Delay "syncing" to avoid flicker on very fast sync cycles.
788
+ if (rawSyncState === 'syncing') {
789
+ syncStateTimerRef.current = window.setTimeout(() => {
790
+ applyState('syncing');
791
+ }, SYNCING_DISPLAY_DELAY_MS);
792
+ return;
793
+ }
794
+ // Hold syncing/error briefly so status doesn't flash rapidly.
795
+ if (displaySyncState === 'syncing') {
796
+ const elapsed = Date.now() - syncStateSinceRef.current;
797
+ const remaining = Math.max(0, SYNCING_MIN_VISIBLE_MS - elapsed);
798
+ syncStateTimerRef.current = window.setTimeout(() => {
799
+ applyState(rawSyncState);
800
+ }, remaining);
801
+ return;
802
+ }
803
+ if (displaySyncState === 'error' && rawSyncState !== 'error') {
804
+ const elapsed = Date.now() - syncStateSinceRef.current;
805
+ const remaining = Math.max(0, ERROR_MIN_VISIBLE_MS - elapsed);
806
+ syncStateTimerRef.current = window.setTimeout(() => {
807
+ applyState(rawSyncState);
808
+ }, remaining);
809
+ return;
810
+ }
811
+ applyState(rawSyncState);
812
+ }, [rawSyncState, displaySyncState]);
813
+ useEffect(() => {
814
+ return () => {
815
+ if (syncStateTimerRef.current !== null) {
816
+ window.clearTimeout(syncStateTimerRef.current);
817
+ syncStateTimerRef.current = null;
818
+ }
819
+ };
820
+ }, []);
821
+ const modalSyncStatusMeta = useMemo(() => {
822
+ if (!workspaceCloudSyncEnabled) {
823
+ return {
824
+ label: 'Sync Off',
825
+ color: '#94a3b8',
826
+ border: 'rgba(148, 163, 184, 0.45)',
827
+ background: 'rgba(148, 163, 184, 0.12)'
828
+ };
829
+ }
830
+ switch (displaySyncState) {
831
+ case 'syncing':
832
+ return {
833
+ label: 'Syncing',
834
+ color: '#f59e0b',
835
+ border: 'rgba(245, 158, 11, 0.45)',
836
+ background: 'rgba(245, 158, 11, 0.16)'
837
+ };
838
+ case 'error':
839
+ return {
840
+ label: 'Sync Error',
841
+ color: '#f87171',
842
+ border: 'rgba(248, 113, 113, 0.45)',
843
+ background: 'rgba(248, 113, 113, 0.14)'
844
+ };
845
+ case 'disconnected':
846
+ return {
847
+ label: 'Disconnected',
848
+ color: '#94a3b8',
849
+ border: 'rgba(148, 163, 184, 0.45)',
850
+ background: 'rgba(148, 163, 184, 0.12)'
851
+ };
852
+ case 'idle':
853
+ default:
854
+ return {
855
+ label: 'Synced',
856
+ color: '#34d399',
857
+ border: 'rgba(52, 211, 153, 0.45)',
858
+ background: 'rgba(52, 211, 153, 0.14)'
859
+ };
860
+ }
861
+ }, [displaySyncState, workspaceCloudSyncEnabled]);
862
+ const headerSyncStatusMeta = useMemo(() => {
863
+ if (!workspaceCloudSyncEnabled) {
864
+ return {
865
+ label: 'Sync Off',
866
+ color: '#94a3b8',
867
+ border: 'rgba(148, 163, 184, 0.45)',
868
+ background: 'rgba(148, 163, 184, 0.12)',
869
+ icon: 'off'
870
+ };
871
+ }
872
+ if (displaySyncState === 'error') {
873
+ return {
874
+ label: 'Sync Error',
875
+ color: '#f87171',
876
+ border: 'rgba(248, 113, 113, 0.45)',
877
+ background: 'rgba(248, 113, 113, 0.14)',
878
+ icon: 'off'
879
+ };
880
+ }
881
+ if (displaySyncState === 'disconnected') {
882
+ return {
883
+ label: 'Disconnected',
884
+ color: '#94a3b8',
885
+ border: 'rgba(148, 163, 184, 0.45)',
886
+ background: 'rgba(148, 163, 184, 0.12)',
887
+ icon: 'off'
888
+ };
889
+ }
890
+ return {
891
+ label: 'Synced',
892
+ color: '#34d399',
893
+ border: 'rgba(52, 211, 153, 0.45)',
894
+ background: 'rgba(52, 211, 153, 0.14)',
895
+ icon: 'cloud'
896
+ };
897
+ }, [displaySyncState, workspaceCloudSyncEnabled]);
898
+ const parentFilterLabel = hierarchyRootTaskId
899
+ ? 'Subtree'
900
+ : parentTaskFilterMode === 'parents'
901
+ ? t('standalone.linksParents')
902
+ : parentTaskFilterMode === 'linked'
903
+ ? t('standalone.linksLinked')
904
+ : parentTaskFilterMode === 'unlinked'
905
+ ? t('standalone.linksUnlinked')
906
+ : t('standalone.linksAll');
907
+ const selectedHierarchyRootTask = hierarchyRootTaskId ? hierarchyTaskById.get(hierarchyRootTaskId) : undefined;
908
+ const hierarchySubtreeStats = useMemo(() => {
909
+ if (!hierarchyDescendantIdSet || !selectedHierarchyRootTask)
910
+ return null;
911
+ const selectedTasks = hierarchyTasks.filter((task) => hierarchyDescendantIdSet.has(task.id));
912
+ let active = 0;
913
+ let terminal = 0;
914
+ let overdue = 0;
915
+ selectedTasks.forEach((task) => {
916
+ const isTerminal = task.status === 'done' || task.status === 'cancelled' || Boolean(task.isArchived);
917
+ if (isTerminal) {
918
+ terminal += 1;
919
+ }
920
+ else {
921
+ active += 1;
922
+ if (task.dueDate && task.dueDate < todayDateOnly)
923
+ overdue += 1;
924
+ }
925
+ });
926
+ let depth = 0;
927
+ const visited = new Set();
928
+ const stack = [{ id: selectedHierarchyRootTask.id, depth: 0 }];
929
+ while (stack.length > 0) {
930
+ const current = stack.pop();
931
+ if (visited.has(current.id))
932
+ continue;
933
+ visited.add(current.id);
934
+ if (current.depth > depth)
935
+ depth = current.depth;
936
+ const children = hierarchyChildrenByParent.get(current.id) || [];
937
+ children.forEach((child) => {
938
+ if (!visited.has(child.id) && hierarchyDescendantIdSet.has(child.id)) {
939
+ stack.push({ id: child.id, depth: current.depth + 1 });
940
+ }
941
+ });
942
+ }
943
+ return { total: selectedTasks.length, active, terminal, overdue, depth };
944
+ }, [hierarchyDescendantIdSet, selectedHierarchyRootTask, hierarchyTasks, todayDateOnly, hierarchyChildrenByParent]);
945
+ const hierarchyVisibleLevelCounts = useMemo(() => {
946
+ const byLevel = new Map();
947
+ hierarchyDepthTasks.forEach((task) => {
948
+ const level = (hierarchyGlobalDepthMap.get(task.id) ?? 0) + 1;
949
+ byLevel.set(level, (byLevel.get(level) || 0) + 1);
950
+ });
951
+ return Array.from(byLevel.entries())
952
+ .sort((a, b) => a[0] - b[0])
953
+ .map(([level, count]) => ({ level, count }));
954
+ }, [hierarchyDepthTasks, hierarchyGlobalDepthMap]);
955
+ const hierarchyVisibleUnlinkedCount = useMemo(() => hierarchyVisibleTasks.filter((task) => !task.parentTaskId && !hierarchyParentIdSet.has(task.id)).length, [hierarchyVisibleTasks, hierarchyParentIdSet]);
956
+ const hierarchyVisibleRootCount = useMemo(() => hierarchyVisibleTasks.filter((task) => !task.parentTaskId && hierarchyParentIdSet.has(task.id)).length, [hierarchyVisibleTasks, hierarchyParentIdSet]);
957
+ const hierarchySubtreeTaskList = useMemo(() => {
958
+ if (!hierarchyRootTaskId || !hierarchyDescendantIdSet)
959
+ return [];
960
+ return hierarchyTasks
961
+ .filter((task) => hierarchyDescendantIdSet.has(task.id))
962
+ .sort((a, b) => {
963
+ const levelA = hierarchyGlobalDepthMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
964
+ const levelB = hierarchyGlobalDepthMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
965
+ if (levelA !== levelB)
966
+ return levelA - levelB;
967
+ return a.title.localeCompare(b.title);
968
+ });
969
+ }, [hierarchyRootTaskId, hierarchyDescendantIdSet, hierarchyTasks, hierarchyGlobalDepthMap]);
970
+ const applyHierarchySearchSelection = useCallback(() => {
971
+ const bestMatch = hierarchySearchMatches[0];
972
+ if (!bestMatch)
973
+ return;
974
+ setHierarchyRootTaskId(bestMatch.id);
975
+ setHierarchyRootSearch('');
976
+ if (parentTaskFilterMode === 'unlinked')
977
+ setParentTaskFilterMode('all');
978
+ }, [hierarchySearchMatches, parentTaskFilterMode, setParentTaskFilterMode]);
979
+ const selectedDateObj = useMemo(() => parseDateOnlyLocal(scheduleSelectedDate) || new Date(), [scheduleSelectedDate]);
980
+ const scheduleWeekStart = useMemo(() => startOfWeek(selectedDateObj, globalWeekStartsOn), [selectedDateObj, globalWeekStartsOn]);
981
+ const scheduleDates = useMemo(() => {
982
+ const map = {
983
+ mon: '',
984
+ tue: '',
985
+ wed: '',
986
+ thu: '',
987
+ fri: '',
988
+ sat: '',
989
+ sun: ''
990
+ };
991
+ SCHEDULE_DAY_KEYS.forEach((key) => {
992
+ const offset = getDayOffsetFromWeekStart(key, globalWeekStartsOn);
993
+ map[key] = toDateOnlyLocal(addDays(scheduleWeekStart, offset));
994
+ });
995
+ return map;
996
+ }, [scheduleWeekStart, globalWeekStartsOn]);
997
+ const mapToSelectedWeekDate = useCallback((sourceDateOnly) => {
998
+ const parsed = parseDateOnlyLocal(sourceDateOnly);
999
+ if (!parsed)
1000
+ return scheduleDates.mon;
1001
+ const day = parsed.getDay();
1002
+ const key = day === 1 ? 'mon' :
1003
+ day === 2 ? 'tue' :
1004
+ day === 3 ? 'wed' :
1005
+ day === 4 ? 'thu' :
1006
+ day === 5 ? 'fri' :
1007
+ day === 6 ? (scheduleShowWeekends ? 'sat' : 'fri') :
1008
+ (scheduleShowWeekends ? 'sun' : 'fri');
1009
+ return scheduleDates[key] || scheduleDates.mon;
1010
+ }, [scheduleDates, scheduleShowWeekends]);
1011
+ const handleMoveExpiredToSelectedWeek = useCallback(async () => {
1012
+ if (scheduleBulkBusy || expiredLeafCandidates.length === 0)
1013
+ return;
1014
+ setScheduleBulkBusy(true);
1015
+ try {
1016
+ for (const task of expiredLeafCandidates) {
1017
+ const sourceDate = task.scheduledDate || task.dueDate || todayDateOnly;
1018
+ const nextDate = mapToSelectedWeekDate(sourceDate);
1019
+ await handleUpdateTask(task.id, {
1020
+ scheduledDate: nextDate,
1021
+ scheduledWeekKey: dateOnlyToIsoWeekKey(nextDate),
1022
+ orderInDay: null
1023
+ });
1024
+ }
1025
+ await fetchTasks(true);
1026
+ }
1027
+ finally {
1028
+ setScheduleBulkBusy(false);
1029
+ }
1030
+ }, [scheduleBulkBusy, expiredLeafCandidates, todayDateOnly, mapToSelectedWeekDate, handleUpdateTask, fetchTasks]);
1031
+ const handleMoveExpiredToBacklog = useCallback(async () => {
1032
+ if (scheduleBulkBusy || expiredRecoveryCandidates.length === 0)
1033
+ return;
1034
+ setScheduleBulkBusy(true);
1035
+ try {
1036
+ for (const task of expiredRecoveryCandidates) {
1037
+ await handleUpdateTask(task.id, {
1038
+ scheduledDate: null,
1039
+ scheduledWeekKey: null,
1040
+ orderInDay: null
1041
+ });
1042
+ }
1043
+ await fetchTasks(true);
1044
+ }
1045
+ finally {
1046
+ setScheduleBulkBusy(false);
1047
+ }
1048
+ }, [scheduleBulkBusy, expiredRecoveryCandidates, handleUpdateTask, fetchTasks]);
1049
+ const handleScheduleParentDrop = useCallback((payload) => {
1050
+ if (payload.targetDate < todayDateOnly) {
1051
+ const byId = new Map(tasks.map((task) => [task.id, task]));
1052
+ const hasNonTerminalLeaf = payload.toScheduleLeafTaskIds.some((taskId) => {
1053
+ const task = byId.get(taskId);
1054
+ if (!task)
1055
+ return true;
1056
+ return task.status !== 'done' && task.status !== 'cancelled';
1057
+ });
1058
+ if (hasNonTerminalLeaf) {
1059
+ window.alert(t('schedule.completedOnlyPastSchedule'));
1060
+ return;
1061
+ }
1062
+ }
1063
+ if (payload.toScheduleLeafTaskIds.length === 0) {
1064
+ window.alert(t('schedule.noUnscheduledLeafTasks', { parentTitle: payload.parentTitle }));
1065
+ return;
1066
+ }
1067
+ setScheduleParentDropPrompt(payload);
1068
+ }, [tasks, todayDateOnly]);
1069
+ const confirmScheduleParentDrop = useCallback(async () => {
1070
+ if (!scheduleParentDropPrompt || scheduleParentDropBusy)
1071
+ return;
1072
+ setScheduleParentDropBusy(true);
1073
+ try {
1074
+ const { targetDate, toScheduleLeafTaskIds } = scheduleParentDropPrompt;
1075
+ const dayTasks = tasks
1076
+ .filter((task) => task.scheduledDate === targetDate && !toScheduleLeafTaskIds.includes(task.id))
1077
+ .sort((a, b) => {
1078
+ const orderA = typeof a.orderInDay === 'number' ? a.orderInDay : Number.MAX_SAFE_INTEGER;
1079
+ const orderB = typeof b.orderInDay === 'number' ? b.orderInDay : Number.MAX_SAFE_INTEGER;
1080
+ if (orderA !== orderB)
1081
+ return orderA - orderB;
1082
+ return String(a.createdAt || '').localeCompare(String(b.createdAt || ''));
1083
+ });
1084
+ let nextOrder = dayTasks.length;
1085
+ for (const taskId of toScheduleLeafTaskIds) {
1086
+ await handleUpdateTask(taskId, {
1087
+ scheduledDate: targetDate,
1088
+ scheduledWeekKey: dateOnlyToIsoWeekKey(targetDate),
1089
+ orderInDay: nextOrder++
1090
+ });
1091
+ }
1092
+ setScheduleParentDropPrompt(null);
1093
+ await fetchTasks(true);
1094
+ }
1095
+ finally {
1096
+ setScheduleParentDropBusy(false);
1097
+ }
1098
+ }, [scheduleParentDropPrompt, scheduleParentDropBusy, tasks, handleUpdateTask, fetchTasks]);
1099
+ const cancelScheduleParentDrop = useCallback(() => {
1100
+ if (scheduleParentDropBusy)
1101
+ return;
1102
+ setScheduleParentDropPrompt(null);
1103
+ }, [scheduleParentDropBusy]);
1104
+ const scheduleMonthDate = useMemo(() => {
1105
+ const [yearRaw, monthRaw] = scheduleCalendarMonth.split('-');
1106
+ const year = Number(yearRaw);
1107
+ const month = Number(monthRaw);
1108
+ if (!Number.isInteger(year) || !Number.isInteger(month) || month < 1 || month > 12) {
1109
+ return new Date(selectedDateObj.getFullYear(), selectedDateObj.getMonth(), 1);
1110
+ }
1111
+ return new Date(year, month - 1, 1);
1112
+ }, [scheduleCalendarMonth, selectedDateObj]);
1113
+ const scheduleCalendarMonthLabel = useMemo(() => formatDate(scheduleMonthDate, { month: 'long', year: 'numeric' }, resolvedLocale), [scheduleMonthDate, resolvedLocale]);
1114
+ const scheduleWeekLabel = useMemo(() => formatDate(scheduleWeekStart, { month: 'short', day: 'numeric', year: 'numeric' }, resolvedLocale), [scheduleWeekStart, resolvedLocale]);
1115
+ const calendarCells = useMemo(() => {
1116
+ const start = startOfWeek(scheduleMonthDate, globalWeekStartsOn);
1117
+ return Array.from({ length: 42 }, (_, i) => addDays(start, i));
1118
+ }, [scheduleMonthDate, globalWeekStartsOn]);
1119
+ const calendarTaskSignals = useMemo(() => {
1120
+ const byDate = new Map();
1121
+ for (const task of scheduleBaseTasks) {
1122
+ const dateOnly = task.scheduledDate || '';
1123
+ if (!dateOnly)
1124
+ continue;
1125
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
1126
+ const isExpired = !isTerminal && dateOnly < todayDateOnly;
1127
+ const existing = byDate.get(dateOnly);
1128
+ if (existing) {
1129
+ existing.count += 1;
1130
+ if (isExpired)
1131
+ existing.hasExpired = true;
1132
+ }
1133
+ else {
1134
+ byDate.set(dateOnly, { count: 1, hasExpired: isExpired });
1135
+ }
1136
+ }
1137
+ return byDate;
1138
+ }, [scheduleBaseTasks, todayDateOnly]);
1139
+ const calendarWeekdayLabels = useMemo(() => (globalWeekStartsOn === 'sunday'
1140
+ ? ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
1141
+ : ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']), [globalWeekStartsOn]);
1142
+ // Calculate columns based on grouping
1143
+ const scheduleColumns = useMemo(() => {
1144
+ const orderedKeys = globalWeekStartsOn === 'sunday'
1145
+ ? ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
1146
+ : SCHEDULE_DAY_KEYS;
1147
+ const visibleKeys = scheduleShowWeekends
1148
+ ? orderedKeys
1149
+ : ['mon', 'tue', 'wed', 'thu', 'fri'];
1150
+ const days = visibleKeys.map((key) => {
1151
+ const date = parseDateOnlyLocal(scheduleDates[key]) || scheduleWeekStart;
1152
+ const dateOnly = scheduleDates[key];
1153
+ const isWeekend = key === 'sat' || key === 'sun';
1154
+ return {
1155
+ value: key,
1156
+ label: `${SCHEDULE_DAY_LABELS[key]} ${formatDate(date, { month: 'short', day: 'numeric' }, resolvedLocale)}`,
1157
+ color: isWeekend ? '#1e3a8a' : '#3b82f6',
1158
+ icon: 'Calendar',
1159
+ date: dateOnly,
1160
+ isPast: dateOnly < todayDateOnly,
1161
+ isSelected: dateOnly === scheduleSelectedDate,
1162
+ isWeekend
1163
+ };
1164
+ });
1165
+ const hasExpiredTasks = filteredExpiredColumnCandidates.length > 0;
1166
+ return [
1167
+ ...(scheduleShowBacklog ? [{ value: 'backlog', label: 'Backlog', color: '#64748b', icon: 'Inbox' }] : []),
1168
+ ...(hasExpiredTasks ? [{ value: 'expired', label: 'Expired', color: '#ef4444', icon: 'AlertTriangle' }] : []),
1169
+ ...days
1170
+ ];
1171
+ }, [scheduleShowWeekends, scheduleShowBacklog, scheduleDates, scheduleWeekStart, filteredExpiredColumnCandidates.length, todayDateOnly, scheduleSelectedDate, globalWeekStartsOn, resolvedLocale]);
1172
+ const kanbanColumns = useMemo(() => {
1173
+ switch (groupBy) {
1174
+ case 'category':
1175
+ return activeCategories;
1176
+ case 'type':
1177
+ return (activeTypes || []).map(t => ({
1178
+ ...t,
1179
+ icon: TypeMeta[t.value]?.icon,
1180
+ color: TypeMeta[t.value]?.color
1181
+ }));
1182
+ case 'priority':
1183
+ return priorities;
1184
+ case 'complexity':
1185
+ return [
1186
+ { value: 1, label: 'Tiny', icon: 'Gauge', color: '#10b981' },
1187
+ { value: 2, label: 'Low', icon: 'Gauge', color: '#14b8a6' },
1188
+ { value: 3, label: 'Medium', icon: 'Gauge', color: '#3b82f6' },
1189
+ { value: 4, label: 'High', icon: 'Gauge', color: '#8b5cf6' },
1190
+ { value: 5, label: 'Epic', icon: 'Gauge', color: '#d946ef' }
1191
+ ];
1192
+ case 'approach':
1193
+ return approaches;
1194
+ case 'assignee':
1195
+ return [
1196
+ { value: 'agent', label: 'AI', icon: 'Bot', color: '#8b5cf6' },
1197
+ { value: 'user', label: 'User', icon: 'User', color: '#22c55e' },
1198
+ { value: 'unassigned', label: 'Unassigned', icon: 'Circle', color: '#64748b' }
1199
+ ];
1200
+ case 'status':
1201
+ return STATUS_OPTIONS.map(opt => {
1202
+ let icon = 'Circle';
1203
+ let color = 'var(--text-secondary)';
1204
+ switch (opt.value) {
1205
+ case 'on-hold':
1206
+ icon = 'Pause';
1207
+ color = 'amber-500';
1208
+ break;
1209
+ case 'in-progress':
1210
+ icon = 'Play';
1211
+ color = 'blue-500';
1212
+ break;
1213
+ case 'review':
1214
+ icon = 'Sparkles';
1215
+ color = 'violet-500';
1216
+ break;
1217
+ case 'done':
1218
+ icon = 'Check';
1219
+ color = 'green-500';
1220
+ break;
1221
+ case 'cancelled':
1222
+ icon = 'Ban';
1223
+ color = 'red-500';
1224
+ break;
1225
+ case 'task':
1226
+ default:
1227
+ icon = 'Circle'; // Or List
1228
+ color = 'blue-200'; // Default light shade
1229
+ break;
1230
+ }
1231
+ return {
1232
+ ...opt,
1233
+ icon,
1234
+ color
1235
+ };
1236
+ });
1237
+ case 'hierarchy': {
1238
+ if (parentTaskFilterMode === 'unlinked') {
1239
+ return [{
1240
+ value: 'standalone',
1241
+ label: 'Unlinked',
1242
+ icon: 'Circle',
1243
+ color: '#64748b'
1244
+ }];
1245
+ }
1246
+ const levels = Array.from({ length: hierarchyDepthTasks.length > 0 ? maxHierarchyDepth + 1 : 0 }, (_, index) => index + 1);
1247
+ const hierarchyColumns = levels.map((level) => ({
1248
+ value: level,
1249
+ label: `Level ${level}`,
1250
+ icon: 'Layers',
1251
+ color: level === 1 ? '#3b82f6' : '#64748b'
1252
+ }));
1253
+ if (parentTaskFilterMode === 'all' && hierarchyShowUnlinkedColumn) {
1254
+ hierarchyColumns.push({
1255
+ value: 'standalone',
1256
+ label: 'Unlinked',
1257
+ icon: 'Circle',
1258
+ color: '#64748b'
1259
+ });
1260
+ }
1261
+ return hierarchyColumns;
1262
+ }
1263
+ case 'schedule':
1264
+ return scheduleColumns;
1265
+ default:
1266
+ return activeCategories;
1267
+ }
1268
+ }, [groupBy, activeCategories, activeTypes, priorities, approaches, maxHierarchyDepth, parentTaskFilterMode, scheduleColumns, hierarchyDepthTasks.length, hierarchyShowUnlinkedColumn]);
1269
+ 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) => {
1270
+ const target = e.currentTarget;
1271
+ if (target.src !== favicon) {
1272
+ target.src = favicon;
1273
+ }
1274
+ else {
1275
+ target.style.display = 'none';
1276
+ }
1277
+ } }), _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}`, style: {
1278
+ marginLeft: '6px',
1279
+ height: '24px',
1280
+ width: '24px',
1281
+ padding: 0,
1282
+ borderRadius: '999px',
1283
+ border: `1px solid ${headerSyncStatusMeta.border}`,
1284
+ background: headerSyncStatusMeta.background,
1285
+ color: headerSyncStatusMeta.color,
1286
+ display: 'inline-flex',
1287
+ alignItems: 'center',
1288
+ justifyContent: 'center'
1289
+ }, 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, 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' ? 'category' : '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: () => {
1290
+ setEmptyColumnMode(prev => {
1291
+ if (prev === 'show')
1292
+ return 'collapse';
1293
+ if (prev === 'collapse')
1294
+ return 'hide';
1295
+ return 'show';
1296
+ });
1297
+ }, title: emptyColumnMode === 'show' ? t('standalone.showEmptyColumns') :
1298
+ emptyColumnMode === 'collapse' ? t('standalone.compressEmptyColumns') :
1299
+ 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 : ''} ${isFiltered ? styles.filterGlow : ''}`, onClick: () => setShowFilters(!showFilters), title: t('standalone.toggleFilters'), children: _jsx(Filter, { size: 16 }) }), _jsxs("button", { className: styles.primaryUpdateBtn, onClick: () => {
1300
+ resetForm();
1301
+ setActiveTab('add');
1302
+ }, 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: [canManageCloudWorkspaces && (_jsxs("div", { className: styles.accountMenuSection, children: [_jsx("div", { className: styles.accountMenuSectionLabel, children: "Workspaces" }), 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: () => {
1303
+ setWorkspaceActionError('');
1304
+ setWorkspacePanelOpen((prev) => !prev);
1305
+ }, 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 }))] })), _jsxs("button", { className: styles.accountMenuItem, onClick: () => {
1306
+ setShowAccountMenu(false);
1307
+ setShowAccountHub(true);
1308
+ }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Account Hub"] }), _jsxs("button", { className: styles.accountMenuItem, onClick: () => {
1309
+ setShowAccountMenu(false);
1310
+ setActiveTab('settings');
1311
+ setSettingsSection('general');
1312
+ }, role: "menuitem", children: [_jsx(Settings, { size: 15 }), "Workspace Settings"] }), _jsxs("button", { className: styles.accountMenuItem, onClick: () => {
1313
+ setShowAccountMenu(false);
1314
+ setShowHelpModal(true);
1315
+ }, role: "menuitem", children: [_jsx(HelpCircle, { size: 15 }), "Help & Tutorial"] }), runtimeMode === 'local' && !isAuthenticated && (_jsxs("button", { className: styles.accountMenuItem, onClick: () => {
1316
+ setShowAccountMenu(false);
1317
+ openCloudSignIn();
1318
+ }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Sign-In"] })), isAuthenticated && (_jsxs("button", { className: styles.accountMenuItem, onClick: async () => {
1319
+ setShowAccountMenu(false);
1320
+ await logout();
1321
+ }, 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: {
1322
+ padding: '8px 24px',
1323
+ borderBottom: '1px solid var(--border-color)',
1324
+ background: 'var(--bg-secondary)',
1325
+ display: 'flex',
1326
+ alignItems: 'center',
1327
+ gap: '12px',
1328
+ flexWrap: 'wrap',
1329
+ position: 'relative',
1330
+ zIndex: 50
1331
+ }, 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: [
1332
+ { value: 'agent', label: 'AI' },
1333
+ { value: 'user', label: 'User' },
1334
+ { value: 'unassigned', label: 'Unassigned' }
1335
+ ], 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) => {
1336
+ setShowArchive(e.target.checked);
1337
+ if (e.target.checked)
1338
+ fetchArchive();
1339
+ } }), _jsx("span", { children: t('standalone.includeArchive') })] }), _jsx("div", { style: { flex: 1 } }), _jsxs("button", { onClick: clearFilters, style: {
1340
+ background: 'none',
1341
+ border: 'none',
1342
+ color: 'var(--text-secondary)',
1343
+ cursor: 'pointer',
1344
+ fontSize: '12px',
1345
+ display: 'flex',
1346
+ alignItems: 'center',
1347
+ gap: '4px'
1348
+ }, 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: {
1349
+ position: 'relative',
1350
+ opacity: props.loadingTasks ? 0.7 : 1,
1351
+ transition: 'opacity 0.2s ease, padding-right 220ms cubic-bezier(0.4, 0, 0.2, 1)',
1352
+ display: 'flex',
1353
+ flex: 1,
1354
+ minHeight: 0,
1355
+ paddingRight: (groupBy === 'schedule' && scheduleSidebarOpen) || (groupBy === 'hierarchy' && hierarchySidebarOpen) ? '332px' : 0
1356
+ }, children: [_jsxs("div", { style: { position: 'relative', width: '100%', minWidth: 0, flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }, children: [_jsx("div", { style: {
1357
+ position: 'absolute',
1358
+ top: 0,
1359
+ right: 0,
1360
+ width: '100%',
1361
+ height: '100%',
1362
+ backgroundImage: `url(${taskforceBg})`,
1363
+ backgroundRepeat: 'no-repeat',
1364
+ backgroundPosition: 'top right',
1365
+ backgroundSize: '60%',
1366
+ opacity: 0.2,
1367
+ pointerEvents: 'none',
1368
+ zIndex: 0
1369
+ } }), 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
1370
+ ? `Focused on ${selectedHierarchyRootTask?.title || hierarchyRootTaskId}. Try clearing subtree focus or broadening filters.`
1371
+ : 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) => {
1372
+ setScheduleSelectedDate(dateOnly);
1373
+ const parsed = parseDateOnlyLocal(dateOnly);
1374
+ if (parsed) {
1375
+ setScheduleCalendarMonth(`${parsed.getFullYear()}-${String(parsed.getMonth() + 1).padStart(2, '0')}`);
1376
+ }
1377
+ }, persistedScrollLeft: groupBy === 'schedule' ? scheduleScrollLeft : undefined, onScrollLeftChange: (scrollLeft) => {
1378
+ if (groupBy !== 'schedule')
1379
+ return;
1380
+ setScheduleScrollLeft(scrollLeft);
1381
+ }, 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: {
1382
+ position: 'absolute',
1383
+ top: '10px',
1384
+ right: '10px',
1385
+ zIndex: 20,
1386
+ border: '1px solid var(--border-color)',
1387
+ background: 'var(--bg-secondary)'
1388
+ }, children: _jsx(PanelRightOpen, { size: 16 }) })), groupBy === 'hierarchy' && !hierarchySidebarOpen && (_jsx("button", { type: "button", className: styles.headerActionBtn, onClick: () => setHierarchySidebarOpen(true), title: "Show Hierarchy Sidebar", style: {
1389
+ position: 'absolute',
1390
+ top: '10px',
1391
+ right: '10px',
1392
+ zIndex: 20,
1393
+ border: '1px solid var(--border-color)',
1394
+ background: 'var(--bg-secondary)'
1395
+ }, children: _jsx(PanelRightOpen, { size: 16 }) }))] }), groupBy === 'hierarchy' && (_jsxs("aside", { className: styles.appScrollbar, style: {
1396
+ position: 'absolute',
1397
+ top: 0,
1398
+ right: 0,
1399
+ bottom: 0,
1400
+ width: '320px',
1401
+ minWidth: '320px',
1402
+ borderLeft: '1px solid var(--border-color)',
1403
+ background: 'var(--bg-secondary)',
1404
+ padding: '12px',
1405
+ display: 'flex',
1406
+ flexDirection: 'column',
1407
+ gap: '12px',
1408
+ zIndex: 40,
1409
+ overflowY: 'auto',
1410
+ transform: hierarchySidebarOpen ? 'translateX(0)' : 'translateX(108%)',
1411
+ pointerEvents: hierarchySidebarOpen ? 'auto' : 'none',
1412
+ transition: 'transform 220ms cubic-bezier(0.4, 0, 0.2, 1)',
1413
+ willChange: 'transform'
1414
+ }, 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: {
1415
+ border: '1px solid var(--border-color)',
1416
+ borderRadius: '10px',
1417
+ padding: '10px',
1418
+ display: 'flex',
1419
+ flexDirection: 'column',
1420
+ gap: '8px'
1421
+ }, 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) => {
1422
+ if (e.key === 'Enter') {
1423
+ e.preventDefault();
1424
+ applyHierarchySearchSelection();
1425
+ }
1426
+ } }), _jsx("div", { style: {
1427
+ border: '1px solid var(--border-color)',
1428
+ borderRadius: '8px',
1429
+ padding: '8px',
1430
+ display: 'flex',
1431
+ flexDirection: 'column',
1432
+ gap: '6px',
1433
+ minHeight: '190px'
1434
+ }, children: hierarchyRootSearch.trim().length > 0 ? (_jsxs("div", { className: styles.appScrollbar, style: {
1435
+ maxHeight: '180px',
1436
+ minHeight: '180px',
1437
+ overflowY: 'auto',
1438
+ border: '1px solid var(--border-color)',
1439
+ borderRadius: '8px',
1440
+ padding: '6px',
1441
+ display: 'flex',
1442
+ flexDirection: 'column',
1443
+ gap: '6px'
1444
+ }, children: [hierarchySearchMatches.slice(0, 24).map((task) => (_jsxs("button", { type: "button", className: styles.headerActionBtn, onClick: () => {
1445
+ setHierarchyRootTaskId(task.id);
1446
+ setHierarchyRootSearch('');
1447
+ if (parentTaskFilterMode === 'unlinked') {
1448
+ setParentTaskFilterMode('all');
1449
+ }
1450
+ }, 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: () => {
1451
+ setHierarchyRootTaskId('');
1452
+ setHierarchyRootSearch('');
1453
+ }, 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
1454
+ ? hierarchyVisibleLevelCounts.map((entry) => `L${entry.level}: ${entry.count}`).join(' • ')
1455
+ : 'No linked hierarchy tasks in current filters.' })] })) })] }), _jsxs("div", { style: {
1456
+ border: '1px solid var(--border-color)',
1457
+ borderRadius: '10px',
1458
+ padding: '10px',
1459
+ display: 'flex',
1460
+ flexDirection: 'column',
1461
+ gap: '8px'
1462
+ }, 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) => {
1463
+ const next = e.target.checked;
1464
+ setHierarchyShowUnlinkedColumn(next);
1465
+ if (!next && parentTaskFilterMode === 'unlinked') {
1466
+ setParentTaskFilterMode('all');
1467
+ }
1468
+ } }), "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: {
1469
+ position: 'absolute',
1470
+ top: 0,
1471
+ right: 0,
1472
+ bottom: 0,
1473
+ width: '320px',
1474
+ minWidth: '320px',
1475
+ borderLeft: '1px solid var(--border-color)',
1476
+ background: 'var(--bg-secondary)',
1477
+ padding: '12px',
1478
+ display: 'flex',
1479
+ flexDirection: 'column',
1480
+ gap: '12px',
1481
+ zIndex: 40,
1482
+ overflowY: 'auto',
1483
+ transform: scheduleSidebarOpen ? 'translateX(0)' : 'translateX(108%)',
1484
+ pointerEvents: scheduleSidebarOpen ? 'auto' : 'none',
1485
+ transition: 'transform 220ms cubic-bezier(0.4, 0, 0.2, 1)',
1486
+ willChange: 'transform'
1487
+ }, 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: {
1488
+ border: '1px solid var(--border-color)',
1489
+ borderRadius: '10px',
1490
+ padding: '10px'
1491
+ }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }, children: [_jsx("button", { className: styles.headerActionBtn, onClick: () => {
1492
+ const prev = new Date(scheduleMonthDate);
1493
+ prev.setMonth(scheduleMonthDate.getMonth() - 1);
1494
+ setScheduleCalendarMonth(`${prev.getFullYear()}-${String(prev.getMonth() + 1).padStart(2, '0')}`);
1495
+ }, title: "Previous month", children: _jsx(ChevronLeft, { size: 14 }) }), _jsx("div", { style: { fontSize: '12px', fontWeight: 600 }, children: scheduleCalendarMonthLabel }), _jsx("button", { className: styles.headerActionBtn, onClick: () => {
1496
+ const next = new Date(scheduleMonthDate);
1497
+ next.setMonth(scheduleMonthDate.getMonth() + 1);
1498
+ setScheduleCalendarMonth(`${next.getFullYear()}-${String(next.getMonth() + 1).padStart(2, '0')}`);
1499
+ }, 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) => {
1500
+ const dateOnly = toDateOnlyLocal(date);
1501
+ const isOutsideMonth = date.getMonth() !== scheduleMonthDate.getMonth();
1502
+ const weekStartForCell = startOfWeek(date, globalWeekStartsOn);
1503
+ const isSelectedWeek = toDateOnlyLocal(weekStartForCell) === toDateOnlyLocal(scheduleWeekStart);
1504
+ const isSelectedDate = dateOnly === scheduleSelectedDate;
1505
+ const isToday = dateOnly === todayDateOnly;
1506
+ const isPast = dateOnly < todayDateOnly;
1507
+ const signal = calendarTaskSignals.get(dateOnly);
1508
+ const hasScheduledTasks = !!signal;
1509
+ const hasExpiredScheduledTasks = !!signal?.hasExpired;
1510
+ return (_jsxs("button", { onClick: () => {
1511
+ setScheduleSelectedDate(dateOnly);
1512
+ setScheduleCalendarMonth(`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`);
1513
+ }, style: {
1514
+ border: isSelectedDate
1515
+ ? '1px solid #2563eb'
1516
+ : isToday
1517
+ ? '1px solid rgba(245, 158, 11, 0.95)'
1518
+ : '1px solid transparent',
1519
+ borderRadius: '8px',
1520
+ padding: '6px 0',
1521
+ fontSize: '12px',
1522
+ position: 'relative',
1523
+ background: isSelectedDate
1524
+ ? 'rgba(59,130,246,0.22)'
1525
+ : isToday
1526
+ ? 'rgba(245,158,11,0.16)'
1527
+ : isSelectedWeek
1528
+ ? 'rgba(59,130,246,0.18)'
1529
+ : 'transparent',
1530
+ color: isOutsideMonth ? 'var(--text-tertiary)' : 'var(--text-primary)',
1531
+ cursor: 'pointer',
1532
+ fontWeight: isSelectedDate || isToday ? 700 : 500,
1533
+ opacity: isPast ? 0.42 : 1
1534
+ }, title: `${isToday ? `${dateOnly} (Today)` : dateOnly}`
1535
+ + (hasScheduledTasks ? ` • ${signal?.count} scheduled` : '')
1536
+ + (hasExpiredScheduledTasks ? ' • includes expired' : ''), children: [date.getDate(), hasScheduledTasks && (_jsx("span", { style: {
1537
+ position: 'absolute',
1538
+ left: '50%',
1539
+ bottom: '3px',
1540
+ transform: 'translateX(-50%)',
1541
+ width: '5px',
1542
+ height: '5px',
1543
+ borderRadius: '999px',
1544
+ background: hasExpiredScheduledTasks ? '#ef4444' : '#3b82f6',
1545
+ opacity: isOutsideMonth ? 0.6 : 0.95
1546
+ } }))] }, dateOnly));
1547
+ }) }), _jsx("div", { style: { display: 'flex', justifyContent: 'space-between', marginTop: '10px' }, children: _jsx("button", { className: styles.headerActionBtn, onClick: () => {
1548
+ const now = new Date();
1549
+ setScheduleSelectedDate(toDateOnlyLocal(now));
1550
+ setScheduleCalendarMonth(`${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`);
1551
+ }, 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: {
1552
+ border: '1px solid var(--border-color)',
1553
+ borderRadius: '10px',
1554
+ padding: '10px',
1555
+ display: 'flex',
1556
+ flexDirection: 'column',
1557
+ gap: '8px'
1558
+ }, 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) => {
1559
+ setSettingsSection(section);
1560
+ setActiveTab('settings');
1561
+ }, onSubmit: handleSubmit, commentsEndRef: commentsEndRef, onAddComment: () => handleAddComment(newCommentText), onSetParentForCurrentTask: handleSetParentForCurrentTask, onAddChildToCurrentTask: handleAddChildToCurrentTask, onCreateChildForCurrentTask: handleCreateChildForCurrentTask, onUnlinkChildTask: handleUnlinkChildTask, onReorderChildTasks: handleReorderChildTasks, onOpenTaskById: handleOpenTaskById, onAddContextFile: (file) => {
1562
+ setScreenshots(prev => [...prev, file]);
1563
+ }, onBrowseContextLinkPath: () => {
1564
+ setBrowserTarget({ type: 'context-link' });
1565
+ fetchFolders('');
1566
+ setShowFolderBrowser(true);
1567
+ }, onRemoveContextFile: async (index) => {
1568
+ const screenshot = screenshots[index];
1569
+ const path = typeof screenshot === 'string' ? screenshot : screenshot.path;
1570
+ setScreenshots(prev => prev.filter((_, i) => i !== index));
1571
+ if (typeof path === 'string' && path.startsWith('/api/taskforce/context/')) {
1572
+ try {
1573
+ await fetch(path, { method: 'DELETE' });
1574
+ }
1575
+ catch { }
1576
+ }
1577
+ }, onUpdateContextCaption: (index, caption) => {
1578
+ setScreenshots(prev => prev.map((s, i) => {
1579
+ if (i !== index)
1580
+ return s;
1581
+ if (typeof s === 'string')
1582
+ return { path: s, caption, timestamp: new Date().toISOString() };
1583
+ return { ...s, caption };
1584
+ }));
1585
+ }, 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: () => {
1586
+ setShowHelpModal(false);
1587
+ setActiveTab('settings');
1588
+ setSettingsSection('general');
1589
+ }, children: "Open Settings" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowHelpModal(false), children: "Close Tutorial" })] })] }) }), _jsx(Modal, { isOpen: showCloudSignInModal, onClose: closeCloudSignInModal, title: "Sign-In", 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: "Sign in with your cloud account to enable sync for local runtime." }), !cloudAuthConfigured && (_jsx("p", { className: styles.loginError, children: "Cloud auth endpoint is not configured." })), _jsx("input", { className: styles.input, type: "email", value: cloudSignInEmail, placeholder: "Email", onChange: (event) => setCloudSignInEmail(event.target.value), disabled: cloudSignInBusy }), _jsx("input", { className: styles.input, type: "password", value: cloudSignInPassword, placeholder: "Password", onChange: (event) => setCloudSignInPassword(event.target.value), disabled: cloudSignInBusy }), cloudSignInError && _jsx("p", { className: styles.loginError, children: cloudSignInError }), _jsxs("div", { className: modalStyles.formActions, style: { display: 'flex', justifyContent: 'flex-end', gap: '10px' }, children: [_jsx("button", { className: styles.cancelBtn, onClick: closeCloudSignInModal, disabled: cloudSignInBusy, children: "Close" }), _jsx("button", { className: styles.submitBtn, disabled: cloudSignInBusy || !cloudSignInEmail.trim() || !cloudSignInPassword.trim(), onClick: async () => {
1590
+ setCloudSignInBusy(true);
1591
+ setCloudSignInError(null);
1592
+ const result = await loginWithCredentials(cloudSignInEmail, cloudSignInPassword);
1593
+ if (!result.success) {
1594
+ setCloudSignInError(result.error || 'Sign in failed.');
1595
+ setCloudSignInBusy(false);
1596
+ return;
1597
+ }
1598
+ if (await routeToWorkspaceSetupIfNeeded(false)) {
1599
+ setCloudSignInBusy(false);
1600
+ return;
1601
+ }
1602
+ setCloudSignInBusy(false);
1603
+ setShowCloudSignInModal(false);
1604
+ }, children: cloudSignInBusy ? 'Signing in...' : 'Sign In' })] })] }) }), _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'
1605
+ ? (authRequiredForApi
1606
+ ? (isAuthenticated ? 'Signed in. API access is enabled.' : 'Cloud mode requires sign in before app usage.')
1607
+ : 'Cloud runtime with optional authentication.')
1608
+ : 'Local runtime allows guest usage without sign 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: "Workspace Settings" }), _jsx("p", { style: { margin: '4px 0 0 0', color: 'var(--text-secondary)' }, children: "Existing project settings remain fully available." })] }), _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
1609
+ ? (workspaceCloudSyncEnabled
1610
+ ? `Enabled${workspaceSyncSourceOfTruth ? ` · Source: ${workspaceSyncSourceOfTruth}` : ''}`
1611
+ : 'Disabled')
1612
+ : '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 () => {
1613
+ if (!canManageWorkspaceSync)
1614
+ return;
1615
+ setWorkspaceSyncError(null);
1616
+ if (!workspaceCloudSyncEnabled) {
1617
+ setWorkspaceSyncBusy(true);
1618
+ const result = await saveWorkspaceCloudSyncSettings({
1619
+ enabled: true,
1620
+ // I6: start false; the orchestrator marks it true after first successful pull.
1621
+ onboardingCompleted: false
1622
+ });
1623
+ if (!result.success)
1624
+ setWorkspaceSyncError(result.error || 'Failed to enable sync.');
1625
+ setWorkspaceSyncBusy(false);
1626
+ return;
1627
+ }
1628
+ setWorkspaceSyncBusy(true);
1629
+ const result = await saveWorkspaceCloudSyncSettings({
1630
+ enabled: false,
1631
+ sourceOfTruth: workspaceSyncSourceOfTruth,
1632
+ onboardingCompleted: workspaceSyncOnboardingCompleted
1633
+ });
1634
+ if (!result.success)
1635
+ setWorkspaceSyncError(result.error || 'Failed to disable sync.');
1636
+ setWorkspaceSyncBusy(false);
1637
+ }, 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: () => {
1638
+ setShowAccountHub(false);
1639
+ setShowHelpModal(true);
1640
+ }, children: "Help & Tutorial" }), _jsx("button", { className: styles.cancelBtn, onClick: () => {
1641
+ setShowAccountHub(false);
1642
+ setActiveTab('settings');
1643
+ setSettingsSection('general');
1644
+ }, children: "Workspace Settings" }), _jsx("button", { className: styles.submitBtn, onClick: () => setShowAccountHub(false), children: "Close" })] })] }) }), _jsx(Modal, { isOpen: showSyncStatusModal, onClose: () => {
1645
+ setShowSyncStatusModal(false);
1646
+ }, 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: {
1647
+ borderColor: modalSyncStatusMeta.border,
1648
+ background: modalSyncStatusMeta.background,
1649
+ color: modalSyncStatusMeta.color
1650
+ }, 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: () => {
1651
+ setShowSyncStatusModal(false);
1652
+ openCloudSignIn();
1653
+ }, children: "Sign-In" })), _jsx("button", { className: styles.cancelBtn, onClick: () => {
1654
+ setShowSyncStatusModal(false);
1655
+ }, 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: () => {
1656
+ const parts = currentBrowsePath.split('/').filter(Boolean);
1657
+ parts.pop();
1658
+ fetchFolders(parts.length ? parts.join('/') + '/' : '');
1659
+ }, 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' }, 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: [_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 })] }) })] }));
1660
+ }