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