makersclaw 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/README.md +17 -4
  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 +4 -4
  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 +5 -5
  26. package/r/chat-pane.json +84 -0
  27. package/r/chat.json +3 -3
  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 +18 -6
  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 +3 -3
  46. package/r/flow-editor.json +79 -0
  47. package/r/footer.json +3 -3
  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 +4 -4
  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 +4 -4
  60. package/r/pace-bar.json +69 -0
  61. package/r/page-builder.json +3 -3
  62. package/r/payment-methods.json +2 -2
  63. package/r/pdf-document.json +2 -2
  64. package/r/pdf-viewer.json +3 -3
  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 +4089 -1510
  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 +3 -3
  82. package/r/signature-pad.json +2 -2
  83. package/r/sketch-canvas.json +3 -3
  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 +4 -4
  101. package/r/workflow-view.json +55 -0
  102. package/registry/blocks/activity-log/README.md +112 -0
  103. package/registry/blocks/activity-log/activity-log.jsx +351 -0
  104. package/registry/blocks/activity-log/block.json +33 -0
  105. package/registry/blocks/activity-log/demo.jsx +48 -0
  106. package/registry/blocks/activity-log/index.js +4 -0
  107. package/registry/blocks/agent-changes/README.md +94 -0
  108. package/registry/blocks/agent-changes/agent-changes.jsx +118 -0
  109. package/registry/blocks/agent-changes/block.json +28 -0
  110. package/registry/blocks/agent-changes/demo.jsx +39 -0
  111. package/registry/blocks/agent-changes/index.js +3 -0
  112. package/registry/blocks/agent-chat/README.md +145 -0
  113. package/registry/blocks/agent-chat/agent-chat.jsx +415 -0
  114. package/registry/blocks/agent-chat/block.json +30 -0
  115. package/registry/blocks/agent-chat/demo.jsx +165 -0
  116. package/registry/blocks/agent-chat/index.js +3 -0
  117. package/registry/blocks/agent-column/README.md +197 -0
  118. package/registry/blocks/agent-column/agent-column.jsx +492 -0
  119. package/registry/blocks/agent-column/block.json +67 -0
  120. package/registry/blocks/agent-column/column-picker.jsx +163 -0
  121. package/registry/blocks/agent-column/demo.jsx +50 -0
  122. package/registry/blocks/agent-column/index.js +7 -0
  123. package/registry/blocks/agent-column/model.js +164 -0
  124. package/registry/blocks/agent-column/receipt.jsx +483 -0
  125. package/registry/blocks/agent-column/spreadsheet-type.jsx +24 -0
  126. package/registry/blocks/agent-dock/README.md +210 -0
  127. package/registry/blocks/agent-dock/agent-dock.jsx +356 -0
  128. package/registry/blocks/agent-dock/block.json +47 -0
  129. package/registry/blocks/agent-dock/demo.jsx +153 -0
  130. package/registry/blocks/agent-dock/dock-model.js +118 -0
  131. package/registry/blocks/agent-dock/index.js +5 -0
  132. package/registry/blocks/agent-kit/README.md +73 -0
  133. package/registry/blocks/agent-kit/agent-kit.jsx +86 -0
  134. package/registry/blocks/agent-kit/block.json +10 -0
  135. package/registry/blocks/agent-kit/demo.jsx +35 -0
  136. package/registry/blocks/agent-kit/index.js +3 -0
  137. package/registry/blocks/agent-marks/README.md +80 -0
  138. package/registry/blocks/agent-marks/agent-marks.jsx +133 -0
  139. package/registry/blocks/agent-marks/block.json +27 -0
  140. package/registry/blocks/agent-marks/demo.jsx +49 -0
  141. package/registry/blocks/agent-marks/index.js +3 -0
  142. package/registry/blocks/agent-pane/README.md +225 -0
  143. package/registry/blocks/agent-pane/agent-pane.jsx +244 -0
  144. package/registry/blocks/agent-pane/block.json +41 -0
  145. package/registry/blocks/agent-pane/composer.jsx +6 -0
  146. package/registry/blocks/agent-pane/demo.jsx +233 -0
  147. package/registry/blocks/agent-pane/dock-context.js +10 -0
  148. package/registry/blocks/agent-pane/index.js +4 -0
  149. package/registry/blocks/agent-pane/prose.jsx +84 -0
  150. package/registry/blocks/agent-pane/questions.jsx +134 -0
  151. package/registry/blocks/agent-pane/transcript.jsx +268 -0
  152. package/registry/blocks/agent-pane/work.jsx +368 -0
  153. package/registry/blocks/ai-action/README.md +94 -0
  154. package/registry/blocks/ai-action/ai-action.jsx +188 -0
  155. package/registry/blocks/ai-action/block.json +34 -0
  156. package/registry/blocks/ai-action/demo.jsx +93 -0
  157. package/registry/blocks/ai-action/index.js +3 -0
  158. package/registry/blocks/annotate/README.md +14 -11
  159. package/registry/blocks/approval/README.md +145 -0
  160. package/registry/blocks/approval/approval.jsx +163 -0
  161. package/registry/blocks/approval/block.json +31 -0
  162. package/registry/blocks/approval/demo.jsx +121 -0
  163. package/registry/blocks/approval/index.js +3 -0
  164. package/registry/blocks/ask-bar/README.md +105 -0
  165. package/registry/blocks/ask-bar/ask-bar.jsx +166 -0
  166. package/registry/blocks/ask-bar/block.json +26 -0
  167. package/registry/blocks/ask-bar/demo.jsx +78 -0
  168. package/registry/blocks/ask-bar/index.js +3 -0
  169. package/registry/blocks/automation-editor/README.md +291 -0
  170. package/registry/blocks/automation-editor/automation-editor.jsx +656 -0
  171. package/registry/blocks/automation-editor/block.json +39 -0
  172. package/registry/blocks/automation-editor/demo.jsx +284 -0
  173. package/registry/blocks/automation-editor/index.js +4 -0
  174. package/registry/blocks/automation-editor/run-inputs.jsx +82 -0
  175. package/registry/blocks/automation-list/README.md +129 -0
  176. package/registry/blocks/automation-list/automation-list.jsx +476 -0
  177. package/registry/blocks/automation-list/block.json +35 -0
  178. package/registry/blocks/automation-list/demo.jsx +67 -0
  179. package/registry/blocks/automation-list/index.js +3 -0
  180. package/registry/blocks/automation-model/README.md +109 -0
  181. package/registry/blocks/automation-model/block.json +135 -0
  182. package/registry/blocks/automation-model/catalog.js +256 -0
  183. package/registry/blocks/automation-model/check.js +161 -0
  184. package/registry/blocks/automation-model/demo.jsx +42 -0
  185. package/registry/blocks/automation-model/edit.js +262 -0
  186. package/registry/blocks/automation-model/fixes.js +287 -0
  187. package/registry/blocks/automation-model/glyph.jsx +13 -0
  188. package/registry/blocks/automation-model/graph.js +410 -0
  189. package/registry/blocks/automation-model/index.js +33 -0
  190. package/registry/blocks/automation-model/overlay.js +175 -0
  191. package/registry/blocks/automation-model/parts.jsx +94 -0
  192. package/registry/blocks/automation-model/schedule.js +53 -0
  193. package/registry/blocks/automation-model/templates.js +128 -0
  194. package/registry/blocks/automation-model/values.js +29 -0
  195. package/registry/blocks/automation-model/words.js +439 -0
  196. package/registry/blocks/automations-screen/README.md +207 -0
  197. package/registry/blocks/automations-screen/automations-screen.jsx +452 -0
  198. package/registry/blocks/automations-screen/block.json +47 -0
  199. package/registry/blocks/automations-screen/demo.jsx +364 -0
  200. package/registry/blocks/automations-screen/index.js +4 -0
  201. package/registry/blocks/automations-screen/screen-model.js +129 -0
  202. package/registry/blocks/chart/cartesian.jsx +1 -1
  203. package/registry/blocks/chart/core.jsx +33 -23
  204. package/registry/blocks/chart/pie.jsx +1 -1
  205. package/registry/blocks/chat/README.md +4 -1
  206. package/registry/blocks/chat/demo.jsx +2 -2
  207. package/registry/blocks/chat-pane/README.md +143 -0
  208. package/registry/blocks/chat-pane/block.json +48 -0
  209. package/registry/blocks/chat-pane/chat-attach.js +166 -0
  210. package/registry/blocks/chat-pane/chat-composer.jsx +195 -0
  211. package/registry/blocks/chat-pane/chat-context.js +17 -0
  212. package/registry/blocks/chat-pane/chat-model.js +43 -0
  213. package/registry/blocks/chat-pane/chat-pane.jsx +398 -0
  214. package/registry/blocks/chat-pane/chats-list.jsx +52 -0
  215. package/registry/blocks/chat-pane/demo.jsx +103 -0
  216. package/registry/blocks/chat-pane/index.js +9 -0
  217. package/registry/blocks/connect-card/README.md +99 -0
  218. package/registry/blocks/connect-card/block.json +10 -0
  219. package/registry/blocks/connect-card/connect-card.jsx +103 -0
  220. package/registry/blocks/connect-card/demo.jsx +49 -0
  221. package/registry/blocks/connect-card/index.js +3 -0
  222. package/registry/blocks/cost/README.md +115 -0
  223. package/registry/blocks/cost/block.json +10 -0
  224. package/registry/blocks/cost/cost.jsx +176 -0
  225. package/registry/blocks/cost/demo.jsx +66 -0
  226. package/registry/blocks/cost/index.js +3 -0
  227. package/registry/blocks/data-picker/README.md +81 -0
  228. package/registry/blocks/data-picker/block.json +38 -0
  229. package/registry/blocks/data-picker/data-picker.jsx +210 -0
  230. package/registry/blocks/data-picker/demo.jsx +34 -0
  231. package/registry/blocks/data-picker/fields.js +89 -0
  232. package/registry/blocks/data-picker/index.js +4 -0
  233. package/registry/blocks/data-table/demo.jsx +1 -1
  234. package/registry/blocks/first-run/README.md +118 -0
  235. package/registry/blocks/first-run/block.json +11 -0
  236. package/registry/blocks/first-run/demo.jsx +88 -0
  237. package/registry/blocks/first-run/first-run-model.js +51 -0
  238. package/registry/blocks/first-run/first-run.jsx +156 -0
  239. package/registry/blocks/first-run/index.js +5 -0
  240. package/registry/blocks/first-run/probe-rules.js +26 -0
  241. package/registry/blocks/fix-note/README.md +79 -0
  242. package/registry/blocks/fix-note/block.json +35 -0
  243. package/registry/blocks/fix-note/demo.jsx +30 -0
  244. package/registry/blocks/fix-note/fix-note.jsx +235 -0
  245. package/registry/blocks/fix-note/index.js +3 -0
  246. package/registry/blocks/flow-canvas/README.md +15 -8
  247. package/registry/blocks/flow-editor/README.md +108 -0
  248. package/registry/blocks/flow-editor/block.json +44 -0
  249. package/registry/blocks/flow-editor/demo.jsx +115 -0
  250. package/registry/blocks/flow-editor/flow-canvas.jsx +568 -0
  251. package/registry/blocks/flow-editor/flow-editor.jsx +154 -0
  252. package/registry/blocks/flow-editor/index.js +6 -0
  253. package/registry/blocks/flow-editor/minimap.jsx +78 -0
  254. package/registry/blocks/flow-editor/outline.jsx +134 -0
  255. package/registry/blocks/footer/README.md +3 -2
  256. package/registry/blocks/library-shelf/README.md +117 -0
  257. package/registry/blocks/library-shelf/block.json +28 -0
  258. package/registry/blocks/library-shelf/demo.jsx +68 -0
  259. package/registry/blocks/library-shelf/index.js +3 -0
  260. package/registry/blocks/library-shelf/library-shelf.jsx +211 -0
  261. package/registry/blocks/library-shelf/probe-rules.js +15 -0
  262. package/registry/blocks/margin-notes/README.md +222 -0
  263. package/registry/blocks/margin-notes/block.json +10 -0
  264. package/registry/blocks/margin-notes/demo.jsx +144 -0
  265. package/registry/blocks/margin-notes/index.js +20 -0
  266. package/registry/blocks/margin-notes/margin-model.js +224 -0
  267. package/registry/blocks/margin-notes/margin-notes.jsx +465 -0
  268. package/registry/blocks/mind-map/README.md +4 -2
  269. package/registry/blocks/needs-you/README.md +177 -0
  270. package/registry/blocks/needs-you/block.json +34 -0
  271. package/registry/blocks/needs-you/demo.jsx +143 -0
  272. package/registry/blocks/needs-you/index.js +3 -0
  273. package/registry/blocks/needs-you/needs-you.jsx +441 -0
  274. package/registry/blocks/org-chart/README.md +4 -2
  275. package/registry/blocks/pace-bar/README.md +230 -0
  276. package/registry/blocks/pace-bar/block.json +10 -0
  277. package/registry/blocks/pace-bar/demo.jsx +255 -0
  278. package/registry/blocks/pace-bar/index.js +22 -0
  279. package/registry/blocks/pace-bar/pace-bar.jsx +461 -0
  280. package/registry/blocks/pace-bar/pace-model.js +288 -0
  281. package/registry/blocks/page-builder/README.md +1 -1
  282. package/registry/blocks/page-builder/demo.jsx +3 -3
  283. package/registry/blocks/pdf-viewer/README.md +4 -1
  284. package/registry/blocks/record-grid/README.md +100 -0
  285. package/registry/blocks/record-grid/block.json +28 -0
  286. package/registry/blocks/record-grid/demo.jsx +43 -0
  287. package/registry/blocks/record-grid/index.js +3 -0
  288. package/registry/blocks/record-grid/record-grid.jsx +251 -0
  289. package/registry/blocks/run-history/README.md +138 -0
  290. package/registry/blocks/run-history/block.json +10 -0
  291. package/registry/blocks/run-history/demo.jsx +74 -0
  292. package/registry/blocks/run-history/index.js +3 -0
  293. package/registry/blocks/run-history/run-history.jsx +218 -0
  294. package/registry/blocks/run-view/README.md +174 -0
  295. package/registry/blocks/run-view/block.json +50 -0
  296. package/registry/blocks/run-view/demo.jsx +286 -0
  297. package/registry/blocks/run-view/index.js +5 -0
  298. package/registry/blocks/run-view/run-controls.jsx +266 -0
  299. package/registry/blocks/run-view/run-view.jsx +236 -0
  300. package/registry/blocks/run-view/runs-list.jsx +99 -0
  301. package/registry/blocks/schedule-card/README.md +121 -0
  302. package/registry/blocks/schedule-card/block.json +10 -0
  303. package/registry/blocks/schedule-card/demo.jsx +82 -0
  304. package/registry/blocks/schedule-card/index.js +3 -0
  305. package/registry/blocks/schedule-card/schedule-card.jsx +136 -0
  306. package/registry/blocks/schedule-engine/README.md +137 -0
  307. package/registry/blocks/schedule-engine/block.json +10 -0
  308. package/registry/blocks/schedule-engine/demo.jsx +82 -0
  309. package/registry/blocks/schedule-engine/engine.js +769 -0
  310. package/registry/blocks/schedule-engine/index.js +9 -0
  311. package/registry/blocks/schedule-engine/ops.js +71 -0
  312. package/registry/blocks/schedule-engine/place.js +294 -0
  313. package/registry/blocks/schedule-engine/tree.js +79 -0
  314. package/registry/blocks/schedule-engine/waits.js +80 -0
  315. package/registry/blocks/schedule-gantt/README.md +159 -0
  316. package/registry/blocks/schedule-gantt/block.json +10 -0
  317. package/registry/blocks/schedule-gantt/demo.jsx +223 -0
  318. package/registry/blocks/schedule-gantt/gantt-model.js +219 -0
  319. package/registry/blocks/schedule-gantt/geometry.js +176 -0
  320. package/registry/blocks/schedule-gantt/index.js +26 -0
  321. package/registry/blocks/schedule-gantt/schedule-gantt.jsx +882 -0
  322. package/registry/blocks/schedule-gantt/sheet.jsx +372 -0
  323. package/registry/blocks/schedule-gantt/track.jsx +190 -0
  324. package/registry/blocks/schedule-ruler/README.md +148 -0
  325. package/registry/blocks/schedule-ruler/block.json +10 -0
  326. package/registry/blocks/schedule-ruler/demo.jsx +181 -0
  327. package/registry/blocks/schedule-ruler/index.js +21 -0
  328. package/registry/blocks/schedule-ruler/ruler-model.js +239 -0
  329. package/registry/blocks/schedule-ruler/schedule-ruler.jsx +548 -0
  330. package/registry/blocks/sidebar/README.md +23 -13
  331. package/registry/blocks/sketch-canvas/README.md +15 -9
  332. package/registry/blocks/source-receipt/README.md +97 -0
  333. package/registry/blocks/source-receipt/block.json +30 -0
  334. package/registry/blocks/source-receipt/demo.jsx +71 -0
  335. package/registry/blocks/source-receipt/index.js +3 -0
  336. package/registry/blocks/source-receipt/source-receipt.jsx +107 -0
  337. package/registry/blocks/spreadsheet/spreadsheet.jsx +15 -1
  338. package/registry/blocks/step-picker/README.md +78 -0
  339. package/registry/blocks/step-picker/block.json +31 -0
  340. package/registry/blocks/step-picker/demo.jsx +23 -0
  341. package/registry/blocks/step-picker/index.js +3 -0
  342. package/registry/blocks/step-picker/step-picker.jsx +144 -0
  343. package/registry/blocks/step-settings/README.md +100 -0
  344. package/registry/blocks/step-settings/block.json +44 -0
  345. package/registry/blocks/step-settings/demo.jsx +93 -0
  346. package/registry/blocks/step-settings/fields.jsx +58 -0
  347. package/registry/blocks/step-settings/flow-settings.jsx +115 -0
  348. package/registry/blocks/step-settings/index.js +5 -0
  349. package/registry/blocks/step-settings/step-settings.jsx +290 -0
  350. package/registry/blocks/suggestion/README.md +171 -0
  351. package/registry/blocks/suggestion/block.json +40 -0
  352. package/registry/blocks/suggestion/demo.jsx +91 -0
  353. package/registry/blocks/suggestion/index.js +3 -0
  354. package/registry/blocks/suggestion/suggestion.jsx +436 -0
  355. package/registry/blocks/sum-bar/README.md +122 -0
  356. package/registry/blocks/sum-bar/block.json +10 -0
  357. package/registry/blocks/sum-bar/demo.jsx +110 -0
  358. package/registry/blocks/sum-bar/index.js +5 -0
  359. package/registry/blocks/sum-bar/sum-bar.jsx +209 -0
  360. package/registry/blocks/sum-bar/sum-model.js +126 -0
  361. package/registry/blocks/tier-map/README.md +217 -0
  362. package/registry/blocks/tier-map/block.json +10 -0
  363. package/registry/blocks/tier-map/demo.jsx +222 -0
  364. package/registry/blocks/tier-map/index.js +24 -0
  365. package/registry/blocks/tier-map/tier-map.jsx +574 -0
  366. package/registry/blocks/tier-map/tier-model.js +318 -0
  367. package/registry/blocks/time-grid/README.md +305 -0
  368. package/registry/blocks/time-grid/block.json +10 -0
  369. package/registry/blocks/time-grid/demo.jsx +279 -0
  370. package/registry/blocks/time-grid/grid-model.js +322 -0
  371. package/registry/blocks/time-grid/index.js +25 -0
  372. package/registry/blocks/time-grid/time-grid.jsx +1325 -0
  373. package/registry/blocks/time-strip/README.md +141 -0
  374. package/registry/blocks/time-strip/block.json +10 -0
  375. package/registry/blocks/time-strip/demo.jsx +129 -0
  376. package/registry/blocks/time-strip/index.js +19 -0
  377. package/registry/blocks/time-strip/strip-model.js +178 -0
  378. package/registry/blocks/time-strip/time-strip.jsx +260 -0
  379. package/registry/blocks/top-bar/README.md +149 -0
  380. package/registry/blocks/top-bar/block.json +35 -0
  381. package/registry/blocks/top-bar/demo.jsx +78 -0
  382. package/registry/blocks/top-bar/index.js +5 -0
  383. package/registry/blocks/top-bar/probe-rules.js +74 -0
  384. package/registry/blocks/top-bar/top-bar-model.js +152 -0
  385. package/registry/blocks/top-bar/top-bar.jsx +244 -0
  386. package/registry/blocks/trigger-picker/README.md +98 -0
  387. package/registry/blocks/trigger-picker/block.json +38 -0
  388. package/registry/blocks/trigger-picker/demo.jsx +59 -0
  389. package/registry/blocks/trigger-picker/index.js +3 -0
  390. package/registry/blocks/trigger-picker/trigger-picker.jsx +374 -0
  391. package/registry/blocks/whiteboard/README.md +20 -14
  392. package/registry/blocks/workflow-view/README.md +123 -0
  393. package/registry/blocks/workflow-view/block.json +10 -0
  394. package/registry/blocks/workflow-view/demo.jsx +90 -0
  395. package/registry/blocks/workflow-view/index.js +3 -0
  396. package/registry/blocks/workflow-view/workflow-view.jsx +203 -0
  397. package/src/cli.mjs +2 -2
  398. package/src/install.mjs +100 -22
  399. package/src/registry.mjs +37 -3
  400. package/templates/next/browser/lib/data.js +6 -1
  401. package/templates/next/files/README.md +909 -26
  402. package/templates/next/files/app/shell.jsx +54 -37
  403. package/templates/next/files/check/automation-format.ts +2103 -0
  404. package/templates/next/files/check/automation-package.ts +142 -0
  405. package/templates/next/files/check/run.mjs +1043 -12
  406. package/templates/next/files/components/ui/field.jsx +17 -4
  407. package/templates/next/files/components/ui/icon.jsx +13 -0
  408. package/templates/next/files/components/ui/index.js +3 -1
  409. package/templates/next/files/components/ui/layout.jsx +382 -0
  410. package/templates/next/files/components/ui/notice.jsx +70 -0
  411. package/templates/next/files/components/ui/page.jsx +124 -0
  412. package/templates/next/files/components/ui/stat.jsx +22 -7
  413. package/templates/next/files/components/ui/toast.jsx +6 -3
  414. package/templates/next/files/lib/automations.js +660 -0
  415. package/templates/next/files/lib/claw.js +244 -5
  416. package/templates/next/files/lib/door.js +92 -0
  417. package/templates/next/files/lib/hooks.js +1694 -6
  418. package/templates/next/files/lib/platform.js +611 -14
  419. package/templates/next/template.json +22 -3
@@ -18,6 +18,33 @@
18
18
  // screen, long after the build (brain). Number 10 is browser mode's rule
19
19
  // (claw's STATIC-SPEC §3): a browser app is files and a door, and the
20
20
  // build refuses its server code late and unclearly, or not at all.
21
+ // Number 12 is the shell that grew with its content: a footer and the
22
+ // buttons after a long article sat past the window's end (viewport).
23
+ // Number 13 (llm, 0.8.0) keeps a model call on llm(): a screen that
24
+ // fetched the model door by its path got none of what the function does.
25
+ // Number 14 (library, 0.8.1) does the same for the Library's door.
26
+ // Number 15 (actions, 0.8.1) keeps every write a screen makes a declared
27
+ // action, the one its agent calls too: the CRM's agent saved fifty people
28
+ // as one note, because it had no other way to make them.
29
+ // Number 16 (squeeze, 0.8.1) refuses a wrapping flex row that holds a
30
+ // growing text cell (min-w-0 flex-1): it squeezed to a word a line in the
31
+ // CRM's list header and its connect card.
32
+ // Number 17 (resize, 0.8.1) keeps a ResizeObserver from changing what it
33
+ // watches inside its own delivery: the CRM's People screen reported "loop
34
+ // completed with undelivered notifications" and every report began a repair.
35
+ // Number 18 (automations, 0.8.1) asks the platform's own validator about the
36
+ // automations an app ships (makersclaw.json `automations`), so a flow the
37
+ // platform would refuse at install is refused here, at build.
38
+ // Number 19 (stretch, 0.8.1) keeps a row of ActionCards one height, their
39
+ // buttons on one line (UI review X6). Number 20 (meter, 0.8.1) wants
40
+ // `compact` on a labelled Meter in a box under 8rem (X1). Number 21
41
+ // (landed, 0.8.1) wants a page that opens a row it just made to wait for
42
+ // the write (X11). Number 25 (agentfirst, 0.8.1) keeps a "have the agent do it"
43
+ // button opening the agent with its line, never a form first (X9). Number 26
44
+ // (barlabel, 0.8.1) wants an icon on a top-bar action whose words are a
45
+ // BarText: under 32rem the words give way, and an icon-less button is empty.
46
+ // Number 27 (sidelong, 0.8.1) keeps a side column short: a PageLayout `side`
47
+ // or an <aside> that holds a form or a long list is refused (X13).
21
48
  //
22
49
  // npm run check source rules, then smoke
23
50
  // npm run check -- --no-build source rules only: no build, no smoke
@@ -73,7 +100,7 @@ import { createServer as createHttpServer } from "node:http";
73
100
  import { createServer } from "node:net";
74
101
  import { basename, dirname, join, posix, relative, sep } from "node:path";
75
102
  import { homedir, tmpdir } from "node:os";
76
- import { fileURLToPath } from "node:url";
103
+ import { fileURLToPath, pathToFileURL } from "node:url";
77
104
 
78
105
  // The app dir: this file's parent, unless the platform runs a canonical
79
106
  // copy of it from elsewhere against an app (CLAW_APP_DIR).
@@ -94,7 +121,7 @@ const ACTIONS_ONLY = process.argv.includes("--actions");
94
121
  // .jsx (a components/ui/depiction.js beside depiction.jsx won); Next 16's
95
122
  // Turbopack tries .jsx before .js (seen on 16.3.7), so the kit's own file
96
123
  // wins there, but the rule holds for every bundler.
97
- const KIT = /^components\/(?:ui\/(?:cx|index)\.js|ui\/(?:radio|toggle|button-group|avatar|breadcrumb|pagination|button|collapsible|item|table|depiction|feedback|stat|distribution|swatch|steps|separator|scroll-area|field|icon|menu|context-menu|popover|calendar|date-picker|tooltip|notice|sheet|tabs|slider|input-group|image|file-input|input-otp|command|combobox|rating|toast|carousel|resizable)\.jsx|parts\/(?:index|sample)\.js|parts\/(?:fixture|record-switcher|live-work)\.jsx)$/;
124
+ const KIT = /^components\/(?:ui\/(?:cx|index)\.js|ui\/(?:radio|toggle|button-group|avatar|breadcrumb|pagination|button|collapsible|item|table|depiction|feedback|stat|distribution|swatch|steps|separator|scroll-area|layout|page|field|icon|menu|context-menu|popover|calendar|date-picker|tooltip|notice|sheet|tabs|slider|input-group|image|file-input|input-otp|command|combobox|rating|toast|carousel|resizable)\.jsx|parts\/(?:index|sample)\.js|parts\/(?:fixture|record-switcher|live-work)\.jsx)$/;
98
125
 
99
126
  // The pins: sha256 of every fixed file, written by `makersclaw init` and
100
127
  // `makersclaw add` (check/pins.json). Read first, because a BLOCK is
@@ -228,13 +255,20 @@ const COLOUR_PREFIXES = "bg|text|border(?:-[xytrbles])?|ring(?:-offset)?|inset-r
228
255
 
229
256
  /** Source without its comments, newlines kept so line numbers hold: a
230
257
  * rule reads code, not the prose beside it (a header that says
231
- * "window." or names <a> is not browser access or a link). */
232
- function stripComments(text) {
258
+ * "window." or names <a> is not browser access or a link). A line comment
259
+ * is a `//` that follows anything but a `:` or a backslash, which also
260
+ * blanks a string that opens with two slashes ("//llm") or holds a
261
+ * doubled one ("/_claw//llm") to the end of its line. `slashStrings`
262
+ * reads those as strings: a `//` is a comment only at the start of a line
263
+ * or after a space or one of ; , ( ) { } [ ]. For a rule that reads a
264
+ * URL's slashes (12, llm). */
265
+ function stripComments(text, { slashStrings = false } = {}) {
233
266
  const blank = (m) => m.replace(/[^\n]/g, " ");
267
+ const line = slashStrings ? /(^|[\s;,(){}[\]])\/\/[^\n]*/g : /(^|[^:\\])\/\/[^\n]*/g;
234
268
  return text
235
269
  .replace(/\{\/\*[\s\S]*?\*\/\}/g, blank)
236
270
  .replace(/\/\*[\s\S]*?\*\//g, blank)
237
- .replace(/(^|[^:\\])\/\/[^\n]*/g, (m, lead) => lead + blank(m.slice(lead.length)));
271
+ .replace(line, (m, lead) => lead + blank(m.slice(lead.length)));
238
272
  }
239
273
  /** JSX attributes up to the tag's `>`: strings and balanced braces may hold a `>` or `=>`. */
240
274
  const ATTRS = String.raw`(?:"[^"]*"|'[^']*'|\{(?:[^{}]|\{[^{}]*\})*\}|[^>"'{])*`;
@@ -251,6 +285,204 @@ const MANIFEST = (() => {
251
285
  }
252
286
  })();
253
287
 
288
+ // The app's declared actions (makersclaw.json `actions`, 0.8.1; claw's
289
+ // docs/APP-TOOLS-PLAN.md D2): the writes its screens make, which its agent
290
+ // calls as tools through the platform's one runner. Read once for
291
+ // `--actions`, rule 8 and rule 15. Once an app declares any, its agent can
292
+ // write no other way, so every write a screen makes must be one (rule 15).
293
+ const DECLARED = (() => {
294
+ const list = MANIFEST.manifest?.actions;
295
+ return Array.isArray(list) ? list.filter((a) => a && typeof a === "object" && !Array.isArray(a) && typeof a.name === "string") : [];
296
+ })();
297
+ const DECLARED_NAMES = new Set(DECLARED.map((a) => a.name));
298
+
299
+ /**
300
+ * The refusals of a declaration, each naming its place: [] when it holds.
301
+ * The same rules as claw's shared/src/app-actions.ts validateActions, so
302
+ * what passes here passes the platform at ship (keep the two together).
303
+ */
304
+ function actionRefusals(actions, brainDecl, mode) {
305
+ if (actions === undefined || actions === null) return [];
306
+ if (!Array.isArray(actions)) return ["actions must be a list"];
307
+ const NAME = /^[a-z][a-z0-9_]{0,39}$/;
308
+ const FN = /^[a-z_][a-z0-9_]{0,62}$/;
309
+ const PROP = /^[a-zA-Z_][a-zA-Z0-9_]{0,63}$/;
310
+ const KEYWORDS = new Set(["type", "description", "title", "properties", "required", "items", "enum", "minItems", "maxItems", "minLength", "maxLength", "minimum", "maximum", "additionalProperties", "format", "default", "examples"]);
311
+ const TYPES = new Set(["object", "array", "string", "number", "integer", "boolean", "null"]);
312
+ const isObj = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
313
+ const out = [];
314
+ if (actions.length > 60) out.push(`actions: at most 60; this app declares ${actions.length}`);
315
+ const b = isObj(brainDecl) ? brainDecl : {};
316
+ const writes = new Set(Array.isArray(b.writes) ? b.writes.filter((w) => typeof w === "string") : []);
317
+ const defs = new Map([["note", { fields: ["title", "body", "imported_from"], link_types: [] }]]);
318
+ for (const d of Array.isArray(b.kinds) ? b.kinds : [])
319
+ if (isObj(d) && typeof d.kind === "string" && Array.isArray(d.fields))
320
+ defs.set(d.kind, { fields: d.fields.filter((f) => isObj(f) && typeof f.name === "string" && f.retired !== true).map((f) => f.name), link_types: Array.isArray(d.link_types) ? d.link_types.filter(isObj) : [] });
321
+ const schema = (sch, at, depth = 0) => {
322
+ if (!isObj(sch)) return [`${at}: a schema is an object`];
323
+ if (depth > 6) return [`${at}: nested deeper than six levels`];
324
+ const r = [];
325
+ for (const k of Object.keys(sch)) if (!KEYWORDS.has(k)) r.push(`${at}: "${k}" is not a schema keyword the platform checks`);
326
+ if (!TYPES.has(sch.type)) return [...r, `${at}: type must be one of ${[...TYPES].join(", ")}`];
327
+ if (sch.type === "object" && sch.properties !== undefined) {
328
+ if (!isObj(sch.properties)) r.push(`${at}: properties must be an object`);
329
+ else for (const [n, sub] of Object.entries(sch.properties)) {
330
+ if (!PROP.test(n)) r.push(`${at}.${n}: a property is a plain name`);
331
+ r.push(...schema(sub, `${at}.${n}`, depth + 1));
332
+ }
333
+ }
334
+ if (sch.type === "object" && sch.required !== undefined)
335
+ for (const q of Array.isArray(sch.required) ? sch.required : [null])
336
+ if (typeof q !== "string" || !isObj(sch.properties) || !(q in sch.properties)) r.push(`${at}: required ${q} is not one of its properties`);
337
+ if (sch.type === "array") r.push(...(sch.items === undefined ? [`${at}: an array says its items`] : schema(sch.items, `${at}[]`, depth + 1)));
338
+ return r;
339
+ };
340
+ const prop = (sch, n) => (isObj(sch) && isObj(sch.properties) && isObj(sch.properties[n]) ? sch.properties[n] : null);
341
+ const WORDS = `a source is "key", "$.key", { "value": … } or { "now": true }`;
342
+ // `edit`: the write names its record (id), so { "value": null } clears.
343
+ const source = (src, at, item, input, edit = false) => {
344
+ if (isObj(src)) {
345
+ const keys = Object.keys(src);
346
+ if (keys.length === 1 && keys[0] === "now") return src.now === true ? null : `${at}: { "now": true } is the time of the call`;
347
+ if (keys.length !== 1 || keys[0] !== "value") return `${at}: ${WORDS}`;
348
+ if (src.value === null) return edit ? null : `${at}: { "value": null } clears a field, only in a write that names its record (id)`;
349
+ return ["string", "number", "boolean"].includes(typeof src.value) ? null : `${at}: a constant is text, a number, true/false, or null to clear`;
350
+ }
351
+ if (typeof src !== "string" || !src) return `${at}: ${WORDS}`;
352
+ const top = src.startsWith("$.");
353
+ return prop(top ? input : item, top ? src.slice(2) : src) ? null : `${at}: ${src} is not a property of the ${top || item === input ? "input" : "item"}`;
354
+ };
355
+ // An arg a write reads by name (id, if_version, clear): its schema and whether it is required.
356
+ const arg = (src, at, item, input) => {
357
+ if (typeof src !== "string" || !src) return `${at}: names an arg, "key" or "$.key"`;
358
+ const top = src.startsWith("$.");
359
+ const from = top ? input : item;
360
+ const key = top ? src.slice(2) : src;
361
+ const sch = prop(from, key);
362
+ if (!sch) return `${at}: ${src} is not a property of the ${top || item === input ? "input" : "item"}`;
363
+ return { sch, required: isObj(from) && Array.isArray(from.required) && from.required.includes(key) };
364
+ };
365
+ const seen = new Set();
366
+ actions.forEach((a, i) => {
367
+ const at = `actions[${i}]${isObj(a) && typeof a.name === "string" ? ` (${a.name})` : ""}`;
368
+ if (!isObj(a)) return out.push(`${at}: an action is an object`);
369
+ for (const k of Object.keys(a)) if (!["name", "description", "input", "brain", "sql", "agent"].includes(k)) out.push(`${at}: unknown key "${k}"`);
370
+ if (!NAME.test(a.name ?? "")) out.push(`${at}: name is lower case, a-z, 0-9 and _, starting with a letter, at most 40`);
371
+ else if (seen.has(a.name)) out.push(`${at}: ${a.name} is declared twice`);
372
+ seen.add(a.name);
373
+ if (typeof a.description !== "string" || !a.description.trim()) out.push(`${at}: needs a description, one line of what it does`);
374
+ else if (a.description.length > 300 || a.description.includes("\n")) out.push(`${at}: the description is one line of at most 300 characters`);
375
+ if (a.agent !== undefined && typeof a.agent !== "boolean") out.push(`${at}: agent is true or false`);
376
+ const input = a.input;
377
+ if (!isObj(input) || input.type !== "object") out.push(`${at}: input is a JSON Schema of type object`);
378
+ else {
379
+ out.push(...schema(input, `${at}.input`));
380
+ if (JSON.stringify(input).length > 12000) out.push(`${at}: input is over 12000 characters of JSON; shorten its descriptions`);
381
+ }
382
+ if (a.brain === undefined && a.sql === undefined) out.push(`${at}: needs brain, sql, or both: an action does something`);
383
+ if (a.sql !== undefined && (typeof a.sql !== "string" || !FN.test(a.sql))) out.push(`${at}: sql names one function of the app's schema, lower case`);
384
+ if (a.brain === undefined) return;
385
+ const br = a.brain;
386
+ if (!isObj(br)) return out.push(`${at}.brain: an object with writes, and each and links when needed`);
387
+ if (mode === "server") out.push(`${at}.brain: company brain writes run only in a browser app ("mode": "browser"), through the platform's door`);
388
+ for (const k of Object.keys(br)) if (!["each", "writes", "links"].includes(k)) out.push(`${at}.brain: unknown key "${k}"`);
389
+ if (prop(input, "records")) out.push(`${at}.input: records is the platform's: it hands the written records to the sql function under that name`);
390
+ let item = input;
391
+ let per = 1;
392
+ if (br.each !== undefined) {
393
+ const each = typeof br.each === "string" ? prop(input, br.each) : null;
394
+ if (!each || each.type !== "array" || !isObj(each.items) || each.items.type !== "object") out.push(`${at}.brain.each: names an input property that is an array of objects`);
395
+ else {
396
+ item = each.items;
397
+ if (typeof each.maxItems !== "number" || each.maxItems > 100) out.push(`${at}.brain.each: ${br.each} needs a maxItems of at most 100`);
398
+ else per = each.maxItems;
399
+ if (!(Array.isArray(input.required) && input.required.includes(br.each))) out.push(`${at}.brain.each: ${br.each} must be required`);
400
+ }
401
+ }
402
+ const steps = br.writes === undefined ? [] : br.writes;
403
+ if (!Array.isArray(steps) || steps.length > 10 || (steps.length === 0 && !(Array.isArray(br.links) && br.links.length)))
404
+ return out.push(`${at}.brain.writes: a list of 1 to 10 writes (none only when the action has links)`);
405
+ if (steps.length * per > 500) out.push(`${at}.brain: up to ${steps.length * per} writes a call; at most 500`);
406
+ const kindOf = new Map();
407
+ steps.forEach((w, j) => {
408
+ const wat = `${at}.brain.writes[${j}]`;
409
+ if (!isObj(w)) return out.push(`${wat}: a write is an object with as, kind and fields`);
410
+ for (const k of Object.keys(w)) if (!["as", "kind", "fields", "when", "id", "if_version", "clear"].includes(k)) out.push(`${wat}: unknown key "${k}"`);
411
+ if (typeof w.as !== "string" || !NAME.test(w.as)) out.push(`${wat}: as is a lower-case name the links and records use`);
412
+ else if (kindOf.has(w.as)) out.push(`${wat}: as ${w.as} is used twice`);
413
+ if (typeof w.kind !== "string" || !w.kind) return out.push(`${wat}: kind names a company brain kind`);
414
+ if (typeof w.as === "string") kindOf.set(w.as, w.kind);
415
+ if (!writes.has("*") && !writes.has(w.kind)) out.push(`${wat}: the app does not declare ${w.kind} in brain.writes`);
416
+ const def = defs.get(w.kind);
417
+ if (!def) out.push(`${wat}: ${w.kind} has no def in brain.kinds`);
418
+ const edit = w.id !== undefined;
419
+ if (!isObj(w.fields) || !Object.keys(w.fields).length) out.push(`${wat}: fields maps the kind's fields to their sources`);
420
+ else for (const [f, src] of Object.entries(w.fields)) {
421
+ if (def && !def.fields.includes(f)) out.push(`${wat}.fields.${f}: not a field of ${w.kind}`);
422
+ const bad = source(src, `${wat}.fields.${f}`, item, input, edit);
423
+ if (bad) out.push(bad);
424
+ }
425
+ if (w.when !== undefined) {
426
+ const bad = source(w.when, `${wat}.when`, item, input);
427
+ if (bad) out.push(bad);
428
+ }
429
+ if (edit) {
430
+ const id = arg(w.id, `${wat}.id`, item, input);
431
+ if (typeof id === "string") out.push(id);
432
+ else {
433
+ if (id.sch.type !== "string") out.push(`${wat}.id: ${w.id} is a string, the record's id`);
434
+ if (!id.required && w.when !== w.id) out.push(`${wat}.id: ${w.id} must be required, or the write's when`);
435
+ }
436
+ }
437
+ if (w.if_version !== undefined) {
438
+ if (!edit) out.push(`${wat}.if_version: only in a write that names its record (id)`);
439
+ const v = arg(w.if_version, `${wat}.if_version`, item, input);
440
+ if (typeof v === "string") out.push(v);
441
+ else if (v.sch.type !== "integer" && v.sch.type !== "number") out.push(`${wat}.if_version: ${w.if_version} is a whole number, the version read`);
442
+ }
443
+ if (w.clear !== undefined) {
444
+ if (!edit) out.push(`${wat}.clear: only in a write that names its record (id)`);
445
+ const c = arg(w.clear, `${wat}.clear`, item, input);
446
+ if (typeof c === "string") out.push(c);
447
+ else {
448
+ const its = isObj(c.sch.items) ? c.sch.items : null;
449
+ const names = its && Array.isArray(its.enum) ? its.enum : null;
450
+ const mapped = isObj(w.fields) ? Object.keys(w.fields) : [];
451
+ if (c.sch.type !== "array" || !its || its.type !== "string" || !names) out.push(`${wat}.clear: ${w.clear} is an array of strings with an enum of the fields it may clear`);
452
+ else for (const n of names) if (typeof n !== "string" || !mapped.includes(n)) out.push(`${wat}.clear: ${JSON.stringify(n)} is not one of this write's fields`);
453
+ }
454
+ }
455
+ });
456
+ if (br.links === undefined) return;
457
+ if (!Array.isArray(br.links)) return out.push(`${at}.brain.links: a list`);
458
+ br.links.forEach((l, j) => {
459
+ const lat = `${at}.brain.links[${j}]`;
460
+ if (!isObj(l)) return out.push(`${lat}: a link is { from, to, type }`);
461
+ const end = (e, which) => {
462
+ if (typeof e === "string") {
463
+ if (!kindOf.has(e)) out.push(`${lat}.${which}: ${e} is no write's as`);
464
+ return kindOf.get(e) ?? null;
465
+ }
466
+ if (isObj(e) && Object.keys(e).length === 1 && "id" in e) {
467
+ const bad = source(e.id, `${lat}.${which}.id`, item, input);
468
+ if (bad) out.push(bad);
469
+ return null;
470
+ }
471
+ out.push(`${lat}.${which}: a write's as, or { "id": <source> }`);
472
+ return null;
473
+ };
474
+ for (const k of Object.keys(l)) if (!["from", "to", "type", "op"].includes(k)) out.push(`${lat}: unknown key "${k}"`);
475
+ const from = end(l.from, "from");
476
+ const to = end(l.to, "to");
477
+ if (l.op !== undefined && l.op !== "add" && l.op !== "remove") out.push(`${lat}: op is "add" or "remove"`);
478
+ if (typeof l.type !== "string" || !NAME.test(l.type)) out.push(`${lat}: type is a link type's name`);
479
+ else if (l.type !== "related" && from && !(defs.get(from)?.link_types ?? []).some((t) => t.type === l.type && t.from === from && (to === null || t.to === to)))
480
+ out.push(`${lat}: ${from} declares no link type ${l.type}${to ? ` to ${to}` : ""}`);
481
+ });
482
+ });
483
+ return out;
484
+ }
485
+
254
486
  /**
255
487
  * The tables and views the app's own schema makes, for rule 9's last
256
488
  * clause: { names, from }, or null where no list is here. The first
@@ -525,6 +757,10 @@ function objectProps(text) {
525
757
  // list.
526
758
  const ROW_WRITES = ["insertRow", "updateRow", "moveRow", "deleteRow", "saveSetting"];
527
759
  const DOOR_MODULES = ["lib/actions", "lib/door"];
760
+ /** The company brain's writes a screen can make (lib/brain.js), for rule
761
+ * 15: once an app declares actions, each is one of them. Erase, confirm
762
+ * and merge stay a person's press, and are not listed. */
763
+ const BRAIN_WRITES = ["writeRecord", "writeRecords", "linkRecords", "unlinkRecords", "createList", "changeList"];
528
764
  /** The one bare, lower-case name the data door calls (ACTION_NAME in lib/actions.js). */
529
765
  const ACTION_NAME = /^[a-z_][a-z0-9_]{0,62}$/;
530
766
  /** A name, escaped for a RegExp (`$` is legal in one). */
@@ -573,6 +809,7 @@ function writeCalls() {
573
809
  // them (`export { runAction as go } from "../lib/actions"`, `export *
574
810
  // from`), until no file adds one.
575
811
  const doors = new Map(DOOR_MODULES.map((m) => [m, all()]));
812
+ doors.set("lib/brain", new Map(BRAIN_WRITES.map((n) => [n, n])));
576
813
  for (let grew = true; grew; ) {
577
814
  grew = false;
578
815
  for (const f of jsFiles) {
@@ -601,6 +838,7 @@ function writeCalls() {
601
838
  const actions = [];
602
839
  const tables = [];
603
840
  const unread = [];
841
+ const brainWrites = [];
604
842
  for (const f of jsFiles) {
605
843
  const code = f.code;
606
844
  const local = all(); // a name in this file → the function it calls
@@ -659,6 +897,10 @@ function writeCalls() {
659
897
  };
660
898
  const entries = mounted.get(moduleOf(f.path));
661
899
  for (const { fn, open } of found.sort((a, b) => a.open - b.open)) {
900
+ if (BRAIN_WRITES.includes(fn)) {
901
+ brainWrites.push({ f, line: lineOf(code, open), fn });
902
+ continue;
903
+ }
662
904
  const start = open + 1 + /^\s*/.exec(code.slice(open + 1))[0].length;
663
905
  if (fn === "runAction" && /["'`]/.test(code[start] ?? "")) {
664
906
  const m = new RegExp(`^${lit(1)}`).exec(code.slice(start));
@@ -677,7 +919,7 @@ function writeCalls() {
677
919
  } else unread.push({ f, line, fn });
678
920
  }
679
921
  }
680
- return { actions, tables, unread };
922
+ return { actions, tables, unread, brainWrites };
681
923
  }
682
924
 
683
925
  // `--actions`: what the screens run and write, as JSON, and nothing else,
@@ -688,7 +930,20 @@ function writeCalls() {
688
930
  if (ACTIONS_ONLY) {
689
931
  const { actions, tables } = writeCalls();
690
932
  const names = (xs) => [...new Set(xs)].sort();
691
- const listed = { actions: names(actions.filter((a) => a.name !== null).map((a) => a.name)), tables: names(tables.map((t) => t.table)) };
933
+ // A declared action's name runs its `sql` function (none for one that
934
+ // only writes the company brain), and every declared function is listed,
935
+ // a screen's or the agent's alone: the ship checks each exists and keeps
936
+ // the live app's from a change build (0.8.1).
937
+ const fnsOf = (name) => {
938
+ const a = DECLARED.find((x) => x.name === name);
939
+ return a ? (typeof a.sql === "string" ? [a.sql] : []) : [name];
940
+ };
941
+ const declaredFns = DECLARED.map((a) => a.sql).filter((x) => typeof x === "string");
942
+ const listed = {
943
+ actions: names([...actions.filter((a) => a.name !== null).flatMap((a) => fnsOf(a.name)), ...declaredFns]),
944
+ tables: names(tables.map((t) => t.table)),
945
+ ...(DECLARED.length ? { declared: names([...DECLARED_NAMES]) } : {}),
946
+ };
692
947
  await new Promise((done) => process.stdout.write(`${JSON.stringify(listed)}\n`, done));
693
948
  process.exit(0);
694
949
  }
@@ -851,11 +1106,13 @@ const surfaceKeys = [];
851
1106
  // A browser app has no proxy.js either; rule 10 (browser) names both files, and why.
852
1107
  if (MANIFEST.manifest?.mode !== "browser" && /^(?:middleware|proxy)\.(?:js|jsx|mjs|cjs|ts|tsx|mts|cts)$/.test(e) && e !== "proxy.js") L.hit(e, "proxy", `Next 16 runs proxy.js, the template's fixed token check; delete ${e}`);
853
1108
  }
854
- // check/ is this script and its pins; a module hidden there is bundled
855
- // by Next and read by no rule.
1109
+ // check/ is this script, its pins and the two copies of the automation
1110
+ // format rule 18 runs (pinned like any fixed file); a module hidden there
1111
+ // is bundled by Next and read by no rule.
856
1112
  if (existsSync(join(ROOT, "check"))) {
857
1113
  for (const e of readdirSync(join(ROOT, "check"))) {
858
- if (e !== "run.mjs" && e !== "pins.json") L.hit(`check/${e}`, "extra", "check/ holds run.mjs and pins.json only; a module goes in components/ or lib/");
1114
+ const copy = (e === "automation-format.ts" || e === "automation-package.ts") && PINS !== null && Object.prototype.hasOwnProperty.call(PINS, `check/${e}`);
1115
+ if (e !== "run.mjs" && e !== "pins.json" && !copy) L.hit(`check/${e}`, "extra", "check/ holds run.mjs, pins.json and the automation format's two pinned copies only; a module goes in components/ or lib/");
859
1116
  }
860
1117
  }
861
1118
  for (const f of files) {
@@ -1250,12 +1507,17 @@ const surfaceKeys = [];
1250
1507
  if (a.name === null) detail = 'runAction\'s name is decided at run time: name the action in the code, so the brief can (runAction({ name: "close_cycle", args }))';
1251
1508
  else if (a.form === "string") detail = `runAction("${a.name}") runs nothing: it takes one object, runAction({ name: "${a.name}", args })`;
1252
1509
  else if (!ACTION_NAME.test(a.name)) detail = `runAction runs "${a.name}": an action is one bare, lower-case name (close_cycle), a function of the app's own schema, the one the door calls`;
1510
+ // A declared action is the agent's own tool (rule 15): its brief
1511
+ // says when, never the SQL.
1512
+ else if (DECLARED_NAMES.has(a.name)) continue;
1253
1513
  else if (!hasBrief) continue;
1254
1514
  else if (!brief.includes(`\`${a.name}\``)) detail = `runAction runs ${a.name}, which the agent's brief does not name: add \`${a.name}\` to ../references/actions.md with its call, \`select ${a.name}($1::jsonb)\` and its params, so the app's agent runs the same function`;
1255
1515
  else if (!selects(a.name)) detail = `the agent's brief names \`${a.name}\` but not its call: write \`select ${a.name}($1::jsonb)\` and its params there, so the app's agent runs the same function the screen does`;
1256
1516
  if (detail) hits.push({ f: a.f, line: a.line, what: `${a.form} action ${a.name}`, detail });
1257
1517
  }
1258
- if (hasBrief) {
1518
+ // An app that declares actions has no one-row writes left to name:
1519
+ // rule 15 makes each an action.
1520
+ if (hasBrief && !DECLARED.length) {
1259
1521
  for (const t of calls.tables) {
1260
1522
  if (!brief.includes(`\`${t.table}\``)) hits.push({ f: t.f, line: t.line, what: `table ${t.table}`, detail: `${t.fn} writes ${t.table}, which the agent's brief does not name: add \`${t.table}\` to ../references/actions.md with the one statement that does the same on it` });
1261
1523
  }
@@ -1871,6 +2133,770 @@ function columnFrom(code, at) {
1871
2133
  }
1872
2134
  }
1873
2135
 
2136
+ // 12. One viewport owner: the kit's AppLayout. Every design-first build
2137
+ // of 2026-10-07 shipped a shell of min-h-dvh that grew with its
2138
+ // content: the page scrolled, no pane scrolled on its own, and a
2139
+ // footer or a button after a 30-page article sat past the end of the
2140
+ // window, where nothing could reach it. So the app's code never sizes
2141
+ // anything by the window: no min-h-dvh, min-h-screen, h-screen, h-dvh
2142
+ // (nor their svh, lvh and max-h- kin), and no vh unit at all
2143
+ // (h-[80vh], max-h-[60vh], h-[calc(100vh-4rem)], style={{ height:
2144
+ // "100vh" }}). The kit is skipped: AppLayout in components/ui is the
2145
+ // one place the window's height is written. Depictions are read too:
2146
+ // a picture is drawn in px, never by the window.
2147
+ {
2148
+ const L = limiter("viewport");
2149
+ const DO =
2150
+ 'the window has one owner, AppLayout from components/ui (the shell\'s frame: the content scrolls inside it, contentMode="fill" for a canvas, an inspector beside it), and a page for print is DocumentLayout; inside it bound a box by its parent (h-full, flex-1 min-h-0) or by a size (max-h-96)';
2151
+ scan(/(?<![\w-])(?:min-|max-)?h-(?:screen|dvh|svh|lvh)(?![\w-])/, (f, m, line) => L.hit(`${f.path}:${line}`, m[0], `${m[0]} sizes by the window, so what follows a long page cannot be reached; ${DO}`), files);
2152
+ scan(/(?<![\w.])\d+(?:\.\d+)?[dsl]?vh(?![A-Za-z0-9_])/, (f, m, line) => {
2153
+ // The whole class or value it sits in: h-[calc(100vh-4rem)], "100vh".
2154
+ const from = f.code.slice(0, m.index).search(/[^\s"'`{}]*$/);
2155
+ const token = f.code.slice(from).match(/^[^\s"'`{}]*/)[0];
2156
+ L.hit(`${f.path}:${line}`, token, `${token} is a fraction of the window, so what follows it cannot be reached; ${DO}`);
2157
+ }, files);
2158
+ if (L.count > 4) fail("viewport", `${L.count - 4} more`, "more of the same: sizes taken from the window");
2159
+ if (!L.count) ok("viewport", "nothing in the app sizes itself by the window");
2160
+ }
2161
+
2162
+ // 13. A model is called through llm() (0.8.0, the platform's _claw/llm
2163
+ // door). A screen that wants one answer from a model (sort, draft,
2164
+ // translate) calls llm() from lib/platform.js, or useLlm() from
2165
+ // lib/hooks.js, and never reaches the door by its URL: the path is the
2166
+ // platform's to move, the two functions are the contract, and an
2167
+ // app that fetches its own copy skips what they do (nothing throws, a
2168
+ // claw from before the door comes back `unavailable`, an abort
2169
+ // answers `aborted`). So no string in the app's code names `_claw/llm`,
2170
+ // and none handed to claw(), clawUrl() or clawStream() starts `llm`.
2171
+ // "Names" is as the browser reads it: clawUrl() drops every leading
2172
+ // slash, and a `./` or a doubled slash in a URL is dropped on the way
2173
+ // (`claw("//llm")`, `claw("./llm")`, `/_claw/./llm` all reach the door;
2174
+ // it reads the code with `slashStrings` so a `//` inside a string is
2175
+ // not taken for a comment). It reads literals, so a path built from
2176
+ // pieces is not seen, as in rule 8. Every mode and every tables
2177
+ // setting: the door is the same for all.
2178
+ {
2179
+ const L = limiter("llm");
2180
+ const LLM_PATH = /_claw\/(?:\.?\/)*llm(?![\w-])/;
2181
+ const LLM_NAME = /^(?:\.?\/)*llm(?![\w-])/;
2182
+ for (const f of jsFiles) {
2183
+ const code = stripComments(f.text, { slashStrings: true });
2184
+ const toClaw = new Set();
2185
+ for (const m of code.matchAll(/(?<![\w$.])claw(?:Url|Stream)?\s*(?:\?\.\s*)?\(\s*(?=["'`])/g)) toClaw.add(m.index + m[0].length);
2186
+ for (const { start, end } of literalsOf(code)) {
2187
+ const inner = code.slice(start + 1, end);
2188
+ if (!(LLM_PATH.test(inner) || (toClaw.has(start) && LLM_NAME.test(inner)))) continue;
2189
+ const path = inner.replace(/\s+/g, " ").slice(0, 60);
2190
+ L.hit(`${f.path}:${lineOf(code, start)}`, "path", `"${path}": the model door is reached through llm(input, { system, schema }) in lib/platform.js, or useLlm() in lib/hooks.js, never by its path (README §5, Calling a model)`);
2191
+ }
2192
+ }
2193
+ if (L.count > 4) fail("llm", `${L.count - 4} more`, "strings that name the model door's path instead of calling llm()");
2194
+ if (!L.count) ok("llm", "no screen names _claw/llm: a model is called through llm() or useLlm() (README §5)");
2195
+ }
2196
+
2197
+ // 14. The Library is read through library() (0.8.1, the platform's
2198
+ // _claw/library door). A screen that shows what Makersclaw keeps
2199
+ // (decks, templates, a dataset) calls library.collections(),
2200
+ // library.items() and the rest from lib/platform.js, or the hooks
2201
+ // useLibraryItems() and kin from lib/hooks.js, and never reaches the
2202
+ // door by its URL: the path is the platform's to move, the functions
2203
+ // are the contract, and an app that fetches its own copy skips what
2204
+ // they do (nothing throws, a claw from before the door comes back
2205
+ // `unavailable`, an id that names nothing asks nothing, an abort
2206
+ // answers `aborted`). So no string in the app's code names
2207
+ // `_claw/library`, and none handed to claw(), clawUrl() or clawStream()
2208
+ // starts `library`, however the browser would read it (rule 13 says
2209
+ // how: `claw("//library")`, `claw("./library/items")` and
2210
+ // `/_claw/./library` are all the door). It reads literals, so a path
2211
+ // built from pieces is not seen, as in rules 8 and 13. Every mode and
2212
+ // every tables setting: the door is the same for all.
2213
+ {
2214
+ const L = limiter("library");
2215
+ const LIBRARY_PATH = /_claw\/(?:\.?\/)*library(?![\w-])/;
2216
+ const LIBRARY_NAME = /^(?:\.?\/)*library(?![\w-])/;
2217
+ for (const f of jsFiles) {
2218
+ const code = stripComments(f.text, { slashStrings: true });
2219
+ const toClaw = new Set();
2220
+ for (const m of code.matchAll(/(?<![\w$.])claw(?:Url|Stream)?\s*(?:\?\.\s*)?\(\s*(?=["'`])/g)) toClaw.add(m.index + m[0].length);
2221
+ for (const { start, end } of literalsOf(code)) {
2222
+ const inner = code.slice(start + 1, end);
2223
+ if (!(LIBRARY_PATH.test(inner) || (toClaw.has(start) && LIBRARY_NAME.test(inner)))) continue;
2224
+ const path = inner.replace(/\s+/g, " ").slice(0, 60);
2225
+ L.hit(`${f.path}:${lineOf(code, start)}`, "path", `"${path}": the Library door is reached through library.collections(), library.items(id) and kin in lib/platform.js, or useLibraryItems() and kin in lib/hooks.js, never by its path (README §5, Showing the Library)`);
2226
+ }
2227
+ }
2228
+ if (L.count > 4) fail("library", `${L.count - 4} more`, "strings that name the Library door's path instead of calling library()");
2229
+ if (!L.count) ok("library", "no screen names _claw/library: the Library is read through library() or its hooks (README §5)");
2230
+ }
2231
+
2232
+ // 15. Every write a screen makes is a declared action (0.8.1; claw's
2233
+ // docs/APP-TOOLS-PLAN.md D2, D6). makersclaw.json `actions` names
2234
+ // them: `name`, a one-line `description`, `input` (a JSON Schema
2235
+ // object), then `sql` (its function) and `brain` (the company brain's
2236
+ // writes the platform runs). The screens call each by its name with
2237
+ // runAction, and the app's agent calls the same name as a tool. Once
2238
+ // an app declares any, its agent can write no other way, so a write a
2239
+ // screen makes that is not one would be a thing only a person can do:
2240
+ // an undeclared runAction name, a one-row write (insertRow, updateRow,
2241
+ // moveRow, deleteRow, saveSetting) and a company brain write from
2242
+ // lib/brain.js (writeRecord(s), linkRecords, unlinkRecords, createList,
2243
+ // changeList) each fail. Erase, confirm and merge stay a person's
2244
+ // press. An app that declares none keeps rule 8 as it was.
2245
+ {
2246
+ const L = limiter("actions");
2247
+ const { manifest } = MANIFEST;
2248
+ const has = manifest && Object.prototype.hasOwnProperty.call(manifest, "actions");
2249
+ if (!has) ok("actions", "no actions declared in makersclaw.json: the app's agent writes as references/actions.md says");
2250
+ else {
2251
+ const refusals = actionRefusals(manifest.actions, manifest.brain, manifest.mode);
2252
+ for (const r of refusals.slice(0, 4)) fail("actions", "makersclaw.json", r);
2253
+ if (refusals.length > 4) fail("actions", "makersclaw.json", `${refusals.length - 4} more`);
2254
+ if (DECLARED.length) {
2255
+ const calls = writeCalls();
2256
+ const DECLARE = "declare it in makersclaw.json actions (its name, description, input, then sql or brain) and call it with runAction({ name, args }): once an app declares actions its agent writes no other way (README §5)";
2257
+ for (const a of calls.actions)
2258
+ if (a.name !== null && !DECLARED_NAMES.has(a.name)) L.hit(`${a.f.path}:${a.line}`, `action ${a.name}`, `runAction runs ${a.name}, which makersclaw.json does not declare: ${DECLARE}`);
2259
+ for (const t of [...calls.tables, ...calls.unread])
2260
+ L.hit(`${t.f.path}:${t.line}`, `${t.fn} ${t.table ?? ""}`, `${t.fn}${t.table ? ` writes ${t.table}` : ""}, a write the app's agent cannot make: make it a function of the app's schema and ${DECLARE}`);
2261
+ for (const b of calls.brainWrites)
2262
+ L.hit(`${b.f.path}:${b.line}`, b.fn, `${b.fn} writes the company brain from the screen, which the app's agent cannot do: give an action brain steps (writes, each, links) and ${DECLARE}`);
2263
+ if (L.count > 4) fail("actions", `${L.count - 4} more`, "writes the screens make that are not declared actions");
2264
+ }
2265
+ if (!refusals.length && !L.count)
2266
+ ok("actions", `${DECLARED.length} declared${DECLARED.length ? "; every write the screens make is one of them" : ""}`);
2267
+ }
2268
+ }
2269
+
2270
+ // 16. A row that wraps does not hold a text cell that grows (0.8.1; the
2271
+ // CRM's list header and its "Connect your mailbox" card, 2026-10-11).
2272
+ // `flex flex-wrap` with a child of `min-w-0 flex-1` looks right at a
2273
+ // wide window and is wrong at the width a real window has: flex-1 has
2274
+ // a basis of zero, so the text never wraps to a line of its own, it
2275
+ // squeezes to what the fixed pieces beside it leave, a word or two a
2276
+ // line, and the row grows tall around it. The founder saw it twice in
2277
+ // one app. So no direct child of a wrapping flex row both grows
2278
+ // (flex-1, grow) and may shrink to nothing (min-w-0) without a basis
2279
+ // of its own (basis-*, min-w-[…], w-full, w-N): stack the pieces
2280
+ // (grid gap-2, flex-col), or give the text a basis so it takes its own
2281
+ // line when it does not fit. It reads the JSX the way prettier lays it
2282
+ // out (a child is a line one indent in from its parent's opening tag,
2283
+ // className a string on that line), so a tag split across lines is not
2284
+ // seen; a cell that truncates (one line, cut short) is fine; the kit
2285
+ // and the blocks are skipped (pinned).
2286
+ {
2287
+ const L = limiter("squeeze");
2288
+ const classesOf = (line) => [...line.matchAll(/"([^"]*)"|`([^`$]*)`|'([^']*)'/g)].map((m) => m[1] ?? m[2] ?? m[3]).join(" ");
2289
+ const has = (cls, re) => new RegExp(`(?:^|\\s)(?:[\\w@\\[\\]/-]+:)*${re}(?![\\w-])`).test(cls);
2290
+ for (const f of appFiles.filter((x) => /\.(?:jsx|tsx)$/.test(x.path))) {
2291
+ const rows = f.code.split("\n");
2292
+ for (let i = 0; i < rows.length; i++) {
2293
+ const open = rows[i].match(/^(\s*)<(\w[\w.]*)\b[^>]*\bclassName=/);
2294
+ if (!open || /\/>\s*$/.test(rows[i])) continue;
2295
+ const cls = classesOf(rows[i]);
2296
+ if (!has(cls, "flex-wrap") || !(has(cls, "flex") || has(cls, "inline-flex")) || has(cls, "flex-col")) continue;
2297
+ const ind = open[1].length;
2298
+ for (let j = i + 1; j < rows.length; j++) {
2299
+ const lead = rows[j].match(/^(\s*)\S/);
2300
+ if (!lead) continue;
2301
+ if (lead[1].length <= ind) break;
2302
+ if (lead[1].length !== ind + 2 || !/^\s*<\w/.test(rows[j])) continue;
2303
+ const kid = classesOf(rows[j].slice(0, rows[j].indexOf(">") > 0 ? rows[j].indexOf(">") : undefined));
2304
+ if (!(has(kid, "flex-1") || has(kid, "grow")) || !has(kid, "min-w-0")) continue;
2305
+ if (has(kid, "truncate") || has(kid, "line-clamp-\\d")) continue; // one line, cut short: it never squeezes to a word a line
2306
+ if (has(kid, "basis-[^\\s]+") || has(kid, "min-w-\\[[^\\s]+") || has(kid, "w-full") || has(kid, "w-\\d[^\\s]*")) continue;
2307
+ L.hit(`${f.path}:${j + 1}`, "squeeze", `a text cell that grows (min-w-0 flex-1) sits in a flex-wrap row (line ${i + 1}) beside fixed pieces: flex-1 never wraps to a line of its own, it squeezes to a word or two a line. Stack them (grid gap-2, or flex-col), or give the text a basis so it wraps below (basis-64, min-w-[16rem])`);
2308
+ }
2309
+ }
2310
+ }
2311
+ if (L.count > 4) fail("squeeze", `${L.count - 4} more`, "wrapping rows with a growing text cell");
2312
+ if (!L.count) ok("squeeze", "no wrapping flex row holds a text cell that grows (min-w-0 flex-1)");
2313
+ }
2314
+
2315
+ // 17. A ResizeObserver hands its work to the next frame (0.8.1; the CRM's
2316
+ // People screen, 2026-10-11). A browser says "ResizeObserver loop
2317
+ // completed with undelivered notifications" when a callback changes the
2318
+ // size of something an observer watches before the frame is over:
2319
+ // setting a height, a width, or state that a synchronous render turns
2320
+ // into one. The page still works, and nobody can say which line did it,
2321
+ // which is why a repair of the app found nothing to change every time.
2322
+ // So in the app's own files a `new ResizeObserver(cb)` takes its
2323
+ // measurements in the callback and acts on them in a
2324
+ // requestAnimationFrame: the callback (or the function it names, in the
2325
+ // same file) must contain one. The kit and the blocks are pinned and
2326
+ // are not read; the template reports no such notice any more
2327
+ // (lib/claw.js drops it), so this rule is what keeps the cause out.
2328
+ {
2329
+ const L = limiter("resize");
2330
+ scan(/new\s+ResizeObserver\s*\(/, (file, m, at) => {
2331
+ // The argument: from the opening bracket to its match.
2332
+ const open = m.index + m[0].length;
2333
+ let depth = 1;
2334
+ let end = open;
2335
+ while (end < file.code.length && depth > 0) {
2336
+ const ch = file.code[end++];
2337
+ if (ch === "(") depth++;
2338
+ else if (ch === ")") depth--;
2339
+ }
2340
+ const arg = file.code.slice(open, end - 1);
2341
+ if (/requestAnimationFrame/.test(arg)) return;
2342
+ // A function named in the same file: read its body.
2343
+ const named = /^\s*([A-Za-z_$][\w$]*)\s*$/.exec(arg);
2344
+ if (named) {
2345
+ const def = new RegExp(`(?:const|let|function)\\s+${named[1].replace(/\$/g, "\\$")}\\b`).exec(file.code);
2346
+ if (def && /requestAnimationFrame/.test(file.code.slice(def.index, def.index + 900))) return;
2347
+ }
2348
+ L.hit(`${file.path}:${at}`, "resize", "a ResizeObserver whose callback acts at once: whatever it sets (a height, a width, state) can change what it watches inside its own delivery, and the browser reports a loop. Measure in the callback and act in requestAnimationFrame: `let raf = 0; new ResizeObserver(() => { cancelAnimationFrame(raf); raf = requestAnimationFrame(apply); })`");
2349
+ });
2350
+ if (L.count > 4) fail("resize", `${L.count - 4} more`, "ResizeObservers that act inside their own delivery");
2351
+ if (!L.count) ok("resize", "every ResizeObserver acts in the next frame");
2352
+ }
2353
+
2354
+ // 18. The automations an app ships hold to the platform's format (0.8.1;
2355
+ // claw's docs/AUTOMATIONS-PLAN.md phase 9). makersclaw.json `automations`
2356
+ // is a list of definitions (format 1), each with an optional
2357
+ // `replaces_wake`; the store ships the same list as seeds.automations.
2358
+ // The one validator is claw's validatePackageAutomations, which calls
2359
+ // validateAutomation: both are copied beside this file
2360
+ // (check/automation-format.ts, check/automation-package.ts; never
2361
+ // edited, refreshed by scripts/sync-automation-format.mjs in the
2362
+ // template repo) and run here as they are, so what passes here passes
2363
+ // the platform. An action step must name a declared action. They are
2364
+ // TypeScript with no runtime import, so node loads them as they are
2365
+ // from 22.18; on an older node the rule says so and is skipped, never
2366
+ // failed (the platform's own door still refuses the flow at install).
2367
+ // Wakes and kinds of account are not known to an app's own check: they
2368
+ // are the store's to check (it knows seeds.wakes and the connections).
2369
+ {
2370
+ const { manifest } = MANIFEST;
2371
+ const has = manifest && Object.prototype.hasOwnProperty.call(manifest, "automations") && manifest.automations !== undefined && manifest.automations !== null;
2372
+ if (!has) ok("automations", "no automations declared in makersclaw.json");
2373
+ else {
2374
+ const here = join(ROOT, "check");
2375
+ // Node says a .ts file with no "type" in package.json is reparsed as a
2376
+ // module, and (before 22.18) that type stripping is experimental. The
2377
+ // copies are ours and fixed: say nothing about how they are read.
2378
+ const emit = process.emitWarning;
2379
+ process.emitWarning = function (warning, ...rest) {
2380
+ const said = [warning, ...rest].map((x) => (typeof x === "string" ? x : (x?.code ?? x?.message ?? ""))).join(" ");
2381
+ if (/MODULE_TYPELESS_PACKAGE_JSON|Module type of file|Type Stripping/i.test(said)) return;
2382
+ return emit.call(this, warning, ...rest);
2383
+ };
2384
+ let loaded = null;
2385
+ let why = null;
2386
+ try {
2387
+ const fmt = await import(pathToFileURL(join(here, "automation-format.ts")).href);
2388
+ const pack = await import(pathToFileURL(join(here, "automation-package.ts")).href);
2389
+ if (typeof pack.validatePackageAutomations !== "function") throw new Error("check/automation-package.ts has no validatePackageAutomations");
2390
+ loaded = { fmt, pack };
2391
+ } catch (e) {
2392
+ why = String(e?.code ?? e?.message ?? e).split("\n")[0];
2393
+ }
2394
+ await new Promise((done) => setImmediate(done));
2395
+ process.emitWarning = emit;
2396
+ if (!loaded) {
2397
+ lines.push(`skip ${"automations".padEnd(10)} not checked: this node (${process.versions.node}) cannot read the format's TypeScript files (it needs 22.18 or newer): ${why}`);
2398
+ } else {
2399
+ const list = manifest.automations;
2400
+ const result = Array.isArray(list)
2401
+ ? loaded.pack.validatePackageAutomations(list, { actionNames: new Set(DECLARED_NAMES), wakes: null, categories: null }, loaded.fmt)
2402
+ : { ok: false, problems: [{ path: "automations", message: "automations is a list" }] };
2403
+ if (result.ok) {
2404
+ const from = /@ (\w+)\./.exec(readFileSync(join(here, "automation-format.ts"), "utf8").split("\n", 1)[0])?.[1];
2405
+ ok("automations", `${result.entries.length} shipped; each holds to the platform's format${from ? ` (claw ${from})` : ""}`);
2406
+ } else {
2407
+ const said = loaded.pack.packageProblemLines(result.problems, 8);
2408
+ for (const line of said) fail("automations", "makersclaw.json", line);
2409
+ }
2410
+ }
2411
+ }
2412
+ }
2413
+
2414
+ // 19. Cards in a row are not set to their own height (0.8.1; UI review X6,
2415
+ // Project Roadmap's four start cards, 2026-10-12). ActionCard stretches
2416
+ // to the height of its row and pins its status and its button to the
2417
+ // bottom, so a row of them ends on one line and the buttons line up.
2418
+ // `self-start` (or self-center, self-end, h-fit, h-auto) on a card, or
2419
+ // `items-start` (items-center, items-end) on the grid or flex row that
2420
+ // holds two or more of them, takes the stretch away again: each card is
2421
+ // then only as tall as its own words, the cards end at different
2422
+ // heights and their buttons sit at different heights. The CRM's fix of
2423
+ // 2026-10-11 had put `self-start` into the kit to stop an empty block
2424
+ // under a card; that block came from a squeezed row (rule 16), not from
2425
+ // the stretch. A card on its own beside something unrelated takes the
2426
+ // `fit` prop instead (`fit` beside other ActionCards is refused too). It
2427
+ // reads the JSX the way rule 16 does (a child is a line one indent in
2428
+ // from its parent's opening tag).
2429
+ {
2430
+ const L = limiter("stretch");
2431
+ const classesOf = (text) => [...text.matchAll(/"([^"]*)"|`([^`$]*)`|'([^']*)'/g)].map((m) => m[1] ?? m[2] ?? m[3]).join(" ");
2432
+ const has = (cls, re) => new RegExp(`(?:^|\\s)(?:[\\w@\\[\\]/-]+:)*!?${re}(?![\\w-])`).test(cls);
2433
+ const card = new RegExp(`<ActionCard\\b(${ATTRS})`, "g");
2434
+ for (const f of appFiles.filter((x) => /\.(?:jsx|tsx)$/.test(x.path))) {
2435
+ if (!/<ActionCard\b/.test(f.code)) continue;
2436
+ for (const m of f.code.matchAll(card)) {
2437
+ const own = classesOf(m[1]);
2438
+ if (has(own, "self-(?:start|center|end)") || has(own, "h-(?:fit|auto|min)"))
2439
+ L.hit(`${f.path}:${lineOf(f.code, m.index)}`, "self", "an ActionCard sized to its own words (self-start, h-fit…): in a row it ends shorter than its neighbours and its button sits higher. A card stretches to its row and pins its button to the bottom; take the class off. One card alone beside something unrelated takes the `fit` prop");
2440
+ }
2441
+ const rows = f.code.split("\n");
2442
+ for (let i = 0; i < rows.length; i++) {
2443
+ const open = rows[i].match(/^(\s*)<(\w[\w.]*)\b[^>]*\bclassName=/);
2444
+ if (!open || /\/>\s*$/.test(rows[i]) || /^\s*<ActionCard\b/.test(rows[i])) continue;
2445
+ const cls = classesOf(rows[i]);
2446
+ if (!(has(cls, "grid") || has(cls, "flex") || has(cls, "inline-flex")) || !has(cls, "items-(?:start|center|end)")) continue;
2447
+ const ind = open[1].length;
2448
+ let cards = 0;
2449
+ let mapped = false;
2450
+ for (let j = i + 1; j < rows.length; j++) {
2451
+ const lead = rows[j].match(/^(\s*)\S/);
2452
+ if (!lead) continue;
2453
+ if (lead[1].length <= ind) break;
2454
+ if (/\.map\(/.test(rows[j])) mapped = true;
2455
+ if (/<ActionCard\b/.test(rows[j])) cards += 1;
2456
+ }
2457
+ if (cards >= 2 || (mapped && cards >= 1)) {
2458
+ L.hit(`${f.path}:${i + 1}`, "items", "a row of ActionCards whose row sets items-start (items-center, items-end): the cards then end at different heights and their buttons do not line up. Leave the row's alignment alone: the cards stretch to the row and pin the button to the bottom");
2459
+ }
2460
+ }
2461
+ // `fit` on a card that has neighbours in the same parent.
2462
+ for (let i = 0; i < rows.length; i++) {
2463
+ if (!/<ActionCard\b[^>]*\sfit(?:[\s/>=]|$)/.test(rows[i])) continue;
2464
+ const ind = rows[i].match(/^(\s*)/)[1].length;
2465
+ let siblings = 0;
2466
+ for (const dir of [-1, 1]) {
2467
+ for (let j = i + dir; j >= 0 && j < rows.length; j += dir) {
2468
+ const lead = rows[j].match(/^(\s*)\S/);
2469
+ if (!lead) continue;
2470
+ if (lead[1].length < ind) break;
2471
+ if (lead[1].length === ind && /^\s*<ActionCard\b/.test(rows[j])) siblings += 1;
2472
+ }
2473
+ }
2474
+ if (siblings) L.hit(`${f.path}:${i + 1}`, "fit", "`fit` on an ActionCard that has ActionCard neighbours: in a row the cards stretch to one height. `fit` is for a card on its own");
2475
+ }
2476
+ }
2477
+ if (L.count > 4) fail("stretch", `${L.count - 4} more`, "ActionCards sized to their own words");
2478
+ if (!L.count) ok("stretch", "no ActionCard is sized to its own words, and no row of them sets items-start");
2479
+ }
2480
+
2481
+ // 20. A Meter in a narrow box is a compact Meter (0.8.1; UI review X1,
2482
+ // Newsletter Engine's top bar, 2026-10-12). A Meter draws its label and
2483
+ // its numbers as visible text above the bar. In a box under about 8rem
2484
+ // (`w-14`, `w-24`…) the label wraps to a second line and runs into the
2485
+ // button beside it ("15,000 cr left" over Find). `compact` draws the bar
2486
+ // alone; the label stays for a screen reader and as the tooltip. So a
2487
+ // `<Meter` with a `label` and a width of 8rem or less in its className
2488
+ // (w-N, w-[Nrem|Npx], max-w-N, size-N) and no `compact` is refused.
2489
+ {
2490
+ const L = limiter("meter");
2491
+ const meter = new RegExp(`<Meter\\b(${ATTRS})`, "g");
2492
+ const narrow = (cls) => {
2493
+ for (const m of cls.matchAll(/(?:^|\s)(?:[\w@[\]/-]+:)*!?(?:max-w|w|size)-(?:(\d+(?:\.\d+)?)|\[(\d+(?:\.\d+)?)(rem|px|em)\])(?![\w%/-])/g)) {
2494
+ const px = m[1] !== undefined ? Number(m[1]) * 4 : Number(m[2]) * (m[3] === "px" ? 1 : 16);
2495
+ if (px <= 128) return m[0].trim();
2496
+ }
2497
+ return null;
2498
+ };
2499
+ for (const f of appFiles.filter((x) => /\.(?:jsx|tsx)$/.test(x.path))) {
2500
+ for (const m of f.code.matchAll(meter)) {
2501
+ const attrs = m[1];
2502
+ if (!/(?:^|\s)label\s*=/.test(attrs) || /(?:^|\s)compact(?:[\s=/]|$)/.test(attrs)) continue;
2503
+ const cls = [...attrs.matchAll(/className\s*=\s*(?:"([^"]*)"|\{\s*(?:"([^"]*)"|`([^`$]*)`)\s*\})/g)].map((c) => c[1] ?? c[2] ?? c[3]).join(" ");
2504
+ const w = narrow(cls);
2505
+ if (w)
2506
+ L.hit(`${f.path}:${lineOf(f.code, m.index)}`, "meter", `a Meter with a label in a box ${w} wide: the label draws as visible text and spills into what is beside it. Add \`compact\` (the bar alone; the label stays as the tooltip and for a screen reader)`);
2507
+ }
2508
+ }
2509
+ if (L.count > 4) fail("meter", `${L.count - 4} more`, "labelled Meters in narrow boxes");
2510
+ if (!L.count) ok("meter", "no labelled Meter sits in a box under 8rem without compact");
2511
+ }
2512
+
2513
+ // 21. A page that opens what it just made waits for the write (0.8.1; UI
2514
+ // review X11, Project Health's "That project isn't here", 2026-10-12).
2515
+ // Apps read their whole world once and share it. A write answers, THEN
2516
+ // the world is read again; `router.push(?row=<new id>)` straight after
2517
+ // the write opens a record the world does not hold yet, and the page
2518
+ // says "not found" until the re-read lands. So a router.push/replace
2519
+ // whose address is built from the answer of an awaited call
2520
+ // (`const out = await addProject(…)` … `${out.id}`) needs the write
2521
+ // wrapped in afterWrite() (lib/data), or an `await whenLanded()` between
2522
+ // them. A read (loadRow, fetch…) is not a write and is not asked. The
2523
+ // other half is the record page's: it says "not found" through
2524
+ // useNotFound() (lib/data), which stays false while a write or a
2525
+ // re-read is out; the template's tests hold that half.
2526
+ {
2527
+ const L = limiter("landed");
2528
+ const READS = /\b(?:loadRows?|countRows|countBy|loadActivity|fetch|claw|loadOne|read[A-Z]\w*)\s*\(/;
2529
+ for (const f of appFiles.filter((x) => /\.(?:jsx?|tsx?)$/.test(x.path))) {
2530
+ const rows = f.code.split("\n");
2531
+ for (let i = 0; i < rows.length; i++) {
2532
+ const m = /\b(?:router|history)\.(?:push|replace)\s*\(|\bnavigate\s*\(/.exec(rows[i]);
2533
+ if (!m) continue;
2534
+ // The address: this line to the call's closing bracket (or 4 lines).
2535
+ const arg = rows.slice(i, i + 4).join(" ").slice(m.index);
2536
+ const vars = new Set([...arg.matchAll(/\b([A-Za-z_$][\w$]*)\??\.[A-Za-z_$]/g)].map((x) => x[1]));
2537
+ for (const v of vars) {
2538
+ if (/^(?:window|location|document|Math|JSON|Object|Number|String|Array|process|props|this)$/.test(v)) continue;
2539
+ const name = v.replace(/\$/g, "\\$");
2540
+ const decl = new RegExp(`(?:\\b(?:const|let|var)\\s+${name}\\s*=|^\\s*${name}\\s*=)\\s*await\\b`);
2541
+ for (let k = i; k >= Math.max(0, i - 40); k--) {
2542
+ if (!decl.test(rows[k])) continue;
2543
+ const between = rows.slice(k, i + 1).join("\n");
2544
+ if (/\bafterWrite\s*\(|\bwhenLanded\s*\(/.test(between) || READS.test(rows[k])) break;
2545
+ L.hit(`${f.path}:${i + 1}`, v, `${m[0].replace(/\s*\($/, "")}() builds its address from \`${v}\`, the answer of a write awaited on line ${k + 1}, before the page holds what was written: the record page then says "not found" until the re-read lands. Wait for it: \`const ${v} = await afterWrite(<the write>)\` (lib/data), or \`await whenLanded()\` first`);
2546
+ break;
2547
+ }
2548
+ }
2549
+ }
2550
+ }
2551
+ if (L.count > 4) fail("landed", `${L.count - 4} more`, "navigations built from a write that has not landed");
2552
+ if (!L.count) ok("landed", "no page opens a row from a write it did not wait for");
2553
+ }
2554
+
2555
+ // 22. A mounted agent can be opened (0.8.1; UI review X8, Gantt Planner,
2556
+ // 2026-10-12). Gantt mounted the agent's dock and defined a function to
2557
+ // toggle it, and no button anywhere called it: the agent could not be
2558
+ // opened unless a screen happened to ask it something. So an app that
2559
+ // mounts an AgentDock needs a way to open it: the TopBar block (which
2560
+ // always draws Ask when it is given the dock, or sits under an
2561
+ // AskProvider), its AskButton, a button of the app's own whose
2562
+ // onClick/onAsk calls the dock's toggle() or setOpen(), or a window event
2563
+ // the shell hears (and opens the dock from) that a bar dispatches. A
2564
+ // toggle that is defined and never wired to a control is what this
2565
+ // refuses.
2566
+ {
2567
+ const L = limiter("ask");
2568
+ const mounts = [];
2569
+ const names = new Set();
2570
+ scan(/<AgentDock\b/, (f, m, at) => {
2571
+ mounts.push(`${f.path}:${at}`);
2572
+ const tag = new RegExp(`^<AgentDock\\b${ATTRS}`).exec(f.code.slice(m.index))?.[0] ?? "";
2573
+ const named = /\bdock=\{\s*([A-Za-z_$][\w$]*)\s*\}/.exec(tag);
2574
+ if (named) names.add(named[1].replace(/\$/g, "\\$"));
2575
+ });
2576
+ if (mounts.length) {
2577
+ const BAR = /from\s*["'][^"']*\bblocks\/top-bar["']/;
2578
+ const bars = appFiles.filter((f) => BAR.test(f.code));
2579
+ const drawn = bars.some((f) => /<(?:TopBar|AskButton)\b/.test(f.code));
2580
+ const fed = bars.some((f) => /<AskProvider\b/.test(f.code) || new RegExp(`<(?:TopBar|AskButton)\\b${ATTRS}\\b(?:dock|onAsk)=`).test(f.code));
2581
+ const who = names.size ? [...names].join("|") : "[A-Za-z_$][\\w$]*";
2582
+ const own = appFiles.some((f) => new RegExp(`\\bon(?:Click|Ask)=\\{[^}]*\\b(?:${who})\\.(?:toggle|setOpen)\\b`).test(f.code));
2583
+ // The older way: a window event the shell listens for (and calls the
2584
+ // dock's toggle/setOpen from) that some bar dispatches by name.
2585
+ const opens = new RegExp(`\\b(?:${who})\\.(?:toggle|setOpen)\\b`);
2586
+ const heard = new Set();
2587
+ scan(/addEventListener\(\s*["']([^"']+)["']/, (f, m) => {
2588
+ if (opens.test(f.code.slice(Math.max(0, m.index - 400), m.index + 400))) heard.add(m[1]);
2589
+ });
2590
+ const sent = [...heard].some((name) => appFiles.some((f) => new RegExp(`new\\s+(?:Custom)?Event\\(\\s*["']${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["']`).test(f.code)));
2591
+ if (!(drawn && fed) && !own && !sent) {
2592
+ L.hit(mounts[0], "ask", "an AgentDock is mounted and nothing opens it: no button calls the dock's toggle() or setOpen(), so the agent cannot be reached unless a screen asks it something. Draw the top bar with the TopBar block (blocks/top-bar) and give it the dock (`<TopBar dock={dock} …/>`, or wrap the shell in `<AskProvider dock={dock}>`): it always draws Ask and Ask opens the dock. In a bar that cannot be a TopBar, use `<AskButton dock={dock}/>`");
2593
+ }
2594
+ }
2595
+ if (!mounts.length) ok("ask", "no agent dock is mounted");
2596
+ else if (!L.count) ok("ask", "the agent dock is mounted and Ask opens it");
2597
+ }
2598
+
2599
+ // 23. A page with a side column uses PageLayout (0.8.1; UI review X12, Project
2600
+ // Roadmap's Today, 2026-10-12). A screen that draws its own main + side
2601
+ // (`grid-cols-[minmax(0,1fr)_19rem]` and an `<aside>` with `border-l`) is
2602
+ // as tall as its words: in a tall window the divider stops halfway, the
2603
+ // bottom half is empty, and the side cannot scroll on its own. PageLayout
2604
+ // (components/ui) takes the full height of the window's content, runs the
2605
+ // divider top to bottom and scrolls each column by itself.
2606
+ {
2607
+ const L = limiter("page");
2608
+ const SIDE = /(?:^|[\s"'`{])(?:[\w@[\]/-]+:)*grid-cols-\[(?:minmax\(0,\s*1fr\)|1fr)_(?:\d+(?:\.\d+)?(?:rem|px)|var\([^)]*\))\]/;
2609
+ const RULED = /(?:^|[\s"'`{])(?:[\w@[\]/-]+:)*border-l(?![\w-])/;
2610
+ for (const f of appFiles.filter((x) => /\.(?:jsx|tsx)$/.test(x.path))) {
2611
+ const grid = SIDE.exec(f.code);
2612
+ if (!grid) continue;
2613
+ const ruled = [...f.code.matchAll(new RegExp(`<aside\\b(${ATTRS})`, "g"))].some((a) => RULED.test(a[1]));
2614
+ if (!ruled) continue;
2615
+ L.hit(`${f.path}:${lineOf(f.code, grid.index)}`, "page", "a page drawn as a main column and a side column of its own (a fixed-width grid column and an <aside> with a left border) is only as tall as its words: in a tall window the divider stops halfway and the side cannot scroll by itself. Use PageLayout from components/ui: `<PageLayout side={<Side/>}>{main}</PageLayout>` in an AppLayout with contentMode=\"fill\"; it fills the height, runs the divider top to bottom and scrolls each column on its own");
2616
+ }
2617
+ if (L.count > 4) fail("page", `${L.count - 4} more`, "pages with a hand-drawn side column");
2618
+ if (!L.count) ok("page", "no screen draws its own main + side columns: PageLayout does");
2619
+ }
2620
+
2621
+ // 24. A screen's heading is a tool's, not a landing page's (0.8.1; UI review
2622
+ // X10, Gantt Planner's home, 2026-10-12: "Plan a project that lands on its
2623
+ // date." at 52px with four stacked cards and a big chart under it). An
2624
+ // <h1> at text-4xl (36px) or more, or a pixel size from 36 up, is a hero.
2625
+ // The first run of an app is the FirstRun block: one plain heading line
2626
+ // at the kit's size, what the app does in a line, and the real first
2627
+ // steps. A picture of someone else's page (components/depictions) is
2628
+ // skipped.
2629
+ {
2630
+ const L = limiter("hero");
2631
+ const BIG = /(?:^|[\s"'`{])(?:[\w@[\]/-]+:)*text-(?:[4-9]xl|\[(?:3[6-9]|[4-9]\d|\d{3,})(?:\.\d+)?px\]|\[(?:2\.(?:2[5-9]|[3-9])\d*|[3-9](?:\.\d+)?)rem\])(?![\w-])/;
2632
+ for (const f of appFiles.filter((x) => /\.(?:jsx|tsx)$/.test(x.path))) {
2633
+ const g = new RegExp(`<h1\\b(${ATTRS})`, "g");
2634
+ let m;
2635
+ while ((m = g.exec(f.code))) {
2636
+ if (!BIG.test(m[1])) continue;
2637
+ L.hit(`${f.path}:${lineOf(f.code, m.index)}`, "hero", "an <h1> at 36px or more reads as a website's hero, not an app's page. Use the kit's heading sizes (text-xl to text-2xl), and for a first run the FirstRun block (blocks/first-run): one heading line, what the app does in a line, then the real first steps");
2638
+ }
2639
+ }
2640
+ if (L.count > 4) fail("hero", `${L.count - 4} more`, "oversized page headings");
2641
+ if (!L.count) ok("hero", "no page heading is set at a landing page's size");
2642
+ }
2643
+
2644
+ // 25. "Have the agent do it" opens the agent, not a form (0.8.1; UI review
2645
+ // X9, Gantt Planner, 2026-10-12: "Have the agent draft it" still made
2646
+ // the member fill in a name, a code and two dates first; the founder:
2647
+ // "the agent should be able to create these"). A button whose words say
2648
+ // the agent will do the work ("Have the agent draft it", "Let the agent
2649
+ // plan it", "Draft it with the agent") calls openAgent("…")
2650
+ // (blocks/agent-dock) or sends to the agent (ask, chat.send): the dock
2651
+ // opens with the line already in hand and the agent asks only for what
2652
+ // it is missing, in the chat. A button that submits a form, follows a
2653
+ // link or only changes a step is what this refuses. A form stays only
2654
+ // for "type it yourself". It reads the buttons' own words (the handler
2655
+ // may be named in the same file), and a short quoted label ("Have the
2656
+ // agent draft it" as a choice's label): that file must call openAgent,
2657
+ // or send to the agent and draw no form of its own. The kit and the
2658
+ // blocks are pinned and are not read.
2659
+ {
2660
+ const L = limiter("agentfirst");
2661
+ const WHO = String.raw`(?:the\s+|your\s+)?(?:agent|ai|assistant)`;
2662
+ const LABEL = String.raw`(?:(?:have|let|get|ask)\s+${WHO}\s+(?:to\s+)?(?:draft|do|create|build|plan|write|make|set\s*up|fill|prepare|handle|generate)\b|(?:draft|plan|create|build|write|generate)\s+(?:it\s+|this\s+|them\s+)?with\s+${WHO}\b)`;
2663
+ const OPENS = /\bopenAgent\s*\(/;
2664
+ const SENDS = /\b(?:openAgent|askAgent|askTheAgent|ask|send)\s*\(/;
2665
+ const FORM = /<(?:form|Field|Input|Textarea|Select|DateInput|DatePicker)\b/;
2666
+ const NOT_FORMS = String.raw`openAgent("Draft a project plan for me") (blocks/agent-dock) opens the agent with that line already sent in a new chat, and the agent asks only for what it is missing, in the chat; a form stays only for "type it yourself"`;
2667
+ /** Does the handler an onClick names (in the same file) open the agent? */
2668
+ const sends = (code, click) => {
2669
+ if (SENDS.test(click)) return true;
2670
+ for (const id of new Set(click.match(/[A-Za-z_$][\w$]*/g) ?? [])) {
2671
+ const def = new RegExp(`(?:const|let|function)\\s+${id.replace(/\$/g, "\\$")}\\b`).exec(code);
2672
+ if (def && SENDS.test(code.slice(def.index, def.index + 900))) return true;
2673
+ }
2674
+ return false;
2675
+ };
2676
+ for (const f of appFiles.filter((x) => /\.(?:jsx|tsx)$/.test(x.path))) {
2677
+ const el = new RegExp(`<(Button|button|Link|a|MenuItem)\\b(${ATTRS})>\\s*(?:<Icon\\b[^>]*/>\\s*)?(${LABEL}[^<{]{0,40})`, "gi");
2678
+ let m;
2679
+ while ((m = el.exec(f.code))) {
2680
+ const click = /\bon(?:Click|Press|Select)=\{((?:[^{}]|\{[^{}]*\})*)\}/.exec(m[2])?.[1] ?? "";
2681
+ if (sends(f.code, click)) continue;
2682
+ L.hit(`${f.path}:${lineOf(f.code, m.index)}`, "agentfirst", `"${m[3].trim()}" opens ${click ? "something other than the agent" : "a form or a route"}: a button that says the agent will do the work must open the agent. ${NOT_FORMS}`);
2683
+ }
2684
+ if (OPENS.test(f.code)) continue;
2685
+ const drawsForm = FORM.test(f.code);
2686
+ if (SENDS.test(f.code) && !drawsForm) continue;
2687
+ const quoted = new RegExp(`(["'\`])\\s*${LABEL}[^"'\`\\n]{0,40}\\1`, "gi");
2688
+ while ((m = quoted.exec(f.code))) {
2689
+ L.hit(`${f.path}:${lineOf(f.code, m.index)}`, "agentfirst", `${m[0]} is a choice that says the agent will do the work, and ${SENDS.test(f.code) ? "this file draws a form around it" : "nothing in this file asks the agent"}: ${NOT_FORMS}`);
2690
+ }
2691
+ }
2692
+ if (L.count > 4) fail("agentfirst", `${L.count - 4} more`, 'buttons that say "have the agent do it" and open a form');
2693
+ if (!L.count) ok("agentfirst", 'every "have the agent do it" button opens the agent');
2694
+ }
2695
+
2696
+ // 26. A bar action with words has an icon (0.8.1; the Newsletter Engine pilot,
2697
+ // 2026-10-12). The top bar's `BarText` hides its words under 32rem so the
2698
+ // action is its icon, and leaves them to a screen reader. A button whose
2699
+ // only content is `<BarText>` and which has no `icon` is, under 32rem,
2700
+ // an empty button: nothing to see, nothing to press. Give the Button an
2701
+ // `icon` (or an <Icon/> beside the BarText).
2702
+ {
2703
+ const L = limiter("barlabel");
2704
+ for (const f of appFiles.filter((x) => /\.(?:jsx|tsx)$/.test(x.path))) {
2705
+ if (!/<BarText\b/.test(f.code)) continue;
2706
+ const g = new RegExp(`<(Button|button|IconButton)\\b(${ATTRS})>`, "g");
2707
+ let m;
2708
+ while ((m = g.exec(f.code))) {
2709
+ if (/\/\s*$/.test(m[2])) continue;
2710
+ const end = f.code.indexOf(`</${m[1]}>`, m.index);
2711
+ if (end < 0) continue;
2712
+ const inner = f.code.slice(m.index + m[0].length, end);
2713
+ if (!/<BarText\b/.test(inner)) continue;
2714
+ if (/(?:^|\s)icon=/.test(m[2]) || /<(?:Icon|svg)\b/.test(inner)) continue;
2715
+ L.hit(`${f.path}:${lineOf(f.code, m.index)}`, "barlabel", "a button whose words are a <BarText> and that has no icon is an empty button under 32rem of bar (BarText gives its words up there, to a screen reader only). Give the Button an icon: `<Button icon=\"plus\"><BarText>New issue</BarText></Button>`");
2716
+ }
2717
+ }
2718
+ if (L.count > 4) fail("barlabel", `${L.count - 4} more`, "bar actions with no icon");
2719
+ if (!L.count) ok("barlabel", "every bar action with words has an icon");
2720
+ }
2721
+
2722
+ // 27. A side column is the fixed one, so it is short (0.8.1; UI review X13,
2723
+ // Newsletter Engine's first run, 2026-10-12: a short intro on the left
2724
+ // and a long "Name it" form in the right column, "one small column and
2725
+ // then all the card long scrolling in other column"). In a main + side
2726
+ // page the main scrolls and the side holds its place; a form, a paste
2727
+ // box or a list that grows with the data does not belong there. Reads a
2728
+ // PageLayout `side` and an <aside>: their markup, and the components the
2729
+ // app defines that they draw (a name resolved in any app file, three
2730
+ // levels; a called function only when it returns JSX). Refused: a <form>, a <Textarea>, three or more of Field ·
2731
+ // Input · Textarea · Select, and a `.map(` of data with no `.slice(` in
2732
+ // the same piece of code (a constant in CAPITALS, an array literal and
2733
+ // Object.entries(CAPITALS) are fixed lists). It cannot see inside a block
2734
+ // or a kit component, content that comes in by props or context, or how
2735
+ // long a list is at run time.
2736
+ {
2737
+ const L = limiter("sidelong");
2738
+ const jsxFiles = appFiles.filter((x) => /\.(?:jsx|tsx)$/.test(x.path));
2739
+ const OPEN = { "{": "}", "(": ")", "[": "]" };
2740
+ /** The index just past the bracket that closes the one at `i`. Double-quoted
2741
+ * and template strings are skipped; single quotes are not (JSX text has
2742
+ * apostrophes). */
2743
+ const closeAt = (code, i) => {
2744
+ let depth = 0;
2745
+ for (let k = i; k < code.length; k++) {
2746
+ const c = code[k];
2747
+ if (c === '"' || c === "`") {
2748
+ const e = code.indexOf(c, k + 1);
2749
+ if (e < 0) return code.length;
2750
+ k = e;
2751
+ } else if (OPEN[c]) depth++;
2752
+ else if (c === "}" || c === ")" || c === "]") {
2753
+ depth--;
2754
+ if (depth === 0) return k + 1;
2755
+ if (depth < 0) return k;
2756
+ }
2757
+ }
2758
+ return code.length;
2759
+ };
2760
+ /** From `i` to the `;` that ends the statement (brackets balanced). */
2761
+ const untilSemi = (code, i) => {
2762
+ let k = i;
2763
+ while (k < code.length) {
2764
+ const c = code[k];
2765
+ if (c === '"' || c === "`") {
2766
+ const e = code.indexOf(c, k + 1);
2767
+ k = e < 0 ? code.length : e + 1;
2768
+ } else if (OPEN[c]) k = closeAt(code, k);
2769
+ else if (c === ";" || c === "}" || c === ")" || c === "]") break;
2770
+ else k++;
2771
+ }
2772
+ return code.slice(i, k);
2773
+ };
2774
+ /** The body of the component or function `name` an app file defines. */
2775
+ const defined = (name) => {
2776
+ const out = [];
2777
+ for (const f of jsxFiles.concat(appFiles.filter((x) => /\.(?:js|ts)$/.test(x.path)))) {
2778
+ const fn = new RegExp(`\\bfunction\\s+${name}\\s*\\(`).exec(f.code);
2779
+ if (fn) {
2780
+ const p0 = fn.index + fn[0].length - 1;
2781
+ const b = f.code.indexOf("{", closeAt(f.code, p0));
2782
+ if (b >= 0) out.push(f.code.slice(b, closeAt(f.code, b)));
2783
+ continue;
2784
+ }
2785
+ const cn = new RegExp(`\\b(?:const|let)\\s+${name}\\s*=`).exec(f.code);
2786
+ if (cn) out.push(untilSemi(f.code, cn.index + cn[0].length));
2787
+ }
2788
+ return out;
2789
+ };
2790
+ /** What a piece of side code holds: a reason, or null. */
2791
+ const holds = (code) => {
2792
+ if (/<form\b/.test(code)) return "a <form>";
2793
+ if (/<Textarea\b/.test(code)) return "a <Textarea> (a paste or text box)";
2794
+ const fields = new Set([...code.matchAll(/<(Field|Input|Select)\b/g)].map((m) => m[1]));
2795
+ const n = [...code.matchAll(/<(?:Field|Input|Select)\b/g)].length;
2796
+ if (n >= 3) return `${n} fields (${[...fields].join(", ")})`;
2797
+ if (!/\.slice\(/.test(code)) {
2798
+ for (const m of code.matchAll(/([A-Za-z_$][\w$.]*|\)|\])\s*\.map\(/g)) {
2799
+ const recv = m[1];
2800
+ if (recv === "]") continue;
2801
+ if (recv === ")") {
2802
+ if (/Object\.(?:entries|keys|values)\(\s*[A-Z][A-Z0-9_]*\s*\)\s*$/.test(code.slice(Math.max(0, m.index - 60), m.index + 1))) continue;
2803
+ if (/\bArray\.from\(/.test(code.slice(Math.max(0, m.index - 60), m.index + 1))) continue;
2804
+ } else if (/^(?:[A-Za-z_$][\w$]*\.)*[A-Z][A-Z0-9_]*$/.test(recv)) continue;
2805
+ return `a list from data (${recv === ")" ? "…)" : recv}.map) with no .slice(...) to bound it`;
2806
+ }
2807
+ }
2808
+ return null;
2809
+ };
2810
+ /** The code without what opens over the page (a Sheet, a Popover, a
2811
+ * Dialog, a menu): its form is not in the column until someone opens it. */
2812
+ const withoutOverlays = (code) => {
2813
+ let out = code;
2814
+ for (const tag of ["Sheet", "Popover", "Dialog", "Modal", "Drawer", "Menu", "DropdownMenu", "Tooltip", "AlertDialog"]) {
2815
+ for (;;) {
2816
+ const open = new RegExp(`<${tag}\\b`).exec(out);
2817
+ if (!open) break;
2818
+ const close = out.indexOf(`</${tag}>`, open.index);
2819
+ out = out.slice(0, open.index) + (close < 0 ? "" : out.slice(close + tag.length + 3));
2820
+ if (close < 0) break;
2821
+ }
2822
+ }
2823
+ return out;
2824
+ };
2825
+ /** The first reason in `code`, or in a component or function it draws. */
2826
+ const reason = (whole, seen = new Set(), depth = 0, via = []) => {
2827
+ const code = withoutOverlays(whole);
2828
+ const own = holds(code);
2829
+ if (own) return { why: own, via };
2830
+ if (depth >= 3) return null;
2831
+ const names = new Set([...code.matchAll(/<([A-Z][\w$]*)\b/g)].map((m) => m[1]));
2832
+ for (const m of code.matchAll(/(?<![\w$.])([a-z][\w$]*)\(/g)) names.add(m[1]);
2833
+ for (const name of names) {
2834
+ if (seen.has(name)) continue;
2835
+ seen.add(name);
2836
+ for (const body of defined(name)) {
2837
+ // A function the side calls draws something only if it returns JSX
2838
+ // (a formatter that maps a list of strings draws nothing).
2839
+ if (/^[a-z]/.test(name) && !/<[A-Za-z]/.test(body)) continue;
2840
+ const r = reason(body, seen, depth + 1, [...via, name]);
2841
+ if (r) return r;
2842
+ }
2843
+ }
2844
+ return null;
2845
+ };
2846
+ /** The value of the `side` attribute of the <PageLayout> that opens at `at`:
2847
+ * JSX or an expression, or a name that stands for one in the same file. */
2848
+ const sideOf = (code, at) => {
2849
+ let k = at + "<PageLayout".length;
2850
+ while (k < code.length) {
2851
+ const c = code[k];
2852
+ if (c === ">") return null;
2853
+ if (c === '"' || c === "'") {
2854
+ const e = code.indexOf(c, k + 1);
2855
+ k = e < 0 ? code.length : e + 1;
2856
+ } else if (c === "{") k = closeAt(code, k);
2857
+ else if (/\s/.test(c) && /^\s+side=\{/.test(code.slice(k, k + 12))) {
2858
+ const b = code.indexOf("{", k);
2859
+ const expr = code.slice(b + 1, closeAt(code, b) - 1);
2860
+ const id = /^\s*([A-Za-z_$][\w$]*)\s*$/.exec(expr);
2861
+ if (!id) return expr;
2862
+ const decl = new RegExp(`\\b(?:const|let)\\s+${id[1]}\\s*=`).exec(code);
2863
+ return decl ? untilSemi(code, decl.index + decl[0].length) : expr;
2864
+ } else k++;
2865
+ }
2866
+ return null;
2867
+ };
2868
+ const SAY = "a side column is the fixed one: it stays put while the main scrolls, so it is short and needs no scroll of its own. Put the form or the long list in the main (a step that opens reveals its own card there) and keep the side to a summary, the next step or a small panel; bound a short list with .slice(0, 5)";
2869
+ for (const f of jsxFiles) {
2870
+ for (const m of f.code.matchAll(/<PageLayout\b/g)) {
2871
+ const side = sideOf(f.code, m.index);
2872
+ const r = side ? reason(side) : null;
2873
+ if (r) L.hit(`${f.path}:${lineOf(f.code, m.index)}`, "side", `a PageLayout side holds ${r.why}${r.via.length ? ` (through ${r.via.join(" → ")})` : ""}: ${SAY}`);
2874
+ }
2875
+ const beside = /(?:^|[\s"'`{])(?:[\w@[\]/-]+:)*grid-cols-\[(?:minmax\(0,\s*1fr\)|1fr)_(?:\d+(?:\.\d+)?(?:rem|px)|var\([^)]*\))\]/.test(f.code);
2876
+ for (const m of f.code.matchAll(new RegExp(`<aside\\b(${ATTRS})>`, "g"))) {
2877
+ if (/\/\s*$/.test(m[1])) continue;
2878
+ // An <aside> is a side column when it is ruled (border-l or border-r) or
2879
+ // sits in a file that draws a fixed-width column beside a main: a note
2880
+ // or a callout that happens to be an <aside> is not.
2881
+ if (!beside && !/(?:^|[\s"'`{])(?:[\w@[\]/-]+:)*border-[lr](?![\w-])/.test(m[1])) continue;
2882
+ const from = m.index + m[0].length;
2883
+ let depth = 1;
2884
+ let end = f.code.length;
2885
+ for (const t of f.code.slice(from).matchAll(/<(\/?)aside\b/g)) {
2886
+ depth += t[1] ? -1 : 1;
2887
+ if (!depth) {
2888
+ end = from + t.index;
2889
+ break;
2890
+ }
2891
+ }
2892
+ const r = reason(f.code.slice(from, end));
2893
+ if (r) L.hit(`${f.path}:${lineOf(f.code, m.index)}`, "aside", `an <aside> holds ${r.why}${r.via.length ? ` (through ${r.via.join(" → ")})` : ""}: ${SAY}`);
2894
+ }
2895
+ }
2896
+ if (L.count > 4) fail("sidelong", `${L.count - 4} more`, "side columns that hold a form or a long list");
2897
+ if (!L.count) ok("sidelong", "no side column holds a form or a long list");
2898
+ }
2899
+
1874
2900
  // 6. Smoke: the app, built and started, answers every surface.
1875
2901
 
1876
2902
  /** Newest mtime among the app's own sources (what a build reads): walk
@@ -1900,7 +2926,12 @@ function writable(dir) {
1900
2926
  * the build goes to `.next` all the same. */
1901
2927
  function buildSite(exported = false) {
1902
2928
  if (!exported && writable(ROOT)) return { dir: ROOT, dist: ".next-check", copied: false };
1903
- const name = basename(dirname(ROOT)) || "app"; // plays/<name>/app
2929
+ // plays/<name>/app names the copy <name>; the store's packages/<key>/files/app
2930
+ // would all be "files", one folder shared by every package's check, so the
2931
+ // package's key names it there (2026-10-12, every builder of the wave).
2932
+ const parent = dirname(ROOT);
2933
+ let name = basename(parent) || "app";
2934
+ if (name === "files") name = basename(dirname(parent)) || name;
1904
2935
  const home = writable(homedir()) ? homedir() : tmpdir();
1905
2936
  return { dir: join(home, ".claw-check", name), dist: ".next", copied: true };
1906
2937
  }