@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,1004 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState, useRef, useEffect, useCallback } from 'react';
3
+ import { DndContext, DragOverlay, closestCorners, KeyboardSensor, PointerSensor, useSensor, useSensors, pointerWithin, defaultDropAnimationSideEffects } from '@dnd-kit/core';
4
+ import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
5
+ import { CSS } from '@dnd-kit/utilities';
6
+ import { createPortal } from 'react-dom';
7
+ import styles from '../../Taskforce.module.css';
8
+ import { TaskCard } from './TaskCard';
9
+ import { isDropInCenterZone } from '../../utils/taskDragZones';
10
+ import { shouldPromptReparent, wouldCreateParentCycle } from '../../utils/taskParenting';
11
+ import { buildHierarchyDepthMap } from '../../utils/taskHierarchy';
12
+ import { sortChildTasksForDisplay } from '../../utils/taskRelations';
13
+ import * as Icons from 'lucide-react';
14
+ import { IconMap, resolveColor } from '../../utils/constants';
15
+ export function TaskKanban({ tasks, columns: kanbanColumns, groupBy, searchQuery, copiedId, availableSpecialists, taxonomies, priorities, approaches, onUpdateTask, onTaskClick, onOpenTaskById, onCopyId, onToggleInProgress, onToggleReview, onToggleComplete, onToggleCancel, onArchiveTask, onUnarchive, onDelete, onUnlinkChildTask, onClearParentTask, allTasks = [], hierarchyTasks = [], onAddTaskToColumn, emptyColumnMode = 'show', filterCategories = [], filterTypes = [], filterPriorities = [], filterStatus = [], filterAssignees = ['agent', 'user', 'unassigned'], assigneeFilterEnabled = false, scheduleFilteredTaskIds, categories = [], compressed = false, recentlyChangedTaskIds = [], scheduleDates, onScheduleDaySelected, onScheduleParentDrop, hierarchyChildOrderMode = 'manual', persistedScrollLeft, onScrollLeftChange, sortBy = 'created', sortOrder = 'desc' }) {
16
+ const [activeId, setActiveId] = useState(null);
17
+ const [recentlyLinkedParentIds, setRecentlyLinkedParentIds] = useState([]);
18
+ const [currentParentDropTargetId, setCurrentParentDropTargetId] = useState(null);
19
+ const [isHeaderPanning, setIsHeaderPanning] = useState(false);
20
+ const [headerPanEnabled, setHeaderPanEnabled] = useState(false);
21
+ const changedTaskIdSet = useMemo(() => new Set(recentlyChangedTaskIds), [recentlyChangedTaskIds]);
22
+ const taskIdSet = useMemo(() => new Set(tasks.map(t => t.id)), [tasks]);
23
+ const scheduleFilteredTaskIdSet = useMemo(() => new Set(scheduleFilteredTaskIds || []), [scheduleFilteredTaskIds]);
24
+ const linkFeedbackTimersRef = useRef(new Map());
25
+ // Sensors
26
+ const sensors = useSensors(useSensor(PointerSensor, {
27
+ activationConstraint: {
28
+ distance: 5, // Prevent accidental drags
29
+ },
30
+ }), useSensor(KeyboardSensor, {
31
+ coordinateGetter: sortableKeyboardCoordinates,
32
+ }));
33
+ // Helper to determine status string from task flags
34
+ const getTaskStatus = (task) => {
35
+ return task.status || 'task';
36
+ };
37
+ const todayDateOnly = useMemo(() => {
38
+ const now = new Date();
39
+ const year = now.getFullYear();
40
+ const month = String(now.getMonth() + 1).padStart(2, '0');
41
+ const day = String(now.getDate()).padStart(2, '0');
42
+ return `${year}-${month}-${day}`;
43
+ }, []);
44
+ const scheduleContext = useMemo(() => {
45
+ const parseToIsoWeekKey = (dateOnly) => {
46
+ const match = dateOnly.match(/^(\d{4})-(\d{2})-(\d{2})$/);
47
+ if (!match)
48
+ return null;
49
+ const dt = new Date(Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])));
50
+ const day = dt.getUTCDay() || 7;
51
+ dt.setUTCDate(dt.getUTCDate() + 4 - day);
52
+ const yearStart = new Date(Date.UTC(dt.getUTCFullYear(), 0, 1));
53
+ const week = Math.ceil((((dt.getTime() - yearStart.getTime()) / 86400000) + 1) / 7);
54
+ return `${dt.getUTCFullYear()}-W${String(week).padStart(2, '0')}`;
55
+ };
56
+ const fallbackDates = (() => {
57
+ const now = new Date();
58
+ const currentDay = now.getDay();
59
+ const mondayDelta = currentDay === 0 ? -6 : 1 - currentDay;
60
+ const monday = new Date(now);
61
+ monday.setDate(now.getDate() + mondayDelta);
62
+ const format = (date) => {
63
+ const y = date.getFullYear();
64
+ const m = String(date.getMonth() + 1).padStart(2, '0');
65
+ const d = String(date.getDate()).padStart(2, '0');
66
+ return `${y}-${m}-${d}`;
67
+ };
68
+ const dates = {
69
+ mon: '',
70
+ tue: '',
71
+ wed: '',
72
+ thu: '',
73
+ fri: '',
74
+ sat: '',
75
+ sun: ''
76
+ };
77
+ const keys = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
78
+ keys.forEach((key, index) => {
79
+ const next = new Date(monday);
80
+ next.setDate(monday.getDate() + index);
81
+ dates[key] = format(next);
82
+ });
83
+ return dates;
84
+ })();
85
+ const dates = {
86
+ ...fallbackDates,
87
+ ...(scheduleDates || {})
88
+ };
89
+ return { dates, parseToIsoWeekKey };
90
+ }, [scheduleDates]);
91
+ const getScheduleColumnValue = (task) => {
92
+ const scheduledDate = task.scheduledDate || null;
93
+ if (!scheduledDate)
94
+ return 'backlog';
95
+ const dateToColumn = new Map([
96
+ [scheduleContext.dates.mon, 'mon'],
97
+ [scheduleContext.dates.tue, 'tue'],
98
+ [scheduleContext.dates.wed, 'wed'],
99
+ [scheduleContext.dates.thu, 'thu'],
100
+ [scheduleContext.dates.fri, 'fri'],
101
+ [scheduleContext.dates.sat, 'sat'],
102
+ [scheduleContext.dates.sun, 'sun']
103
+ ]);
104
+ return dateToColumn.get(scheduledDate) || '__offweek';
105
+ };
106
+ const hierarchyDepthSource = hierarchyTasks.length > 0
107
+ ? hierarchyTasks
108
+ : (allTasks.length > 0 ? allTasks : tasks);
109
+ const hierarchyDepthMap = useMemo(() => buildHierarchyDepthMap(hierarchyDepthSource), [hierarchyDepthSource]);
110
+ const hierarchyParentIdSet = useMemo(() => {
111
+ const set = new Set();
112
+ for (const task of hierarchyDepthSource) {
113
+ if (task.parentTaskId)
114
+ set.add(task.parentTaskId);
115
+ }
116
+ return set;
117
+ }, [hierarchyDepthSource]);
118
+ const childrenByParentId = useMemo(() => {
119
+ const map = new Map();
120
+ for (const task of hierarchyDepthSource) {
121
+ if (!task.parentTaskId)
122
+ continue;
123
+ const list = map.get(task.parentTaskId) || [];
124
+ list.push(task);
125
+ map.set(task.parentTaskId, list);
126
+ }
127
+ return map;
128
+ }, [hierarchyDepthSource]);
129
+ const getHierarchyColumnValue = (task) => {
130
+ const isStandalone = !task.parentTaskId && !hierarchyParentIdSet.has(task.id);
131
+ if (isStandalone)
132
+ return 'standalone';
133
+ return String((hierarchyDepthMap.get(task.id) ?? 0) + 1);
134
+ };
135
+ const columnLookup = useMemo(() => {
136
+ const map = new Map();
137
+ for (const column of kanbanColumns) {
138
+ map.set(String(column.value), column);
139
+ if (!map.has(column.label)) {
140
+ map.set(column.label, column);
141
+ }
142
+ }
143
+ return map;
144
+ }, [kanbanColumns]);
145
+ // Totals for "X / Y" display
146
+ const columnTotals = useMemo(() => {
147
+ const counts = {};
148
+ kanbanColumns.forEach(col => { counts[String(col.value)] = 0; });
149
+ const totalsSource = groupBy === 'hierarchy' ? tasks : allTasks;
150
+ totalsSource.forEach(task => {
151
+ let stringValue = '';
152
+ if (groupBy === 'status') {
153
+ stringValue = getTaskStatus(task);
154
+ }
155
+ else if (groupBy === 'schedule') {
156
+ stringValue = getScheduleColumnValue(task);
157
+ }
158
+ else if (groupBy === 'hierarchy') {
159
+ stringValue = getHierarchyColumnValue(task);
160
+ }
161
+ else {
162
+ const rawValue = task[groupBy];
163
+ stringValue = String(rawValue ?? '');
164
+ }
165
+ const matchedColumn = columnLookup.get(stringValue);
166
+ if (matchedColumn) {
167
+ counts[String(matchedColumn.value)]++;
168
+ }
169
+ if (groupBy === 'schedule') {
170
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
171
+ const isExpired = !!task.scheduledDate && !isTerminal && task.scheduledDate < todayDateOnly;
172
+ if (isExpired && columnLookup.has('expired')) {
173
+ counts.expired = (counts.expired || 0) + 1;
174
+ }
175
+ }
176
+ });
177
+ return counts;
178
+ }, [allTasks, tasks, kanbanColumns, groupBy, hierarchyDepthMap, columnLookup, hierarchyParentIdSet, todayDateOnly]);
179
+ // Group items by the groupBy field
180
+ const groupedTasks = useMemo(() => {
181
+ const groups = {};
182
+ // Initialize all columns
183
+ kanbanColumns.forEach(col => {
184
+ groups[String(col.value)] = [];
185
+ });
186
+ // Distribute tasks
187
+ tasks.forEach(task => {
188
+ let stringValue = '';
189
+ if (groupBy === 'status') {
190
+ stringValue = getTaskStatus(task);
191
+ }
192
+ else if (groupBy === 'schedule') {
193
+ stringValue = getScheduleColumnValue(task);
194
+ }
195
+ else if (groupBy === 'hierarchy') {
196
+ stringValue = getHierarchyColumnValue(task);
197
+ }
198
+ else {
199
+ const rawValue = task[groupBy];
200
+ // Fix: Use ?? to allow 0 as a valid value
201
+ stringValue = String(rawValue ?? '');
202
+ }
203
+ // Find matching column by value or label
204
+ const matchedColumn = columnLookup.get(stringValue);
205
+ if (matchedColumn) {
206
+ if (groupBy === 'schedule') {
207
+ const columnValue = String(matchedColumn.value);
208
+ const shouldApplyFilter = columnValue === 'backlog' || columnValue === 'expired';
209
+ if (shouldApplyFilter && !scheduleFilteredTaskIdSet.has(task.id)) {
210
+ return;
211
+ }
212
+ }
213
+ groups[String(matchedColumn.value)].push(task);
214
+ }
215
+ if (groupBy === 'schedule') {
216
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
217
+ const isExpired = !!task.scheduledDate && !isTerminal && task.scheduledDate < todayDateOnly;
218
+ if (isExpired && groups.expired) {
219
+ if (scheduleFilteredTaskIdSet.has(task.id)) {
220
+ groups.expired.push(task);
221
+ }
222
+ }
223
+ }
224
+ });
225
+ if (groupBy === 'schedule') {
226
+ const sortByScheduleOrder = (a, b) => {
227
+ const orderA = typeof a.orderInDay === 'number' ? a.orderInDay : Number.MAX_SAFE_INTEGER;
228
+ const orderB = typeof b.orderInDay === 'number' ? b.orderInDay : Number.MAX_SAFE_INTEGER;
229
+ if (orderA !== orderB)
230
+ return orderA - orderB;
231
+ return String(a.createdAt || '').localeCompare(String(b.createdAt || ''));
232
+ };
233
+ const compareByHeaderSort = (a, b) => {
234
+ let comparison = 0;
235
+ if (sortBy === 'created') {
236
+ comparison = new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
237
+ }
238
+ else if (sortBy === 'updated') {
239
+ const aUpdated = a.updatedAt || a.createdAt;
240
+ const bUpdated = b.updatedAt || b.createdAt;
241
+ comparison = new Date(bUpdated).getTime() - new Date(aUpdated).getTime();
242
+ }
243
+ else if (sortBy === 'priority') {
244
+ const priorityA = typeof a.priority === 'number' ? a.priority : 2;
245
+ const priorityB = typeof b.priority === 'number' ? b.priority : 2;
246
+ comparison = priorityB - priorityA;
247
+ if (comparison === 0) {
248
+ comparison = new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
249
+ }
250
+ }
251
+ else if (sortBy === 'complexity') {
252
+ const complexityA = typeof a.complexity === 'number' ? a.complexity : 3;
253
+ const complexityB = typeof b.complexity === 'number' ? b.complexity : 3;
254
+ comparison = complexityB - complexityA;
255
+ if (comparison === 0) {
256
+ comparison = new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
257
+ }
258
+ }
259
+ return sortOrder === 'desc' ? comparison : -comparison;
260
+ };
261
+ Object.keys(groups).forEach((key) => {
262
+ if (key === 'backlog' || key === 'expired') {
263
+ groups[key].sort(compareByHeaderSort);
264
+ return;
265
+ }
266
+ groups[key].sort(sortByScheduleOrder);
267
+ });
268
+ }
269
+ else if (groupBy === 'hierarchy' && hierarchyChildOrderMode === 'manual') {
270
+ const depthSource = hierarchyDepthSource;
271
+ const sourceIndexById = new Map();
272
+ depthSource.forEach((task, index) => sourceIndexById.set(task.id, index));
273
+ const sourceTaskById = new Map();
274
+ depthSource.forEach((task) => sourceTaskById.set(task.id, task));
275
+ const sourceChildrenByParent = new Map();
276
+ depthSource.forEach((task) => {
277
+ if (!task.parentTaskId)
278
+ return;
279
+ const siblings = sourceChildrenByParent.get(task.parentTaskId) || [];
280
+ siblings.push(task);
281
+ sourceChildrenByParent.set(task.parentTaskId, siblings);
282
+ });
283
+ sourceChildrenByParent.forEach((siblings, parentId) => {
284
+ sourceChildrenByParent.set(parentId, sortChildTasksForDisplay(siblings));
285
+ });
286
+ const roots = depthSource.filter((task) => !task.parentTaskId || !sourceTaskById.has(task.parentTaskId));
287
+ roots.sort((a, b) => {
288
+ const idxA = sourceIndexById.get(a.id) ?? Number.MAX_SAFE_INTEGER;
289
+ const idxB = sourceIndexById.get(b.id) ?? Number.MAX_SAFE_INTEGER;
290
+ return idxA - idxB;
291
+ });
292
+ const pathByTaskId = new Map();
293
+ const visited = new Set();
294
+ const assignPaths = (task, path) => {
295
+ if (visited.has(task.id))
296
+ return;
297
+ visited.add(task.id);
298
+ pathByTaskId.set(task.id, path);
299
+ const children = sourceChildrenByParent.get(task.id) || [];
300
+ children.forEach((child, index) => {
301
+ assignPaths(child, [...path, index]);
302
+ });
303
+ };
304
+ roots.forEach((root, index) => {
305
+ assignPaths(root, [index]);
306
+ });
307
+ depthSource.forEach((task) => {
308
+ if (!pathByTaskId.has(task.id)) {
309
+ const fallbackIndex = sourceIndexById.get(task.id) ?? Number.MAX_SAFE_INTEGER;
310
+ pathByTaskId.set(task.id, [Number.MAX_SAFE_INTEGER, fallbackIndex]);
311
+ }
312
+ });
313
+ const compareHierarchyPath = (a, b) => {
314
+ const pathA = pathByTaskId.get(a.id) || [Number.MAX_SAFE_INTEGER];
315
+ const pathB = pathByTaskId.get(b.id) || [Number.MAX_SAFE_INTEGER];
316
+ const maxLen = Math.max(pathA.length, pathB.length);
317
+ for (let i = 0; i < maxLen; i += 1) {
318
+ const segA = pathA[i] ?? -1;
319
+ const segB = pathB[i] ?? -1;
320
+ if (segA !== segB)
321
+ return segA - segB;
322
+ }
323
+ const idxA = sourceIndexById.get(a.id) ?? Number.MAX_SAFE_INTEGER;
324
+ const idxB = sourceIndexById.get(b.id) ?? Number.MAX_SAFE_INTEGER;
325
+ return idxA - idxB;
326
+ };
327
+ Object.keys(groups).forEach((key) => {
328
+ groups[key].sort(compareHierarchyPath);
329
+ });
330
+ }
331
+ return groups;
332
+ }, [tasks, kanbanColumns, groupBy, hierarchyDepthMap, columnLookup, hierarchyParentIdSet, scheduleFilteredTaskIdSet, todayDateOnly, hierarchyChildOrderMode, hierarchyDepthSource, sortBy, sortOrder]);
333
+ const activeTaskOverlay = activeId ? tasks.find(t => t.id === activeId) : null;
334
+ // Scroll Syncing Refs and Logic
335
+ const containerRef = useRef(null);
336
+ const topScrollRef = useRef(null);
337
+ const panStateRef = useRef({
338
+ pointerId: null,
339
+ startX: 0,
340
+ startY: 0,
341
+ startScrollLeft: 0,
342
+ didPan: false
343
+ });
344
+ const lastReportedScrollLeftRef = useRef(-1);
345
+ // Calculate total width for scroll syncing
346
+ const getIsColumnHidden = (columnValue, title) => {
347
+ if (groupBy === 'category')
348
+ return !filterCategories.includes(String(columnValue));
349
+ if (groupBy === 'type')
350
+ return !filterTypes.includes(String(columnValue));
351
+ if (groupBy === 'priority')
352
+ return !filterPriorities.some(p => Number(p) === Number(columnValue));
353
+ if (groupBy === 'status')
354
+ return !filterStatus.includes(String(columnValue));
355
+ if (groupBy === 'assignee')
356
+ return assigneeFilterEnabled && !filterAssignees.includes(String(columnValue));
357
+ return false;
358
+ };
359
+ const totalWidth = useMemo(() => {
360
+ const GAP = 4;
361
+ const COL_WIDTH = 320;
362
+ const COL_COLLAPSED = 72;
363
+ let width = 0;
364
+ kanbanColumns.forEach((col, index) => {
365
+ const items = groupedTasks[String(col.value)] || [];
366
+ const isExplicitlyHidden = getIsColumnHidden(col.value, col.label);
367
+ const isAutoHidden = groupBy !== 'schedule' && emptyColumnMode === 'hide' && items.length === 0;
368
+ if (isExplicitlyHidden || isAutoHidden)
369
+ return;
370
+ const isCollapsed = emptyColumnMode === 'collapse' && items.length === 0;
371
+ width += isCollapsed ? COL_COLLAPSED : COL_WIDTH;
372
+ width += GAP;
373
+ });
374
+ return width;
375
+ }, [kanbanColumns, groupedTasks, emptyColumnMode, groupBy, filterCategories, filterTypes, filterPriorities, filterStatus]);
376
+ const emitScrollLeft = useCallback((scrollLeft) => {
377
+ if (!onScrollLeftChange)
378
+ return;
379
+ const normalized = Math.max(0, Math.round(scrollLeft));
380
+ if (normalized === lastReportedScrollLeftRef.current)
381
+ return;
382
+ lastReportedScrollLeftRef.current = normalized;
383
+ onScrollLeftChange(normalized);
384
+ }, [onScrollLeftChange]);
385
+ // Sync scroll handlers
386
+ useEffect(() => {
387
+ const bottom = containerRef.current;
388
+ const top = topScrollRef.current;
389
+ if (!bottom || !top)
390
+ return;
391
+ let isSyncingTop = false;
392
+ let isSyncingBottom = false;
393
+ const handleScrollTop = () => {
394
+ if (!bottom || isSyncingBottom)
395
+ return;
396
+ isSyncingTop = true;
397
+ bottom.scrollLeft = top.scrollLeft;
398
+ emitScrollLeft(top.scrollLeft);
399
+ setTimeout(() => isSyncingTop = false, 0); // Release lock
400
+ };
401
+ const handleScrollBottom = () => {
402
+ if (!top || isSyncingTop)
403
+ return;
404
+ isSyncingBottom = true;
405
+ top.scrollLeft = bottom.scrollLeft;
406
+ emitScrollLeft(bottom.scrollLeft);
407
+ setTimeout(() => isSyncingBottom = false, 0);
408
+ };
409
+ top.addEventListener('scroll', handleScrollTop);
410
+ bottom.addEventListener('scroll', handleScrollBottom);
411
+ return () => {
412
+ top.removeEventListener('scroll', handleScrollTop);
413
+ bottom.removeEventListener('scroll', handleScrollBottom);
414
+ };
415
+ }, [emitScrollLeft]);
416
+ const hasAppliedInitialScrollRef = useRef(false);
417
+ useEffect(() => {
418
+ if (hasAppliedInitialScrollRef.current)
419
+ return;
420
+ if (typeof persistedScrollLeft !== 'number' || !Number.isFinite(persistedScrollLeft)) {
421
+ hasAppliedInitialScrollRef.current = true;
422
+ return;
423
+ }
424
+ const bottom = containerRef.current;
425
+ const top = topScrollRef.current;
426
+ if (!bottom || !top)
427
+ return;
428
+ const maxLeft = Math.max(0, bottom.scrollWidth - bottom.clientWidth);
429
+ const nextLeft = Math.min(Math.max(0, persistedScrollLeft), maxLeft);
430
+ bottom.scrollLeft = nextLeft;
431
+ top.scrollLeft = nextLeft;
432
+ hasAppliedInitialScrollRef.current = true;
433
+ }, [persistedScrollLeft, totalWidth]);
434
+ useEffect(() => {
435
+ const updatePanAvailability = () => {
436
+ const el = containerRef.current;
437
+ if (!el) {
438
+ setHeaderPanEnabled(false);
439
+ return;
440
+ }
441
+ setHeaderPanEnabled(el.scrollWidth > el.clientWidth + 4);
442
+ };
443
+ updatePanAvailability();
444
+ window.addEventListener('resize', updatePanAvailability);
445
+ return () => {
446
+ window.removeEventListener('resize', updatePanAvailability);
447
+ };
448
+ }, [totalWidth, kanbanColumns.length, groupedTasks]);
449
+ useEffect(() => {
450
+ const handlePointerMove = (event) => {
451
+ const pan = panStateRef.current;
452
+ if (pan.pointerId === null || pan.pointerId !== event.pointerId)
453
+ return;
454
+ const deltaX = event.clientX - pan.startX;
455
+ const deltaY = event.clientY - pan.startY;
456
+ if (!pan.didPan) {
457
+ if (Math.abs(deltaX) < 7 || Math.abs(deltaX) < Math.abs(deltaY))
458
+ return;
459
+ pan.didPan = true;
460
+ setIsHeaderPanning(true);
461
+ document.body.style.userSelect = 'none';
462
+ }
463
+ const nextLeft = pan.startScrollLeft - deltaX;
464
+ if (containerRef.current)
465
+ containerRef.current.scrollLeft = nextLeft;
466
+ if (topScrollRef.current)
467
+ topScrollRef.current.scrollLeft = nextLeft;
468
+ event.preventDefault();
469
+ };
470
+ const endPan = () => {
471
+ const pan = panStateRef.current;
472
+ if (pan.pointerId === null)
473
+ return;
474
+ pan.pointerId = null;
475
+ pan.didPan = false;
476
+ if (isHeaderPanning) {
477
+ setIsHeaderPanning(false);
478
+ document.body.style.userSelect = '';
479
+ }
480
+ };
481
+ window.addEventListener('pointermove', handlePointerMove);
482
+ window.addEventListener('pointerup', endPan);
483
+ window.addEventListener('pointercancel', endPan);
484
+ return () => {
485
+ window.removeEventListener('pointermove', handlePointerMove);
486
+ window.removeEventListener('pointerup', endPan);
487
+ window.removeEventListener('pointercancel', endPan);
488
+ document.body.style.userSelect = '';
489
+ };
490
+ }, [isHeaderPanning]);
491
+ useEffect(() => {
492
+ return () => {
493
+ linkFeedbackTimersRef.current.forEach((timerId) => window.clearTimeout(timerId));
494
+ linkFeedbackTimersRef.current.clear();
495
+ };
496
+ }, []);
497
+ const handleHeaderPointerDown = (event) => {
498
+ if (!headerPanEnabled)
499
+ return;
500
+ const target = event.target;
501
+ if (target?.closest('button, a, input, select, textarea, [role="button"], [data-no-header-pan="true"]')) {
502
+ return;
503
+ }
504
+ panStateRef.current.pointerId = event.pointerId;
505
+ panStateRef.current.startX = event.clientX;
506
+ panStateRef.current.startY = event.clientY;
507
+ panStateRef.current.startScrollLeft = containerRef.current?.scrollLeft || 0;
508
+ panStateRef.current.didPan = false;
509
+ };
510
+ const handleDragStart = (event) => {
511
+ setActiveId(event.active.id);
512
+ setCurrentParentDropTargetId(null);
513
+ };
514
+ const handleDragOver = (event) => {
515
+ if (groupBy === 'schedule') {
516
+ setCurrentParentDropTargetId(null);
517
+ return;
518
+ }
519
+ const { active, over } = event;
520
+ if (!over) {
521
+ setCurrentParentDropTargetId(null);
522
+ return;
523
+ }
524
+ const activeId = active.id;
525
+ const overId = over.id;
526
+ const activeTask = tasks.find(t => t.id === activeId);
527
+ const overTask = tasks.find(t => t.id === overId);
528
+ if (!activeTask || !overTask || activeTask.id === overTask.id) {
529
+ setCurrentParentDropTargetId(null);
530
+ return;
531
+ }
532
+ setCurrentParentDropTargetId(overTask.id);
533
+ };
534
+ const handleDragEnd = (event) => {
535
+ const { active, over } = event;
536
+ setActiveId(null);
537
+ setCurrentParentDropTargetId(null);
538
+ const activeId = String(active.id);
539
+ const overId = over ? String(over.id) : null;
540
+ const activeTask = tasks.find(t => t.id === activeId);
541
+ if (!activeTask || !overId)
542
+ return;
543
+ const overTask = tasks.find(t => t.id === overId);
544
+ if (groupBy !== 'schedule' && overTask && overTask.id !== activeTask.id) {
545
+ const overRect = over?.rect;
546
+ const translatedActiveRect = active.rect.current.translated;
547
+ const initialActiveRect = active.rect.current.initial;
548
+ const activeRect = translatedActiveRect || initialActiveRect;
549
+ if (overRect && activeRect) {
550
+ const droppedInCenterZone = isDropInCenterZone(overRect, activeRect);
551
+ if (droppedInCenterZone) {
552
+ if (activeTask.parentTaskId === overTask.id)
553
+ return;
554
+ const relationshipTasks = allTasks.length > 0 ? allTasks : tasks;
555
+ if (wouldCreateParentCycle(activeTask.id, overTask.id, relationshipTasks)) {
556
+ window.alert(`Cannot link child ${activeTask.id} to parent ${overTask.id}: link would create a cycle.`);
557
+ return;
558
+ }
559
+ if (shouldPromptReparent(activeTask.parentTaskId, overTask.id)) {
560
+ const confirmed = window.confirm(`Task ${activeTask.id} is already parented to ${activeTask.parentTaskId}. Re-parent it to ${overTask.id}?`);
561
+ if (!confirmed)
562
+ return;
563
+ }
564
+ onUpdateTask(activeTask.id, { parentTaskId: overTask.id });
565
+ setRecentlyLinkedParentIds((prev) => Array.from(new Set([...prev, overTask.id])));
566
+ const existing = linkFeedbackTimersRef.current.get(overTask.id);
567
+ if (existing)
568
+ window.clearTimeout(existing);
569
+ const timerId = window.setTimeout(() => {
570
+ linkFeedbackTimersRef.current.delete(overTask.id);
571
+ setRecentlyLinkedParentIds((prev) => prev.filter((id) => id !== overTask.id));
572
+ }, 1800);
573
+ linkFeedbackTimersRef.current.set(overTask.id, timerId);
574
+ return;
575
+ }
576
+ }
577
+ }
578
+ // Find the container (column) we dropped into
579
+ // If we dropped over a task, findContainer resolves to that task's column
580
+ // If we dropped over a column (empty or not), it resolves to the column ID
581
+ const sourceContainerId = findContainer(activeId);
582
+ const visualSourceContainerId = String(active?.data?.current?.sortable?.containerId || '');
583
+ const resolvedSourceContainerId = visualSourceContainerId || sourceContainerId;
584
+ const visualTargetContainerId = String(over?.data?.current?.sortable?.containerId || '');
585
+ const containerId = visualTargetContainerId || findContainer(overId);
586
+ if (!containerId)
587
+ return;
588
+ // Find the column definition
589
+ const column = columnLookup.get(containerId)
590
+ || kanbanColumns.find(c => String(c.value) === containerId || c.label === containerId);
591
+ if (column) {
592
+ if (groupBy === 'hierarchy') {
593
+ // Hierarchy columns are derived from relationships, not directly writable fields.
594
+ // Re-parenting remains card-to-card only.
595
+ return;
596
+ }
597
+ if (groupBy === 'schedule') {
598
+ const targetColumn = String(column.value);
599
+ const scheduleKeys = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
600
+ const isScheduleReorderColumn = targetColumn === 'backlog' || targetColumn === 'expired' || scheduleKeys.includes(targetColumn);
601
+ const buildInsertIndex = (targetTasks) => {
602
+ let insertAt = targetTasks.length;
603
+ const overTaskId = overTask?.id || (taskIdSet.has(overId) ? overId : null);
604
+ if (!overTaskId || !taskIdSet.has(overTaskId))
605
+ return insertAt;
606
+ const overIndex = targetTasks.findIndex(t => t.id === overTaskId);
607
+ if (overIndex < 0)
608
+ return insertAt;
609
+ const overRect = over?.rect;
610
+ const translatedActiveRect = active.rect.current.translated;
611
+ const activeRect = translatedActiveRect || active.rect.current.initial;
612
+ const isBelowOverItem = Boolean(overRect
613
+ && activeRect
614
+ && (activeRect.top + activeRect.height / 2) > (overRect.top + overRect.height / 2));
615
+ return overIndex + (isBelowOverItem ? 1 : 0);
616
+ };
617
+ const reindexSourceColumnAfterMove = (sourceColumnId, targetColumnId, pendingUpdates) => {
618
+ if (!sourceColumnId || sourceColumnId === targetColumnId)
619
+ return;
620
+ const sourceTasks = (groupedTasks[String(sourceColumnId)] || []).filter(t => t.id !== activeId);
621
+ sourceTasks.forEach((task, index) => {
622
+ pendingUpdates.set(task.id, {
623
+ ...(pendingUpdates.get(task.id) || {}),
624
+ orderInDay: index
625
+ });
626
+ });
627
+ };
628
+ if (targetColumn === 'expired') {
629
+ // Expired is virtual and source-only: allow reordering within Expired,
630
+ // but do not allow drops from other columns into Expired.
631
+ if (resolvedSourceContainerId !== 'expired')
632
+ return;
633
+ const pendingUpdates = new Map();
634
+ const targetTasks = (groupedTasks.expired || []).filter(t => t.id !== activeId);
635
+ const insertAt = buildInsertIndex(targetTasks);
636
+ const reorderedTarget = [...targetTasks];
637
+ reorderedTarget.splice(insertAt, 0, activeTask);
638
+ reorderedTarget.forEach((task, index) => {
639
+ pendingUpdates.set(task.id, {
640
+ ...(pendingUpdates.get(task.id) || {}),
641
+ orderInDay: index
642
+ });
643
+ });
644
+ pendingUpdates.forEach((updates, taskId) => onUpdateTask(taskId, updates));
645
+ return;
646
+ }
647
+ if (targetColumn === 'backlog') {
648
+ const pendingUpdates = new Map();
649
+ const targetTasks = (groupedTasks.backlog || []).filter(t => t.id !== activeId);
650
+ const insertAt = buildInsertIndex(targetTasks);
651
+ const reorderedTarget = [...targetTasks];
652
+ reorderedTarget.splice(insertAt, 0, activeTask);
653
+ reorderedTarget.forEach((task, index) => {
654
+ if (task.id === activeId) {
655
+ if (activeTask.scheduledDate) {
656
+ pendingUpdates.set(task.id, {
657
+ ...(pendingUpdates.get(task.id) || {}),
658
+ scheduledDate: null,
659
+ scheduledWeekKey: null,
660
+ orderInDay: index
661
+ });
662
+ }
663
+ else {
664
+ pendingUpdates.set(task.id, {
665
+ ...(pendingUpdates.get(task.id) || {}),
666
+ orderInDay: index
667
+ });
668
+ }
669
+ }
670
+ else {
671
+ pendingUpdates.set(task.id, {
672
+ ...(pendingUpdates.get(task.id) || {}),
673
+ orderInDay: index
674
+ });
675
+ }
676
+ });
677
+ reindexSourceColumnAfterMove(resolvedSourceContainerId, targetColumn, pendingUpdates);
678
+ pendingUpdates.forEach((updates, taskId) => onUpdateTask(taskId, updates));
679
+ return;
680
+ }
681
+ if (!isScheduleReorderColumn || !scheduleKeys.includes(targetColumn))
682
+ return;
683
+ const scheduledDate = scheduleContext.dates[targetColumn];
684
+ if (!scheduledDate)
685
+ return;
686
+ const isTerminal = activeTask.status === 'done' || activeTask.status === 'cancelled';
687
+ if (!isTerminal && scheduledDate < todayDateOnly) {
688
+ window.alert('Only completed work can be scheduled in the past.');
689
+ return;
690
+ }
691
+ const scheduledWeekKey = scheduleContext.parseToIsoWeekKey(scheduledDate);
692
+ if (childrenByParentId.has(activeTask.id)) {
693
+ const stack = [...(childrenByParentId.get(activeTask.id) || [])];
694
+ const descendants = [];
695
+ while (stack.length > 0) {
696
+ const next = stack.shift();
697
+ descendants.push(next);
698
+ const children = childrenByParentId.get(next.id) || [];
699
+ if (children.length > 0)
700
+ stack.push(...children);
701
+ }
702
+ const leafDescendants = descendants.filter((task) => !childrenByParentId.has(task.id));
703
+ const activeLeafDescendants = leafDescendants.filter((task) => !task.isArchived && task.status !== 'done' && task.status !== 'cancelled');
704
+ const toSchedule = activeLeafDescendants.filter((task) => !task.scheduledDate);
705
+ const alreadyScheduledLeafCount = Math.max(0, activeLeafDescendants.length - toSchedule.length);
706
+ onScheduleParentDrop?.({
707
+ parentTaskId: activeTask.id,
708
+ parentTitle: activeTask.title,
709
+ targetColumn: targetColumn,
710
+ targetDate: scheduledDate,
711
+ toScheduleLeafTaskIds: toSchedule.map((task) => task.id),
712
+ alreadyScheduledLeafCount,
713
+ totalLeafDescendants: activeLeafDescendants.length
714
+ });
715
+ onScheduleDaySelected?.(scheduledDate);
716
+ return;
717
+ }
718
+ const pendingUpdates = new Map();
719
+ const targetTasks = (groupedTasks[targetColumn] || []).filter(t => t.id !== activeId);
720
+ const insertAt = buildInsertIndex(targetTasks);
721
+ const reorderedTarget = [...targetTasks];
722
+ reorderedTarget.splice(insertAt, 0, activeTask);
723
+ reorderedTarget.forEach((task, index) => {
724
+ if (task.id === activeId) {
725
+ pendingUpdates.set(task.id, {
726
+ ...(pendingUpdates.get(task.id) || {}),
727
+ scheduledDate,
728
+ scheduledWeekKey,
729
+ orderInDay: index
730
+ });
731
+ }
732
+ else {
733
+ pendingUpdates.set(task.id, {
734
+ ...(pendingUpdates.get(task.id) || {}),
735
+ orderInDay: index
736
+ });
737
+ }
738
+ });
739
+ reindexSourceColumnAfterMove(resolvedSourceContainerId, targetColumn, pendingUpdates);
740
+ pendingUpdates.forEach((updates, taskId) => onUpdateTask(taskId, updates));
741
+ onScheduleDaySelected?.(scheduledDate);
742
+ return;
743
+ }
744
+ // Determine the value to update based on the grouping
745
+ let targetValue = (groupBy === 'category') ? column.label : column.value;
746
+ // STATUS HANDLING
747
+ if (groupBy === 'status') {
748
+ const currentStatus = getTaskStatus(activeTask);
749
+ const targetStatus = String(targetValue);
750
+ if (currentStatus !== targetStatus) {
751
+ const updates = {
752
+ status: targetStatus
753
+ };
754
+ if (targetStatus === 'task') {
755
+ updates.inProgress = false;
756
+ updates.readyForReview = false;
757
+ updates.completed = false;
758
+ updates.cancelled = false;
759
+ }
760
+ else if (targetStatus === 'on-hold') {
761
+ updates.inProgress = false;
762
+ updates.readyForReview = false;
763
+ updates.completed = false;
764
+ updates.cancelled = false;
765
+ }
766
+ else if (targetStatus === 'in-progress') {
767
+ updates.inProgress = true;
768
+ updates.readyForReview = false;
769
+ updates.completed = false;
770
+ updates.cancelled = false;
771
+ }
772
+ else if (targetStatus === 'review') {
773
+ updates.inProgress = false;
774
+ updates.readyForReview = true;
775
+ updates.completed = false;
776
+ updates.cancelled = false;
777
+ }
778
+ else if (targetStatus === 'done') {
779
+ updates.inProgress = false;
780
+ updates.readyForReview = false;
781
+ updates.completed = true;
782
+ updates.cancelled = false;
783
+ updates.completedAt = new Date().toISOString();
784
+ }
785
+ else if (targetStatus === 'cancelled') {
786
+ updates.status = 'cancelled';
787
+ }
788
+ onUpdateTask(activeId, updates);
789
+ }
790
+ return;
791
+ }
792
+ // Explicitly cast numeric fields when grouping by them.
793
+ if (groupBy === 'priority' || groupBy === 'complexity') {
794
+ targetValue = Number(targetValue);
795
+ }
796
+ const currentValue = activeTask[groupBy];
797
+ // Use string comparison for the "changed" check to be safe with number/string values
798
+ if (String(currentValue ?? '') !== String(targetValue ?? '')) {
799
+ const updates = { [groupBy]: targetValue };
800
+ onUpdateTask(activeId, updates);
801
+ }
802
+ }
803
+ };
804
+ const findContainer = (id) => {
805
+ // 1. If ID matches a column value directly
806
+ if (columnLookup.has(id)) {
807
+ const directMatch = columnLookup.get(id);
808
+ return directMatch ? String(directMatch.value) : id;
809
+ }
810
+ // 2. If ID belongs to a task, find its group's column
811
+ const task = tasks.find(t => t.id === id);
812
+ if (task) {
813
+ let val = '';
814
+ if (groupBy === 'status') {
815
+ val = getTaskStatus(task);
816
+ }
817
+ else if (groupBy === 'schedule') {
818
+ val = getScheduleColumnValue(task);
819
+ }
820
+ else if (groupBy === 'hierarchy') {
821
+ val = getHierarchyColumnValue(task);
822
+ }
823
+ else {
824
+ val = String(task[groupBy] ?? '');
825
+ }
826
+ const col = columnLookup.get(val);
827
+ return col ? String(col.value) : null;
828
+ }
829
+ return null;
830
+ };
831
+ const dropAnimation = {
832
+ sideEffects: defaultDropAnimationSideEffects({
833
+ styles: {
834
+ active: {
835
+ opacity: '0',
836
+ },
837
+ },
838
+ }),
839
+ };
840
+ const customCollisionDetection = (args) => {
841
+ // First, look for collisions with the pointer (mouse cursor)
842
+ // This is crucial for collapsed columns which are narrow
843
+ const pointerCollisions = pointerWithin(args);
844
+ // If we have pointer collisions, user is explicitly hovering over something
845
+ if (pointerCollisions.length > 0) {
846
+ const pointerCoords = args.pointerCoordinates;
847
+ if (!pointerCoords)
848
+ return [pointerCollisions[0]];
849
+ // Prefer task cards over columns when both collide.
850
+ const taskCollisions = pointerCollisions.filter((c) => {
851
+ const id = String(c.id);
852
+ return taskIdSet.has(id) && id !== activeId;
853
+ });
854
+ if (taskCollisions.length === 0) {
855
+ // Pointer is often between cards (gap), where only column collision exists.
856
+ // Constrain nearest-task fallback to the column under pointer to avoid cross-column/day drift.
857
+ const columnCollisions = pointerCollisions.filter((c) => {
858
+ const id = String(c.id);
859
+ return !taskIdSet.has(id);
860
+ });
861
+ const hoveredColumnCollision = columnCollisions.find((collision) => {
862
+ const mapped = findContainer(String(collision.id));
863
+ return Boolean(mapped);
864
+ }) || null;
865
+ const hoveredColumnId = hoveredColumnCollision
866
+ ? findContainer(String(hoveredColumnCollision.id))
867
+ : null;
868
+ const taskDroppables = (args.droppableContainers || []).filter((container) => {
869
+ const id = String(container.id);
870
+ if (!taskIdSet.has(id) || id === activeId)
871
+ return false;
872
+ if (!hoveredColumnId)
873
+ return true;
874
+ return findContainer(id) === hoveredColumnId;
875
+ });
876
+ if (taskDroppables.length > 0) {
877
+ const nearestTask = closestCorners({
878
+ ...args,
879
+ droppableContainers: taskDroppables
880
+ });
881
+ if (nearestTask.length > 0) {
882
+ return [nearestTask[0]];
883
+ }
884
+ }
885
+ if (hoveredColumnCollision) {
886
+ return [hoveredColumnCollision];
887
+ }
888
+ return [pointerCollisions[0]];
889
+ }
890
+ const candidates = taskCollisions;
891
+ let best = candidates[0];
892
+ let bestDistance = Number.POSITIVE_INFINITY;
893
+ for (const collision of candidates) {
894
+ const rect = args.droppableRects?.get(collision.id);
895
+ if (!rect)
896
+ continue;
897
+ const centerX = rect.left + rect.width / 2;
898
+ const centerY = rect.top + rect.height / 2;
899
+ const dx = pointerCoords.x - centerX;
900
+ const dy = pointerCoords.y - centerY;
901
+ const distance = dx * dx + dy * dy;
902
+ if (distance < bestDistance) {
903
+ bestDistance = distance;
904
+ best = collision;
905
+ }
906
+ }
907
+ return [best];
908
+ }
909
+ // Fallback to closestCorners for other cases (like keyboard or large overlaps)
910
+ return closestCorners(args);
911
+ };
912
+ const commonCardProps = {
913
+ searchQuery,
914
+ copiedId,
915
+ // Use a stable relationship source so parent/child UI does not disappear
916
+ // when list filters hide related tasks from the board.
917
+ allTasks: hierarchyTasks.length > 0
918
+ ? hierarchyTasks
919
+ : (allTasks.length > 0 ? allTasks : tasks),
920
+ availableSpecialists,
921
+ taxonomies,
922
+ priorities,
923
+ approaches,
924
+ onCopyId,
925
+ onToggleInProgress,
926
+ onToggleReview,
927
+ onToggleComplete,
928
+ onToggleCancel,
929
+ onArchiveTask,
930
+ onUnarchive,
931
+ onDelete,
932
+ onOpenTaskById,
933
+ onUnlinkChildTask,
934
+ onClearParentTask,
935
+ categories,
936
+ changedTaskIdSet,
937
+ linkedParentIdSet: new Set(recentlyLinkedParentIds),
938
+ parentDropTargetId: currentParentDropTargetId,
939
+ groupBy,
940
+ scheduleParentIdSet: hierarchyParentIdSet
941
+ };
942
+ return (_jsxs(DndContext, { sensors: sensors, collisionDetection: customCollisionDetection, onDragStart: handleDragStart, onDragOver: handleDragOver, onDragEnd: handleDragEnd, onDragCancel: () => {
943
+ setActiveId(null);
944
+ setCurrentParentDropTargetId(null);
945
+ }, children: [_jsxs("div", { className: styles.kanbanWrapper, children: [_jsx("div", { ref: topScrollRef, className: styles.kanbanTopScroll, children: _jsx("div", { className: styles.kanbanTopScrollSpacer, style: { width: `${totalWidth}px` } }) }), _jsx("div", { className: styles.kanbanContainer, ref: containerRef, children: kanbanColumns.map((column) => {
946
+ const columnTasks = groupedTasks[String(column.value)] || [];
947
+ const isExplicitlyHidden = getIsColumnHidden(column.value, column.label);
948
+ const isAutoHidden = groupBy !== 'schedule' && emptyColumnMode === 'hide' && columnTasks.length === 0;
949
+ const isScheduleMode = groupBy === 'schedule';
950
+ const hasBacklogColumn = isScheduleMode && kanbanColumns.some((entry) => String(entry.value) === 'backlog');
951
+ const isBacklog = isScheduleMode && String(column.value) === 'backlog';
952
+ const isExpired = isScheduleMode && String(column.value) === 'expired';
953
+ const backlogTasks = groupedTasks.backlog || [];
954
+ const backlogCollapsed = emptyColumnMode === 'collapse' && backlogTasks.length === 0;
955
+ const backlogWidth = backlogCollapsed ? 72 : 320;
956
+ const stickyLeft = isBacklog ? 0 : (isExpired ? (hasBacklogColumn ? backlogWidth + 4 : 0) : undefined);
957
+ const stickyZIndex = isBacklog ? 6 : (isExpired ? 5 : undefined);
958
+ if (isExplicitlyHidden || isAutoHidden)
959
+ return null;
960
+ return (_jsx(KanbanColumn, { id: String(column.value), title: column.label, color: column.color, icon: column.icon, tasks: columnTasks, activeId: activeId, totalCount: columnTotals[String(column.value)], onAddTask: () => onAddTaskToColumn?.(groupBy, column.value), onTaskClick: onTaskClick, commonCardProps: commonCardProps, collapseEmpty: emptyColumnMode === 'collapse', compressed: compressed, onHeaderPointerDown: handleHeaderPointerDown, isHeaderPanning: isHeaderPanning, headerPanEnabled: headerPanEnabled, stickyLeft: stickyLeft, stickyZIndex: stickyZIndex, isPast: !!column.isPast, isSelected: !!column.isSelected, isWeekend: !!column.isWeekend, disableSorting: false, scheduleDate: groupBy === 'schedule' ? scheduleContext.dates[String(column.value)] : undefined, onScheduleDaySelected: onScheduleDaySelected }, String(column.value)));
961
+ }) })] }), createPortal(_jsx(DragOverlay, { dropAnimation: dropAnimation, children: activeTaskOverlay ? (_jsx(TaskCard, { task: activeTaskOverlay, isOverlay: true, ...commonCardProps, isArchived: activeTaskOverlay.isArchived, categories: categories, compressed: compressed, isRecentlyChanged: changedTaskIdSet.has(activeTaskOverlay.id) })) : null }), document.body)] }));
962
+ }
963
+ function KanbanColumn({ id, title, color, icon, tasks, activeId = null, totalCount, onAddTask, onTaskClick, commonCardProps, collapseEmpty, compressed, onHeaderPointerDown, isHeaderPanning = false, headerPanEnabled = false, stickyLeft, stickyZIndex, isPast = false, isSelected = false, isWeekend = false, disableSorting = false, scheduleDate, onScheduleDaySelected }) {
964
+ const { setNodeRef } = useSortable({
965
+ id: id,
966
+ data: {
967
+ type: 'Column',
968
+ },
969
+ });
970
+ const isCollapsed = collapseEmpty && tasks.length === 0;
971
+ const visibleTasks = activeId ? tasks.filter((t) => t.id !== activeId) : tasks;
972
+ const isSticky = typeof stickyLeft === 'number';
973
+ const scrollRef = useRef(null);
974
+ const handleColumnClick = (event) => {
975
+ if (!scheduleDate || !onScheduleDaySelected)
976
+ return;
977
+ const target = event.target;
978
+ if (!target)
979
+ return;
980
+ if (target.closest(`.${styles.taskItem}`) || target.closest(`.${styles.kanbanCardWrapper}`))
981
+ return;
982
+ onScheduleDaySelected(scheduleDate);
983
+ };
984
+ return (_jsxs("div", { ref: setNodeRef, className: `${styles.kanbanColumn} ${isCollapsed ? styles.kanbanColumnCollapsed : ''} ${isSticky ? styles.kanbanColumnSticky : ''} ${isPast ? styles.kanbanColumnPast : ''} ${isSelected ? styles.kanbanColumnSelectedDay : ''} ${isWeekend ? styles.kanbanColumnWeekend : ''}`, style: isSticky ? { left: `${stickyLeft}px`, zIndex: stickyZIndex ?? 4 } : undefined, onClick: handleColumnClick, children: [_jsxs("div", { className: `${styles.kanbanHeader} ${headerPanEnabled ? styles.kanbanHeaderDraggable : ''} ${isHeaderPanning ? styles.kanbanHeaderPanning : ''}`, style: { borderTopColor: resolveColor(color) || 'var(--color-purple)' }, onPointerDown: onHeaderPointerDown, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0, overflow: 'hidden' }, children: [(() => {
985
+ const IconComp = icon && IconMap[icon] ? IconMap[icon] : Icons.Folder;
986
+ const resolvedColor = resolveColor(color) || 'var(--color-purple)';
987
+ return _jsx(IconComp, { size: 16, style: { color: resolvedColor } });
988
+ })(), !isCollapsed && _jsx("span", { style: { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }, children: title })] }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 }, children: [(!isCollapsed || totalCount > 0) && (_jsx("span", { className: styles.kanbanCount, children: isCollapsed ? totalCount : `${tasks.length} / ${totalCount}` })), _jsx("button", { className: styles.kanbanQuickAdd, onClick: (e) => { e.stopPropagation(); onAddTask(); }, title: `Add task to ${title}`, "data-no-header-pan": "true", children: _jsx(Icons.Plus, { size: 14 }) })] })] }), _jsx("div", { className: styles.kanbanDroppable, children: _jsx("div", { className: styles.kanbanDroppableScroll, ref: scrollRef, children: disableSorting ? (visibleTasks.map((task) => (_jsx("div", { className: styles.kanbanCardWrapper, children: _jsx(TaskCard, { task: task, onClick: onTaskClick, ...commonCardProps, isArchived: task.isArchived, compressed: compressed, isRecentlyChanged: false }) }, task.id)))) : (_jsx(SortableContext, { id: id, items: visibleTasks.map(t => t.id), strategy: verticalListSortingStrategy, children: visibleTasks.map((task) => (_jsx(SortableTaskCard, { task: task, onClick: onTaskClick, commonCardProps: commonCardProps, compressed: compressed, isRecentlyChanged: commonCardProps.changedTaskIdSet?.has(task.id) }, task.id))) })) }) })] }));
989
+ }
990
+ function SortableTaskCard({ task, onClick, commonCardProps, compressed, isRecentlyChanged = false }) {
991
+ const dragDisabled = false;
992
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: task.id, disabled: dragDisabled });
993
+ const style = {
994
+ transform: CSS.Transform.toString(transform),
995
+ transition,
996
+ opacity: isDragging ? 0 : 1,
997
+ pointerEvents: isDragging ? 'none' : 'auto',
998
+ };
999
+ const isScheduleMode = commonCardProps?.groupBy === 'schedule';
1000
+ const isGlowActive = !isScheduleMode && (isRecentlyChanged
1001
+ || commonCardProps?.parentDropTargetId === task.id
1002
+ || !!commonCardProps?.linkedParentIdSet?.has(task.id));
1003
+ return (_jsx("div", { ref: setNodeRef, style: style, ...(dragDisabled ? {} : attributes), ...(dragDisabled ? {} : listeners), className: `${styles.kanbanCardWrapper} ${isGlowActive ? styles.kanbanCardWrapperRaised : ''}`, children: _jsx(TaskCard, { task: task, onClick: onClick, ...commonCardProps, isArchived: task.isArchived, compressed: compressed, isRecentlyChanged: isScheduleMode ? false : isRecentlyChanged }) }));
1004
+ }