makersclaw 0.8.0 → 0.8.1

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 (401) hide show
  1. package/README.md +9 -3
  2. package/package.json +1 -1
  3. package/r/activity-log.json +52 -0
  4. package/r/agent-changes.json +51 -0
  5. package/r/agent-chat.json +53 -0
  6. package/r/agent-column.json +85 -0
  7. package/r/agent-dock.json +57 -0
  8. package/r/agent-kit.json +49 -0
  9. package/r/agent-marks.json +49 -0
  10. package/r/agent-pane.json +93 -0
  11. package/r/ai-action.json +56 -0
  12. package/r/annotate.json +3 -3
  13. package/r/approval-request.json +2 -2
  14. package/r/approval.json +54 -0
  15. package/r/ask-bar.json +50 -0
  16. package/r/audio-player.json +2 -2
  17. package/r/auth-form.json +2 -2
  18. package/r/automation-editor.json +66 -0
  19. package/r/automation-list.json +57 -0
  20. package/r/automation-model.json +118 -0
  21. package/r/automations-screen.json +71 -0
  22. package/r/board.json +2 -2
  23. package/r/calendar-view.json +3 -3
  24. package/r/cart.json +2 -2
  25. package/r/chart.json +2 -2
  26. package/r/chat-pane.json +84 -0
  27. package/r/chat.json +2 -2
  28. package/r/code-editor.json +2 -2
  29. package/r/comment-box.json +3 -3
  30. package/r/connect-card.json +51 -0
  31. package/r/contact-section.json +2 -2
  32. package/r/core.json +13 -7
  33. package/r/cost.json +51 -0
  34. package/r/csv-import.json +3 -3
  35. package/r/data-picker.json +60 -0
  36. package/r/data-table.json +2 -2
  37. package/r/diagram.json +2 -2
  38. package/r/doc-editor.json +2 -2
  39. package/r/doc-page.json +3 -3
  40. package/r/email-render.json +2 -2
  41. package/r/faq.json +2 -2
  42. package/r/file-upload.json +2 -2
  43. package/r/first-run.json +63 -0
  44. package/r/fix-note.json +57 -0
  45. package/r/flow-canvas.json +2 -2
  46. package/r/flow-editor.json +79 -0
  47. package/r/footer.json +2 -2
  48. package/r/gantt.json +3 -3
  49. package/r/job-panel.json +2 -2
  50. package/r/json-editor.json +3 -3
  51. package/r/library-shelf.json +56 -0
  52. package/r/map.json +2 -2
  53. package/r/margin-notes.json +67 -0
  54. package/r/menubar.json +2 -2
  55. package/r/mind-map.json +3 -3
  56. package/r/navigation-menu.json +2 -2
  57. package/r/needs-you.json +55 -0
  58. package/r/order-detail.json +2 -2
  59. package/r/org-chart.json +3 -3
  60. package/r/pace-bar.json +69 -0
  61. package/r/page-builder.json +2 -2
  62. package/r/payment-methods.json +2 -2
  63. package/r/pdf-document.json +2 -2
  64. package/r/pdf-viewer.json +2 -2
  65. package/r/product-detail.json +2 -2
  66. package/r/product-grid.json +3 -3
  67. package/r/record-form.json +2 -2
  68. package/r/record-grid.json +52 -0
  69. package/r/record-notes.json +4 -4
  70. package/r/registry.json +4086 -1512
  71. package/r/run-history.json +52 -0
  72. package/r/run-view.json +80 -0
  73. package/r/schedule-card.json +54 -0
  74. package/r/schedule-engine.json +79 -0
  75. package/r/schedule-gantt.json +83 -0
  76. package/r/schedule-ruler.json +64 -0
  77. package/r/search-dialog.json +2 -2
  78. package/r/settings-list.json +2 -2
  79. package/r/setup-checklist.json +2 -2
  80. package/r/share-dialog.json +2 -2
  81. package/r/sidebar.json +2 -2
  82. package/r/signature-pad.json +2 -2
  83. package/r/sketch-canvas.json +2 -2
  84. package/r/source-receipt.json +52 -0
  85. package/r/spreadsheet.json +3 -3
  86. package/r/stat-cards.json +3 -3
  87. package/r/step-picker.json +53 -0
  88. package/r/step-settings.json +72 -0
  89. package/r/suggestion.json +58 -0
  90. package/r/sum-bar.json +64 -0
  91. package/r/task-list.json +3 -3
  92. package/r/task-model.json +2 -2
  93. package/r/testimonials.json +2 -2
  94. package/r/tier-map.json +67 -0
  95. package/r/time-grid.json +73 -0
  96. package/r/time-strip.json +64 -0
  97. package/r/top-bar.json +66 -0
  98. package/r/transaction-list.json +2 -2
  99. package/r/trigger-picker.json +61 -0
  100. package/r/whiteboard.json +3 -3
  101. package/r/workflow-view.json +55 -0
  102. package/registry/blocks/activity-log/README.md +112 -0
  103. package/registry/blocks/activity-log/activity-log.jsx +351 -0
  104. package/registry/blocks/activity-log/block.json +33 -0
  105. package/registry/blocks/activity-log/demo.jsx +48 -0
  106. package/registry/blocks/activity-log/index.js +4 -0
  107. package/registry/blocks/agent-changes/README.md +94 -0
  108. package/registry/blocks/agent-changes/agent-changes.jsx +118 -0
  109. package/registry/blocks/agent-changes/block.json +28 -0
  110. package/registry/blocks/agent-changes/demo.jsx +39 -0
  111. package/registry/blocks/agent-changes/index.js +3 -0
  112. package/registry/blocks/agent-chat/README.md +145 -0
  113. package/registry/blocks/agent-chat/agent-chat.jsx +415 -0
  114. package/registry/blocks/agent-chat/block.json +30 -0
  115. package/registry/blocks/agent-chat/demo.jsx +165 -0
  116. package/registry/blocks/agent-chat/index.js +3 -0
  117. package/registry/blocks/agent-column/README.md +197 -0
  118. package/registry/blocks/agent-column/agent-column.jsx +492 -0
  119. package/registry/blocks/agent-column/block.json +67 -0
  120. package/registry/blocks/agent-column/column-picker.jsx +163 -0
  121. package/registry/blocks/agent-column/demo.jsx +50 -0
  122. package/registry/blocks/agent-column/index.js +7 -0
  123. package/registry/blocks/agent-column/model.js +164 -0
  124. package/registry/blocks/agent-column/receipt.jsx +483 -0
  125. package/registry/blocks/agent-column/spreadsheet-type.jsx +24 -0
  126. package/registry/blocks/agent-dock/README.md +210 -0
  127. package/registry/blocks/agent-dock/agent-dock.jsx +356 -0
  128. package/registry/blocks/agent-dock/block.json +47 -0
  129. package/registry/blocks/agent-dock/demo.jsx +153 -0
  130. package/registry/blocks/agent-dock/dock-model.js +118 -0
  131. package/registry/blocks/agent-dock/index.js +5 -0
  132. package/registry/blocks/agent-kit/README.md +73 -0
  133. package/registry/blocks/agent-kit/agent-kit.jsx +86 -0
  134. package/registry/blocks/agent-kit/block.json +10 -0
  135. package/registry/blocks/agent-kit/demo.jsx +35 -0
  136. package/registry/blocks/agent-kit/index.js +3 -0
  137. package/registry/blocks/agent-marks/README.md +80 -0
  138. package/registry/blocks/agent-marks/agent-marks.jsx +133 -0
  139. package/registry/blocks/agent-marks/block.json +27 -0
  140. package/registry/blocks/agent-marks/demo.jsx +49 -0
  141. package/registry/blocks/agent-marks/index.js +3 -0
  142. package/registry/blocks/agent-pane/README.md +225 -0
  143. package/registry/blocks/agent-pane/agent-pane.jsx +244 -0
  144. package/registry/blocks/agent-pane/block.json +41 -0
  145. package/registry/blocks/agent-pane/composer.jsx +6 -0
  146. package/registry/blocks/agent-pane/demo.jsx +233 -0
  147. package/registry/blocks/agent-pane/dock-context.js +10 -0
  148. package/registry/blocks/agent-pane/index.js +4 -0
  149. package/registry/blocks/agent-pane/prose.jsx +84 -0
  150. package/registry/blocks/agent-pane/questions.jsx +134 -0
  151. package/registry/blocks/agent-pane/transcript.jsx +268 -0
  152. package/registry/blocks/agent-pane/work.jsx +368 -0
  153. package/registry/blocks/ai-action/README.md +94 -0
  154. package/registry/blocks/ai-action/ai-action.jsx +188 -0
  155. package/registry/blocks/ai-action/block.json +34 -0
  156. package/registry/blocks/ai-action/demo.jsx +93 -0
  157. package/registry/blocks/ai-action/index.js +3 -0
  158. package/registry/blocks/approval/README.md +145 -0
  159. package/registry/blocks/approval/approval.jsx +163 -0
  160. package/registry/blocks/approval/block.json +31 -0
  161. package/registry/blocks/approval/demo.jsx +121 -0
  162. package/registry/blocks/approval/index.js +3 -0
  163. package/registry/blocks/ask-bar/README.md +105 -0
  164. package/registry/blocks/ask-bar/ask-bar.jsx +166 -0
  165. package/registry/blocks/ask-bar/block.json +26 -0
  166. package/registry/blocks/ask-bar/demo.jsx +78 -0
  167. package/registry/blocks/ask-bar/index.js +3 -0
  168. package/registry/blocks/automation-editor/README.md +291 -0
  169. package/registry/blocks/automation-editor/automation-editor.jsx +656 -0
  170. package/registry/blocks/automation-editor/block.json +39 -0
  171. package/registry/blocks/automation-editor/demo.jsx +284 -0
  172. package/registry/blocks/automation-editor/index.js +4 -0
  173. package/registry/blocks/automation-editor/run-inputs.jsx +82 -0
  174. package/registry/blocks/automation-list/README.md +129 -0
  175. package/registry/blocks/automation-list/automation-list.jsx +476 -0
  176. package/registry/blocks/automation-list/block.json +35 -0
  177. package/registry/blocks/automation-list/demo.jsx +67 -0
  178. package/registry/blocks/automation-list/index.js +3 -0
  179. package/registry/blocks/automation-model/README.md +109 -0
  180. package/registry/blocks/automation-model/block.json +135 -0
  181. package/registry/blocks/automation-model/catalog.js +256 -0
  182. package/registry/blocks/automation-model/check.js +161 -0
  183. package/registry/blocks/automation-model/demo.jsx +42 -0
  184. package/registry/blocks/automation-model/edit.js +262 -0
  185. package/registry/blocks/automation-model/fixes.js +287 -0
  186. package/registry/blocks/automation-model/glyph.jsx +13 -0
  187. package/registry/blocks/automation-model/graph.js +410 -0
  188. package/registry/blocks/automation-model/index.js +33 -0
  189. package/registry/blocks/automation-model/overlay.js +175 -0
  190. package/registry/blocks/automation-model/parts.jsx +94 -0
  191. package/registry/blocks/automation-model/schedule.js +53 -0
  192. package/registry/blocks/automation-model/templates.js +128 -0
  193. package/registry/blocks/automation-model/values.js +29 -0
  194. package/registry/blocks/automation-model/words.js +439 -0
  195. package/registry/blocks/automations-screen/README.md +207 -0
  196. package/registry/blocks/automations-screen/automations-screen.jsx +452 -0
  197. package/registry/blocks/automations-screen/block.json +47 -0
  198. package/registry/blocks/automations-screen/demo.jsx +364 -0
  199. package/registry/blocks/automations-screen/index.js +4 -0
  200. package/registry/blocks/automations-screen/screen-model.js +129 -0
  201. package/registry/blocks/chat-pane/README.md +143 -0
  202. package/registry/blocks/chat-pane/block.json +48 -0
  203. package/registry/blocks/chat-pane/chat-attach.js +166 -0
  204. package/registry/blocks/chat-pane/chat-composer.jsx +195 -0
  205. package/registry/blocks/chat-pane/chat-context.js +17 -0
  206. package/registry/blocks/chat-pane/chat-model.js +43 -0
  207. package/registry/blocks/chat-pane/chat-pane.jsx +398 -0
  208. package/registry/blocks/chat-pane/chats-list.jsx +52 -0
  209. package/registry/blocks/chat-pane/demo.jsx +103 -0
  210. package/registry/blocks/chat-pane/index.js +9 -0
  211. package/registry/blocks/connect-card/README.md +99 -0
  212. package/registry/blocks/connect-card/block.json +10 -0
  213. package/registry/blocks/connect-card/connect-card.jsx +103 -0
  214. package/registry/blocks/connect-card/demo.jsx +49 -0
  215. package/registry/blocks/connect-card/index.js +3 -0
  216. package/registry/blocks/cost/README.md +115 -0
  217. package/registry/blocks/cost/block.json +10 -0
  218. package/registry/blocks/cost/cost.jsx +176 -0
  219. package/registry/blocks/cost/demo.jsx +66 -0
  220. package/registry/blocks/cost/index.js +3 -0
  221. package/registry/blocks/data-picker/README.md +81 -0
  222. package/registry/blocks/data-picker/block.json +38 -0
  223. package/registry/blocks/data-picker/data-picker.jsx +210 -0
  224. package/registry/blocks/data-picker/demo.jsx +34 -0
  225. package/registry/blocks/data-picker/fields.js +89 -0
  226. package/registry/blocks/data-picker/index.js +4 -0
  227. package/registry/blocks/data-table/demo.jsx +1 -1
  228. package/registry/blocks/first-run/README.md +118 -0
  229. package/registry/blocks/first-run/block.json +11 -0
  230. package/registry/blocks/first-run/demo.jsx +88 -0
  231. package/registry/blocks/first-run/first-run-model.js +51 -0
  232. package/registry/blocks/first-run/first-run.jsx +156 -0
  233. package/registry/blocks/first-run/index.js +5 -0
  234. package/registry/blocks/first-run/probe-rules.js +26 -0
  235. package/registry/blocks/fix-note/README.md +79 -0
  236. package/registry/blocks/fix-note/block.json +35 -0
  237. package/registry/blocks/fix-note/demo.jsx +30 -0
  238. package/registry/blocks/fix-note/fix-note.jsx +235 -0
  239. package/registry/blocks/fix-note/index.js +3 -0
  240. package/registry/blocks/flow-editor/README.md +108 -0
  241. package/registry/blocks/flow-editor/block.json +44 -0
  242. package/registry/blocks/flow-editor/demo.jsx +115 -0
  243. package/registry/blocks/flow-editor/flow-canvas.jsx +568 -0
  244. package/registry/blocks/flow-editor/flow-editor.jsx +154 -0
  245. package/registry/blocks/flow-editor/index.js +6 -0
  246. package/registry/blocks/flow-editor/minimap.jsx +78 -0
  247. package/registry/blocks/flow-editor/outline.jsx +134 -0
  248. package/registry/blocks/library-shelf/README.md +117 -0
  249. package/registry/blocks/library-shelf/block.json +28 -0
  250. package/registry/blocks/library-shelf/demo.jsx +68 -0
  251. package/registry/blocks/library-shelf/index.js +3 -0
  252. package/registry/blocks/library-shelf/library-shelf.jsx +211 -0
  253. package/registry/blocks/library-shelf/probe-rules.js +15 -0
  254. package/registry/blocks/margin-notes/README.md +222 -0
  255. package/registry/blocks/margin-notes/block.json +10 -0
  256. package/registry/blocks/margin-notes/demo.jsx +144 -0
  257. package/registry/blocks/margin-notes/index.js +20 -0
  258. package/registry/blocks/margin-notes/margin-model.js +224 -0
  259. package/registry/blocks/margin-notes/margin-notes.jsx +465 -0
  260. package/registry/blocks/needs-you/README.md +177 -0
  261. package/registry/blocks/needs-you/block.json +34 -0
  262. package/registry/blocks/needs-you/demo.jsx +143 -0
  263. package/registry/blocks/needs-you/index.js +3 -0
  264. package/registry/blocks/needs-you/needs-you.jsx +441 -0
  265. package/registry/blocks/pace-bar/README.md +230 -0
  266. package/registry/blocks/pace-bar/block.json +10 -0
  267. package/registry/blocks/pace-bar/demo.jsx +255 -0
  268. package/registry/blocks/pace-bar/index.js +22 -0
  269. package/registry/blocks/pace-bar/pace-bar.jsx +461 -0
  270. package/registry/blocks/pace-bar/pace-model.js +288 -0
  271. package/registry/blocks/record-grid/README.md +100 -0
  272. package/registry/blocks/record-grid/block.json +28 -0
  273. package/registry/blocks/record-grid/demo.jsx +43 -0
  274. package/registry/blocks/record-grid/index.js +3 -0
  275. package/registry/blocks/record-grid/record-grid.jsx +251 -0
  276. package/registry/blocks/run-history/README.md +138 -0
  277. package/registry/blocks/run-history/block.json +10 -0
  278. package/registry/blocks/run-history/demo.jsx +74 -0
  279. package/registry/blocks/run-history/index.js +3 -0
  280. package/registry/blocks/run-history/run-history.jsx +218 -0
  281. package/registry/blocks/run-view/README.md +174 -0
  282. package/registry/blocks/run-view/block.json +50 -0
  283. package/registry/blocks/run-view/demo.jsx +286 -0
  284. package/registry/blocks/run-view/index.js +5 -0
  285. package/registry/blocks/run-view/run-controls.jsx +266 -0
  286. package/registry/blocks/run-view/run-view.jsx +236 -0
  287. package/registry/blocks/run-view/runs-list.jsx +99 -0
  288. package/registry/blocks/schedule-card/README.md +121 -0
  289. package/registry/blocks/schedule-card/block.json +10 -0
  290. package/registry/blocks/schedule-card/demo.jsx +82 -0
  291. package/registry/blocks/schedule-card/index.js +3 -0
  292. package/registry/blocks/schedule-card/schedule-card.jsx +136 -0
  293. package/registry/blocks/schedule-engine/README.md +137 -0
  294. package/registry/blocks/schedule-engine/block.json +10 -0
  295. package/registry/blocks/schedule-engine/demo.jsx +82 -0
  296. package/registry/blocks/schedule-engine/engine.js +769 -0
  297. package/registry/blocks/schedule-engine/index.js +9 -0
  298. package/registry/blocks/schedule-engine/ops.js +71 -0
  299. package/registry/blocks/schedule-engine/place.js +294 -0
  300. package/registry/blocks/schedule-engine/tree.js +79 -0
  301. package/registry/blocks/schedule-engine/waits.js +80 -0
  302. package/registry/blocks/schedule-gantt/README.md +159 -0
  303. package/registry/blocks/schedule-gantt/block.json +10 -0
  304. package/registry/blocks/schedule-gantt/demo.jsx +223 -0
  305. package/registry/blocks/schedule-gantt/gantt-model.js +219 -0
  306. package/registry/blocks/schedule-gantt/geometry.js +176 -0
  307. package/registry/blocks/schedule-gantt/index.js +26 -0
  308. package/registry/blocks/schedule-gantt/schedule-gantt.jsx +882 -0
  309. package/registry/blocks/schedule-gantt/sheet.jsx +372 -0
  310. package/registry/blocks/schedule-gantt/track.jsx +190 -0
  311. package/registry/blocks/schedule-ruler/README.md +148 -0
  312. package/registry/blocks/schedule-ruler/block.json +10 -0
  313. package/registry/blocks/schedule-ruler/demo.jsx +181 -0
  314. package/registry/blocks/schedule-ruler/index.js +21 -0
  315. package/registry/blocks/schedule-ruler/ruler-model.js +239 -0
  316. package/registry/blocks/schedule-ruler/schedule-ruler.jsx +548 -0
  317. package/registry/blocks/source-receipt/README.md +97 -0
  318. package/registry/blocks/source-receipt/block.json +30 -0
  319. package/registry/blocks/source-receipt/demo.jsx +71 -0
  320. package/registry/blocks/source-receipt/index.js +3 -0
  321. package/registry/blocks/source-receipt/source-receipt.jsx +107 -0
  322. package/registry/blocks/spreadsheet/spreadsheet.jsx +15 -1
  323. package/registry/blocks/step-picker/README.md +78 -0
  324. package/registry/blocks/step-picker/block.json +31 -0
  325. package/registry/blocks/step-picker/demo.jsx +23 -0
  326. package/registry/blocks/step-picker/index.js +3 -0
  327. package/registry/blocks/step-picker/step-picker.jsx +144 -0
  328. package/registry/blocks/step-settings/README.md +100 -0
  329. package/registry/blocks/step-settings/block.json +44 -0
  330. package/registry/blocks/step-settings/demo.jsx +93 -0
  331. package/registry/blocks/step-settings/fields.jsx +58 -0
  332. package/registry/blocks/step-settings/flow-settings.jsx +115 -0
  333. package/registry/blocks/step-settings/index.js +5 -0
  334. package/registry/blocks/step-settings/step-settings.jsx +290 -0
  335. package/registry/blocks/suggestion/README.md +171 -0
  336. package/registry/blocks/suggestion/block.json +40 -0
  337. package/registry/blocks/suggestion/demo.jsx +91 -0
  338. package/registry/blocks/suggestion/index.js +3 -0
  339. package/registry/blocks/suggestion/suggestion.jsx +436 -0
  340. package/registry/blocks/sum-bar/README.md +122 -0
  341. package/registry/blocks/sum-bar/block.json +10 -0
  342. package/registry/blocks/sum-bar/demo.jsx +110 -0
  343. package/registry/blocks/sum-bar/index.js +5 -0
  344. package/registry/blocks/sum-bar/sum-bar.jsx +209 -0
  345. package/registry/blocks/sum-bar/sum-model.js +126 -0
  346. package/registry/blocks/tier-map/README.md +217 -0
  347. package/registry/blocks/tier-map/block.json +10 -0
  348. package/registry/blocks/tier-map/demo.jsx +222 -0
  349. package/registry/blocks/tier-map/index.js +24 -0
  350. package/registry/blocks/tier-map/tier-map.jsx +574 -0
  351. package/registry/blocks/tier-map/tier-model.js +318 -0
  352. package/registry/blocks/time-grid/README.md +305 -0
  353. package/registry/blocks/time-grid/block.json +10 -0
  354. package/registry/blocks/time-grid/demo.jsx +279 -0
  355. package/registry/blocks/time-grid/grid-model.js +322 -0
  356. package/registry/blocks/time-grid/index.js +25 -0
  357. package/registry/blocks/time-grid/time-grid.jsx +1325 -0
  358. package/registry/blocks/time-strip/README.md +141 -0
  359. package/registry/blocks/time-strip/block.json +10 -0
  360. package/registry/blocks/time-strip/demo.jsx +129 -0
  361. package/registry/blocks/time-strip/index.js +19 -0
  362. package/registry/blocks/time-strip/strip-model.js +178 -0
  363. package/registry/blocks/time-strip/time-strip.jsx +260 -0
  364. package/registry/blocks/top-bar/README.md +149 -0
  365. package/registry/blocks/top-bar/block.json +35 -0
  366. package/registry/blocks/top-bar/demo.jsx +78 -0
  367. package/registry/blocks/top-bar/index.js +5 -0
  368. package/registry/blocks/top-bar/probe-rules.js +74 -0
  369. package/registry/blocks/top-bar/top-bar-model.js +152 -0
  370. package/registry/blocks/top-bar/top-bar.jsx +244 -0
  371. package/registry/blocks/trigger-picker/README.md +98 -0
  372. package/registry/blocks/trigger-picker/block.json +38 -0
  373. package/registry/blocks/trigger-picker/demo.jsx +59 -0
  374. package/registry/blocks/trigger-picker/index.js +3 -0
  375. package/registry/blocks/trigger-picker/trigger-picker.jsx +374 -0
  376. package/registry/blocks/workflow-view/README.md +123 -0
  377. package/registry/blocks/workflow-view/block.json +10 -0
  378. package/registry/blocks/workflow-view/demo.jsx +90 -0
  379. package/registry/blocks/workflow-view/index.js +3 -0
  380. package/registry/blocks/workflow-view/workflow-view.jsx +203 -0
  381. package/src/install.mjs +77 -16
  382. package/src/registry.mjs +3 -0
  383. package/templates/next/browser/lib/data.js +6 -1
  384. package/templates/next/files/README.md +627 -24
  385. package/templates/next/files/check/automation-format.ts +2103 -0
  386. package/templates/next/files/check/automation-package.ts +142 -0
  387. package/templates/next/files/check/run.mjs +968 -9
  388. package/templates/next/files/components/ui/field.jsx +17 -4
  389. package/templates/next/files/components/ui/icon.jsx +13 -0
  390. package/templates/next/files/components/ui/index.js +2 -1
  391. package/templates/next/files/components/ui/layout.jsx +77 -2
  392. package/templates/next/files/components/ui/notice.jsx +70 -0
  393. package/templates/next/files/components/ui/page.jsx +124 -0
  394. package/templates/next/files/components/ui/stat.jsx +22 -7
  395. package/templates/next/files/components/ui/toast.jsx +6 -3
  396. package/templates/next/files/lib/automations.js +660 -0
  397. package/templates/next/files/lib/claw.js +241 -5
  398. package/templates/next/files/lib/door.js +92 -0
  399. package/templates/next/files/lib/hooks.js +1627 -8
  400. package/templates/next/files/lib/platform.js +529 -21
  401. package/templates/next/template.json +13 -3
@@ -0,0 +1,882 @@
1
+ "use client";
2
+
3
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import { Icon, cx } from "../../ui";
5
+ import { datesOf, dayOf, isoOf, linkProblem, makeCalendar, schedule, shiftDependents, spanOf, taskTree, waitsWords } from "../schedule-engine";
6
+ import { ZOOMS, axisFor, rangeFor, routeLink } from "./geometry";
7
+ import { baselineDelta, barLabel, columnsFor, dayWords, describeDrag, dragLines, fixedWidth, gapOf, linkLines, selectionMoves, slackWords } from "./gantt-model";
8
+ import { AddRow, ROW, SheetHead, SheetRow } from "./sheet";
9
+ import { HEAD, Ghosts, LaneLabels, LaneTrack, Links, Paper, TrackHead } from "./track";
10
+
11
+ // ScheduleGantt: the task sheet beside a working-day time scale, one picture
12
+ // row for row. Bars are steel grey with their progress inside; the critical
13
+ // path turns red when the page switches it on; a phase is a bracket over its
14
+ // work; a milestone a diamond. The target is a dashed line, the finish a solid
15
+ // one, and the gap between them is measured in working days in the header.
16
+ // Dashed outlines show where bars were (`ghosts`) and where a suggestion
17
+ // would put them (`pencil`).
18
+ //
19
+ // Drag a bar to move it, an end to change its length, the dot at its end onto
20
+ // another bar to link them: everything that waits on it moves while you drag,
21
+ // and a note by the bar says what it costs. README.md lists every prop.
22
+ // Built from divs and one svg for the links, tokens only.
23
+
24
+ const BAR = 14;
25
+ const CAL = makeCalendar("weekdays");
26
+ const EMPTY = [];
27
+ const NONE = new Set();
28
+ const KIND_OF = { "finish:start": "finish_to_start", "start:start": "start_to_start", "finish:finish": "finish_to_finish", "start:finish": "start_to_finish" };
29
+ const NUM = "tabular-nums";
30
+
31
+ const firstName = (name) => String(name ?? "").trim().split(/\s+/)[0] ?? "";
32
+
33
+ export function ScheduleGantt({
34
+ tasks: tasksProp,
35
+ links: linksProp,
36
+ people,
37
+ today,
38
+ showToday = true,
39
+ target,
40
+ zoom = "week",
41
+ fitWidth,
42
+ columns,
43
+ vsLabel = "vs baseline",
44
+ rowH = ROW,
45
+ gridWidth = 470,
46
+ onGridWidth,
47
+ height = "fill",
48
+ critical = false,
49
+ selectedId,
50
+ selectedIds,
51
+ onSelect,
52
+ collapsed,
53
+ onToggle,
54
+ onOpen,
55
+ onMove,
56
+ onLink,
57
+ onEdit,
58
+ onAdd,
59
+ onPreview,
60
+ onOpenLink,
61
+ highlight,
62
+ addUnder,
63
+ editRequest,
64
+ jump,
65
+ ghosts,
66
+ pencil,
67
+ baseline,
68
+ guesses,
69
+ notes,
70
+ tags,
71
+ moving,
72
+ rowMenu,
73
+ lanes,
74
+ lanesTitle,
75
+ loading = false,
76
+ error = null,
77
+ emptyText = "No tasks yet. Type the first one in the last row.",
78
+ label = "Gantt chart",
79
+ className,
80
+ }) {
81
+ const tasks = tasksProp ?? EMPTY;
82
+ const links = linksProp ?? EMPTY;
83
+ const scroller = useRef(null);
84
+ const track = useRef(null);
85
+ const dragRef = useRef(null);
86
+ const linkRef = useRef(null);
87
+ const suppress = useRef(false);
88
+ const addRef = useRef(null);
89
+ const [boxW, setBoxW] = useState(null);
90
+ const [preview, setPreview] = useState(null);
91
+ const [linking, setLinking] = useState(null);
92
+ const [editing, setEditing] = useState(null);
93
+ const [draft, setDraft] = useState({ title: "", days: "", milestone: false, parentId: null, afterId: null });
94
+ const [adding, setAdding] = useState(false);
95
+ const [hovered, setHovered] = useState(null);
96
+ const [busy, setBusy] = useState(false);
97
+ const [note, setNote] = useState(null);
98
+ const [gridState, setGridState] = useState(gridWidth);
99
+ const selected = selectedId ? String(selectedId) : null;
100
+ const picked = selectedIds ?? NONE;
101
+
102
+ // Measured in the callback, applied in the next frame (an observer that sets what it watches loops).
103
+ useLayoutEffect(() => {
104
+ const el = scroller.current;
105
+ if (!el) return undefined;
106
+ let raf = 0;
107
+ const apply = (w) => {
108
+ cancelAnimationFrame(raf);
109
+ raf = requestAnimationFrame(() => setBoxW(w));
110
+ };
111
+ setBoxW(el.clientWidth);
112
+ const onResize = () => apply(el.clientWidth);
113
+ window.addEventListener("resize", onResize);
114
+ const ro = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => apply(el.clientWidth));
115
+ ro?.observe(el);
116
+ return () => {
117
+ cancelAnimationFrame(raf);
118
+ window.removeEventListener("resize", onResize);
119
+ ro?.disconnect();
120
+ };
121
+ }, []);
122
+
123
+ // The page opens the add row: { id (a parent) | null, afterId, milestone, nonce }.
124
+ useEffect(() => {
125
+ if (!addUnder) return;
126
+ setAdding(true);
127
+ setDraft({ title: "", days: "", milestone: Boolean(addUnder.milestone), parentId: addUnder.id ? String(addUnder.id) : null, afterId: addUnder.afterId ? String(addUnder.afterId) : null });
128
+ const t = setTimeout(() => {
129
+ addRef.current?.focus();
130
+ addRef.current?.scrollIntoView?.({ block: "nearest" });
131
+ }, 20);
132
+ return () => clearTimeout(t);
133
+ }, [addUnder]);
134
+
135
+ // The page opens a cell: { id, field, nonce } (the row menu's Rename).
136
+ useEffect(() => {
137
+ if (editRequest?.id) setEditing({ id: String(editRequest.id), field: editRequest.field ?? "title" });
138
+ }, [editRequest]);
139
+
140
+ useEffect(() => {
141
+ if (jump?.date) scrollToDay(jump.date, true);
142
+ }, [jump]); // eslint-disable-line react-hooks/exhaustive-deps
143
+
144
+ // ---- the model ----------------------------------------------------------
145
+ const plan = useMemo(() => schedule(tasks, links), [tasks, links]);
146
+ const live = preview?.plan ?? plan;
147
+ const tree = useMemo(() => taskTree(tasks), [tasks]);
148
+ const byId = useMemo(() => new Map(tasks.map((t) => [String(t.id), t])), [tasks]);
149
+ const closed = collapsed ?? NONE;
150
+ const visible = useMemo(() => {
151
+ const out = [];
152
+ for (const e of tree.order) {
153
+ let hidden = false;
154
+ for (let p = e.parentId; p; p = tree.byId.get(p)?.parentId) if (closed.has(p)) hidden = true;
155
+ if (!hidden) out.push(e);
156
+ }
157
+ return out;
158
+ }, [tree, closed]);
159
+ const rowOf = useMemo(() => new Map(visible.map((e, i) => [e.id, i])), [visible]);
160
+ const outlineOf = (id) => tree.byId.get(String(id))?.outline;
161
+ // The add row sits under the row it adds after; everything below it moves down by its height.
162
+ const ADD_H = ROW + 6;
163
+ const addIndex = adding ? (draft.afterId && rowOf.has(draft.afterId) ? rowOf.get(draft.afterId) : draft.parentId && rowOf.has(draft.parentId) ? rowOf.get(draft.parentId) : visible.length - 1) : null;
164
+ const rowTop = (i) => i * rowH + (addIndex != null && i > addIndex ? ADD_H : 0);
165
+ const rowAt = (y) => {
166
+ for (let i = 0; i < visible.length; i += 1) if (y >= rowTop(i) && y < rowTop(i) + rowH) return i;
167
+ return -1;
168
+ };
169
+ const shownRow = (id) => {
170
+ for (let cur = String(id); cur; cur = tree.byId.get(cur)?.parentId) if (rowOf.has(cur)) return rowOf.get(cur);
171
+ return null;
172
+ };
173
+
174
+ const shown = useMemo(() => {
175
+ const out = new Map();
176
+ for (const t of tasks) {
177
+ const id = String(t.id);
178
+ const r = live.rows.get(id);
179
+ if (!r || !r.dated || r.summary) continue;
180
+ const p = preview?.dates?.get(id);
181
+ out.set(id, { start: p?.start ?? r.start, due: p?.due ?? r.due });
182
+ }
183
+ for (const r of live.rows.values()) {
184
+ if (!r.summary) continue;
185
+ let s = null;
186
+ let d = null;
187
+ for (const leaf of r.leafIds) {
188
+ const x = out.get(leaf);
189
+ if (!x || !x.start) continue;
190
+ if (!s || x.start < s) s = x.start;
191
+ if (!d || x.due > d) d = x.due;
192
+ }
193
+ if (s) out.set(r.id, { start: s, due: d });
194
+ }
195
+ return out;
196
+ }, [tasks, live, preview]);
197
+
198
+ // ---- the sheet ----------------------------------------------------------
199
+ const narrow = boxW != null && boxW < 760;
200
+ const cols = useMemo(() => columnsFor(columns, { vsLabel, narrow, outlineWidth: tree.order.some((e) => e.depth >= 3) ? 78 : undefined }), [columns, vsLabel, narrow, tree]);
201
+ const gridW = narrow ? Math.max(170, Math.min(260, Math.round((boxW ?? 400) * 0.46))) : Math.max(fixedWidth(cols) + 160, gridState);
202
+ const cellsOf = useMemo(() => {
203
+ const byTask = new Map();
204
+ for (const l of links) {
205
+ const k = String(l.task_id);
206
+ if (!byTask.has(k)) byTask.set(k, []);
207
+ byTask.get(k).push(l);
208
+ }
209
+ return (id) => {
210
+ const t = byId.get(id);
211
+ const r = live.rows.get(id);
212
+ const mine = byTask.get(id) ?? [];
213
+ const person = people?.get?.(String(t?.assignee_id ?? ""));
214
+ const name = person?.name ?? t?.assignee_name ?? "";
215
+ const out = {
216
+ owner: { name: firstName(name), title: name, left: Boolean(person?.left) },
217
+ waits: waitsWords(mine, id, outlineOf),
218
+ waitsLinks: mine.map((l) => live.links.get(String(l.id))).filter(Boolean),
219
+ waitsBroken: mine.some((l) => live.links.get(String(l.id))?.violated) || Boolean(r?.stuck),
220
+ slack: slackWords(r),
221
+ guessed: Boolean(guesses?.has?.(id)),
222
+ };
223
+ if (baseline) out.delta = baselineDelta(r, baseline.get?.(id));
224
+ return out;
225
+ };
226
+ }, [links, byId, live, people, baseline, guesses, tree]); // eslint-disable-line react-hooks/exhaustive-deps
227
+
228
+ // ---- the axis -----------------------------------------------------------
229
+ const z = ZOOMS[zoom] ? zoom : "week";
230
+ const range = useMemo(() => {
231
+ const dates = [];
232
+ for (const r of plan.rows.values()) if (r.dated) dates.push(r.start, r.due, r.planDue);
233
+ if (target) dates.push(target);
234
+ if (ghosts) for (const g of ghosts.values()) dates.push(g.start, g.due);
235
+ if (pencil) for (const g of pencil.values()) dates.push(g.start, g.due);
236
+ if (baseline) for (const g of baseline.values()) dates.push(g.start, g.due);
237
+ return rangeFor(z, dates, today);
238
+ }, [plan, target, ghosts, pencil, baseline, z, today]);
239
+ // Whole project: the span fits the width, with room on the right for the notes after the bars.
240
+ const ppd = z === "fit" ? Math.max(2, ((fitWidth ?? boxW ?? 1200) - gridW - (notes?.size ? 200 : 16)) / (range.to - range.from + 1)) : ZOOMS[z].ppd;
241
+ const axis = useMemo(() => axisFor(z, range.from, range.to), [z, range.from, range.to]);
242
+ const trackW = Math.round((range.to - range.from + 1) * ppd);
243
+ const X = (iso) => (dayOf(iso) - range.from) * ppd;
244
+ const inRange = (iso) => iso && dayOf(iso) >= range.from && dayOf(iso) <= range.to;
245
+
246
+ const boxOf = (id) => {
247
+ const row = shownRow(id);
248
+ const d = shown.get(String(id));
249
+ if (row == null || !d) return null;
250
+ const r = live.rows.get(String(id));
251
+ const y = rowTop(row) + rowH / 2;
252
+ const own = rowOf.get(String(id)) === row;
253
+ if (r?.milestone) {
254
+ const cx0 = X(d.due) + ppd / 2;
255
+ return { x0: cx0 - 7, x1: cx0 + 7, y, top: y - 7, bottom: y + 7, milestone: true, row, own };
256
+ }
257
+ const h = r?.summary || !own ? 6 : BAR;
258
+ return { x0: X(d.start), x1: X(d.due) + ppd, y, top: y - h / 2, bottom: y + h / 2, milestone: false, row, own };
259
+ };
260
+
261
+ const lines = useMemo(() => {
262
+ const out = [];
263
+ const moved = new Set((preview?.changes ?? []).map((c) => String(c.id)));
264
+ for (const l of live.links.values()) {
265
+ if (!l.valid) continue;
266
+ const a = boxOf(l.dependsOnId);
267
+ const b = boxOf(l.taskId);
268
+ if (!a || !b || a.row === b.row || !a.own || !b.own) continue;
269
+ const kind = { finish_to_start: "FS", start_to_start: "SS", finish_to_finish: "FF", start_to_finish: "SF" }[l.kind] ?? l.kind;
270
+ const route = routeLink(a, b, kind, { rowH });
271
+ out.push({ id: l.id, link: l, critical: l.critical, lag: l.lag, route, touches: selected === l.taskId || selected === l.dependsOnId, moving: moved.has(l.taskId) && moved.has(l.dependsOnId) });
272
+ }
273
+ return out;
274
+ }, [live, shown, visible, ppd, range.from, selected, rowH, preview]); // eslint-disable-line react-hooks/exhaustive-deps
275
+
276
+ function scrollToDay(iso, smooth) {
277
+ const el = scroller.current;
278
+ if (!el || !iso) return;
279
+ const left = Math.max(0, X(iso) - (el.clientWidth - gridW) * 0.3);
280
+ el.scrollTo({ left, behavior: smooth ? "smooth" : "auto" });
281
+ }
282
+ useLayoutEffect(() => {
283
+ if (z !== "fit") scrollToDay(today, false);
284
+ }, [z, range.from]); // eslint-disable-line react-hooks/exhaustive-deps
285
+
286
+ // ---- dragging bars ----------------------------------------------------------
287
+ function movesFor(d, delta) {
288
+ const t = byId.get(d.id);
289
+ const r = plan.rows.get(d.id);
290
+ if (!t || !r) return [];
291
+ if (r.summary) return [{ id: d.id, start: isoOf(dayOf(d.start) + delta) }];
292
+ const span = spanOf(t);
293
+ if (d.mode === "move") {
294
+ if (r.milestone) {
295
+ const m = datesOf(CAL.lastIndex(dayOf(r.due) + delta) + 1, 0, true);
296
+ return [{ id: d.id, start: m.start, due: m.due }];
297
+ }
298
+ const m = datesOf(CAL.startIndex(dayOf(r.start) + delta), span.d, false);
299
+ return [{ id: d.id, start: m.start, due: m.due }];
300
+ }
301
+ if (d.mode === "end") return [{ id: d.id, start: r.start, due: isoOf(Math.max(dayOf(r.start), dayOf(r.due) + delta)) }];
302
+ return [{ id: d.id, start: isoOf(Math.min(dayOf(r.due), dayOf(r.start) + delta)), due: r.due }];
303
+ }
304
+
305
+ function previewFor(moves, extra = {}) {
306
+ const res = shiftDependents(tasks, links, moves, extra);
307
+ const dates = new Map(res.changes.map((c) => [String(c.id), { start: c.start_date, due: c.due_date }]));
308
+ const from = new Map(res.changes.filter((c) => c.from?.start_date).map((c) => [String(c.id), { start: c.from.start_date, due: c.from.due_date }]));
309
+ return { dates, from, plan: res.after, finishBefore: res.finishBefore, finishAfter: res.finishAfter, clamped: res.clamped, moves, changes: res.changes };
310
+ }
311
+
312
+ const describe = (p, id) => describeDrag({ before: plan, after: p.plan, changes: p.changes, clamped: p.clamped, id, titleOf: (x) => byId.get(x)?.title ?? "" });
313
+
314
+ function startDrag(e, id, modeName) {
315
+ if (!onMove) return;
316
+ if (e.pointerType === "mouse" && e.button !== 0) return;
317
+ const r = plan.rows.get(String(id));
318
+ if (!r?.dated || r.pinned) return;
319
+ if (e.pointerType !== "mouse" && modeName === "move" && !r.summary && !r.milestone) return;
320
+ e.stopPropagation();
321
+ const together = modeName === "move" && picked.size > 1 && picked.has(String(id)) ? [...picked].map(String) : null;
322
+ dragRef.current = { id: String(id), mode: modeName, x0: e.clientX, start: shown.get(String(id))?.start, delta: 0, moved: false, pointerId: e.pointerId, together };
323
+ try {
324
+ track.current?.setPointerCapture(e.pointerId);
325
+ } catch {
326
+ // the pointer is gone already
327
+ }
328
+ }
329
+
330
+ function movesOfDrag(d, delta) {
331
+ if (!d.together) return movesFor(d, delta);
332
+ return selectionMoves(d.together, (id) => {
333
+ const r = plan.rows.get(id);
334
+ if (!r?.dated || r.pinned) return [];
335
+ return movesFor({ id, mode: "move", start: shown.get(id)?.start ?? r.start }, delta);
336
+ });
337
+ }
338
+
339
+ function onTrackMove(e) {
340
+ if (linkRef.current) return moveLink(e);
341
+ const d = dragRef.current;
342
+ if (!d || e.pointerId !== d.pointerId) return;
343
+ const dx = e.clientX - d.x0;
344
+ if (!d.moved && Math.abs(dx) < 4) return;
345
+ d.moved = true;
346
+ const delta = Math.round(dx / ppd);
347
+ if (delta === d.delta && preview) return;
348
+ d.delta = delta;
349
+ const p = previewFor(movesOfDrag(d, delta));
350
+ p.info = describe(p, d.id);
351
+ d.preview = p;
352
+ setPreview(p);
353
+ onPreview?.({ mode: d.mode === "end" ? "stretch" : "move", task: byId.get(d.id), ...p.info });
354
+ }
355
+
356
+ async function onTrackUp(e) {
357
+ if (linkRef.current) return finishLink(e);
358
+ const d = dragRef.current;
359
+ if (!d || e.pointerId !== d.pointerId) return;
360
+ dragRef.current = null;
361
+ if (!d.moved) return;
362
+ suppress.current = true;
363
+ setTimeout(() => {
364
+ suppress.current = false;
365
+ }, 0);
366
+ const p = d.preview;
367
+ setPreview(null);
368
+ onPreview?.(null);
369
+ if (!p || d.delta === 0) return;
370
+ const res = await onMove?.(p.moves, p);
371
+ if (res && res.ok === false) setNote(res.error ?? "That move was not saved.");
372
+ }
373
+
374
+ function cancelAll() {
375
+ dragRef.current = null;
376
+ linkRef.current = null;
377
+ setPreview(null);
378
+ setLinking(null);
379
+ onPreview?.(null);
380
+ }
381
+
382
+ // ---- drawing links ----------------------------------------------------------
383
+ function trackPoint(e) {
384
+ const rect = track.current?.getBoundingClientRect();
385
+ return rect ? { x: e.clientX - rect.left, y: e.clientY - rect.top } : { x: 0, y: 0 };
386
+ }
387
+
388
+ function startLink(e, id, end) {
389
+ if (!onLink) return;
390
+ e.stopPropagation();
391
+ e.preventDefault();
392
+ const b = boxOf(id);
393
+ if (!b) return;
394
+ const from = { x: end === "start" ? b.x0 : b.x1, y: b.y };
395
+ const l = { fromId: String(id), fromEnd: end, from, to: from, targetId: null, targetEnd: null, problem: null, pointerId: e.pointerId, preview: null };
396
+ linkRef.current = l;
397
+ setLinking(l);
398
+ try {
399
+ track.current?.setPointerCapture(e.pointerId);
400
+ } catch {
401
+ // the pointer is gone already
402
+ }
403
+ }
404
+
405
+ function moveLink(e) {
406
+ const cur = linkRef.current;
407
+ if (!cur || e.pointerId !== cur.pointerId) return;
408
+ const pt = trackPoint(e);
409
+ const entry = visible[rowAt(pt.y)];
410
+ let targetId = null;
411
+ let targetEnd = null;
412
+ let problem = null;
413
+ let p = null;
414
+ if (entry && entry.id !== cur.fromId) {
415
+ const b = boxOf(entry.id);
416
+ if (b && pt.x >= b.x0 - 12 && pt.x <= b.x1 + 12) {
417
+ targetId = entry.id;
418
+ targetEnd = b.milestone ? "start" : pt.x < (b.x0 + b.x1) / 2 ? "start" : "finish";
419
+ problem = linkProblem(tasks, links, targetId, cur.fromId);
420
+ if (!problem) {
421
+ p = previewFor([], { nextLinks: [...links, { id: "__new", task_id: targetId, depends_on_id: cur.fromId, kind: KIND_OF[`${cur.fromEnd}:${targetEnd}`], lag_days: 0 }] });
422
+ p.info = describe(p, targetId);
423
+ }
424
+ }
425
+ }
426
+ linkRef.current = { ...cur, to: pt, targetId, targetEnd, problem, preview: p };
427
+ setLinking(linkRef.current);
428
+ setPreview(p);
429
+ onPreview?.(p ? { mode: "link", task: byId.get(targetId), ...p.info } : null);
430
+ }
431
+
432
+ async function finishLink(e) {
433
+ const l = linkRef.current;
434
+ if (!l || e.pointerId !== l.pointerId) return;
435
+ linkRef.current = null;
436
+ setLinking(null);
437
+ const p = l.preview;
438
+ setPreview(null);
439
+ onPreview?.(null);
440
+ if (!l.targetId) return;
441
+ if (l.problem) return setNote(l.problem);
442
+ const res = await onLink?.({ taskId: l.targetId, dependsOnId: l.fromId, kind: KIND_OF[`${l.fromEnd}:${l.targetEnd}`] }, p);
443
+ if (res && res.ok === false) setNote(res.error ?? "That link was not saved.");
444
+ }
445
+
446
+ useEffect(() => {
447
+ if (!preview && !linking) return undefined;
448
+ const onKey = (e) => {
449
+ if (e.key === "Escape") cancelAll();
450
+ };
451
+ window.addEventListener("keydown", onKey);
452
+ return () => window.removeEventListener("keydown", onKey);
453
+ }, [preview, linking]); // eslint-disable-line react-hooks/exhaustive-deps
454
+
455
+ // ---- keyboard on the chart ----------------------------------------------------
456
+ function onKeyDown(e) {
457
+ if (editing || e.target?.tagName === "INPUT") return;
458
+ const i = selected ? rowOf.get(selected) : null;
459
+ const task = selected ? byId.get(selected) : null;
460
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
461
+ e.preventDefault();
462
+ const next = i == null ? 0 : Math.max(0, Math.min(visible.length - 1, i + (e.key === "ArrowDown" ? 1 : -1)));
463
+ if (visible[next]) onSelect?.(visible[next].id);
464
+ return;
465
+ }
466
+ if (!task) return;
467
+ if (e.altKey && (e.key === "ArrowRight" || e.key === "ArrowLeft") && onMove && !e.shiftKey) {
468
+ e.preventDefault();
469
+ const r = plan.rows.get(String(task.id));
470
+ if (!r?.dated || r.pinned) return;
471
+ const step = e.key === "ArrowRight" ? 1 : -1;
472
+ const from = dayOf(r.milestone ? r.due : r.start);
473
+ const to = CAL.dayAt((r.milestone ? CAL.lastIndex(from) : CAL.startIndex(from)) + step);
474
+ const p = previewFor(movesFor({ id: String(task.id), mode: "move", start: r.start }, to - from));
475
+ onMove(p.moves, p).then((res) => res && res.ok === false && setNote(res.error ?? "That move was not saved."));
476
+ } else if (e.key === "Enter") {
477
+ e.preventDefault();
478
+ onOpen?.(task);
479
+ } else if (e.key === "F2") {
480
+ e.preventDefault();
481
+ setEditing({ id: String(task.id), field: "title" });
482
+ } else if (e.key === "Escape") onSelect?.(null);
483
+ }
484
+
485
+ async function commitEdit(task, field, value) {
486
+ setEditing(null);
487
+ const res = await onEdit?.(task, field, value);
488
+ if (res && res.ok === false) setNote(res.error ?? "That change was not saved.");
489
+ }
490
+
491
+ async function submitDraft(milestoneKey = false) {
492
+ const title = draft.title.trim();
493
+ if (!title || !onAdd || busy) return;
494
+ setBusy(true);
495
+ const milestone = draft.milestone || milestoneKey;
496
+ const res = await onAdd({ title, days: Number(draft.days) || null, milestone, parentId: draft.parentId, afterId: draft.afterId });
497
+ setBusy(false);
498
+ if (res && res.ok === false) setNote(res.error ?? "That task was not added.");
499
+ else setDraft((d) => ({ ...d, title: "", days: "", milestone: false, afterId: res?.id ? String(res.id) : d.afterId }));
500
+ addRef.current?.focus();
501
+ }
502
+
503
+ // ---- the sheet's divider --------------------------------------------------------
504
+ function startDivider(e) {
505
+ e.preventDefault();
506
+ const x0 = e.clientX;
507
+ const w0 = gridW;
508
+ let last = w0;
509
+ const move = (ev) => {
510
+ last = Math.max(fixedWidth(cols) + 140, Math.min(760, w0 + ev.clientX - x0));
511
+ setGridState(last);
512
+ };
513
+ const up = () => {
514
+ window.removeEventListener("pointermove", move);
515
+ window.removeEventListener("pointerup", up);
516
+ onGridWidth?.(last);
517
+ };
518
+ window.addEventListener("pointermove", move);
519
+ window.addEventListener("pointerup", up);
520
+ }
521
+ const divider = narrow ? null : (
522
+ <div
523
+ role="separator"
524
+ aria-orientation="vertical"
525
+ aria-label="Resize the task sheet"
526
+ aria-valuenow={gridW}
527
+ tabIndex={0}
528
+ onPointerDown={startDivider}
529
+ onKeyDown={(e) => {
530
+ if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
531
+ e.preventDefault();
532
+ setGridState(Math.max(fixedWidth(cols) + 140, Math.min(760, gridW + (e.key === "ArrowRight" ? 16 : -16))));
533
+ }
534
+ }}
535
+ className="absolute inset-y-0 -right-[3px] z-50 w-[6px] cursor-col-resize outline-none hover:bg-ring/30 focus-visible:bg-ring/40"
536
+ />
537
+ );
538
+
539
+ // ---- render -------------------------------------------------------------------
540
+ const finish = preview?.finishAfter ?? plan.finish;
541
+ const gap = gapOf(finish, target);
542
+ const marks = {
543
+ today: showToday && inRange(today) ? X(today) + ppd / 2 : null,
544
+ target: target && inRange(target) ? X(target) + ppd : null,
545
+ targetIso: target,
546
+ finish: finish && inRange(finish) ? X(finish) + ppd : null,
547
+ finishFrom: preview && plan.finish && inRange(plan.finish) ? X(plan.finish) + ppd : null,
548
+ moving: Boolean(preview && !linking),
549
+ gap,
550
+ };
551
+ const bodyH = Math.max(visible.length, 1) * rowH + (addIndex != null ? ADD_H : 0);
552
+ const addAfter = addIndex != null && addIndex < visible.length - 1 ? addIndex : null;
553
+ const addParent = draft.parentId ? byId.get(String(draft.parentId)) : null;
554
+ const ghostMap = preview?.from?.size ? preview.from : ghosts;
555
+ const dragInfo = preview?.info ?? null;
556
+ const laneH = 20;
557
+ const laneHead = 28;
558
+ const hasLanes = Boolean(lanes?.length);
559
+ const lanesH = hasLanes ? laneHead + lanes.length * laneH : 0;
560
+ const rowTops = visible.map((_, i) => rowTop(i));
561
+ const selectedRows = new Set([...(selected ? [selected] : []), ...picked].map((id) => rowOf.get(String(id))).filter((i) => i != null));
562
+ const addAt = addIndex != null && addIndex < visible.length ? rowTop(addIndex) + rowH : bodyH;
563
+
564
+ const addRow = onAdd ? (
565
+ <AddRow
566
+ open={adding}
567
+ onOpen={() => {
568
+ setAdding(true);
569
+ setDraft({ title: "", days: "", milestone: false, parentId: null, afterId: null });
570
+ setTimeout(() => addRef.current?.focus(), 20);
571
+ }}
572
+ draft={draft}
573
+ setDraft={setDraft}
574
+ onSubmit={submitDraft}
575
+ busy={busy}
576
+ addRef={addRef}
577
+ parentTitle={addParent?.title}
578
+ aboveTitle={draft.afterId ? byId.get(draft.afterId)?.title : null}
579
+ onIndent={() => setDraft((d) => ({ ...d, parentId: d.afterId, afterId: null }))}
580
+ height={rowH}
581
+ />
582
+ ) : null;
583
+ const addExtra = onAdd && !adding ? rowH : 0;
584
+
585
+ const rootStyle = height === "fill" ? undefined : { height };
586
+
587
+ return (
588
+ <div className={cx("flex min-h-0 flex-col", height === "fill" && "h-full", className)} style={rootStyle}>
589
+ {error ? (
590
+ <p role="alert" className="shrink-0 border-b border-destructive px-3 py-2 text-[13px] text-destructive">
591
+ {typeof error === "string" ? error : "The plan could not be read."}
592
+ </p>
593
+ ) : null}
594
+ {note ? (
595
+ <p role="alert" className="flex shrink-0 items-start justify-between gap-3 border-b border-border bg-card px-3 py-2 text-[13px]">
596
+ <span>{note}</span>
597
+ <button type="button" onClick={() => setNote(null)} className="shrink-0 text-muted-foreground outline-none hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring" aria-label="Dismiss the message">
598
+ <Icon name="x" size={16} />
599
+ </button>
600
+ </p>
601
+ ) : null}
602
+ <div
603
+ ref={scroller}
604
+ role="region"
605
+ aria-label={label}
606
+ aria-busy={loading || undefined}
607
+ tabIndex={0}
608
+ onKeyDown={onKeyDown}
609
+ className="relative min-h-0 flex-1 overflow-auto bg-card outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
610
+ >
611
+ <div className="relative" style={{ width: gridW + trackW, minHeight: HEAD + bodyH + addExtra + lanesH }}>
612
+ <div className="sticky top-0 z-30 flex border-b border-border bg-background" style={{ height: HEAD }}>
613
+ <div className="sticky left-0 z-40 shrink-0 border-r border-border bg-background" style={{ width: gridW }}>
614
+ <SheetHead cols={cols} />
615
+ {divider}
616
+ </div>
617
+ <div className="relative" style={{ width: trackW }}>
618
+ <TrackHead axis={axis} range={range} ppd={ppd} z={z} gridW={gridW} marks={marks} />
619
+ </div>
620
+ </div>
621
+
622
+ <div className="flex">
623
+ <div className="sticky left-0 z-20 shrink-0 border-r border-border bg-card" style={{ width: gridW }}>
624
+ {loading
625
+ ? Array.from({ length: 8 }, (_, i) => (
626
+ <div key={i} className="flex items-center gap-2 border-b border-border px-3" style={{ height: rowH }}>
627
+ <span className="h-2.5 w-8 animate-pulse rounded-[2px] bg-muted" />
628
+ <span className="h-2.5 flex-1 animate-pulse rounded-[2px] bg-muted" style={{ maxWidth: `${40 + ((i * 17) % 50)}%` }} />
629
+ </div>
630
+ ))
631
+ : visible.map((e, i) => {
632
+ const t = byId.get(e.id);
633
+ const r = live.rows.get(e.id);
634
+ const d = shown.get(e.id);
635
+ const late = r && !r.summary && !r.pinned && r.dated && !r.milestone && d?.due && d.due < today && r.progress < 100;
636
+ const lateMs = r?.milestone && !r.pinned && d?.due && d.due < today;
637
+ return (
638
+ <div key={e.id}>
639
+ <SheetRow
640
+ e={e}
641
+ t={t}
642
+ r={r}
643
+ cols={cols}
644
+ rowH={rowH}
645
+ critical={critical}
646
+ selected={selected === e.id || picked.has(e.id)}
647
+ closed={closed.has(e.id)}
648
+ cells={cellsOf(e.id)}
649
+ foldedCount={r?.summary ? `${r.leafIds.length} ${r.leafIds.length === 1 ? "task" : "tasks"}` : null}
650
+ tags={tags?.get?.(e.id)}
651
+ late={late || lateMs}
652
+ dim={highlight && !highlight.has(e.id) && !r?.summary}
653
+ moving={moving}
654
+ editing={editing?.id === e.id ? editing : null}
655
+ onSelect={onSelect}
656
+ onOpen={onOpen}
657
+ onToggle={onToggle}
658
+ onOpenLink={onOpenLink}
659
+ onStartEdit={(field) => {
660
+ onSelect?.(e.id);
661
+ setEditing({ id: e.id, field });
662
+ }}
663
+ onCommit={(field, v) => commitEdit(t, field, v)}
664
+ onCancel={() => setEditing(null)}
665
+ onHover={() => setHovered(e.id)}
666
+ menu={rowMenu && (hovered === e.id || selected === e.id) ? rowMenu(t, { row: e, plan: r }) : null}
667
+ />
668
+ {addAfter === i && adding ? addRow : null}
669
+ </div>
670
+ );
671
+ })}
672
+ {addAfter == null ? addRow : null}
673
+ {hasLanes ? (
674
+ <div>
675
+ <div className="flex items-center gap-2 border-b border-t border-border bg-muted px-3 text-[12px] font-semibold" style={{ height: laneHead }}>
676
+ {lanesTitle}
677
+ </div>
678
+ <LaneLabels lanes={lanes} laneH={laneH} width={gridW - 1} />
679
+ </div>
680
+ ) : null}
681
+ </div>
682
+
683
+ <div
684
+ ref={track}
685
+ className={cx("relative", linking && "cursor-crosshair")}
686
+ style={{ width: trackW, height: bodyH + addExtra + lanesH, touchAction: dragRef.current || linking ? "none" : "pan-x pan-y" }}
687
+ onPointerMove={onTrackMove}
688
+ onPointerUp={onTrackUp}
689
+ onPointerCancel={cancelAll}
690
+ >
691
+ <Paper axis={axis} range={range} ppd={ppd} z={z} marks={marks} rowTops={rowTops} rowH={rowH} selectedRows={selectedRows} />
692
+ <Links lines={lines} width={trackW} height={bodyH} critical={critical} onOpenLink={onOpenLink} />
693
+ <Ghosts ghosts={ghostMap} visible={visible} X={X} ppd={ppd} rowH={rowH} rowTop={rowTop} live={live} kind="was" bar={BAR} />
694
+ <Ghosts ghosts={pencil} visible={visible} X={X} ppd={ppd} rowH={rowH} rowTop={rowTop} live={live} kind="pencil" bar={BAR} />
695
+ {addIndex != null && !draft.milestone ? (
696
+ <span aria-hidden="true" className="absolute h-3.5 rounded-[2px] border border-dashed border-info bg-info/15" style={{ left: today ? X(today) : 0, width: Math.max(1, Number(draft.days) || 3) * ppd * 1.4, top: rowTop(addIndex) + rowH + (ADD_H - 14) / 2 }} />
697
+ ) : null}
698
+
699
+ {visible.map((e, i) => {
700
+ const t = byId.get(e.id);
701
+ const r = live.rows.get(e.id);
702
+ const d = shown.get(e.id);
703
+ const y = rowTop(i) + rowH / 2;
704
+ const n = notes?.get?.(e.id);
705
+ if (!r || !d) {
706
+ return n ? (
707
+ <span key={e.id} className={cx("absolute -translate-y-1/2 whitespace-nowrap text-[12px]", n.tone === "problem" ? "font-medium text-destructive" : "text-muted-foreground")} style={{ left: 12, top: y }}>
708
+ {n.text}
709
+ </span>
710
+ ) : null;
711
+ }
712
+ const isSel = selected === e.id || picked.has(e.id);
713
+ const dim = highlight && !highlight.has(e.id) && !r.summary;
714
+ const done = r.pinned;
715
+ const late = !r.summary && !done && !r.milestone && d.due < today && r.progress < 100;
716
+ const title = barLabel(t, r);
717
+ const common = {
718
+ tabIndex: -1,
719
+ onClick: (ev) => {
720
+ ev.stopPropagation();
721
+ if (suppress.current) return;
722
+ onSelect?.(e.id, { additive: ev.metaKey || ev.ctrlKey || ev.shiftKey });
723
+ },
724
+ onDoubleClick: () => onOpen?.(t),
725
+ title,
726
+ "aria-label": title,
727
+ };
728
+ const crit = critical && r.critical && !done;
729
+ const labelEl = (x, extra = 0) => {
730
+ const person = people?.get?.(String(t.assignee_id ?? ""));
731
+ const who = firstName(person?.name ?? t.assignee_name);
732
+ const text = n?.text ?? who;
733
+ return text || late ? (
734
+ <span className="pointer-events-none absolute top-1/2 flex -translate-y-1/2 items-center gap-1.5 whitespace-nowrap text-[11px]" style={{ left: x + 6 + extra }}>
735
+ {late ? <span className="rounded-[3px] border border-warning px-1 text-[10px] font-semibold leading-4 text-warning">Late</span> : null}
736
+ {text ? <span className={n?.tone === "problem" ? "font-medium text-destructive" : n?.tone === "crit" ? "font-medium text-destructive" : "text-muted-foreground"}>{text}</span> : null}
737
+ </span>
738
+ ) : null;
739
+ };
740
+ const handles =
741
+ onLink && !r.summary && !done ? (
742
+ <>
743
+ <button type="button" tabIndex={-1} aria-label={`Link from the start of ${t.title}`} onPointerDown={(ev) => startLink(ev, e.id, "start")} className={cx("absolute top-1/2 z-20 size-2 -translate-y-1/2 rounded-full border-2 border-ring bg-background opacity-0 outline-none group-hover:opacity-100", isSel && "opacity-100")} style={{ left: -11, touchAction: "none" }} />
744
+ <button type="button" tabIndex={-1} aria-label={`Link from the end of ${t.title}`} onPointerDown={(ev) => startLink(ev, e.id, "finish")} className={cx("absolute top-1/2 z-20 size-2 -translate-y-1/2 rounded-full border-2 border-ring bg-background opacity-0 outline-none group-hover:opacity-100", isSel && "opacity-100")} style={{ right: -11, touchAction: "none" }} />
745
+ </>
746
+ ) : null;
747
+ const target = linking?.targetId === e.id;
748
+
749
+ if (r.milestone) {
750
+ const cx0 = X(d.due) + ppd / 2;
751
+ return (
752
+ <div key={e.id} className={cx("group absolute z-10", dim && "opacity-40")} style={{ left: cx0 - 9, top: y - 9, width: 18, height: 18 }}>
753
+ <button type="button" {...common} onPointerDown={(ev) => startDrag(ev, e.id, "move")} className={cx("absolute inset-0 grid place-items-center rounded-[2px] outline-none focus-visible:ring-2 focus-visible:ring-ring", onMove && !done && "cursor-grab", target && (linking.problem ? "ring-2 ring-destructive" : "ring-2 ring-ring"))}>
754
+ <span className={cx("block size-3 rotate-45", done ? "bg-chart-2" : crit ? "bg-destructive" : "bg-foreground", isSel && "ring-2 ring-ring ring-offset-1 ring-offset-card")} />
755
+ </button>
756
+ {handles}
757
+ <span className={cx(NUM, "pointer-events-none absolute left-full top-1/2 ml-2 -translate-y-1/2 whitespace-nowrap text-[11px]", crit ? "font-semibold text-destructive" : "text-muted-foreground")}>{n?.text ?? dayWords(d.due)}</span>
758
+ </div>
759
+ );
760
+ }
761
+
762
+ const x0 = X(d.start);
763
+ const w = Math.max(ppd, X(d.due) + ppd - x0);
764
+ if (r.summary) {
765
+ const folded = closed.has(e.id);
766
+ const kids = folded ? r.leafIds.map((id) => ({ id, row: live.rows.get(id), d: shown.get(id) })).filter((k) => k.row?.milestone && k.d) : [];
767
+ return (
768
+ <div key={e.id} className="group absolute z-10" style={{ left: x0, top: y - 4, width: w, height: 12 }}>
769
+ <button type="button" {...common} onPointerDown={(ev) => startDrag(ev, e.id, "move")} className={cx("absolute inset-x-0 top-0 h-[6px] outline-none focus-visible:ring-2 focus-visible:ring-ring", done ? "bg-chart-2" : "bg-foreground", onMove && !done && "cursor-grab")}>
770
+ <span className={cx("absolute left-0 top-[6px] size-0 border-r-[5px] border-t-[5px] border-r-transparent", done ? "border-t-chart-2" : "border-t-foreground")} />
771
+ <span className={cx("absolute right-0 top-[6px] size-0 border-l-[5px] border-t-[5px] border-l-transparent", done ? "border-t-chart-2" : "border-t-foreground")} />
772
+ </button>
773
+ {kids.map((k) => (
774
+ <span key={k.id} title={byId.get(k.id)?.title} className={cx("absolute -top-[10px] size-[10px] -translate-x-1/2 rotate-45 border-[1.5px] border-card", k.row.pinned ? "bg-chart-2" : critical && k.row.critical ? "bg-destructive" : "bg-foreground")} style={{ left: X(k.d.due) + ppd / 2 - x0 }} />
775
+ ))}
776
+ {n ? (
777
+ <span className="pointer-events-none absolute -top-[4px] whitespace-nowrap text-[11px] text-muted-foreground" style={{ left: w + 8 }}>
778
+ {n.text}
779
+ </span>
780
+ ) : null}
781
+ </div>
782
+ );
783
+ }
784
+ const loop = r.stuck;
785
+ const guess = guesses?.has?.(e.id);
786
+ const barTop = y - BAR / 2 - (baseline ? 2 : 0);
787
+ const base = baseline?.get?.(e.id);
788
+ const dragging = preview && dragRef.current?.id === e.id;
789
+ const movedByDrag = preview?.dates?.has(e.id);
790
+ const trackClass = loop ? "border border-dashed border-destructive bg-destructive/15" : guess ? "border border-dashed border-chart-2 bg-transparent" : crit ? "bg-destructive" : done ? "bg-chart-2" : "bg-chart-1";
791
+ const fill = crit ? "bg-foreground/35" : "bg-chart-2";
792
+ const mark = tags?.get?.(e.id)?.some((g) => g.kind === "unsaved");
793
+ return (
794
+ <div key={e.id} className={cx("group absolute z-10", dim && "opacity-40")} style={{ left: x0, top: barTop, width: w, height: BAR }}>
795
+ <button
796
+ type="button"
797
+ {...common}
798
+ onPointerDown={(ev) => startDrag(ev, e.id, "move")}
799
+ className={cx(
800
+ "absolute inset-0 overflow-hidden rounded-[2px] outline-none focus-visible:ring-2 focus-visible:ring-ring",
801
+ trackClass,
802
+ isSel && "ring-2 ring-ring",
803
+ (dragging || movedByDrag) && !isSel && "ring-[1.5px] ring-ring",
804
+ mark && "ring-2 ring-destructive",
805
+ target && (linking.problem ? "ring-2 ring-destructive" : "ring-2 ring-ring"),
806
+ onMove && !done && "cursor-grab",
807
+ )}
808
+ >
809
+ {loop ? <span className="absolute inset-0 opacity-40" style={{ backgroundImage: "repeating-linear-gradient(135deg, currentColor 0 2px, transparent 2px 6px)" }} /> : null}
810
+ {!loop && !guess && !done && r.progress > 0 ? <span className={cx("absolute inset-y-0 left-0", fill)} style={{ width: `${r.progress}%` }} /> : null}
811
+ </button>
812
+ {base ? <span aria-hidden="true" className="pointer-events-none absolute h-1 rounded-[1px] bg-chart-3" style={{ left: X(base.start) - x0, width: Math.max(ppd, X(base.due) + ppd - X(base.start)), top: BAR + 2 }} /> : null}
813
+ {onMove && !done ? (
814
+ <>
815
+ <span onPointerDown={(ev) => startDrag(ev, e.id, "start")} aria-hidden="true" className={cx("absolute z-10 cursor-ew-resize", dragging || isSel ? "-inset-y-[2px] -left-[2px] w-1.5 rounded-[2px] bg-ring" : "inset-y-0 left-0 w-2")} style={{ touchAction: "none" }} />
816
+ <span onPointerDown={(ev) => startDrag(ev, e.id, "end")} aria-hidden="true" className={cx("absolute z-10 cursor-ew-resize", dragging || isSel ? "-inset-y-[2px] -right-[2px] w-1.5 rounded-[2px] bg-ring" : "inset-y-0 right-0 w-2")} style={{ touchAction: "none" }} />
817
+ </>
818
+ ) : null}
819
+ {handles}
820
+ {labelEl(w, isSel ? 8 : 0)}
821
+ </div>
822
+ );
823
+ })}
824
+
825
+ {dragInfo && dragRef.current ? <DragTip info={dragInfo} task={byId.get(dragInfo.id)} box={boxOf(dragInfo.id)} tasks={tasks} plan={plan} /> : null}
826
+
827
+ {linking ? (
828
+ (() => {
829
+ const dx = linking.to.x - linking.from.x;
830
+ const dy = linking.to.y - linking.from.y;
831
+ const lines2 = linking.targetId
832
+ ? linkLines({ task: byId.get(linking.targetId), predecessor: byId.get(linking.fromId), kind: KIND_OF[`${linking.fromEnd}:${linking.targetEnd}`], info: linking.preview?.info, problem: linking.problem })
833
+ : null;
834
+ return (
835
+ <>
836
+ <span aria-hidden="true" className={cx("pointer-events-none absolute z-30 h-0 origin-left border-t-[1.5px] border-dashed", linking.problem ? "border-destructive" : "border-ring")} style={{ left: linking.from.x, top: linking.from.y, width: Math.sqrt(dx * dx + dy * dy), transform: `rotate(${(Math.atan2(dy, dx) * 180) / Math.PI}deg)` }} />
837
+ {lines2 ? (
838
+ <div role="status" className="pointer-events-none absolute z-40 max-w-80 rounded-[4px] border border-border bg-popover px-2.5 py-2 text-[12px] leading-snug text-popover-foreground shadow-lg" style={{ left: linking.to.x + 12, top: linking.to.y + 10 }}>
839
+ <div className={lines2.tone === "problem" ? "font-medium text-destructive" : undefined}>{lines2.text}</div>
840
+ {lines2.tail ? <div className={lines2.tone === "late" ? "font-semibold text-warning" : "text-muted-foreground"}>{lines2.tail}</div> : null}
841
+ </div>
842
+ ) : null}
843
+ </>
844
+ );
845
+ })()
846
+ ) : null}
847
+
848
+ {visible.length === 0 && !loading ? (
849
+ <div className="absolute inset-x-0 top-6 grid place-items-center px-6 text-center">
850
+ <p className="max-w-sm text-[13px] text-muted-foreground">{emptyText}</p>
851
+ </div>
852
+ ) : null}
853
+
854
+ {hasLanes ? <LaneTrack lanes={lanes} X={X} ppd={ppd} laneH={laneH} top={bodyH + addExtra + laneHead} /> : null}
855
+ </div>
856
+ </div>
857
+ </div>
858
+ </div>
859
+ </div>
860
+ );
861
+ }
862
+
863
+ /** The note by a bar while it moves: what it is, where it ends, what it costs, in the canvas's words. */
864
+ function DragTip({ info, task, box, tasks, plan }) {
865
+ if (!box || !task) return null;
866
+ const next = (() => {
867
+ const l = [...plan.links.values()].find((x) => x.valid && x.dependsOnId === info.id);
868
+ return l ? tasks.find((t) => String(t.id) === l.taskId)?.title : null;
869
+ })();
870
+ const lines = dragLines(info, task, { nextTitle: next });
871
+ return (
872
+ <div role="status" className="pointer-events-none absolute z-40 grid w-max max-w-80 gap-0.5 rounded-[4px] border border-border bg-popover px-2.5 py-2 text-[12px] leading-snug text-popover-foreground shadow-lg" style={{ left: box.x1 + 14, top: Math.max(4, box.y - 64) }}>
873
+ <span className="text-[13px] font-semibold">{lines.title}</span>
874
+ <span className={NUM}>{lines.first}</span>
875
+ <span>{lines.second}</span>
876
+ {lines.slack ? <span className="text-muted-foreground">{lines.slack}</span> : null}
877
+ <span className={cx(NUM, lines.third.tone === "late" ? "font-semibold text-warning" : lines.third.tone === "early" ? "font-semibold text-success" : "text-muted-foreground")}>{lines.third.text}</span>
878
+ <span className="text-muted-foreground">{lines.last}</span>
879
+ </div>
880
+ );
881
+ }
882
+