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,110 @@
1
+ "use client";
2
+
3
+ // SumBar in every state — the gallery's and the smoke test's surface. Not
4
+ // installed into apps: `makersclaw add sum-bar` lays index.js, sum-bar.jsx and
5
+ // sum-model.js only.
6
+ // A project's score two reds, the sum below both caps; then a cap that
7
+ // bites (the sum 56, held at 49); then one red holding a
8
+ // 74.
9
+ // Nothing taken off a full bar.
10
+ // Pressable onSelect: every cut is a button, one selected.
11
+ // Sizes, legend sm, md, lg; the legend; the receipt turned off.
12
+ // Odd input a negative cost, a cost past the total, text for a
13
+ // cost, a zero total.
14
+ // Narrow and wide a 320 px container and a wide one.
15
+ // Loading, failed.
16
+ import { useState } from "react";
17
+ import { SumBar } from "../components/blocks/sum-bar";
18
+
19
+ const BANDS = [
20
+ { from: 0, to: 50, tone: "danger", label: "off track" },
21
+ { from: 50, to: 75, tone: "warning", label: "at risk" },
22
+ { from: 75, to: 100, tone: "success", label: "on track" },
23
+ ];
24
+ const POSTS = [
25
+ { at: 74, label: "cap 74 · one red" },
26
+ { at: 49, label: "cap 49 · two reds" },
27
+ ];
28
+ const SIGNALS = [
29
+ { id: "schedule", label: "Schedule", cost: 22, tone: "danger" },
30
+ { id: "budget", label: "Budget", cost: 25, tone: "danger" },
31
+ { id: "late", label: "Late work", cost: 5, tone: "warning" },
32
+ { id: "blocked", label: "Blocked", cost: 8, tone: "warning" },
33
+ { id: "activity", label: "Activity", cost: 0 },
34
+ { id: "client", label: "Client", cost: 5, tone: "warning" },
35
+ ];
36
+ const CAPPED = [
37
+ { id: "schedule", label: "Schedule", cost: 14, tone: "danger" },
38
+ { id: "budget", label: "Budget", cost: 8, tone: "warning" },
39
+ { id: "late", label: "Late work", cost: 8, tone: "warning" },
40
+ { id: "blocked", label: "Blocked", cost: 10, tone: "danger" },
41
+ { id: "activity", label: "Activity", cost: 4, tone: "warning" },
42
+ ];
43
+ const ONE_RED = [
44
+ { id: "blocked", label: "Blocked", cost: 8, tone: "danger" },
45
+ { id: "client", label: "Client", cost: 0 },
46
+ ];
47
+
48
+ function Section({ title, note, children }) {
49
+ return (
50
+ <section className="grid min-w-0 gap-2">
51
+ <h2 className="text-sm font-semibold">{title}</h2>
52
+ {note ? <p className="text-xs text-muted-foreground">{note}</p> : null}
53
+ {children}
54
+ </section>
55
+ );
56
+ }
57
+
58
+ export default function Demo() {
59
+ const [on, setOn] = useState("budget");
60
+ return (
61
+ <div className="grid min-w-0 gap-8 p-4">
62
+ <Section title="Two reds: the sum is below both caps" note="100 − 22 − 25 − 5 − 8 − 0 − 5 = 35. The caps change nothing here.">
63
+ <SumBar label="Website rebuild, score sheet" size="lg" parts={SIGNALS} result={35} posts={POSTS} bands={BANDS} unit="points" />
64
+ </Section>
65
+
66
+ <Section title="A cap that bites" note="The sum is 56; two reds hold it at 49. The stretch between is dotted.">
67
+ <SumBar label="Online banking help centre, score sheet" size="lg" parts={CAPPED} result={49} posts={POSTS} bands={BANDS} />
68
+ </Section>
69
+
70
+ <Section title="One red holding a 74" note="Only 8 points lost, yet the cap says 74.">
71
+ <SumBar label="Mortgage calculator copy, score sheet" parts={ONE_RED} result={74} posts={POSTS} bands={BANDS} />
72
+ </Section>
73
+
74
+ <Section title="Nothing taken off">
75
+ <SumBar label="Launch campaign, score sheet" parts={[{ id: "a", label: "Schedule", cost: 0 }, { id: "b", label: "Budget", cost: 0 }]} posts={POSTS} bands={BANDS} />
76
+ <SumBar label="No parts at all" parts={[]} />
77
+ </Section>
78
+
79
+ <Section title="Pressable" note="Every cut is a button. The pressed one is ringed.">
80
+ <SumBar label="Press a cut" parts={SIGNALS} result={35} selected={on} onSelect={(p) => setOn(p.id)} bands={BANDS} legend />
81
+ <p className="text-xs text-muted-foreground">Pressed: {SIGNALS.find((p) => p.id === on)?.label}</p>
82
+ </Section>
83
+
84
+ <Section title="Sizes, no receipt" note="sm and md under a title, without the written sum.">
85
+ <SumBar size="sm" label="Small" parts={CAPPED} result={49} receipt={false} />
86
+ <SumBar size="md" label="Medium" parts={CAPPED} result={49} receipt={false} />
87
+ </Section>
88
+
89
+ <Section title="Small cuts" note="Under 5% wide: the cost is in the tooltip and the receipt.">
90
+ <SumBar label="Small cuts" parts={[{ id: "a", label: "Late", cost: 3, tone: "warning" }, { id: "b", label: "Blocked", cost: 2, tone: "warning" }, { id: "c", label: "Client", cost: 1 }]} bands={BANDS} />
91
+ </Section>
92
+
93
+ <Section title="Odd input" note="A negative cost, a cost past the total, text for a cost, a zero total.">
94
+ <SumBar label="Odd costs" parts={[{ id: "a", label: "Negative", cost: -5 }, { id: "b", label: "Text", cost: "12" }, { id: "c", label: "Past the total", cost: 400, tone: "danger" }, { id: "d", label: "Nothing", cost: "n/a" }]} />
95
+ <SumBar label="Zero total" total={0} parts={[{ id: "a", label: "A", cost: 10 }]} />
96
+ </Section>
97
+
98
+ <Section title="Narrow (a 320 px container)">
99
+ <div className="w-80 max-w-full">
100
+ <SumBar label="Narrow" parts={SIGNALS} result={35} posts={POSTS} bands={BANDS} legend />
101
+ </div>
102
+ </Section>
103
+
104
+ <Section title="Loading, failed">
105
+ <SumBar label="Loading" status="loading" />
106
+ <SumBar label="Failed" status="failed" error="The check could not be read." onRetry={() => {}} />
107
+ </Section>
108
+ </div>
109
+ );
110
+ }
@@ -0,0 +1,5 @@
1
+ // The SumBar block — `makersclaw add sum-bar`. Fixed: compose it through its
2
+ // props (README.md beside this file). The model's helpers are plain
3
+ // functions; they work on the server too.
4
+ export { SumBar } from "./sum-bar";
5
+ export { bandsOf, costOf, cutsOf, labelledCuts, lostOf, postsOf, receiptOf, resultOf, sumWords, totalOf } from "./sum-model";
@@ -0,0 +1,209 @@
1
+ "use client";
2
+
3
+ import { Button, Notice, Skeleton, cx } from "../../ui";
4
+ import { bandsOf, cutsOf, labelledCuts, postsOf, receiptOf, resultOf, sumWords } from "./sum-model";
5
+
6
+ // A total with its parts cut out of it. The bar runs from 0 to the total;
7
+ // what is left is solid, each part's cost is a hatched block cut from the
8
+ // top end downward, and a post stands where a rule holds the result down (a
9
+ // cap). Under it, the sum written out so no number is taken on trust.
10
+ //
11
+ // Props in, one callback out (`onSelect`); the block imports only React and
12
+ // the kit, reads no data and works nothing out beyond placing what it is
13
+ // given. Positions are percentages, so the server draws what the browser
14
+ // draws. README.md beside this file lists every prop.
15
+
16
+ // Literal class names, so Tailwind sees every one of them.
17
+ const CUT = {
18
+ neutral: "border-border text-muted-foreground bg-muted",
19
+ info: "border-info text-info bg-info/10",
20
+ warning: "border-warning text-warning bg-warning/10",
21
+ danger: "border-destructive text-destructive bg-destructive/10",
22
+ };
23
+ const BAND = { success: "bg-success", warning: "bg-warning", danger: "bg-destructive", info: "bg-info", neutral: "bg-muted-foreground" };
24
+ const SIZE = { sm: "h-6", md: "h-9", lg: "h-12" };
25
+
26
+ const HATCH = { backgroundImage: "repeating-linear-gradient(135deg, currentColor 0 1px, transparent 1px 5px)" };
27
+ const DOTS = { backgroundImage: "repeating-linear-gradient(90deg, currentColor 0 2px, transparent 2px 6px)" };
28
+
29
+ const say = (n) => (Number.isInteger(n) ? String(n) : String(Math.round(n * 1000) / 1000));
30
+
31
+ /**
32
+ * The bar.
33
+ *
34
+ * Props: `total` (100), `parts` `[{ id, label, cost, tone?, note? }]` (taken
35
+ * off in the order given, from the top end), `result` (the number after a
36
+ * rule held it down; default total minus the costs), `posts` `[{ at, label,
37
+ * tone? }]`, `bands` `[{ from, to, tone, label? }]`, `selected`, `onSelect(part)`,
38
+ * `size` (sm | md | lg), `names` (the names under the cuts, default true),
39
+ * `legend` (a list of the parts under the bar), `receipt` (the written sum,
40
+ * default true), `unit`, `noun`, `label`, `status`, `error`, `onRetry`,
41
+ * `className`.
42
+ */
43
+ export function SumBar({
44
+ total = 100,
45
+ parts,
46
+ result,
47
+ posts,
48
+ bands,
49
+ selected,
50
+ onSelect,
51
+ size = "md",
52
+ names = true,
53
+ legend = false,
54
+ receipt = true,
55
+ unit = "",
56
+ noun = "point",
57
+ label = "Score sheet",
58
+ status = "ready",
59
+ error,
60
+ onRetry,
61
+ className,
62
+ }) {
63
+ if (status === "loading") {
64
+ return (
65
+ <div role="status" aria-label={`${label}, loading`} className={cx("grid gap-2", className)}>
66
+ <Skeleton className={cx("w-full", SIZE[size] ?? SIZE.md)} />
67
+ <Skeleton className="h-3 w-2/3" />
68
+ </div>
69
+ );
70
+ }
71
+ if (status === "failed") {
72
+ return (
73
+ <Notice
74
+ tone="danger"
75
+ className={className}
76
+ action={
77
+ onRetry ? (
78
+ <Button size="sm" onClick={onRetry}>
79
+ Try again
80
+ </Button>
81
+ ) : undefined
82
+ }
83
+ >
84
+ {error || "The sum could not be read."}
85
+ </Notice>
86
+ );
87
+ }
88
+
89
+ const r = resultOf({ total, parts, result });
90
+ const cuts = cutsOf({ total, parts });
91
+ const pins = postsOf({ total: r.total, posts });
92
+ const strips = bandsOf({ total: r.total, bands });
93
+ const named = names ? labelledCuts(cuts) : [];
94
+ const keptPct = Math.round((Math.min(r.kept, r.result) / r.total) * 100000) / 1000;
95
+ const resultPct = Math.round((r.result / r.total) * 100000) / 1000;
96
+ const sum = receiptOf({ total, parts, result, unit });
97
+ const inside = resultPct >= 12;
98
+ const words = sumWords({ total, parts, result, posts, noun });
99
+
100
+ return (
101
+ <div role="group" aria-label={label} className={cx("@container grid min-w-0 gap-2", className)}>
102
+ <div className="relative min-w-0 pt-6">
103
+ {pins.map((p) => (
104
+ <span
105
+ key={`post-label-${p.index}`}
106
+ aria-hidden="true"
107
+ className={cx("absolute top-0 left-(--at) -translate-x-1/2 whitespace-nowrap text-[11px] font-semibold leading-none", p.tone === "danger" ? "text-destructive" : "text-muted-foreground")}
108
+ style={{ "--at": `${p.pct}%` }}
109
+ >
110
+ {p.label}
111
+ </span>
112
+ ))}
113
+ <div className={cx("relative overflow-hidden rounded-[4px] border border-border bg-muted", SIZE[size] ?? SIZE.md)} role="img" aria-label={words}>
114
+ {resultPct > 0 ? (
115
+ <span aria-hidden="true" className="absolute inset-y-0 left-0 w-(--w) bg-foreground" style={{ "--w": `${Math.min(keptPct, resultPct)}%` }} />
116
+ ) : null}
117
+ {r.held > 0 ? (
118
+ <span
119
+ aria-hidden="true"
120
+ title={`Held down by a rule: ${say(r.kept)} on the sum, ${say(r.result)} after it`}
121
+ className="absolute inset-y-0 left-(--at) w-(--width) text-muted-foreground"
122
+ style={{ ...DOTS, "--at": `${resultPct}%`, "--width": `${Math.round((r.held / r.total) * 100000) / 1000}%` }}
123
+ />
124
+ ) : null}
125
+ {inside ? (
126
+ <span aria-hidden="true" className="absolute inset-y-0 left-2 flex items-center text-sm font-semibold tabular-nums text-background">
127
+ {say(r.result)}
128
+ </span>
129
+ ) : null}
130
+ {cuts
131
+ .filter((c) => c.cut)
132
+ .map((c) => {
133
+ const tone = CUT[c.tone] ?? CUT.neutral;
134
+ const on = selected != null && c.id === selected;
135
+ const className = cx("absolute inset-y-0 left-(--at) w-(--width) overflow-hidden border-x outline-none focus-visible:ring-2 focus-visible:ring-ring", tone, on && "ring-2 ring-ring");
136
+ const style = { "--at": `${c.leftPct}%`, "--width": `${c.widthPct}%` };
137
+ const inner = (
138
+ <>
139
+ <span aria-hidden="true" className="absolute inset-0 opacity-60" style={HATCH} />
140
+ {c.widthPct >= 5 ? <span className="relative flex h-full items-center justify-center text-[11px] font-semibold tabular-nums text-foreground">−{say(c.cost)}</span> : null}
141
+ </>
142
+ );
143
+ return onSelect ? (
144
+ <button key={c.id ?? c.index} type="button" aria-label={`${c.label ?? "Part"}, took ${say(c.cost)}`} aria-pressed={on} title={`${c.label ?? "Part"} took ${say(c.cost)}`} className={className} style={style} onClick={() => onSelect(c)}>
145
+ {inner}
146
+ </button>
147
+ ) : (
148
+ <span key={c.id ?? c.index} title={`${c.label ?? "Part"} took ${say(c.cost)}`} className={className} style={style}>
149
+ {inner}
150
+ </span>
151
+ );
152
+ })}
153
+ {pins.map((p) => (
154
+ <span key={`post-${p.index}`} aria-hidden="true" className={cx("absolute inset-y-0 left-(--at) w-0 border-l-2 border-dashed", p.tone === "danger" ? "border-destructive" : "border-muted-foreground")} style={{ "--at": `${p.pct}%` }} />
155
+ ))}
156
+ {!inside && resultPct > 0 ? (
157
+ <span aria-hidden="true" className="absolute inset-y-0 left-(--at) ml-1.5 flex items-center text-sm font-semibold tabular-nums" style={{ "--at": `${resultPct}%` }}>
158
+ {say(r.result)}
159
+ </span>
160
+ ) : null}
161
+ </div>
162
+ {strips.length ? (
163
+ <div className="relative mt-1 h-1.5 overflow-hidden rounded-[2px]" aria-hidden="true">
164
+ {strips.map((b) => (
165
+ <span key={b.index} className={cx("absolute inset-y-0 left-(--at) w-(--width)", BAND[b.tone] ?? BAND.neutral)} style={{ "--at": `${b.leftPct}%`, "--width": `${b.widthPct}%` }} />
166
+ ))}
167
+ </div>
168
+ ) : null}
169
+ {strips.some((b) => b.label) ? (
170
+ <div className="relative mt-1 h-4 text-[11px] text-muted-foreground" aria-hidden="true">
171
+ {strips.map((b) =>
172
+ b.label ? (
173
+ <span key={b.index} className="absolute top-0 left-(--at) -translate-x-1/2 whitespace-nowrap" style={{ "--at": `${b.leftPct + b.widthPct / 2}%` }}>
174
+ {b.label}
175
+ </span>
176
+ ) : null,
177
+ )}
178
+ </div>
179
+ ) : null}
180
+ {named.length ? (
181
+ <div className="relative mt-1 hidden h-4 text-[11px] text-muted-foreground @lg:block" aria-hidden="true">
182
+ {named.map((c) => (
183
+ <span key={c.id ?? c.index} className="absolute top-0 left-(--at) -translate-x-1/2 whitespace-nowrap" style={{ "--at": `${c.leftPct + c.widthPct / 2}%` }}>
184
+ {c.label}
185
+ </span>
186
+ ))}
187
+ </div>
188
+ ) : null}
189
+ </div>
190
+ {legend && cuts.length ? (
191
+ <ul className="m-0 grid list-none gap-1 p-0 text-xs @md:grid-cols-2">
192
+ {cuts.map((c) => (
193
+ <li key={c.id ?? c.index} className="flex min-w-0 items-baseline gap-2">
194
+ <span className="truncate">{c.label}</span>
195
+ <span className="ml-auto shrink-0 tabular-nums text-muted-foreground">{c.cost > 0 ? `−${say(c.cost)}` : "0"}</span>
196
+ </li>
197
+ ))}
198
+ </ul>
199
+ ) : null}
200
+ {receipt ? (
201
+ <p className="m-0 text-xs tabular-nums text-muted-foreground">
202
+ <span className="font-medium text-foreground">{sum.text}</span>
203
+ {sum.held > 0 ? <span> · a rule holds it down by {say(sum.held)}</span> : null}
204
+ {sum.raised > 0 ? <span> · a rule lifts it by {say(sum.raised)}</span> : null}
205
+ </p>
206
+ ) : null}
207
+ </div>
208
+ );
209
+ }
@@ -0,0 +1,126 @@
1
+ // sum-bar: the arithmetic. Plain functions, no React, no clock: they work on
2
+ // the server and in a test.
3
+ //
4
+ // A SUM starts at a total (100) and takes costs off it, one per part. What is
5
+ // left is the result. A rule may hold the result down (a cap): then the result
6
+ // is lower than total minus the costs, and the stretch between the two is
7
+ // "held". The block draws what the app has already worked out; these helpers
8
+ // only place it and say it.
9
+
10
+ /** A cost: a finite number, never below 0. Anything else counts as 0. */
11
+ export function costOf(value) {
12
+ const n = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
13
+ return Number.isFinite(n) && n > 0 ? n : 0;
14
+ }
15
+
16
+ const round = (n) => Math.round(n * 1000) / 1000;
17
+ const clampPct = (n) => Math.min(100, Math.max(0, n));
18
+
19
+ /** The total as a positive number (default 100). */
20
+ export function totalOf(total) {
21
+ const n = Number(total);
22
+ return Number.isFinite(n) && n > 0 ? n : 100;
23
+ }
24
+
25
+ /** Everything the parts cost, together. */
26
+ export function lostOf(parts) {
27
+ return round((parts ?? []).reduce((sum, p) => sum + costOf(p?.cost), 0));
28
+ }
29
+
30
+ /**
31
+ * What is left once the costs are off, and what the result is:
32
+ * `kept` = total minus the costs (never below 0), `result` = the app's own
33
+ * number when it gives one (a cap held it down), else `kept`.
34
+ * `held` is how far a rule pushed the result below `kept`.
35
+ */
36
+ export function resultOf({ total, parts, result }) {
37
+ const whole = totalOf(total);
38
+ const lost = lostOf(parts);
39
+ const kept = Math.max(0, round(whole - lost));
40
+ const given = typeof result === "number" && Number.isFinite(result) ? Math.min(Math.max(result, 0), whole) : null;
41
+ const final = given == null ? kept : round(given);
42
+ return { total: whole, lost, kept, result: final, held: final < kept ? round(kept - final) : 0, raised: final > kept ? round(final - kept) : 0 };
43
+ }
44
+
45
+ /**
46
+ * Each part's place on the bar. The costs are cut out from the total
47
+ * downward, in the order the parts are given: the first part sits at the top
48
+ * end. A part that would run below 0 is cut short there. Percentages of the
49
+ * total.
50
+ */
51
+ export function cutsOf({ total, parts }) {
52
+ const whole = totalOf(total);
53
+ let top = whole;
54
+ const out = [];
55
+ for (const [index, p] of (parts ?? []).entries()) {
56
+ const cost = costOf(p?.cost);
57
+ const low = Math.max(0, top - cost);
58
+ out.push({
59
+ ...p,
60
+ index,
61
+ cost,
62
+ from: round(low),
63
+ to: round(top),
64
+ leftPct: round(clampPct((low / whole) * 100)),
65
+ widthPct: round(clampPct(((top - low) / whole) * 100)),
66
+ cut: cost > 0,
67
+ });
68
+ top = low;
69
+ }
70
+ return out;
71
+ }
72
+
73
+ /** A post's place (a cap, a threshold) as a percentage. Posts out of range are dropped. */
74
+ export function postsOf({ total, posts }) {
75
+ const whole = totalOf(total);
76
+ return (posts ?? [])
77
+ .map((p, index) => ({ ...p, index, at: Number(p?.at) }))
78
+ .filter((p) => Number.isFinite(p.at) && p.at >= 0 && p.at <= whole)
79
+ .map((p) => ({ ...p, pct: round(clampPct((p.at / whole) * 100)) }));
80
+ }
81
+
82
+ /** The coloured bands under the bar (off track, at risk, on track), as percentages. */
83
+ export function bandsOf({ total, bands }) {
84
+ const whole = totalOf(total);
85
+ return (bands ?? [])
86
+ .map((b, index) => ({ ...b, index, from: Number(b?.from), to: Number(b?.to) }))
87
+ .filter((b) => Number.isFinite(b.from) && Number.isFinite(b.to) && b.to > b.from)
88
+ .map((b) => {
89
+ const from = Math.max(0, b.from);
90
+ const to = Math.min(whole, b.to);
91
+ return { ...b, leftPct: round(clampPct((from / whole) * 100)), widthPct: round(clampPct(((to - from) / whole) * 100)) };
92
+ })
93
+ .filter((b) => b.widthPct > 0);
94
+ }
95
+
96
+ const num = (n) => (Number.isInteger(n) ? String(n) : String(round(n)));
97
+
98
+ /**
99
+ * The receipt, as a sum anyone can check: "100 − 22 − 25 − 5 − 8 − 0 − 5 = 35".
100
+ * Every part is a term, a part that cost nothing included. When a rule held
101
+ * the result down: "100 − 44 = 56, held at 49".
102
+ */
103
+ export function receiptOf({ total, parts, result, unit = "" }) {
104
+ const r = resultOf({ total, parts, result });
105
+ const list = parts ?? [];
106
+ const terms = list.map((p) => ({ id: p?.id ?? p?.label, label: p?.label ?? "", cost: costOf(p?.cost) }));
107
+ const sum = [num(r.total), ...terms.map((t) => `− ${num(t.cost)}`)].join(" ");
108
+ const text = r.held > 0 ? `${r.total} − ${num(r.lost)} = ${num(r.kept)}, held at ${num(r.result)}` : list.length ? `${sum} = ${num(r.result)}` : `${num(r.total)} = ${num(r.result)}`;
109
+ return { ...r, terms, text: unit ? `${text} ${unit}` : text };
110
+ }
111
+
112
+ /** The whole bar in a sentence for a screen reader. */
113
+ export function sumWords({ total, parts, result, posts, noun = "point" }) {
114
+ const r = resultOf({ total, parts, result });
115
+ const list = (parts ?? []).filter((p) => costOf(p?.cost) > 0);
116
+ const plural = (n) => (n === 1 ? noun : `${noun}s`);
117
+ const took = list.length ? ` ${list.map((p) => `${p.label ?? "A part"} took ${num(costOf(p.cost))}`).join(", ")}.` : " Nothing was taken off.";
118
+ const held = r.held > 0 ? ` A rule holds it at ${num(r.result)} where the sum is ${num(r.kept)}.` : "";
119
+ const pins = (posts ?? []).filter((p) => p?.label).map((p) => `${p.label} at ${num(Number(p.at))}`);
120
+ return `${num(r.result)} of ${num(r.total)} ${plural(r.total)}.${took}${held}${pins.length ? ` Posts: ${pins.join(", ")}.` : ""}`;
121
+ }
122
+
123
+ /** Which of the parts to show a name under, when the bar is too short for all of them. */
124
+ export function labelledCuts(cuts, minPct = 7) {
125
+ return cuts.filter((c) => c.cut && c.widthPct >= minPct);
126
+ }
@@ -0,0 +1,217 @@
1
+ # TierMap
2
+
3
+ Cards in tiers, from a flat list where each item names its parent.
4
+
5
+ The first tier is the tops. Press a card and the cards under it open as the
6
+ next tier beneath, joined to it by elbow lines. Press it again and they close.
7
+ **One layer opens at a time**: opening a card closes what was open beside it.
8
+ A card with something under it says how many ("6 serving it") and whether it
9
+ is open.
10
+
11
+ Items that sit under nothing and are not tops are shown together in an
12
+ *unparented* group, with a note and a slot for an action. Items marked `ghost`
13
+ (drafts: counted nowhere yet) are drawn dashed in a group of their own, and a
14
+ draft can serve another draft.
15
+
16
+ It draws a structure the app has already worked out. It moves nothing between
17
+ parents and collects nothing. For a person-and-manager chart you pan and zoom
18
+ use `org-chart`; for a free map of ideas `mind-map`; for nodes and edges you
19
+ draw `flow-canvas`; for a table with rows that expand `data-table`.
20
+
21
+ Born in a goals tracker, on the screen "How goals fit": company goals in the
22
+ first tier, the team goals that serve them beneath. The same block holds
23
+ initiatives under goals, work under a roadmap theme, an account's
24
+ sub-accounts, a strategy map.
25
+
26
+ ```jsx
27
+ import { TierMap } from "../components/blocks/tier-map"; // a Makersclaw app ("./blocks/tier-map" from components/)
28
+ import { TierMap } from "@/components/makersclaw/blocks/tier-map"; // a shadcn project
29
+ ```
30
+
31
+ A client component. No other block and no npm package. The lines are CSS only:
32
+ each tier is a grid of equal columns and the lines are placed in percent of
33
+ its width, so nothing is measured, there is no observer, and the server draws
34
+ what the browser draws. The helpers (`buildTierModel`, `openPath`,
35
+ `togglePath`, `neighbour`, `elbowOf`...) are plain functions and work on the
36
+ server too.
37
+
38
+ ## The data
39
+
40
+ One row per card, as a table holds them:
41
+
42
+ ```js
43
+ [
44
+ { id: "c1", title: "Reach $1M in annual revenue", kicker: "Company", by: <Avatar name="Dana" size="xs" />, value: 0.62, tone: "warning", foot: "6 teams serve it" },
45
+ { id: "s1", parent: "c1", title: "Close the enterprise gap", kicker: "Sales", value: 0.58 },
46
+ { id: "g1", ghost: true, title: "Grow MRR to $80k", kicker: "Draft" },
47
+ ]
48
+ ```
49
+
50
+ - `id` required, unique (a repeat is ignored). Numbers are read as text.
51
+ - `parent` the parent's `id`. Missing, empty, itself, or **an id that is not in
52
+ the list**: the card has no parent. A **loop** of parents (a names b, b names
53
+ a) is broken at the card that comes first in the list, which becomes
54
+ parentless, and never hangs the map.
55
+ - `title` the card's heading. Long words break; nothing overflows.
56
+ - `kicker` the small line above the title ("Sales", "Sub-account").
57
+ - `mark` a node before the kicker (the app's status mark: a `StatusDot`, an
58
+ icon).
59
+ - `by` a node at the card's top right (an `Avatar`). It sits inside the
60
+ card's button, so it must not be interactive.
61
+ - `value` 0 to 1: a bar with its percent. Missing, `null` or not a number: no
62
+ bar. A draft never has one.
63
+ - `tone` the bar's colour, **a status only**: `ink` (default) · `quiet` · `info`
64
+ · `success` · `warning` · `danger` · `auto` (ink, and success when full).
65
+ - `foot` one muted line at the card's foot.
66
+ - `ghost` `true`: a draft. Dashed, no bar, never in the tiers, never counted.
67
+ A draft under a draft is drawn under it; a draft under a real card shows
68
+ `Serves <that card's title>`.
69
+ - `href` the card is also a link (the app's record page).
70
+ - Any other key is kept and handed back to `onSelect` and `renderCard`.
71
+
72
+ Where a card goes:
73
+
74
+ | It is | and it goes |
75
+ | --- | --- |
76
+ | a top (named in `tops`, or by default a real card with no parent and kids) | the first tier |
77
+ | under a top, or under a card in the tiers | the tier beneath its parent when that card is open |
78
+ | real, and not reachable from any top | the unparented group |
79
+ | `ghost` | the drafts group |
80
+
81
+ A card named in `tops` is drawn as a top and nowhere else, even if it names a
82
+ parent. A real card whose parent is a draft is unparented.
83
+
84
+ ## Props
85
+
86
+ - `items` required. The rows above. With none, `empty` shows.
87
+ - `tops` ids drawn as the first tier, in that order. Default: the real cards
88
+ with no parent that have kids. Name them yourself to show a top that has
89
+ nothing under it yet.
90
+ - `topsLabel` the heading over the first tier ("Company · Q3 2026"). A node.
91
+ - `label` the map's name for a reader. Default "Tiers".
92
+ - `childrenLabel` the words after the count on a card with kids: a string
93
+ ("serving it" gives "6 serving it") or `(count) => text`. Default "below".
94
+ - `openWord` said after the count while a card is open. Default "open".
95
+ - `open` the open path, controlled: an array of ids, **one per tier** (a top,
96
+ then one of its kids, then one of theirs). An id that cannot be open there
97
+ ends the path at that point.
98
+ - `defaultOpen` the same, uncontrolled (the initial path). Default none.
99
+ - `onOpenChange(ids)` called with the new path when a card opens or closes.
100
+ - `onSelect(item)` makes a card's title and body a button that selects it (open
101
+ the record); the count and the chevron then open the tier. Without it the
102
+ whole card opens the tier, and a card with nothing under it does nothing.
103
+ - `selected` the id marked as current.
104
+ - `unparentedLabel` the group's heading. Default "Not under anything"; the
105
+ count is added ("Serves nothing · 1"). The group is drawn only when there is
106
+ something in it.
107
+ - `unparentedNote` a node beside the cards: the note and the action, as you
108
+ compose them ("Serves nothing: is it work the company needs, or work nobody
109
+ asked for?" and a Button "Link to a company goal").
110
+ - `ghostLabel` the drafts group's heading. Default "Drafts".
111
+ - `ghostNote` a node under that heading.
112
+ - `servesLabel` the word before the parent's title on a draft that serves a
113
+ real card. Default "Serves"; `""` leaves the line out.
114
+ - `renderCard(item, state)` replaces the default card. Return nothing (`null`)
115
+ to keep the default for that card. `state` is `{ id, item, open, selected,
116
+ count, tier, ghost, serves, pressable, select, toggle, holderProps,
117
+ primaryProps, toggleProps }`. Spread `holderProps` on your root,
118
+ `primaryProps` on the element that takes the focus, and `toggleProps` (null
119
+ when the card has nothing under it) on the button that opens the tier; the
120
+ arrow keys, the roving focus and the tier lines depend on them.
121
+ - `loading` three placeholder cards.
122
+ - `error` the words for a failure (a string or a node). Pass plain words:
123
+ the block shows them as given. With `onRetry`, a Try again button.
124
+ - `empty` a node shown when there are no items. Default: "Nothing to map yet".
125
+ - `className` layout: width and margin. Never colour.
126
+
127
+ `TierCard` is exported too, for a `renderCard` that wants the default card in
128
+ some cases. Helpers, pure and without React: `buildTierModel(items, { tops })`,
129
+ `openPath(model, ids)`, `togglePath(model, path, id)`, `pathTo(model, id)`
130
+ (the ids to open so that a card is on screen), `tiersOf`, `neighbour`,
131
+ `siblingsOf`, `elbowOf`, `centreOf`, `barOf`, `toneOf`, `countText`, `fitKey`,
132
+ `thresholdRem`.
133
+
134
+ ## Example
135
+
136
+ ```jsx
137
+ const [path, setPath] = useState(["c1"]);
138
+
139
+ <TierMap
140
+ label="How goals fit"
141
+ items={goals} // [{ id, parent, title, kicker, by, value, tone, ghost }]
142
+ tops={companyGoalIds}
143
+ topsLabel="Company · Q3 2026"
144
+ childrenLabel={(n) => `${n} serving it`}
145
+ open={path}
146
+ onOpenChange={setPath}
147
+ selected={goalId}
148
+ onSelect={(goal) => router.push(`?goal=${goal.id}`)}
149
+ unparentedLabel="Serves nothing"
150
+ unparentedNote={
151
+ <>
152
+ <p>Is it work the company needs, or work nobody asked for?</p>
153
+ <Button size="sm" onClick={linkIt}>Link to a company goal</Button>
154
+ </>
155
+ }
156
+ ghostLabel="Q4 2026 drafts · aligned already"
157
+ />
158
+
159
+ // Open the parents of a goal a link points at:
160
+ import { buildTierModel, pathTo } from "../components/blocks/tier-map";
161
+ const path = pathTo(buildTierModel(goals, { tops: companyGoalIds }), goalId);
162
+ ```
163
+
164
+ ## Keys
165
+
166
+ Cards are buttons (a link when they have an `href`), with one tab stop for the
167
+ whole map; the arrows move inside it.
168
+
169
+ - Left and right: the card beside, in the same tier. Home and End: the first
170
+ and the last.
171
+ - Down: into the first card under this one (opening it first if it is closed).
172
+ - Up: to the parent.
173
+ - Escape: close this card's tier; on a card that is not open, close its
174
+ parent's tier and move to the parent.
175
+ - Enter and Space press the focused button: select the card, or open its tier.
176
+
177
+ ## Layout
178
+
179
+ Everything follows the block's own width (container queries), never the
180
+ window's. A tier sits in **one row of equal columns** when the container has
181
+ 11 rem for each of its cards (and for each card of the tiers above it, so a
182
+ tier never sits in a row under one that stacks); cards are at most 26 rem wide
183
+ and centred in their column, and an elbow runs from the open card across to
184
+ each card under it. A tier of more than eight cards always stacks.
185
+
186
+ Below that, the tier **stacks**: cards go one under another, full width, and
187
+ the cards under an open card hang from a line down their left. In between
188
+ (the parent in a row, its kids too many for one), the line leaves the parent
189
+ and runs left to that rail.
190
+
191
+ ## States drawn
192
+
193
+ - **Empty**, **one top with nothing under it**, **many tops**, **one open**.
194
+ - **A deep path**: three tiers open, controlled or not.
195
+ - **Very long titles, kickers, feet and names**: they wrap or truncate; no
196
+ card grows past its column.
197
+ - **Narrow** (a 320 px container): tiers stack, cards full width, a rail.
198
+ - **Wide** (past 1000 px): a tier of six in one row with its elbows.
199
+ - **Unparented group** with its note and action; **drafts group** where a draft
200
+ serves a draft and one serves a real card.
201
+ - **A loop of parents** (broken), **a parent that is not in the list**, a card
202
+ that names itself.
203
+ - **Read-only** (no `onSelect`): a card only opens its tier.
204
+ - **Loading**, **failed** (with Try again).
205
+ - Reduced motion: the chevron does not turn.
206
+
207
+ ## Notes for the app that places it
208
+
209
+ - **Colour is a signal.** The bar is ink unless the goal is behind (warning,
210
+ danger) or done (success). Work out the status yourself; the block has no
211
+ words for "behind" and no thresholds.
212
+ - Put the numbers in words as well: `foot` is the place ("Own key results 82%").
213
+ - The count on a card is the cards drawn under it. A draft is never counted.
214
+ - Keep the open path in the address (`?open=c1,s1`) if a member should be able
215
+ to send the page; `pathTo` gives the path for a card a link points at.
216
+ - It does not fetch. Give it every item of the map at once; a list of hundreds
217
+ is fine, only the open tiers are drawn.