makersclaw 0.8.0 → 0.8.2

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 +234 -0
  143. package/registry/blocks/agent-pane/agent-pane.jsx +246 -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 +1677 -8
  400. package/templates/next/files/lib/platform.js +529 -21
  401. package/templates/next/template.json +13 -3
@@ -0,0 +1,548 @@
1
+ "use client";
2
+
3
+ import { Button, Empty, Icon, Notice, Skeleton, cx } from "../../ui";
4
+ import {
5
+ dayName,
6
+ dayNumber,
7
+ gapOf,
8
+ gapWords,
9
+ gateOf,
10
+ axisOf,
11
+ frameOf,
12
+ labelPct,
13
+ layoutLanes,
14
+ placeLabel,
15
+ readMilestones,
16
+ readSegments,
17
+ shortDay,
18
+ summaryOf,
19
+ } from "./ruler-model";
20
+
21
+ // ScheduleRuler: a dated chain drawn to scale, like a surveyor's chain.
22
+ // A baseline of days (a tick each Monday, a taller one on the 1st); the
23
+ // segments of the chain as bars with their names; a gate post at the end of
24
+ // a segment, built from its exit criteria as pips; milestone diamonds; the
25
+ // target as a solid line and the forecast as a dashed one, and between them
26
+ // the gap hatched: green when the forecast lands early (slack), red when it
27
+ // lands late (an overrun), with its size in working days ("+3 d"). Today is
28
+ // a line in the accent.
29
+ //
30
+ // GapRuler is the target against the forecast and nothing else, for a
31
+ // sentence to sit beside. ScheduleStrip is the chain small, one row, for a
32
+ // row in a list.
33
+ //
34
+ // Positions are percentages of the chain's width: nothing measures the page,
35
+ // so the server's HTML and the browser agree. The chain is never narrower
36
+ // than `minWidth`; a container narrower than that scrolls it sideways and,
37
+ // under 36rem of its own width, also lists the chain in words below it.
38
+ // Props in, one callback out (`onOpen`); it imports only React and the kit.
39
+ // README.md beside this file lists every prop.
40
+
41
+ const AXIS = 30;
42
+ const MILE_H = 20;
43
+ const GHOST_H = 14;
44
+ const GATE_H = 38;
45
+ const FLAG_H = 22;
46
+
47
+ // The hatch is drawn in currentColor, so a text-* token paints it.
48
+ const HATCH = { backgroundImage: "repeating-linear-gradient(135deg, currentColor 0 1.5px, transparent 1.5px 7px)" };
49
+ const HATCH_FINE = { backgroundImage: "repeating-linear-gradient(135deg, currentColor 0 1.2px, transparent 1.2px 5px)" };
50
+
51
+ // A segment's status: done (solid, success) · active (solid ink) · planned
52
+ // (an ink outline) · late (solid accent: past its gate) · draft (dashed,
53
+ // breathing: still being written).
54
+ const TONE = {
55
+ done: "bg-success text-background border border-success",
56
+ active: "bg-foreground text-background border border-foreground",
57
+ planned: "bg-background text-foreground border border-foreground",
58
+ late: "bg-primary text-primary-foreground border border-primary",
59
+ draft: "border-[1.5px] border-dashed border-muted-foreground bg-background text-muted-foreground motion-safe:animate-pulse",
60
+ };
61
+ const STRIP = {
62
+ done: "bg-success",
63
+ active: "bg-foreground",
64
+ late: "bg-primary",
65
+ planned: "border border-foreground bg-background",
66
+ draft: "border border-dashed border-muted-foreground",
67
+ };
68
+ const STATUS_WORDS = { done: "done", active: "in progress", planned: "planned", late: "late", draft: "draft" };
69
+
70
+ const GAP_TEXT = { over: "text-destructive", slack: "text-success", on: "text-foreground" };
71
+ const GAP_LINE = { over: "border-destructive", slack: "border-success", on: "border-foreground" };
72
+
73
+ const WORDS = {
74
+ target: "Target",
75
+ forecast: "Forecast",
76
+ today: "Today",
77
+ on: "On target",
78
+ over: (n) => `+${n} d`,
79
+ slack: (n) => `${n} d slack`,
80
+ undated: (n) => `${n} without dates, left off`,
81
+ early: "Earlier plan",
82
+ };
83
+
84
+ const pct = (n) => `${n}%`;
85
+
86
+ /** The things every form of the ruler reads the same way. */
87
+ function readProps({ segments, milestones, target, forecast, today, count, workweek }) {
88
+ const { segs, skipped } = readSegments(segments);
89
+ const miles = readMilestones(milestones);
90
+ const tgt = dayNumber(target);
91
+ const t = dayNumber(today);
92
+ // forecast: undefined → the chain's own last day; null or "" → none.
93
+ const derived = forecast === undefined && segs.length ? Math.max(...segs.map((p) => p.e)) : null;
94
+ const fc = forecast === undefined ? derived : dayNumber(forecast);
95
+ const gap = gapOf({ target: tgt, forecast: fc, count, workweek });
96
+ return { segs, skipped, miles, tgt, fc, t, gap };
97
+ }
98
+
99
+ /**
100
+ * The full chain.
101
+ *
102
+ * Props: see README.md. In short: `segments` [{ id, label, start, end,
103
+ * status, sub, gate }], `milestones` [{ id, date, label, done }], `target`,
104
+ * `forecast`, `today`, `ghost`, `count`, `words`, `compact`, `minWidth`,
105
+ * `onOpen`, `loading`, `error`, `onRetry`, `empty`, `emptyAction`.
106
+ */
107
+ export function ScheduleRuler({
108
+ segments,
109
+ milestones,
110
+ start,
111
+ target,
112
+ forecast,
113
+ today,
114
+ ghost,
115
+ count = "working",
116
+ workweek,
117
+ words: wordsIn,
118
+ compact = false,
119
+ minWidth = 560,
120
+ fitToday = true,
121
+ from,
122
+ to,
123
+ onOpen,
124
+ loading = false,
125
+ error,
126
+ onRetry,
127
+ empty,
128
+ emptyAction,
129
+ label = "Schedule",
130
+ className,
131
+ }) {
132
+ const words = { ...WORDS, ...wordsIn };
133
+
134
+ if (loading) {
135
+ return (
136
+ <div aria-busy="true" aria-label={`${label}, loading`} className={cx("@container grid min-w-0 gap-3", className)}>
137
+ <Skeleton shape="rect" className="h-4 w-full" />
138
+ <Skeleton shape="rect" className="h-12 w-full" />
139
+ <Skeleton shape="rect" className="h-4 w-2/3" />
140
+ </div>
141
+ );
142
+ }
143
+ if (error) {
144
+ return (
145
+ <div className={cx("@container min-w-0", className)}>
146
+ <Notice
147
+ tone="danger"
148
+ title={`${label} did not load`}
149
+ action={onRetry ? <Button size="sm" onClick={onRetry}>Try again</Button> : null}
150
+ >
151
+ {typeof error === "string" ? error : "Something went wrong reading the dates."}
152
+ </Notice>
153
+ </div>
154
+ );
155
+ }
156
+
157
+ const { segs, skipped, miles, tgt, fc, t, gap } = readProps({ segments, milestones, target, forecast, today, count, workweek });
158
+ const startDay = dayNumber(start);
159
+ const ghosts = (ghost ?? [])
160
+ .map((g) => ({ s: dayNumber(g?.start), e: dayNumber(g?.end) }))
161
+ .filter((g) => g.s != null && g.e != null && g.e >= g.s);
162
+ const days = [
163
+ startDay,
164
+ ...segs.flatMap((p) => [p.s, p.e + 1]),
165
+ ...miles.flatMap((m) => [m.day, m.day + 1]),
166
+ tgt != null ? tgt + 1 : null,
167
+ fc != null ? fc + 1 : null,
168
+ ...ghosts.flatMap((g) => [g.s, g.e + 1]),
169
+ fitToday && t != null ? t : null,
170
+ ].filter((d) => d != null);
171
+ const frame = segs.length || miles.length || tgt != null ? frameOf({ days, from: dayNumber(from), to: dayNumber(to) }) : null;
172
+
173
+ if (!frame) {
174
+ return (
175
+ <div className={cx("@container min-w-0", className)}>
176
+ <Empty
177
+ icon="calendar"
178
+ title={empty ?? "Nothing is dated yet"}
179
+ body={skipped ? words.undated(skipped) : null}
180
+ action={emptyAction}
181
+ />
182
+ </div>
183
+ );
184
+ }
185
+
186
+ const { r0, r1, x } = frame;
187
+ const axis = axisOf(r0, r1);
188
+ const inWindow = (d) => d != null && d >= r0 && d <= r1;
189
+ const tIn = inWindow(t) ? t : null;
190
+
191
+ // ---- rows, top to bottom, in px
192
+ const mileItems = miles.map((m) => {
193
+ const text = `${m.label} ${shortDay(m.day)}`;
194
+ const w = labelPct(text.length, minWidth, 26);
195
+ const px = x(m.day) + (100 / (r1 - r0)) / 2;
196
+ return { id: m.id, m, px, ...placeLabel({ x: px, width: w, prefer: m.side ?? "right" }) };
197
+ });
198
+ const mileLay = layoutLanes(mileItems);
199
+ const mileZone = miles.length ? mileLay.count * MILE_H + 6 : 0;
200
+ const ghostZone = ghosts.length ? GHOST_H + 4 : 0;
201
+ const bandTop = AXIS + mileZone + ghostZone + 8;
202
+ const bandH = compact ? 36 : 48;
203
+ const bandBottom = bandTop + bandH;
204
+
205
+ const gateItems = compact
206
+ ? []
207
+ : segs
208
+ .map((p) => ({ p, g: gateOf(p.gate, p.e, t) }))
209
+ .filter((r) => r.g)
210
+ .map(({ p, g }) => {
211
+ const gx = x(g.day + 1);
212
+ // The date and the count share a line; the pips (12px each) are the other.
213
+ const chars = Math.max(shortDay(g.day).length + (g.total ? 5 : 0), g.total * 1.7);
214
+ return { id: p.id, p, g, gx, ...placeLabel({ x: gx, width: labelPct(chars, minWidth, 8), prefer: "left" }) };
215
+ });
216
+ const gateLay = layoutLanes(gateItems);
217
+ const gateZone = gateItems.length ? 8 + gateLay.count * GATE_H : 0;
218
+
219
+ const gapTop = bandBottom + gateZone + 4;
220
+ const gapRow = gap ? 20 : 0;
221
+
222
+ const flags = [];
223
+ if (tgt != null) flags.push({ id: "target", day: tgt + 1, text: `${words.target} ${dayName(tgt)}`, kind: "target" });
224
+ if (fc != null && gap?.state !== "on" && (tgt != null || forecast !== undefined)) flags.push({ id: "forecast", day: fc + 1, text: `${words.forecast} ${dayName(fc)}`, kind: "forecast" });
225
+ if (tIn != null) flags.push({ id: "today", day: tIn, text: `${words.today} ${shortDay(tIn)}`, kind: "today" });
226
+ const flagItems = compact
227
+ ? []
228
+ : flags.map((f) => {
229
+ const fx = x(f.day);
230
+ return { ...f, fx, ...placeLabel({ x: fx, width: labelPct(f.text.length, minWidth, 18), prefer: "right" }) };
231
+ });
232
+ const flagLay = layoutLanes(flagItems);
233
+ const flagTop = gapTop + gapRow + 6;
234
+ const flagZone = flagItems.length ? flagLay.count * FLAG_H : 0;
235
+ const height = (compact ? bandBottom + 6 + gapRow : flagTop + flagZone) + 4;
236
+ const bottomOf = (lane) => flagTop + lane * FLAG_H + 18;
237
+ // A line runs down to its own flag; with no flag drawn (compact) it stops under the band.
238
+ const lineBottom = (id) => (flagLay.lanes[id] != null ? bottomOf(flagLay.lanes[id]) : bandBottom + 8);
239
+
240
+ const summary = summaryOf({
241
+ label,
242
+ segs: segs.map((p) => ({ ...p, status: STATUS_WORDS[p.status] ?? p.status, gate: gateOf(p.gate, p.e, t) })),
243
+ milestones: miles,
244
+ target: tgt,
245
+ forecast: fc,
246
+ gap,
247
+ today: t,
248
+ words,
249
+ });
250
+ const open = (kind, item) => (onOpen ? () => onOpen({ kind, id: item.id, item }) : undefined);
251
+
252
+ return (
253
+ <div role="group" aria-label={label} className={cx("@container grid min-w-0 gap-3", className)}>
254
+ <p className="sr-only">{summary}</p>
255
+ <div role="region" aria-label={`${label}, scrolls sideways`} tabIndex={0} className="overflow-x-auto rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring">
256
+ <div className="relative w-full" style={{ minWidth, height }}>
257
+ {/* the axis */}
258
+ <span aria-hidden="true" className="absolute inset-x-0 h-px bg-foreground" style={{ top: AXIS - 4 }} />
259
+ {axis.weeks.map((d) => (
260
+ <span key={`w${d}`} aria-hidden="true" className="absolute h-[5px] w-px bg-foreground" style={{ left: pct(x(d)), top: AXIS - 9 }} />
261
+ ))}
262
+ {axis.months.map((m) => (
263
+ <span key={`m${m.day}`} aria-hidden="true">
264
+ {m.tick ? <span className="absolute h-[17px] w-px bg-foreground" style={{ left: pct(x(m.day)), top: AXIS - 21 }} /> : null}
265
+ <span className="absolute top-0 whitespace-nowrap pl-1.5 font-mono text-xs uppercase tracking-[0.06em] text-muted-foreground" style={{ left: pct(x(m.day)) }}>
266
+ {m.label}
267
+ </span>
268
+ </span>
269
+ ))}
270
+
271
+ {/* milestones */}
272
+ {mileItems.map((m) => {
273
+ const lane = mileLay.lanes[m.id];
274
+ const top = AXIS + 4 + lane * MILE_H;
275
+ const Mark = onOpen ? "button" : "span";
276
+ return (
277
+ <Mark
278
+ key={m.id}
279
+ {...(onOpen ? { type: "button", onClick: open("milestone", m.m), "aria-label": `${m.m.label}${m.m.done ? ", done" : ""}, ${dayName(m.m.day)}` } : { "aria-hidden": "true" })}
280
+ className={cx("absolute flex h-[18px] items-center gap-1.5 whitespace-nowrap rounded-sm bg-background pr-1 text-left font-mono text-xs outline-none", onOpen && "focus-visible:ring-2 focus-visible:ring-ring")}
281
+ style={m.side === "right" ? { left: `calc(${pct(m.px)} - 6px)`, top } : { right: `calc(${pct(100 - m.px)} - 6px)`, top, flexDirection: "row-reverse" }}
282
+ >
283
+ <span aria-hidden="true" className={cx("block size-[11px] shrink-0 rotate-45 border-[1.5px] border-foreground", m.m.done ? "bg-foreground" : "bg-background")} />
284
+ <span className="font-sans text-xs font-medium text-foreground">{m.m.label}</span>
285
+ <span className="text-muted-foreground">{shortDay(m.m.day)}</span>
286
+ </Mark>
287
+ );
288
+ })}
289
+
290
+ {/* where the chain sat before */}
291
+ {ghosts.map((g, i) => (
292
+ <span
293
+ key={`g${i}`}
294
+ aria-hidden="true"
295
+ title={words.early}
296
+ className="absolute border border-dashed border-muted-foreground"
297
+ style={{ left: pct(x(g.s)), width: `calc(${x(g.e + 1) - x(g.s)}% - 3px)`, top: bandTop - GHOST_H - 3, height: GHOST_H - 4 }}
298
+ />
299
+ ))}
300
+
301
+ {/* the segments */}
302
+ {segs.map((p, i) => {
303
+ const x0 = x(p.s);
304
+ const x1 = x(p.e + 1);
305
+ const Seg = onOpen ? "button" : "span";
306
+ const status = STATUS_WORDS[p.status] ?? p.status ?? "planned";
307
+ return (
308
+ <Seg
309
+ key={p.id}
310
+ {...(onOpen
311
+ ? { type: "button", onClick: open("segment", p), "aria-label": `${p.label}, ${status}, ${shortDay(p.s)} to ${shortDay(p.e)}` }
312
+ : { "aria-hidden": "true" })}
313
+ title={`${p.label}, ${status}, ${shortDay(p.s)} to ${shortDay(p.e)}`}
314
+ className={cx(
315
+ "absolute flex min-w-[3px] flex-col justify-between overflow-hidden px-2.5 py-2 text-left outline-none",
316
+ TONE[p.status] ?? TONE.planned,
317
+ onOpen && "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
318
+ )}
319
+ style={{ left: pct(x0), width: `calc(${x1 - x0}% - 3px)`, top: bandTop, height: bandH }}
320
+ >
321
+ <span className="truncate text-sm font-semibold leading-tight">{p.label}</span>
322
+ {compact ? null : <span className="truncate font-mono text-xs leading-tight">{p.sub ?? i + 1}</span>}
323
+ </Seg>
324
+ );
325
+ })}
326
+
327
+ {/* the gap: slack in green, an overrun in red, hatched */}
328
+ {gap && gap.state !== "on" ? (
329
+ <span
330
+ aria-hidden="true"
331
+ className={cx("absolute border-2 border-current bg-background/70", GAP_TEXT[gap.state])}
332
+ style={{ ...HATCH, left: `calc(${x(gap.from)}% + 1px)`, width: `max(3px, calc(${x(gap.to + 1) - x(gap.from)}% - 3px))`, top: bandTop, height: bandH }}
333
+ />
334
+ ) : null}
335
+ {gap ? (
336
+ <span
337
+ aria-hidden="true"
338
+ className={cx("absolute whitespace-nowrap font-mono text-xs font-semibold", GAP_TEXT[gap.state])}
339
+ style={
340
+ gap.state === "on"
341
+ ? { left: pct(x(gap.target + 1)), top: gapTop, transform: x(gap.target + 1) > 78 ? "translateX(-100%)" : undefined }
342
+ : x(gap.from) > 72
343
+ ? { right: pct(100 - x(gap.to + 1)), top: gapTop }
344
+ : { left: pct(x(gap.from)), top: gapTop }
345
+ }
346
+ >
347
+ {gapWords(gap, words)}
348
+ </span>
349
+ ) : null}
350
+
351
+ {/* gate posts */}
352
+ {gateItems.map(({ id, p, g, gx, side }) => {
353
+ const lane = gateLay.lanes[id];
354
+ const top = bandBottom + 8 + lane * GATE_H;
355
+ const stateTone = g.state === "overdue" ? "text-destructive" : "text-muted-foreground";
356
+ const Post = onOpen && g.total ? "button" : "span";
357
+ const said = `${p.label} gate, ${shortDay(g.day)}${g.total ? `, ${g.met} of ${g.total} criteria met` : ""}${g.state === "overdue" ? ", overdue" : ""}`;
358
+ return (
359
+ <span key={`gate-${id}`}>
360
+ <span aria-hidden="true" className="absolute w-px bg-foreground" style={{ left: `calc(${pct(gx)} - 3.5px)`, top: bandBottom, height: 8 + lane * GATE_H }} />
361
+ <span
362
+ aria-hidden="true"
363
+ className={cx("absolute size-[9px] border border-foreground", g.state === "met" ? "bg-success border-success" : "bg-background")}
364
+ style={{ left: `calc(${pct(gx)} - 8px)`, top: bandBottom - 4 }}
365
+ />
366
+ <Post
367
+ {...(Post === "button" ? { type: "button", onClick: open("gate", p), "aria-label": said } : { "aria-hidden": "true" })}
368
+ className={cx(
369
+ "absolute grid gap-1 rounded-sm bg-background px-1 text-left outline-none",
370
+ Post === "button" && "focus-visible:ring-2 focus-visible:ring-ring",
371
+ side === "left" ? "justify-items-end" : "justify-items-start",
372
+ )}
373
+ style={side === "left" ? { right: `calc(${pct(100 - gx)} + 6px)`, top } : { left: `calc(${pct(gx)} + 3px)`, top }}
374
+ >
375
+ <span className={cx("font-mono text-xs leading-none", stateTone)}>
376
+ {shortDay(g.day)}
377
+ {g.total ? <span className="ml-1.5 tabular-nums">{g.met}/{g.total}</span> : null}
378
+ </span>
379
+ {g.total ? (
380
+ <span className="flex items-center gap-[3px]">
381
+ {g.pips.map((pip, k) => (
382
+ <span
383
+ key={pip.id ?? k}
384
+ title={`${pip.label ?? "Criterion"}${pip.met ? ", met" : ", not met"}`}
385
+ className={cx("block size-[9px] rounded-full border", pip.met ? "border-success bg-success" : "border-foreground bg-background")}
386
+ />
387
+ ))}
388
+ </span>
389
+ ) : null}
390
+ </Post>
391
+ </span>
392
+ );
393
+ })}
394
+
395
+ {/* the target, the forecast, today */}
396
+ {tgt != null ? (
397
+ <span aria-hidden="true" className="absolute w-0.5 bg-foreground" style={{ left: `calc(${pct(x(tgt + 1))} - 2px)`, top: AXIS - 14, height: lineBottom("target") - (AXIS - 14) }} />
398
+ ) : null}
399
+ {fc != null && gap?.state !== "on" && flags.some((f) => f.id === "forecast") ? (
400
+ <span
401
+ aria-hidden="true"
402
+ className={cx("absolute w-0 border-l-2 border-dashed", gap ? GAP_LINE[gap.state] : "border-foreground")}
403
+ style={{ left: `calc(${pct(x(fc + 1))} - 2px)`, top: AXIS - 14, height: lineBottom("forecast") - (AXIS - 14) }}
404
+ />
405
+ ) : null}
406
+ {tIn != null ? (
407
+ <span aria-hidden="true" className="absolute w-0.5 bg-primary" style={{ left: `calc(${pct(x(tIn))} - 1px)`, top: AXIS - 4, height: (compact ? bandBottom + 6 : lineBottom("today")) - (AXIS - 4) }} />
408
+ ) : null}
409
+ {flagItems.map((f) => {
410
+ const lane = flagLay.lanes[f.id];
411
+ const top = flagTop + lane * FLAG_H;
412
+ const place = f.side === "left" ? { right: `calc(${pct(100 - f.fx)} + 6px)` } : { left: `calc(${pct(f.fx)} + 6px)` };
413
+ const tone = f.kind === "forecast" ? (gap ? GAP_TEXT[gap.state] : "text-foreground") : "text-foreground";
414
+ return f.kind === "today" ? (
415
+ <span
416
+ key={f.id}
417
+ aria-hidden="true"
418
+ className="absolute whitespace-nowrap bg-primary px-1.5 py-0.5 font-mono text-xs font-medium text-primary-foreground"
419
+ style={f.side === "left" ? { right: `calc(${pct(100 - f.fx)} - 1px)`, top } : { left: `calc(${pct(f.fx)} - 1px)`, top }}
420
+ >
421
+ {f.text}
422
+ </span>
423
+ ) : (
424
+ <span key={f.id} aria-hidden="true" className={cx("absolute flex items-center gap-1 whitespace-nowrap bg-background px-1 font-mono text-xs font-medium", tone)} style={{ ...place, top }}>
425
+ <Icon name="flag" size={12} className="shrink-0" />
426
+ {f.text}
427
+ </span>
428
+ );
429
+ })}
430
+ </div>
431
+ </div>
432
+
433
+ {skipped ? <p className="text-xs text-muted-foreground">{words.undated(skipped)}</p> : null}
434
+
435
+ {/* under 36rem the same chain in words, because a scrolled picture hides its ends */}
436
+ <ol aria-hidden="true" className="grid min-w-0 gap-1.5 @xl:hidden">
437
+ {segs.map((p, i) => {
438
+ const g = gateOf(p.gate, p.e, t);
439
+ return (
440
+ <li key={p.id} className="flex min-w-0 items-baseline justify-between gap-3 border-t border-border pt-1.5 text-sm">
441
+ <span className="min-w-0 truncate font-medium">{i + 1}. {p.label}</span>
442
+ <span className="shrink-0 text-right font-mono text-xs tabular-nums text-muted-foreground">
443
+ {shortDay(p.s)} to {shortDay(p.e)}
444
+ {g?.total ? ` · ${g.met}/${g.total} met` : ""}
445
+ </span>
446
+ </li>
447
+ );
448
+ })}
449
+ {miles.map((m) => (
450
+ <li key={m.id} className="flex min-w-0 items-baseline justify-between gap-3 border-t border-border pt-1.5 text-sm">
451
+ <span className="min-w-0 truncate font-medium">{m.label}</span>
452
+ <span className="shrink-0 font-mono text-xs tabular-nums text-muted-foreground">{shortDay(m.day)}{m.done ? " · done" : ""}</span>
453
+ </li>
454
+ ))}
455
+ {tgt != null || gap ? (
456
+ <li className="flex min-w-0 flex-col gap-0.5 border-t border-border pt-1.5 font-mono text-xs tabular-nums">
457
+ {tgt != null ? <span className="text-foreground">{words.target} {dayName(tgt)}</span> : null}
458
+ {fc != null && gap?.state !== "on" ? <span className={gap ? GAP_TEXT[gap.state] : "text-foreground"}>{words.forecast} {dayName(fc)}</span> : null}
459
+ {gap ? <span className={cx("font-semibold", GAP_TEXT[gap.state])}>{gapWords(gap, words)}</span> : null}
460
+ </li>
461
+ ) : null}
462
+ </ol>
463
+ </div>
464
+ );
465
+ }
466
+
467
+ /**
468
+ * The target against the forecast, drawn and nothing else: a dashed line
469
+ * for the target, a solid bar for the forecast, the gap between them
470
+ * hatched (green early, red late) and its size in words underneath. For a
471
+ * sentence to sit beside ("Public launch lands Tue 3 Nov").
472
+ */
473
+ export function GapRuler({ target, forecast, count = "working", workweek, words: wordsIn, label, width = 380, className }) {
474
+ const words = { ...WORDS, ...wordsIn };
475
+ const gap = gapOf({ target, forecast, count, workweek });
476
+ if (!gap) return null;
477
+ const lo = Math.min(gap.target, gap.forecast);
478
+ const hi = Math.max(gap.target, gap.forecast);
479
+ const total = Math.max(hi - lo + 6, 14);
480
+ const r0 = lo - Math.floor((total - (hi - lo)) / 2);
481
+ const at = (d) => ((d - r0) / total) * 100;
482
+ const tx = at(gap.target);
483
+ const fx = at(gap.forecast);
484
+ const a = Math.min(tx, fx);
485
+ const b = Math.max(tx, fx);
486
+ const tone = GAP_TEXT[gap.state];
487
+ const earlierIsTarget = gap.target <= gap.forecast;
488
+ const said = label ?? gapWords(gap, words);
489
+ return (
490
+ <div
491
+ role="img"
492
+ aria-label={`${words.target} ${dayName(gap.target)}, ${words.forecast.toLowerCase()} ${dayName(gap.forecast)}: ${said}`}
493
+ className={cx("@container relative h-[92px] w-full min-w-0", className)}
494
+ style={{ maxWidth: width }}
495
+ >
496
+ <span aria-hidden="true" className="absolute inset-x-0 top-[46px] h-px bg-foreground" />
497
+ {gap.state !== "on" ? (
498
+ <span aria-hidden="true" className={cx("absolute top-[34px] h-6 border-y-2 border-current", tone)} style={{ ...HATCH, left: pct(a), width: pct(b - a) }} />
499
+ ) : null}
500
+ <span aria-hidden="true" className="absolute top-6 h-11 w-0 border-l-[1.5px] border-dashed border-foreground" style={{ left: pct(tx) }} />
501
+ <span aria-hidden="true" className={cx("absolute top-5 h-[52px] w-[3px] -translate-x-px bg-current", tone)} style={{ left: pct(fx) }} />
502
+ <span aria-hidden="true" className={cx("absolute top-0 whitespace-nowrap font-mono text-xs leading-tight text-muted-foreground", earlierIsTarget ? "-translate-x-full pr-1.5 text-right" : "pl-1.5")} style={{ left: pct(tx) }}>
503
+ {words.target.toUpperCase()}
504
+ <br />
505
+ {shortDay(gap.target)}
506
+ </span>
507
+ <span aria-hidden="true" className={cx("absolute top-0 whitespace-nowrap font-mono text-xs font-semibold leading-tight", tone, earlierIsTarget ? "pl-1.5" : "-translate-x-full pr-1.5 text-right")} style={{ left: pct(fx) }}>
508
+ {words.forecast.toUpperCase()}
509
+ <br />
510
+ {shortDay(gap.forecast)}
511
+ </span>
512
+ <span aria-hidden="true" className={cx("absolute top-[72px] whitespace-nowrap font-mono text-xs font-semibold", tone)} style={{ left: pct((a + b) / 2), transform: "translateX(-50%)" }}>
513
+ {said}
514
+ </span>
515
+ </div>
516
+ );
517
+ }
518
+
519
+ /**
520
+ * The chain small: one row of bars with the gap hatched, the target in ink
521
+ * and today in the accent. For a row in a list; put the numbers in words
522
+ * beside it.
523
+ */
524
+ export function ScheduleStrip({ segments, target, forecast, today, count = "working", workweek, height = 16, label = "Schedule", className }) {
525
+ const { segs, tgt, fc, t, gap } = readProps({ segments, target, forecast, today, count, workweek });
526
+ const days = [...segs.flatMap((p) => [p.s, p.e + 1]), tgt != null ? tgt + 1 : null, fc != null ? fc + 1 : null].filter((d) => d != null);
527
+ const frame = frameOf({ days, padBefore: 0, padAfter: 1 });
528
+ if (!frame || (!segs.length && tgt == null)) return <div aria-hidden="true" className={cx("w-full border-b border-border", className)} style={{ height: height + 6 }} />;
529
+ const { x, r0, r1 } = frame;
530
+ const summary = segs.map((p) => `${p.label} ${STATUS_WORDS[p.status] ?? p.status ?? "planned"}`).join(", ");
531
+ return (
532
+ <div
533
+ role="img"
534
+ aria-label={`${label}: ${summary || "no dated items"}${gap ? `; ${gapWords(gap, { ...WORDS, over: (n) => `${n} days over`, slack: (n) => `${n} days of slack` })}` : ""}`}
535
+ className={cx("relative w-full min-w-0 border-b border-border", className)}
536
+ style={{ height: height + 6 }}
537
+ >
538
+ {segs.map((p) => (
539
+ <span key={p.id} aria-hidden="true" className={cx("absolute top-0", STRIP[p.status] ?? STRIP.planned)} style={{ left: pct(x(p.s)), width: `calc(${x(p.e + 1) - x(p.s)}% - 2px)`, height }} />
540
+ ))}
541
+ {gap && gap.state !== "on" ? (
542
+ <span aria-hidden="true" className={cx("absolute top-0 border border-current bg-background/70", GAP_TEXT[gap.state])} style={{ ...HATCH_FINE, left: pct(x(gap.from)), width: `max(2px, calc(${x(gap.to + 1) - x(gap.from)}% - 1px))`, height }} />
543
+ ) : null}
544
+ {tgt != null ? <span aria-hidden="true" className="absolute -top-1 w-0.5 bg-foreground" style={{ left: `calc(${pct(x(tgt + 1))} - 1px)`, height: height + 10 }} /> : null}
545
+ {t != null && t >= r0 && t <= r1 ? <span aria-hidden="true" className="absolute w-0.5 bg-primary" style={{ left: `calc(${pct(x(t))} - 1px)`, top: -5, height: height + 11 }} /> : null}
546
+ </div>
547
+ );
548
+ }
@@ -0,0 +1,97 @@
1
+ # SourceReceipt
2
+
3
+ Where one value came from, drawn as a clipping:
4
+
5
+ - the page's or the mail's title, with the trust mark's shape beside it;
6
+ - its address (linked) or the mailbox, and the date it was read;
7
+ - the words on it, with the **deciding words underlined** wherever they
8
+ appear;
9
+ - a trust word ("their own site", "a 2023 directory");
10
+ - **Open the source**.
11
+
12
+ A member reads the claim and its proof in one look, and decides how far
13
+ to trust it.
14
+
15
+ It is not a second receipt. The clipping is `agent-column`'s (Data
16
+ Enrichment's "the receipt"); this block wraps it for one source standing
17
+ on its own: a CRM's found title, a note's proof margin, a newsletter's
18
+ source line. Inside a column of answers, use `agent-column`'s `Receipt`.
19
+
20
+ ```jsx
21
+ import { SourceReceipt } from "../components/blocks/source-receipt"; // a Makersclaw app ("./blocks/source-receipt" from components/)
22
+ import { SourceReceipt } from "@/components/makersclaw/blocks/source-receipt"; // a shadcn project
23
+ ```
24
+
25
+ A client component. It requires `agent-column` (and through it
26
+ `agent-marks`), laid with it.
27
+
28
+ ## Which hook feeds it
29
+
30
+ None. The source is app data the agent wrote beside the value: its
31
+ address, its title, the words it read, the words that decided. Keep
32
+ them in the record (a `source` column, a `sources` table) and pass them
33
+ in.
34
+
35
+ ## Props
36
+
37
+ - `title` — the page's or the mail's title ("Careers · Klarwerk").
38
+ - `url` — the source's address. The clipping shows it short
39
+ ("klarwerk.de/careers") and links it.
40
+ - `site` — where, when there is no `url`: a mailbox ("Sofia Reyes
41
+ <sofia@crumb.co>") or a site's name. From `url` when left out.
42
+ - `kind` — `site` (default) or `mail`: the icon before the trust word.
43
+ - `date` — when it was read or sent, in words ("read 22 Sep").
44
+ - `trust` — a word for how far to trust it ("their own site", "her
45
+ signature", "a 2023 directory").
46
+ - `sure` — `sure` · `likely` · `guess`: the trust mark's shape beside
47
+ the title (filled, half, dashed).
48
+ - `excerpt` — the words on it.
49
+ - `marks` — the deciding words, `string[]`. Every occurrence of each is
50
+ underlined, whatever its case. Without `marks`, an excerpt's own
51
+ `[[…]]` (the agent's marking) are underlined instead.
52
+ - `onOpen()` — opens the source your way. Without it, a `url` opens in a
53
+ new tab.
54
+ - `openLabel` — default "Open the source".
55
+ - `action` — a node at the head's end: a "Pick 70" when two sources
56
+ disagree.
57
+ - `picked` — drawn as the one chosen.
58
+ - `className` — layout around it. Never colour.
59
+
60
+ `markExcerpt(excerpt, marks)` is exported: the excerpt with `[[…]]`
61
+ around every deciding word, as the clipping reads it.
62
+
63
+ ## Who may do what
64
+
65
+ Anyone who can see the value can see its source. The receipt has no
66
+ writes; a Pick is your own `action`, and your own action's rules hold.
67
+
68
+ ## Never
69
+
70
+ - Never build the excerpt from HTML or inject it: it is drawn as text,
71
+ and the underlines are elements, never markup in a string.
72
+ - Never show a value as found with no receipt to open.
73
+ - Never colour the trust alone: the mark's shape and the word say it.
74
+
75
+ ## Example
76
+
77
+ The source of a contact's job title, under the field.
78
+
79
+ ```jsx
80
+ import { SourceReceipt } from "./blocks/source-receipt";
81
+
82
+ export function TitleSource({ person }) {
83
+ const s = person.title_source;
84
+ if (!s) return null;
85
+ return (
86
+ <SourceReceipt
87
+ title={s.title}
88
+ url={s.url}
89
+ date={`read ${s.read_on}`}
90
+ trust={s.trust}
91
+ sure={s.sure}
92
+ excerpt={s.words}
93
+ marks={[person.job_title]}
94
+ />
95
+ );
96
+ }
97
+ ```
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "source-receipt",
3
+ "title": "SourceReceipt",
4
+ "description": "Where one value came from, as a clipping: the page or the mail and its title, the address, the date it was read, a trust word (\"their own site\", \"a 2023 directory\") and the trust mark's shape, the words on it with the deciding ones underlined wherever they appear (case-insensitive, never HTML), and Open the source. A thin wrapper over agent-column's clipping (Data Enrichment's receipt) for a value that stands on its own, so every app draws a source the same way.",
5
+ "useWhen": "A value the agent found should show its proof beside it: a contact's title from an email signature, a company's size from its site, a figure in a note or a newsletter with its source in the margin, two sources side by side for a person to pick. For a whole column of answers across records use agent-column; for a value proposed but not kept yet, put the receipt under a suggestion.",
6
+ "tags": [
7
+ "source",
8
+ "citation",
9
+ "receipt",
10
+ "provenance",
11
+ "evidence",
12
+ "quote",
13
+ "clipping",
14
+ "trust",
15
+ "proof",
16
+ "reference",
17
+ "found"
18
+ ],
19
+ "exports": [
20
+ "SourceReceipt",
21
+ "markExcerpt"
22
+ ],
23
+ "files": [
24
+ "index.js",
25
+ "source-receipt.jsx"
26
+ ],
27
+ "requires": [
28
+ "agent-column"
29
+ ]
30
+ }