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,148 @@
1
+ # ScheduleRuler
2
+
3
+ A dated chain drawn to scale, and how it stands against its promise.
4
+
5
+ One picture answers "when does this finish, and against what?". A day axis
6
+ (a tick each Monday, a taller one on the 1st). The chain's segments as
7
+ bars. A gate post at the end of a segment, built from its exit criteria as
8
+ pips. Milestones as diamonds. The target as a solid line, the forecast as a
9
+ dashed one, today as a line in the accent. Between target and forecast the
10
+ gap, hatched: **green when the forecast lands early (slack), red when it
11
+ lands late (an overrun)**, counted in working days and labelled "+3 d" or
12
+ "3 d slack".
13
+
14
+ It draws a result the app has already worked out. It does not schedule,
15
+ drag or link anything: for bars you edit, with dependencies, use `gantt`.
16
+
17
+ Born in three store apps that each drew it by hand: a phase planner's phase
18
+ ruler with gate dates, a Gantt planner's gap ruler beside the sentence, a
19
+ roadmap's forecast against its target.
20
+
21
+ ```jsx
22
+ import { ScheduleRuler, GapRuler, ScheduleStrip } from "../components/blocks/schedule-ruler"; // a Makersclaw app ("./blocks/schedule-ruler" from components/)
23
+ // shadcn project: import { ScheduleRuler } from "@/components/makersclaw/blocks/schedule-ruler"
24
+ ```
25
+
26
+ A client component. No npm packages, no other block. Dates are `'YYYY-MM-DD'`
27
+ strings and are days, never times, so the server and the browser draw the
28
+ same picture. Positions are percentages of the chain; nothing is measured.
29
+
30
+ ## Props
31
+
32
+ `ScheduleRuler`:
33
+
34
+ - `segments` — `[{ id, label, start, end, status, sub, gate }]`, in order.
35
+ - `start`, `end`: 'YYYY-MM-DD', both days included. A segment without
36
+ both, or ending before it starts, is left off; a line below the picture
37
+ says how many.
38
+ - `status`: `done` (solid, success) · `active` (solid ink) · `planned`
39
+ (an ink outline, the default) · `late` (solid accent) · `draft` (dashed,
40
+ breathing).
41
+ - `sub`: the small line under the name; default the segment's number.
42
+ - `gate`: `{ date?, pips: [{ id, label, met }] }`. The post stands at
43
+ `date` (default the segment's end). The pips are the exit criteria:
44
+ met is filled green, not met is a ring, with "2/4" beside the date.
45
+ A gate whose date has passed with criteria open shows its date in the
46
+ destructive tone; one with every criterion met has a green post. A gate
47
+ with no `pips` is just a dated post.
48
+ - `milestones` — `[{ id, date, label, done, side }]`. `done` fills the
49
+ diamond. `side` ("right" default, or "left") is where the name goes; it
50
+ flips when there is no room.
51
+ - `target` — the promise, 'YYYY-MM-DD'. Drawn as a solid line at the end of
52
+ that day.
53
+ - `forecast` — where the chain will finish. **Left out, it is the chain's
54
+ last day.** `null` for none. Drawn as a dashed line, in the gap's tone.
55
+ - `today` — 'YYYY-MM-DD' or null for no line.
56
+ - `start` — the chain's start, when it begins before its first segment.
57
+ - `ghost` — `[{ start, end }]`: where the segments sat in an earlier
58
+ version, dashed above the bars.
59
+ - `count` — `"working"` (default; Monday to Friday) or `"calendar"`: which
60
+ days the gap counts. `workweek` — weekday numbers that count, 0 being
61
+ Monday (default `[0,1,2,3,4]`).
62
+ - `words` — the app's vocabulary: `{ target: "Deadline", forecast, today,
63
+ on: "On target", over: (n) => "+" + n + " d", slack: (n) => n + " d slack",
64
+ undated: (n) => ..., early }`. Every key is optional.
65
+ - `compact` — no gate posts and no flag words; the picture only.
66
+ - `minWidth` — the least width in px the chain is drawn at (560). A
67
+ narrower container scrolls it sideways.
68
+ - `from`, `to` — 'YYYY-MM-DD' to fix the window (`to` is its last day).
69
+ Default: everything drawn, with a little air.
70
+ - `fitToday` — widen the window to take in today (default true). `false`:
71
+ today is drawn only when it already falls inside.
72
+ - `onOpen({ kind, id, item })` — makes bars, gates and diamonds buttons
73
+ (`kind` is "segment", "gate" or "milestone"; `item` is what you passed,
74
+ with `s`/`e` day numbers added on a segment). Leave it out for a
75
+ read-only picture: nothing is pressable.
76
+ - `loading`, `error`, `onRetry`, `empty`, `emptyAction` — the other states:
77
+ a skeleton; a danger notice with a "Try again" button when `onRetry` is
78
+ given (`error` may be a string); and with nothing dated, an empty panel
79
+ titled `empty` (default "Nothing is dated yet") with `emptyAction` in it.
80
+ - `label` — what a screen reader hears first ("Website relaunch").
81
+ `className` — layout only.
82
+
83
+ `GapRuler` — the target against the forecast and nothing else, about 380px
84
+ wide, for a sentence to sit beside: `target`, `forecast` (both required),
85
+ `count`, `workweek`, `words`, `label` (replaces the words under the ruler),
86
+ `width` (the most px it takes), `className`.
87
+
88
+ `ScheduleStrip` — the chain in one small row for a list: `segments`,
89
+ `target`, `forecast`, `today`, `count`, `workweek`, `height` (16), `label`,
90
+ `className`. Put the numbers in words beside it.
91
+
92
+ Also exported, pure and without React, for a screen that wants the same
93
+ numbers in a sentence: `gapOf({ target, forecast, count })` (`{ state:
94
+ "over" | "slack" | "on", days, from, to }`), `gapWords`, `daysBetween`,
95
+ `dayNumber`, `isoOf`, `shortDay`, `dayName`, `gateOf`, `frameOf`, `axisOf`,
96
+ `layoutLanes`, `placeLabel`, `readSegments`, `readMilestones`,
97
+ `summaryOf`, `WORKWEEK`.
98
+
99
+ ## Example
100
+
101
+ ```jsx
102
+ <ScheduleRuler
103
+ label="Website relaunch"
104
+ segments={[
105
+ { id: "d", label: "Discovery", start: "2026-09-07", end: "2026-09-25", status: "done",
106
+ gate: { pips: [{ id: "a", met: true }, { id: "b", met: true }] } },
107
+ { id: "x", label: "Design", start: "2026-09-28", end: "2026-10-23", status: "active", sub: "1 of 3 met",
108
+ gate: { pips: [{ id: "a", met: true }, { id: "b", met: false }, { id: "c", met: false }] } },
109
+ ]}
110
+ milestones={[{ id: "k", date: "2026-10-02", label: "Kickoff held", done: true }]}
111
+ target="2026-10-16"
112
+ today="2026-10-12"
113
+ words={{ target: "Deadline" }}
114
+ onOpen={({ kind, id }) => router.push(`?${kind}=${id}`)}
115
+ />
116
+
117
+ <GapRuler target={project.target} forecast={plan.finish} />
118
+ ```
119
+
120
+ ## States drawn
121
+
122
+ - **Empty**: nothing dated. A panel with what to do (`empty`, `emptyAction`).
123
+ - **One segment**, **many segments**: the window fits what is drawn.
124
+ - **Very long names**: a bar's name is cut to one line (its full text is
125
+ the bar's title and, with `onOpen`, its accessible name); milestones and
126
+ gates that would touch take another row instead of sitting on one
127
+ another.
128
+ - **Slack, overrun, on target**: green hatch and "3 d slack"; red hatch and
129
+ "+3 d"; no hatch and "On target".
130
+ - **Narrow** (a 320px container): the chain keeps its `minWidth` and scrolls
131
+ sideways (focusable, so the keyboard can scroll it); under 36rem of its
132
+ own width the chain is also listed in words below it.
133
+ - **Wide**: the chain stretches; the labels have more room than they need.
134
+ - **Loading**, **failed** (with Try again), **read-only** (no `onOpen`).
135
+ - Reduced motion: only a `draft` bar breathes, and it holds still.
136
+
137
+ ## Notes for the app that places it
138
+
139
+ - It is a picture. Always put the numbers in words beside it as well (the
140
+ sentence "Public launch lands Tue 3 Nov, seven working days after the
141
+ target"). A screen reader hears one summary line of the whole chain, built
142
+ from what is drawn.
143
+ - The block counts the gap in days; it does not know your holidays. If your
144
+ finish date already allows for them, pass `count="calendar"` or a
145
+ `workweek`, or draw the gap words yourself with `words`.
146
+ - `forecast` left out is the chain's last day. To draw a promise with no
147
+ forecast, pass `forecast={null}`.
148
+ - Colour is a signal here and nowhere else: green is early, red is late.
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "schedule-ruler",
3
+ "title": "Schedule ruler",
4
+ "description": "A dated chain drawn to scale on a day axis (a tick each Monday, a taller one on the 1st): the segments of the chain as bars with their names and status (done, in progress, planned, late, draft), a gate post at the end of a segment built from its exit criteria as pips (met are filled, with a 2/4 count, and the date turns to the warning tone once it has passed with criteria open), milestone diamonds with their names, a solid target line, a dashed forecast line, today as a line in the accent, and between target and forecast the gap hatched: green slack or a red overrun, counted in working days and labelled \"+3 d\" or \"3 d slack\". Earlier positions can show as dashed ghosts. GapRuler is the target against the forecast alone, for a sentence to sit beside; ScheduleStrip is the chain in one small row for a list. Positions are percentages, so it draws the same on the server; under 36rem of its own width the chain scrolls and is also listed in words. Loading, failed and empty states are drawn.",
5
+ "useWhen": "A screen that answers 'when does this finish, and against what promise': a project's phases and gates, a launch plan's finish against its target date, a roadmap row's forecast against its deadline, a delivery chain, a renewal or audit timeline. Draws a result the app has already worked out; it does not schedule. Not for editable bars with dependencies (gantt), days on a month grid (calendar-view), or the age of one thing against a limit (day-ruler).",
6
+ "tags": ["schedule", "timeline", "ruler", "phases", "gates", "milestones", "target", "deadline", "forecast", "slack", "overrun", "slip", "working days", "finish", "today line", "exit criteria", "roadmap", "project"],
7
+ "exports": ["ScheduleRuler", "GapRuler", "ScheduleStrip", "axisOf", "dayName", "dayNumber", "daysBetween", "frameOf", "gapOf", "gapWords", "gateOf", "isoOf", "layoutLanes", "placeLabel", "readMilestones", "readSegments", "shortDay", "summaryOf", "WORKWEEK"],
8
+ "files": ["index.js", "schedule-ruler.jsx", "ruler-model.js"],
9
+ "requires": []
10
+ }
@@ -0,0 +1,181 @@
1
+ "use client";
2
+
3
+ // ScheduleRuler in every state — the gallery's and the smoke test's
4
+ // surface. Not installed into apps: `makersclaw add schedule-ruler` lays
5
+ // index.js, schedule-ruler.jsx and ruler-model.js only.
6
+ // A chain five stages with gates built from pips, milestones, a
7
+ // target the forecast overruns, the earlier plan as ghosts;
8
+ // a press on a bar, a gate or a diamond says what it was.
9
+ // Slack the same chain finishing early: green.
10
+ // Long words a very long stage and milestone name in a crowded stretch.
11
+ // Compact the chain without gate words and flags.
12
+ // Narrow a 320 px container: the chain scrolls and is listed below.
13
+ // Wide a container well past 1000 px.
14
+ // Read-only no onOpen: nothing is pressable.
15
+ // Loading, failed and empty.
16
+ // GapRuler and ScheduleStrip alone.
17
+ import { useState } from "react";
18
+ import { Notice } from "../components/ui";
19
+ import { GapRuler, ScheduleRuler, ScheduleStrip } from "../components/blocks/schedule-ruler";
20
+
21
+ const TODAY = "2026-10-12";
22
+
23
+ const STAGES = [
24
+ {
25
+ id: "disc",
26
+ label: "Discovery",
27
+ start: "2026-09-07",
28
+ end: "2026-09-25",
29
+ status: "done",
30
+ sub: "3 of 3 met",
31
+ gate: { date: "2026-09-25", pips: [{ id: "a", label: "Scope signed", met: true }, { id: "b", label: "Budget approved", met: true }, { id: "c", label: "Owner named", met: true }] },
32
+ },
33
+ {
34
+ id: "design",
35
+ label: "Design",
36
+ start: "2026-09-28",
37
+ end: "2026-10-23",
38
+ status: "active",
39
+ sub: "2 of 4 met",
40
+ gate: {
41
+ date: "2026-10-23",
42
+ pips: [{ id: "a", label: "Wireframes reviewed", met: true }, { id: "b", label: "Brand approved", met: true }, { id: "c", label: "Accessibility pass", met: false }, { id: "d", label: "Content outline", met: false }],
43
+ },
44
+ },
45
+ {
46
+ id: "build",
47
+ label: "Build",
48
+ start: "2026-10-26",
49
+ end: "2026-11-27",
50
+ status: "planned",
51
+ sub: "0 of 3 met",
52
+ gate: { date: "2026-11-27", pips: [{ id: "a", label: "Feature complete", met: false }, { id: "b", label: "Tests green", met: false }, { id: "c", label: "Security review", met: false }] },
53
+ },
54
+ { id: "test", label: "Testing", start: "2026-11-30", end: "2026-12-11", status: "planned", sub: "0 of 2 met", gate: { date: "2026-12-11", pips: [{ id: "a", label: "No open blockers", met: false }, { id: "b", label: "Sign-off", met: false }] } },
55
+ { id: "launch", label: "Launch", start: "2026-12-14", end: "2026-12-16", status: "draft", sub: "being written" },
56
+ ];
57
+ const MILESTONES = [
58
+ { id: "m1", date: "2026-10-02", label: "Kickoff held", done: true },
59
+ { id: "m2", date: "2026-11-06", label: "Beta to ten customers" },
60
+ { id: "m3", date: "2026-12-04", label: "Press embargo lifts" },
61
+ ];
62
+ const EARLIER = [
63
+ { start: "2026-09-07", end: "2026-09-22" },
64
+ { start: "2026-09-23", end: "2026-10-16" },
65
+ { start: "2026-10-19", end: "2026-11-20" },
66
+ { start: "2026-11-23", end: "2026-12-04" },
67
+ ];
68
+
69
+ const EARLY = STAGES.map((s) => (s.id === "launch" ? { ...s, status: "planned", sub: "0 of 1 met" } : s)).slice(0, 4);
70
+
71
+ const CROWDED = [
72
+ { id: "one", label: "An exceptionally long name for the first stage of this chain", start: "2026-10-05", end: "2026-10-09", status: "active", sub: "a very long second line that cannot fit", gate: { date: "2026-10-09", pips: [{ id: "a", met: true }, { id: "b", met: false }, { id: "c", met: false }, { id: "d", met: false }, { id: "e", met: false }, { id: "f", met: false }] } },
73
+ { id: "two", label: "Second", start: "2026-10-12", end: "2026-10-13", status: "planned", gate: { date: "2026-10-13", pips: [{ id: "a", met: false }] } },
74
+ { id: "three", label: "Third", start: "2026-10-14", end: "2026-10-15", status: "planned", gate: { date: "2026-10-15", pips: [{ id: "a", met: false }, { id: "b", met: false }] } },
75
+ { id: "four", label: "Fourth stage", start: "2026-10-16", end: "2026-10-30", status: "late", gate: { date: "2026-10-30" } },
76
+ ];
77
+ const CROWDED_MILES = [
78
+ { id: "x1", date: "2026-10-07", label: "The extremely long milestone name goes here" },
79
+ { id: "x2", date: "2026-10-08", label: "Another one right beside it" },
80
+ { id: "x3", date: "2026-10-14", label: "Third" },
81
+ ];
82
+
83
+ const ROWS = [
84
+ { name: "Website relaunch", segments: STAGES, target: "2026-12-11" },
85
+ { name: "Billing migration", segments: EARLY, target: "2026-12-24" },
86
+ { name: "On the nose", segments: EARLY, target: "2026-12-11" },
87
+ { name: "No promise yet", segments: STAGES.slice(0, 3) },
88
+ ];
89
+
90
+ function Section({ title, note, children }) {
91
+ return (
92
+ <section className="grid min-w-0 gap-2">
93
+ <h2 className="text-sm font-semibold">{title}</h2>
94
+ {note ? <p className="text-xs text-muted-foreground">{note}</p> : null}
95
+ {children}
96
+ </section>
97
+ );
98
+ }
99
+
100
+ export default function Demo() {
101
+ const [pressed, setPressed] = useState(null);
102
+ return (
103
+ <div className="grid min-w-0 gap-8 p-6">
104
+ <Section title="A chain" note="Press a bar, a gate or a diamond. The forecast runs three working days past the target.">
105
+ <ScheduleRuler
106
+ label="Website relaunch"
107
+ segments={STAGES}
108
+ milestones={MILESTONES}
109
+ target="2026-12-11"
110
+ forecast="2026-12-16"
111
+ today={TODAY}
112
+ ghost={EARLIER}
113
+ onOpen={setPressed}
114
+ />
115
+ <p aria-live="polite" className="font-mono text-xs text-muted-foreground">
116
+ {pressed ? `Pressed ${pressed.kind}: ${pressed.item.label}` : "Nothing pressed yet"}
117
+ </p>
118
+ </Section>
119
+
120
+ <Section title="Slack" note="Four stages, the last gate on 11 Dec; the target is 24 Dec, so the days between are slack.">
121
+ <ScheduleRuler label="Billing migration" segments={EARLY} target="2026-12-24" today={TODAY} words={{ target: "Deadline" }} />
122
+ </Section>
123
+
124
+ <Section title="On the nose" note="The forecast is the target: one line and the words 'On target'.">
125
+ <ScheduleRuler label="On the nose" segments={EARLY} target="2026-12-11" today={TODAY} />
126
+ </Section>
127
+
128
+ <Section title="Long words, crowded" note="Milestones and gates a day apart stack into lanes instead of sitting on one another.">
129
+ <ScheduleRuler label="Crowded" segments={CROWDED} milestones={CROWDED_MILES} target="2026-10-28" today="2026-10-13" />
130
+ </Section>
131
+
132
+ <Section title="Compact">
133
+ <ScheduleRuler compact label="Compact" segments={STAGES} target="2026-12-11" forecast="2026-12-16" today={TODAY} />
134
+ </Section>
135
+
136
+ <Section title="Narrow, 320 px" note="The chain scrolls sideways; below it the same chain in words.">
137
+ <div className="w-[320px] max-w-full rounded-md border border-border p-3">
138
+ <ScheduleRuler label="Narrow" segments={STAGES} milestones={MILESTONES} target="2026-12-11" forecast="2026-12-16" today={TODAY} />
139
+ </div>
140
+ </Section>
141
+
142
+ <Section title="Wide">
143
+ <div className="w-[1200px] max-w-none rounded-md border border-border p-4">
144
+ <ScheduleRuler label="Wide" segments={STAGES} milestones={MILESTONES} target="2026-12-11" forecast="2026-12-16" today={TODAY} />
145
+ </div>
146
+ </Section>
147
+
148
+ <Section title="Read-only" note="No onOpen: nothing is a button.">
149
+ <ScheduleRuler label="Read-only" segments={STAGES} target="2026-12-11" forecast="2026-12-16" today={TODAY} />
150
+ </Section>
151
+
152
+ <Section title="Loading, failed, empty">
153
+ <ScheduleRuler label="Loading" loading />
154
+ <ScheduleRuler label="Schedule" error="The dates could not be read. Check the connection." onRetry={() => setPressed({ kind: "retry", item: { label: "Try again" } })} />
155
+ <ScheduleRuler label="Empty" empty="No phases have dates yet" segments={[{ id: "u", label: "Draft", start: "", end: "" }]} />
156
+ </Section>
157
+
158
+ <Section title="GapRuler" note="The target against the forecast, for a sentence to sit beside.">
159
+ <div className="grid gap-4 @container">
160
+ <GapRuler target="2026-12-11" forecast="2026-12-16" />
161
+ <GapRuler target="2026-12-24" forecast="2026-12-11" />
162
+ <GapRuler target="2026-12-11" forecast="2026-12-11" />
163
+ <GapRuler target="2026-12-11" forecast="2026-12-12" count="calendar" words={{ over: (n) => `${n} day late` }} />
164
+ </div>
165
+ </Section>
166
+
167
+ <Section title="ScheduleStrip" note="One row of the chain, for a row in a list.">
168
+ <div className="grid max-w-xl gap-3">
169
+ {ROWS.map((r) => (
170
+ <div key={r.name} className="grid grid-cols-[minmax(0,10rem)_minmax(0,1fr)] items-center gap-4">
171
+ <span className="truncate text-sm font-medium">{r.name}</span>
172
+ <ScheduleStrip label={r.name} segments={r.segments} target={r.target} today={TODAY} />
173
+ </div>
174
+ ))}
175
+ </div>
176
+ </Section>
177
+
178
+ <Notice tone="info">Every date is a day, never a time: 2026-12-11 means the same day on the server and in the browser.</Notice>
179
+ </div>
180
+ );
181
+ }
@@ -0,0 +1,21 @@
1
+ // The ScheduleRuler block — `makersclaw add schedule-ruler`. Fixed: compose
2
+ // it through its props (README.md beside this file).
3
+ export { ScheduleRuler, GapRuler, ScheduleStrip } from "./schedule-ruler";
4
+ export {
5
+ axisOf,
6
+ dayName,
7
+ dayNumber,
8
+ daysBetween,
9
+ frameOf,
10
+ gapOf,
11
+ gapWords,
12
+ gateOf,
13
+ isoOf,
14
+ layoutLanes,
15
+ placeLabel,
16
+ readMilestones,
17
+ readSegments,
18
+ shortDay,
19
+ summaryOf,
20
+ WORKWEEK,
21
+ } from "./ruler-model";
@@ -0,0 +1,239 @@
1
+ // The schedule ruler's arithmetic: dates, working days, the gap between a
2
+ // target and a forecast, the window, the axis ticks, and where labels go
3
+ // so none sits on another. Pure: no React, no DOM, no clock. The component
4
+ // (schedule-ruler.jsx) draws what this returns; test/schedule-ruler.test.mjs
5
+ // pins it.
6
+ //
7
+ // A "day" here is a whole number: days since 1970-01-01 in UTC, so a date
8
+ // string means the same day on the server and in the browser.
9
+
10
+ export const DAY_MS = 86400000;
11
+ export const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
12
+ export const WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
13
+ /** Monday to Friday, as weekdayOf() numbers it (0 is Monday). */
14
+ export const WORKWEEK = [0, 1, 2, 3, 4];
15
+
16
+ /** 'YYYY-MM-DD' (or an ISO time) to a day number; null when it is not a date. */
17
+ export function dayNumber(iso) {
18
+ const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(iso ?? ""));
19
+ if (!m) return null;
20
+ const t = Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
21
+ const d = new Date(t);
22
+ // 2026-02-31 is not a day: Date rolls it over, and the roll-over shows.
23
+ if (d.getUTCMonth() !== Number(m[2]) - 1) return null;
24
+ return Math.round(t / DAY_MS);
25
+ }
26
+
27
+ /** A day number back to 'YYYY-MM-DD'. */
28
+ export function isoOf(day) {
29
+ const d = new Date(day * DAY_MS);
30
+ return `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}-${String(d.getUTCDate()).padStart(2, "0")}`;
31
+ }
32
+
33
+ /** 0 is Monday, 6 is Sunday. */
34
+ export const weekdayOf = (day) => (new Date(day * DAY_MS).getUTCDay() + 6) % 7;
35
+ export const dateOfMonth = (day) => new Date(day * DAY_MS).getUTCDate();
36
+ export const monthOf = (day) => new Date(day * DAY_MS).getUTCMonth();
37
+ export const yearOf = (day) => new Date(day * DAY_MS).getUTCFullYear();
38
+
39
+ /** "11 Dec" */
40
+ export const shortDay = (day) => `${dateOfMonth(day)} ${MONTHS[monthOf(day)]}`;
41
+ /** "Fri 11 Dec" */
42
+ export const dayName = (day) => `${WEEKDAYS[weekdayOf(day)]} ${shortDay(day)}`;
43
+
44
+ /** Does this day count? `count` is "working" (the workweek only) or "calendar" (every day). */
45
+ export function isCounted(day, count = "working", workweek = WORKWEEK) {
46
+ return count === "calendar" ? true : workweek.includes(weekdayOf(day));
47
+ }
48
+
49
+ /**
50
+ * Signed days from `from` to `to`, counting the days after `from` up to and
51
+ * including `to`: Mon to Wed is 2 working days, Wed to Mon is -2. The same
52
+ * rule everywhere, so "+3 d" means the same on the ruler, in a sentence and
53
+ * in a table.
54
+ */
55
+ export function daysBetween(from, to, { count = "working", workweek = WORKWEEK } = {}) {
56
+ if (from == null || to == null) return null;
57
+ if (to === from) return 0;
58
+ const [a, b, sign] = to > from ? [from + 1, to, 1] : [to + 1, from, -1];
59
+ let n = 0;
60
+ for (let d = a; d <= b; d += 1) if (isCounted(d, count, workweek)) n += 1;
61
+ return sign * n;
62
+ }
63
+
64
+ /**
65
+ * The gap between a target and a forecast finish.
66
+ * over the forecast is later than the target: the days between are an overrun
67
+ * slack the forecast is earlier: the days between are room to spare
68
+ * on no counted day between them
69
+ * `from` and `to` are the first and last day of the hatched stretch.
70
+ * Null when either date is missing.
71
+ */
72
+ export function gapOf({ target, forecast, count = "working", workweek = WORKWEEK } = {}) {
73
+ const t = typeof target === "number" ? target : dayNumber(target);
74
+ const f = typeof forecast === "number" ? forecast : dayNumber(forecast);
75
+ if (t == null || f == null) return null;
76
+ const days = Math.abs(daysBetween(t, f, { count, workweek }));
77
+ if (days === 0) return { state: "on", days: 0, from: null, to: null, target: t, forecast: f };
78
+ return f > t
79
+ ? { state: "over", days, from: t + 1, to: f, target: t, forecast: f }
80
+ : { state: "slack", days, from: f + 1, to: t, target: t, forecast: f };
81
+ }
82
+
83
+ /** "+3 d" for an overrun, "3 d slack" for room to spare, "On target" for none. */
84
+ export function gapWords(gap, words = {}) {
85
+ if (!gap) return "";
86
+ if (gap.state === "over") return (words.over ?? ((n) => `+${n} d`))(gap.days);
87
+ if (gap.state === "slack") return (words.slack ?? ((n) => `${n} d slack`))(gap.days);
88
+ return words.on ?? "On target";
89
+ }
90
+
91
+ /**
92
+ * The segments that can be drawn: both dates real and the end not before
93
+ * the start. `skipped` counts the rest, so a screen can say so.
94
+ */
95
+ export function readSegments(segments) {
96
+ const out = [];
97
+ let skipped = 0;
98
+ for (const [i, seg] of (segments ?? []).entries()) {
99
+ const s = dayNumber(seg?.start);
100
+ const e = dayNumber(seg?.end);
101
+ if (s == null || e == null || e < s) {
102
+ skipped += 1;
103
+ continue;
104
+ }
105
+ out.push({ ...seg, id: seg.id ?? `segment-${i}`, s, e });
106
+ }
107
+ return { segs: out, skipped };
108
+ }
109
+
110
+ /** Milestones with a real date, in date order. */
111
+ export function readMilestones(milestones) {
112
+ const out = [];
113
+ for (const [i, m] of (milestones ?? []).entries()) {
114
+ const d = dayNumber(m?.date);
115
+ if (d != null) out.push({ ...m, id: m.id ?? `milestone-${i}`, day: d });
116
+ }
117
+ return out.sort((a, b) => a.day - b.day);
118
+ }
119
+
120
+ /**
121
+ * How a gate stands: how many of its exit criteria are met, and
122
+ * met every criterion met (a gate with criteria)
123
+ * open some are not, and the gate day is today or ahead
124
+ * overdue some are not, and the gate day has passed
125
+ * bare no criteria listed: just a date
126
+ */
127
+ export function gateOf(gate, endDay, today) {
128
+ if (!gate) return null;
129
+ const day = dayNumber(gate.date) ?? endDay;
130
+ const pips = Array.isArray(gate.pips) ? gate.pips : [];
131
+ const total = pips.length;
132
+ const met = pips.filter((p) => p?.met).length;
133
+ const t = typeof today === "number" ? today : dayNumber(today);
134
+ let state = "bare";
135
+ if (total) state = met === total ? "met" : t != null && day < t ? "overdue" : "open";
136
+ return { day, pips, met, total, state };
137
+ }
138
+
139
+ /**
140
+ * The window of days the ruler shows. `days` are day numbers (a segment
141
+ * contributes its start and the day after its end). The window gets a little
142
+ * air either side; `from` and `to` (day numbers, `to` exclusive) override.
143
+ * x(day) is the day's left edge as a percentage of the window.
144
+ */
145
+ export function frameOf({ days = [], from = null, to = null, padBefore = null, padAfter = null } = {}) {
146
+ const real = days.filter((d) => Number.isFinite(d));
147
+ if (!real.length && from == null && to == null) return null;
148
+ const lo = Math.min(...(real.length ? real : [from ?? to]));
149
+ const hi = Math.max(...(real.length ? real : [to ?? from]));
150
+ const span0 = Math.max(1, hi - lo);
151
+ const air = Math.max(2, Math.round(span0 * 0.025));
152
+ const r0 = from ?? lo - (padBefore ?? air);
153
+ const r1 = Math.max(r0 + 1, to ?? hi + (padAfter ?? air));
154
+ const span = r1 - r0;
155
+ return { r0, r1, span, x: (day) => ((day - r0) / span) * 100 };
156
+ }
157
+
158
+ /**
159
+ * The axis: a tick each Monday while the window is short enough to read
160
+ * them, a taller one on each month's 1st (each quarter's, past about two
161
+ * years), and the month names. The window's first day names its month
162
+ * unless the next month starts right after it.
163
+ */
164
+ export function axisOf(r0, r1) {
165
+ const span = r1 - r0;
166
+ const quarters = span > 800;
167
+ const weeks = [];
168
+ const months = [];
169
+ if (span <= 150) for (let d = r0; d < r1; d += 1) if (weekdayOf(d) === 0) weeks.push(d);
170
+ let nextFirst = null;
171
+ for (let d = r0; d < r1; d += 1) {
172
+ if (dateOfMonth(d) === 1 && (!quarters || monthOf(d) % 3 === 0)) {
173
+ nextFirst ??= d;
174
+ months.push({ day: d, label: MONTHS[monthOf(d)], year: monthOf(d) === 0, tick: true });
175
+ }
176
+ }
177
+ const startsOnFirst = dateOfMonth(r0) === 1;
178
+ const crowded = !startsOnFirst && nextFirst != null && ((nextFirst - r0) / span) * 100 < 9;
179
+ if (!startsOnFirst && !crowded && !quarters) months.unshift({ day: r0, label: MONTHS[monthOf(r0)], year: true, tick: false });
180
+ else if (crowded && months.length) months[0] = { ...months[0], year: true };
181
+ if (!months.length) months.push({ day: r0, label: MONTHS[monthOf(r0)], year: true, tick: false });
182
+ return { weeks, months: months.map((m) => ({ ...m, label: m.year ? `${m.label} ${yearOf(m.day)}` : m.label })) };
183
+ }
184
+
185
+ /** Pixels a character of the small mono face takes (about); a label's width is a guess from its text. */
186
+ export const CHAR_PX = 7.4;
187
+
188
+ /** A label's width as a percentage of a chain `minWidth` px wide (the narrowest it is ever drawn). */
189
+ export const labelPct = (chars, minWidth, extraPx = 12) => ((chars * CHAR_PX + extraPx) / minWidth) * 100;
190
+
191
+ /**
192
+ * Where a label sits against the line it belongs to. It starts at the line
193
+ * and runs right; when it would run past the window's right edge it ends at
194
+ * the line and runs left instead (and the reverse at the left edge).
195
+ * Returns the side and the percentage interval the label covers.
196
+ */
197
+ export function placeLabel({ x, width, prefer = "right" }) {
198
+ let side = prefer;
199
+ if (side === "right" && x + width > 100) side = "left";
200
+ if (side === "left" && x - width < 0 && x + width <= 100) side = "right";
201
+ const x0 = side === "right" ? x : x - width;
202
+ return { side, x0, x1: x0 + width };
203
+ }
204
+
205
+ /**
206
+ * Lanes for labels that would overlap: sorted left to right, each item takes
207
+ * the first lane whose last label ended at least `gap` percent before it
208
+ * starts. `items` are { id, x0, x1 } in percent; returns { lanes: {id: n},
209
+ * count } (count is the number of lanes used, 0 for none).
210
+ */
211
+ export function layoutLanes(items, gap = 1) {
212
+ const ends = [];
213
+ const lanes = {};
214
+ const sorted = [...items].sort((a, b) => a.x0 - b.x0 || String(a.id).localeCompare(String(b.id)));
215
+ for (const it of sorted) {
216
+ let lane = ends.findIndex((end) => end + gap <= it.x0);
217
+ if (lane === -1) lane = ends.length;
218
+ ends[lane] = it.x1;
219
+ lanes[it.id] = lane;
220
+ }
221
+ return { lanes, count: ends.length };
222
+ }
223
+
224
+ /** The same line a screen reader hears for the whole ruler, built from what is drawn. */
225
+ export function summaryOf({ label = "Schedule", segs = [], milestones = [], target = null, forecast = null, gap = null, today = null, words = {} }) {
226
+ const parts = segs.map((p) => {
227
+ const g = p.gate;
228
+ const crit = g?.total ? `, gate ${g.met} of ${g.total} met` : "";
229
+ return `${p.label}${p.status ? `, ${p.status}` : ""}, ${shortDay(p.s)} to ${shortDay(p.e)}${crit}`;
230
+ });
231
+ for (const m of milestones) parts.push(`${m.label}${m.done ? ", done" : ""}, ${shortDay(m.day)}`);
232
+ const head = parts.length ? parts.join("; ") : "nothing dated";
233
+ const tail = [];
234
+ if (target != null) tail.push(`${words.target ?? "Target"} ${dayName(target)}`);
235
+ if (forecast != null && gap?.state !== "on") tail.push(`${words.forecast ?? "Forecast"} ${dayName(forecast)}`);
236
+ if (gap) tail.push(gap.state === "on" ? (words.on ?? "On target") : `${gap.days} ${gap.days === 1 ? "day" : "days"} ${gap.state === "over" ? "over" : "of slack"}`);
237
+ if (today != null) tail.push(`${words.today ?? "Today"} ${dayName(today)}`);
238
+ return `${label}: ${head}${tail.length ? `; ${tail.join("; ")}` : ""}`;
239
+ }