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,568 @@
1
+ "use client";
2
+
3
+ // An automation drawn as a real node canvas: pan, zoom, fit, a map, icon
4
+ // cards with ports, curved connectors with arrowheads, a + on every
5
+ // connector and at every path's end, labelled branches that meet again, a
6
+ // dashed box for "for each", grouped steps that fold to one card, and a
7
+ // palette.
8
+ //
9
+ // Built by hand, no graph library: the format is a tree, so the layout is
10
+ // exact (automation-model graph.js). It takes a layout and callbacks and
11
+ // paints with the app's tokens.
12
+ //
13
+ // Two uses, one picture:
14
+ // - editing (`editable`): + inserts, a card selects, the palette adds;
15
+ // - a run (`overlay`): each card wears how it went, the path taken is lit
16
+ // and the rest dimmed; nothing can be changed.
17
+ //
18
+ // Input: drag the ground to pan; the wheel pans, Ctrl/Cmd-wheel and pinch
19
+ // zoom; the arrow keys walk the cards, Esc lets go. Everything the canvas
20
+ // does with a pointer is also reachable by keys and by the outline.
21
+
22
+ import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
23
+ import { Icon, cx } from "../../ui";
24
+ import { Glyph, KINDS, LABEL_W, T, curveBetween, fixTag, nodeIndex, readingOrder, slotWords, triggerGlyph } from "../automation-model";
25
+ import { StepPicker, PaletteBar } from "../step-picker";
26
+ import { MAP_H, MAP_W, Minimap } from "./minimap";
27
+
28
+ const MIN_K = 0.2;
29
+ const MAX_K = 1.6;
30
+ const clampK = (k) => Math.min(MAX_K, Math.max(MIN_K, k));
31
+
32
+ /** One decimal only when there is one: 3 → "3", 2.4 → "2.4". */
33
+ const formatPrice = (n) => String(Math.round(n * 10) / 10);
34
+
35
+ const STATE_ICON = { done: "check", would: "pencil", running: "loader", failed: "x", fixed: "wand", waiting: "clock", skipped: "minus", unknown: "circle-help" };
36
+ const BADGE = {
37
+ done: "bg-success",
38
+ would: "bg-muted-foreground",
39
+ running: "bg-info",
40
+ failed: "bg-destructive",
41
+ fixed: "bg-primary",
42
+ waiting: "bg-warning",
43
+ skipped: "bg-muted-foreground",
44
+ unknown: "bg-muted-foreground",
45
+ };
46
+ // A tile: tone first, then how the run left it (the later wins by order).
47
+ const TONE = {
48
+ plain: "border-border text-muted-foreground",
49
+ model: "border-primary/55 text-primary",
50
+ agent: "border-primary bg-primary/15 text-primary",
51
+ };
52
+ const RUNSTATE = {
53
+ done: "border-success/60",
54
+ // The pencil look: it would run, it did not. Dashed, no solid fill.
55
+ would: "border-dashed border-primary/70 bg-transparent text-primary",
56
+ running: "border-info ring-4 ring-info/20",
57
+ failed: "border-destructive",
58
+ fixed: "border-primary",
59
+ waiting: "border-warning ring-4 ring-warning/20",
60
+ skipped: "border-dashed border-muted-foreground/45 bg-transparent text-muted-foreground/60",
61
+ notreached: "border-dashed border-muted-foreground/45 bg-transparent text-muted-foreground/60",
62
+ unknown: "border-warning",
63
+ };
64
+ const EDGE = {
65
+ n: { path: "fill-none stroke-muted-foreground/55", head: "fill-muted-foreground/55", width: 1.6 },
66
+ t: { path: "fill-none stroke-success", head: "fill-success", width: 2.6 },
67
+ r: { path: "fill-none stroke-info", head: "fill-info", width: 2.6 },
68
+ d: { path: "fill-none stroke-muted-foreground/25", head: "fill-muted-foreground/25", width: 1.6 },
69
+ };
70
+
71
+ /**
72
+ * layout automation-model's layoutAutomation()
73
+ * selectedId a step id, "trigger", or null
74
+ * onSelect(id|null)
75
+ * editable the + buttons, the palette and the add button
76
+ * onInsert(slot, kind) a + was pressed and a kind chosen
77
+ * onAddAtSelection(kind) the palette (or the round add) chose a kind
78
+ * onToggleCollapse(stepId) fold or open a for-each
79
+ * overlay a run's overlayOf(): each card and connector, how it went
80
+ * (in a test run a step that stood in for itself is "would":
81
+ * a dashed card with a pencil and one line of what it would do;
82
+ * a step it could not check says "Not checked")
83
+ * flagged a Set of step ids with something to fix (a red mark)
84
+ * prices a Map of step id to { credits, max }: the price tag
85
+ * fitKey changes when the picture should fit again
86
+ * revealId pan so this step is in view (after an insert)
87
+ * info floating, top left, after the zoom controls
88
+ * runnable a Set of kinds the platform runs (the picker says "soon" for the rest)
89
+ */
90
+ export function FlowCanvas({ layout, selectedId = null, onSelect, editable = false, onInsert, onAddAtSelection, onToggleCollapse, overlay = null, flagged, prices, fitKey, revealId, info, runnable, className }) {
91
+ const uid = useId().replace(/[^a-zA-Z0-9]/g, "");
92
+ const rootRef = useRef(null);
93
+ const [view, setViewState] = useState({ x: 40, y: 40, k: 1 });
94
+ const viewRef = useRef(view);
95
+ const [size, setSize] = useState({ w: 0, h: 0 });
96
+ const [picker, setPicker] = useState(null);
97
+ const fitted = useRef(null);
98
+
99
+ const setView = useCallback((next) => {
100
+ const v = typeof next === "function" ? next(viewRef.current) : next;
101
+ viewRef.current = v;
102
+ setViewState(v);
103
+ }, []);
104
+
105
+ const index = useMemo(() => nodeIndex(layout), [layout]);
106
+
107
+ /* ---- measuring and fitting ---------------------------------------- */
108
+
109
+ const fit = useCallback(() => {
110
+ const el = rootRef.current;
111
+ if (!el) return;
112
+ const w = el.clientWidth;
113
+ const h = el.clientHeight;
114
+ const b = layout.bounds;
115
+ if (!w || !h || b.w <= 0) return;
116
+ const k = clampK(Math.min(w / b.w, h / b.h, 1));
117
+ setView({ k, x: (w - b.w * k) / 2 - b.x * k, y: (h - b.h * k) / 2 - b.y * k });
118
+ }, [layout.bounds, setView]);
119
+
120
+ useLayoutEffect(() => {
121
+ const el = rootRef.current;
122
+ if (!el) return undefined;
123
+ const measure = () => setSize({ w: el.clientWidth, h: el.clientHeight });
124
+ measure();
125
+ if (typeof ResizeObserver === "undefined") return undefined;
126
+ // Measure in the callback, act in the next frame: setting state that
127
+ // changes what is watched, inside its own delivery, is the browser's
128
+ // "ResizeObserver loop" notice (check rule 17).
129
+ let raf = 0;
130
+ const ro = new ResizeObserver(() => {
131
+ cancelAnimationFrame(raf);
132
+ raf = requestAnimationFrame(measure);
133
+ });
134
+ ro.observe(el);
135
+ return () => {
136
+ cancelAnimationFrame(raf);
137
+ ro.disconnect();
138
+ };
139
+ }, []);
140
+
141
+ // Fit once per fitKey, as soon as there is a size to fit into.
142
+ useEffect(() => {
143
+ const key = fitKey ?? "";
144
+ if (fitted.current === key || !size.w || !size.h) return;
145
+ fitted.current = key;
146
+ fit();
147
+ }, [fitKey, size.w, size.h, fit]);
148
+
149
+ // Bring a new step into view without moving the picture if it is already seen.
150
+ useEffect(() => {
151
+ if (!revealId || !size.w) return;
152
+ const n = index.get(revealId);
153
+ if (!n) return;
154
+ const v = viewRef.current;
155
+ const sx = n.x * v.k + v.x;
156
+ const sy = n.y * v.k + v.y;
157
+ const pad = 80;
158
+ const inside = sx > pad && sy > pad && sx + n.w * v.k < size.w - pad && sy + n.h * v.k < size.h - pad;
159
+ if (!inside) setView({ k: v.k, x: size.w / 2 - (n.x + n.w / 2) * v.k, y: size.h / 2 - (n.y + n.h / 2) * v.k });
160
+ }, [revealId, index, size.w, size.h, setView]);
161
+
162
+ /* ---- zoom and pan -------------------------------------------------- */
163
+
164
+ const zoomAt = useCallback(
165
+ (factor, cx0, cy0) => {
166
+ setView((v) => {
167
+ const k = clampK(v.k * factor);
168
+ const r = k / v.k;
169
+ return { k, x: cx0 - (cx0 - v.x) * r, y: cy0 - (cy0 - v.y) * r };
170
+ });
171
+ },
172
+ [setView],
173
+ );
174
+ const zoomCentre = (factor) => zoomAt(factor, size.w / 2, size.h / 2);
175
+
176
+ // The wheel needs a non-passive listener to keep the page from zooming.
177
+ useEffect(() => {
178
+ const el = rootRef.current;
179
+ if (!el) return undefined;
180
+ const onWheel = (e) => {
181
+ if (e.target.closest?.("[data-scroll]")) return;
182
+ e.preventDefault();
183
+ const r = el.getBoundingClientRect();
184
+ if (e.ctrlKey || e.metaKey) zoomAt(Math.exp(-e.deltaY * 0.01), e.clientX - r.left, e.clientY - r.top);
185
+ else setView((v) => ({ ...v, x: v.x - e.deltaX, y: v.y - e.deltaY }));
186
+ };
187
+ el.addEventListener("wheel", onWheel, { passive: false });
188
+ return () => el.removeEventListener("wheel", onWheel);
189
+ }, [zoomAt, setView]);
190
+
191
+ const pointers = useRef(new Map());
192
+ const drag = useRef(null);
193
+ const pinch = useRef(null);
194
+
195
+ const onPointerDown = (e) => {
196
+ if (e.target.closest?.("button, input, select, textarea, [data-nopan]")) return;
197
+ pointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
198
+ e.currentTarget.setPointerCapture?.(e.pointerId);
199
+ if (pointers.current.size === 1) drag.current = { x: e.clientX, y: e.clientY, moved: false };
200
+ else {
201
+ drag.current = null;
202
+ const [a, b] = [...pointers.current.values()];
203
+ pinch.current = Math.hypot(a.x - b.x, a.y - b.y);
204
+ }
205
+ };
206
+ const onPointerMove = (e) => {
207
+ if (!pointers.current.has(e.pointerId)) return;
208
+ const prev = pointers.current.get(e.pointerId);
209
+ pointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
210
+ if (pointers.current.size === 2 && pinch.current) {
211
+ const [a, b] = [...pointers.current.values()];
212
+ const dist = Math.hypot(a.x - b.x, a.y - b.y);
213
+ const r = rootRef.current?.getBoundingClientRect();
214
+ if (r && dist > 0) zoomAt(dist / pinch.current, (a.x + b.x) / 2 - r.left, (a.y + b.y) / 2 - r.top);
215
+ pinch.current = dist;
216
+ return;
217
+ }
218
+ const d = drag.current;
219
+ if (!d) return;
220
+ if (!d.moved && Math.hypot(e.clientX - d.x, e.clientY - d.y) < 4) return;
221
+ d.moved = true;
222
+ setView((v) => ({ ...v, x: v.x + (e.clientX - prev.x), y: v.y + (e.clientY - prev.y) }));
223
+ };
224
+ const onPointerUp = (e) => {
225
+ pointers.current.delete(e.pointerId);
226
+ pinch.current = null;
227
+ const d = drag.current;
228
+ drag.current = null;
229
+ // A press on the ground that went nowhere lets go of the selection.
230
+ if (d && !d.moved) {
231
+ onSelect?.(null);
232
+ setPicker(null);
233
+ }
234
+ };
235
+
236
+ const onKeyDown = (e) => {
237
+ if (e.target.closest?.("input, textarea, select")) return;
238
+ if (e.key === "Escape") {
239
+ if (picker) setPicker(null);
240
+ else onSelect?.(null);
241
+ } else if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
242
+ const order = readingOrder(layout);
243
+ if (!order.length) return;
244
+ const at = order.findIndex((n) => (n.stepId ?? n.id) === selectedId || n.id === selectedId);
245
+ const next = order[Math.min(order.length - 1, Math.max(0, at + (e.key === "ArrowRight" ? 1 : -1)))];
246
+ if (next) onSelect?.(next.stepId ?? next.id);
247
+ e.preventDefault();
248
+ } else if (e.key === "+" || e.key === "=") zoomCentre(1.2);
249
+ else if (e.key === "-") zoomCentre(1 / 1.2);
250
+ else if (e.key === "0") fit();
251
+ };
252
+
253
+ /* ---- the picker ---------------------------------------------------- */
254
+
255
+ const openPicker = (slot, at) => {
256
+ const r = rootRef.current?.getBoundingClientRect();
257
+ const PW = 330;
258
+ const PH = 400;
259
+ const left = Math.max(8, Math.min(at.x - (r?.left ?? 0) - PW / 2, (r?.width ?? 800) - PW - 8));
260
+ const below = at.y - (r?.top ?? 0) + 16;
261
+ const top = Math.max(8, Math.min(below, (r?.height ?? 600) - PH - 8));
262
+ setPicker({ slot, left, top });
263
+ };
264
+
265
+ const pickedKind = (kind) => {
266
+ const p = picker;
267
+ setPicker(null);
268
+ if (!p) return;
269
+ if (p.slot) onInsert?.(p.slot, kind);
270
+ else onAddAtSelection?.(kind);
271
+ };
272
+
273
+ /* ---- drawing ------------------------------------------------------- */
274
+
275
+ const edges = layout.edges.map((e) => {
276
+ const a = index.get(e.from);
277
+ const b = index.get(e.to);
278
+ return a && b ? { e, curve: curveBetween(a, b) } : null;
279
+ });
280
+
281
+ const edgeKind = (id) => {
282
+ if (!overlay) return "n";
283
+ const s = overlay.edges.get(id) ?? "dim";
284
+ return s === "taken" ? "t" : s === "running" ? "r" : "d";
285
+ };
286
+ const stateOf = (n) => (overlay ? (overlay.nodes.get(n.id) ?? "notreached") : null);
287
+
288
+ const small = view.k < 0.45;
289
+ const showMap = size.w > MAP_W * 2 && size.h > MAP_H * 2.4;
290
+ const showPalette = editable && onAddAtSelection && size.w >= 560;
291
+ const dot = 24 * view.k;
292
+
293
+ return (
294
+ <div
295
+ ref={rootRef}
296
+ role="group"
297
+ aria-label="Automation canvas"
298
+ tabIndex={0}
299
+ onKeyDown={onKeyDown}
300
+ onPointerDown={onPointerDown}
301
+ onPointerMove={onPointerMove}
302
+ onPointerUp={onPointerUp}
303
+ onPointerCancel={onPointerUp}
304
+ className={cx("relative isolate min-h-[320px] flex-1 cursor-grab touch-none select-none overflow-hidden bg-background outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring active:cursor-grabbing", className)}
305
+ >
306
+ {/* The dotted ground travels with the picture. */}
307
+ <span
308
+ aria-hidden="true"
309
+ className="pointer-events-none absolute inset-0 -z-10 opacity-30"
310
+ style={{
311
+ backgroundImage: "radial-gradient(circle, var(--color-muted-foreground) 1px, transparent 1.4px)",
312
+ backgroundSize: `${dot}px ${dot}px`,
313
+ backgroundPosition: `${view.x}px ${view.y}px`,
314
+ }}
315
+ />
316
+ <div className="absolute left-0 top-0 origin-top-left will-change-transform" style={{ transform: `translate(${view.x}px, ${view.y}px) scale(${view.k})` }}>
317
+ <svg className="pointer-events-none absolute left-0 top-0 overflow-visible" width="1" height="1" aria-hidden="true">
318
+ <defs>
319
+ {["n", "t", "r", "d"].map((k) => (
320
+ <marker key={k} id={`arr-${uid}-${k}`} viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto">
321
+ <path d="M 1 1 L 9 5 L 1 9 z" className={EDGE[k].head} />
322
+ </marker>
323
+ ))}
324
+ </defs>
325
+ {edges.map((x) => {
326
+ if (!x) return null;
327
+ const k = edgeKind(x.e.id);
328
+ const toEnd = index.get(x.e.to)?.type === "end";
329
+ return <path key={x.e.id} d={x.curve.d} className={EDGE[k].path} strokeWidth={EDGE[k].width} strokeDasharray={k === "d" ? "6 6" : toEnd ? "5 5" : undefined} markerEnd={`url(#arr-${uid}-${k})`} />;
330
+ })}
331
+ </svg>
332
+
333
+ {layout.groups.map((g) => {
334
+ const state = overlay ? (overlay.nodes.get(g.id) ?? "notreached") : null;
335
+ const sel = selectedId === g.stepId;
336
+ return (
337
+ <div
338
+ key={g.id}
339
+ style={{ left: g.x, top: g.y, width: g.w, height: g.h }}
340
+ className={cx(
341
+ "pointer-events-none absolute rounded-[20px] border-[1.5px] border-dashed bg-muted/55",
342
+ sel ? "border-solid border-foreground" : "border-muted-foreground/45",
343
+ (state === "done" || state === "fixed") && "border-success/70",
344
+ state === "would" && "border-primary/60",
345
+ state === "running" && "border-info",
346
+ state === "failed" && "border-destructive",
347
+ state === "notreached" && "opacity-50",
348
+ )}
349
+ >
350
+ <div data-nopan className="pointer-events-auto absolute -top-3.5 left-4 flex h-7 max-w-[calc(100%_-_2rem)] items-center gap-0.5 rounded-full border-[1.5px] border-dashed border-muted-foreground/45 bg-background">
351
+ <button type="button" aria-pressed={sel} onClick={() => onSelect?.(g.stepId)} className="inline-flex h-full min-w-0 items-center gap-1.5 overflow-hidden whitespace-nowrap pl-3 pr-1 text-[13px] font-semibold focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
352
+ <Glyph name="loop" size={14} />
353
+ <span className="truncate">{g.title}</span>
354
+ <span className="truncate font-medium text-muted-foreground">{g.sub}</span>
355
+ </button>
356
+ {onToggleCollapse ? (
357
+ <button type="button" aria-label={`Fold ${g.title}`} onClick={() => onToggleCollapse(g.stepId)} className="mr-0.5 inline-flex size-6 items-center justify-center rounded-full text-muted-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
358
+ <Icon name="chevron-down" size={14} />
359
+ </button>
360
+ ) : null}
361
+ </div>
362
+ </div>
363
+ );
364
+ })}
365
+
366
+ {layout.nodes.map((n) => {
367
+ const state = stateOf(n);
368
+ if (n.type === "join") return <i key={n.id} style={{ left: n.x, top: n.y, width: n.w, height: n.h }} className="absolute rounded-full border-2 border-background bg-muted-foreground/70" />;
369
+ if (n.type === "port") {
370
+ return (
371
+ <i
372
+ key={n.id}
373
+ style={{ left: n.x, top: n.y, width: n.w, height: n.h }}
374
+ className={cx("absolute rounded border-2 border-background", state === "done" || state === "fixed" ? "bg-success" : state === "would" ? "bg-primary/60" : "bg-muted-foreground/70")}
375
+ />
376
+ );
377
+ }
378
+ if (n.type === "end") {
379
+ if (!editable || !n.slot) return <i key={n.id} style={{ left: n.x + n.w / 2 - 4, top: n.y + n.h / 2 - 4 }} className="absolute size-2 rounded-full bg-muted-foreground/50" />;
380
+ const slot = n.slot;
381
+ return (
382
+ <button
383
+ key={n.id}
384
+ type="button"
385
+ data-nopan
386
+ style={{ left: n.x, top: n.y, width: n.w, height: n.h }}
387
+ aria-label={`${n.title} ${slotWords(slot)}`}
388
+ title={n.title}
389
+ onClick={(ev) => openPicker(slot, { x: ev.clientX, y: ev.clientY })}
390
+ className="absolute flex items-center justify-center rounded-full border-[1.5px] border-dashed border-muted-foreground/70 bg-background text-muted-foreground hover:border-solid hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
391
+ >
392
+ <Icon name="plus" size={14} />
393
+ {n.title === "Add the first step" ? <span className="absolute left-[38px] top-[5px] whitespace-nowrap text-[12.5px] text-muted-foreground">Add the first step</span> : null}
394
+ </button>
395
+ );
396
+ }
397
+ if (n.type === "card") {
398
+ const sel = selectedId === n.id;
399
+ return (
400
+ <div
401
+ key={n.id}
402
+ style={{ left: n.x, top: n.y, width: n.w, height: n.h }}
403
+ className={cx(
404
+ "absolute rounded-2xl border bg-card p-3 text-card-foreground",
405
+ sel && "outline outline-2 outline-offset-[3px] outline-foreground",
406
+ state === "done" ? "border-success/60" : state === "would" ? "border-dashed border-primary/70" : state === "failed" ? "border-destructive" : state === "notreached" ? "border-dashed border-border opacity-60" : "border-border",
407
+ )}
408
+ >
409
+ <i className="pointer-events-none absolute -left-1 top-[calc(50%_-_8px)] h-4 w-[7px] rounded-sm bg-border" />
410
+ <i className="pointer-events-none absolute -right-1.5 top-[calc(50%_-_5px)] size-[11px] rounded-full border-2 border-background bg-muted-foreground/70" />
411
+ <div data-nopan className="flex items-center justify-between gap-2">
412
+ <button type="button" aria-pressed={sel} onClick={() => onSelect?.(n.id)} className="inline-flex min-w-0 items-center gap-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
413
+ <Glyph name="loop" size={16} />
414
+ <b className="truncate">{n.title}</b>
415
+ </button>
416
+ <button type="button" onClick={() => onToggleCollapse?.(n.id)} className="inline-flex items-center gap-0.5 whitespace-nowrap text-xs text-muted-foreground underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
417
+ <Icon name="chevron-right" size={14} /> Show steps
418
+ </button>
419
+ </div>
420
+ <p className="m-0 mb-2 mt-0.5 text-xs text-muted-foreground">{n.sub}</p>
421
+ <ol className="m-0 flex list-none flex-col gap-1.5 p-0">
422
+ {(n.items ?? []).map((it) => (
423
+ <li key={it.id} className="rounded-lg border border-border bg-background px-2.5 py-1 text-[12.5px]">
424
+ {it.title}
425
+ </li>
426
+ ))}
427
+ {(n.items ?? []).length === 0 ? <li className="rounded-lg border border-border bg-background px-2.5 py-1 text-[12.5px] text-muted-foreground">No steps yet</li> : null}
428
+ </ol>
429
+ </div>
430
+ );
431
+ }
432
+ // A step or the trigger: an icon tile with its words below.
433
+ const glyph = n.type === "trigger" ? triggerGlyph(n.triggerType) : (KINDS[n.kind]?.glyph ?? "action");
434
+ const selected = selectedId === (n.stepId ?? n.id);
435
+ const badgeIcon = state ? STATE_ICON[state] : undefined;
436
+ const bad = n.stepId ? flagged?.has(n.stepId) : false;
437
+ const sum = overlay && n.stepId ? overlay.summary.get(n.stepId) : null;
438
+ const price = !overlay && n.stepId ? prices?.get(n.stepId) : null;
439
+ return (
440
+ <div key={n.id} style={{ left: n.x, top: n.y, width: T, height: T }} className={cx("absolute", state === "notreached" || state === "skipped" ? "opacity-55" : undefined)}>
441
+ {n.type !== "trigger" ? <i className="pointer-events-none absolute -left-1 top-[calc(50%_-_8px)] h-4 w-[7px] rounded-sm bg-border" /> : null}
442
+ {n.kind !== "stop" ? <i className="pointer-events-none absolute -right-1.5 top-[calc(50%_-_5px)] z-[1] size-[11px] rounded-full border-2 border-background bg-muted-foreground/70" /> : null}
443
+ <button
444
+ type="button"
445
+ data-nopan
446
+ aria-pressed={selected}
447
+ aria-label={`${n.title}${state ? `. ${state === "would" ? "would run" : state}` : ""}`}
448
+ onClick={() => onSelect?.(n.stepId ?? n.id)}
449
+ className={cx(
450
+ "relative flex size-full cursor-pointer items-center justify-center rounded-xl border bg-card transition-colors hover:border-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
451
+ n.type === "trigger" && "rounded-l-[36px]",
452
+ TONE[n.tone] ?? TONE.plain,
453
+ state ? RUNSTATE[state] : null,
454
+ bad && "border-destructive",
455
+ selected && "outline outline-2 outline-offset-[3px] outline-foreground",
456
+ )}
457
+ >
458
+ <Glyph name={glyph} size={26} />
459
+ {n.mark ? (
460
+ <span className={cx("absolute -bottom-2 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-full border px-1.5 py-0.5 text-2xs font-semibold leading-none", n.tone === "agent" ? "border-transparent bg-primary text-primary-foreground" : "border-primary/55 bg-background text-primary")}>{n.mark}</span>
461
+ ) : null}
462
+ </button>
463
+ {n.num > 0 ? <span className="pointer-events-none absolute -left-1 -top-2.5 rounded bg-background px-1 font-mono text-2xs leading-[15px] text-muted-foreground">{n.num}</span> : null}
464
+ {badgeIcon && state ? (
465
+ <span aria-hidden="true" className={cx("pointer-events-none absolute -right-2 -top-2 z-[2] flex size-5 items-center justify-center rounded-full border-2 border-background text-background", BADGE[state])}>
466
+ <Icon name={badgeIcon} size={11} className={state === "running" ? "animate-spin motion-reduce:animate-none" : undefined} />
467
+ </span>
468
+ ) : null}
469
+ {bad && !badgeIcon ? (
470
+ <span aria-label="Needs a look" className="pointer-events-none absolute -right-2 -top-2 z-[2] flex size-5 items-center justify-center rounded-full border-2 border-background bg-destructive text-xs font-extrabold text-background">
471
+ !
472
+ </span>
473
+ ) : null}
474
+ <div className="pointer-events-none absolute flex flex-col gap-[3px] text-center" style={{ top: T + 12, left: (T - LABEL_W) / 2, width: LABEL_W }}>
475
+ <b className="break-words text-[13px] font-semibold leading-tight text-foreground [overflow-wrap:anywhere]">{n.title}</b>
476
+ {n.sub ? <span className="text-xs leading-snug text-muted-foreground [overflow-wrap:anywhere]">{n.sub}</span> : null}
477
+ {price ? <span className="self-center rounded-full border border-border px-2 font-mono text-2xs text-muted-foreground">about {formatPrice(price.credits)} cr</span> : null}
478
+ {sum && sum.passes > 1 ? <span className="font-mono text-xs text-muted-foreground">{sum.passes} times</span> : null}
479
+ {sum?.fix ? <span className="text-xs text-primary">{fixTag(sum.fix)}</span> : null}
480
+ {sum && state === "failed" && sum.words ? <span className="text-xs text-destructive">{sum.words}</span> : null}
481
+ {sum?.unchecked ? <span className="text-xs text-muted-foreground">Not checked</span> : sum && state === "would" && sum.would ? <span className="text-xs text-primary">{sum.would}</span> : null}
482
+ </div>
483
+ </div>
484
+ );
485
+ })}
486
+
487
+ {edges.map((x) => {
488
+ if (!x) return null;
489
+ const { e, curve } = x;
490
+ const state = overlay?.edges.get(e.id);
491
+ const dim = overlay && state === "dim";
492
+ const lab = e.label ? curve.at(0.24) : null;
493
+ return (
494
+ <span key={`m:${e.id}`}>
495
+ {lab ? (
496
+ <span
497
+ style={{ left: lab.x, top: lab.y }}
498
+ className={cx(
499
+ "pointer-events-none absolute -translate-x-1/2 -translate-y-1/2 whitespace-nowrap rounded-full border bg-background px-2.5 py-px text-xs font-semibold",
500
+ state === "taken" || state === "running" ? "border-success text-success" : "border-border text-muted-foreground",
501
+ dim && "opacity-50",
502
+ )}
503
+ >
504
+ {e.label}
505
+ </span>
506
+ ) : null}
507
+ {editable && e.plus && e.slot ? (
508
+ <button
509
+ type="button"
510
+ data-nopan
511
+ style={{ left: curve.mid.x, top: curve.mid.y }}
512
+ aria-label={`Add a step ${slotWords(e.slot)}`}
513
+ title="Add a step"
514
+ onClick={(ev) => openPicker(e.slot, { x: ev.clientX, y: ev.clientY })}
515
+ className="absolute flex size-[22px] -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full border border-border bg-background text-muted-foreground hover:bg-foreground hover:text-background focus-visible:bg-foreground focus-visible:text-background focus-visible:outline-none"
516
+ >
517
+ <Icon name="plus" size={12} />
518
+ </button>
519
+ ) : null}
520
+ </span>
521
+ );
522
+ })}
523
+ </div>
524
+
525
+ <div data-nopan className="pointer-events-none absolute left-3 top-3 z-[6] flex flex-wrap items-center gap-2 pr-16 [&>*]:pointer-events-auto">
526
+ <div role="group" aria-label="Zoom" className="flex items-center gap-px rounded-lg border border-border bg-card p-0.5">
527
+ <button type="button" aria-label="Zoom out" onClick={() => zoomCentre(1 / 1.25)} className="inline-flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
528
+ <Icon name="minus" size={15} />
529
+ </button>
530
+ <span aria-live="polite" className="min-w-[46px] text-center font-mono text-xs text-card-foreground">
531
+ {Math.round(view.k * 100)}%
532
+ </span>
533
+ <button type="button" aria-label="Zoom in" onClick={() => zoomCentre(1.25)} className="inline-flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
534
+ <Icon name="plus" size={15} />
535
+ </button>
536
+ <i className="mx-0.5 h-4 w-px bg-border" />
537
+ <button type="button" aria-label="Fit to the screen" title="Fit" onClick={fit} className="inline-flex size-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring">
538
+ <Icon name="maximize" size={15} />
539
+ </button>
540
+ </div>
541
+ {info}
542
+ </div>
543
+
544
+ {editable && onAddAtSelection && !showPalette ? (
545
+ <button
546
+ type="button"
547
+ data-nopan
548
+ aria-label="Add a step"
549
+ title="Add a step"
550
+ onClick={(ev) => openPicker(null, { x: ev.clientX, y: ev.clientY })}
551
+ className="absolute right-3.5 top-3 z-[6] flex size-[38px] items-center justify-center rounded-full bg-foreground text-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
552
+ >
553
+ <Icon name="plus" size={18} />
554
+ </button>
555
+ ) : null}
556
+
557
+ {showPalette ? (
558
+ <div data-nopan className="absolute bottom-3 left-3 z-[6] overflow-x-auto" style={{ maxWidth: showMap ? `calc(100% - ${MAP_W + 36}px)` : "calc(100% - 24px)" }}>
559
+ <PaletteBar onAdd={onAddAtSelection} />
560
+ </div>
561
+ ) : null}
562
+
563
+ {showMap ? <Minimap layout={layout} view={view} size={size} overlay={overlay} onCenter={(wx, wy) => setView((v) => ({ ...v, x: size.w / 2 - wx * v.k, y: size.h / 2 - wy * v.k }))} /> : null}
564
+
565
+ {picker ? <StepPicker style={{ left: picker.left, top: picker.top }} runnable={runnable} onClose={() => setPicker(null)} onPick={pickedKind} /> : null}
566
+ </div>
567
+ );
568
+ }