@tuongaz/seeflow 0.1.64 → 0.1.68

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 (314) hide show
  1. package/README.md +4 -0
  2. package/dist/web/assets/abap-DsBKuouk.js +1 -0
  3. package/dist/web/assets/actionscript-3-D_z4Izcz.js +1 -0
  4. package/dist/web/assets/ada-727ZlQH0.js +1 -0
  5. package/dist/web/assets/andromeeda-C3khCPGq.js +1 -0
  6. package/dist/web/assets/angular-html-4alyEGLm.js +1 -0
  7. package/dist/web/assets/angular-ts-BixEUTMq.js +1 -0
  8. package/dist/web/assets/apache-Dn00JSTd.js +1 -0
  9. package/dist/web/assets/apex-COJ4H7py.js +1 -0
  10. package/dist/web/assets/apl-BBq3IX1j.js +1 -0
  11. package/dist/web/assets/applescript-Bu5BbsvL.js +1 -0
  12. package/dist/web/assets/ara-7O62HKoU.js +1 -0
  13. package/dist/web/assets/asciidoc-BPT9niGB.js +1 -0
  14. package/dist/web/assets/asm-Dhn9LcZ4.js +1 -0
  15. package/dist/web/assets/astro-CqkE3fuf.js +1 -0
  16. package/dist/web/assets/aurora-x-D-2ljcwZ.js +1 -0
  17. package/dist/web/assets/awk-eg146-Ew.js +1 -0
  18. package/dist/web/assets/ayu-dark-Cv9koXgw.js +1 -0
  19. package/dist/web/assets/ballerina-Du268qiB.js +1 -0
  20. package/dist/web/assets/bat-fje9CFhw.js +1 -0
  21. package/dist/web/assets/beancount-BwXTMy5W.js +1 -0
  22. package/dist/web/assets/berry-3xVqZejG.js +1 -0
  23. package/dist/web/assets/bibtex-xW4inM5L.js +1 -0
  24. package/dist/web/assets/bicep-DHo0CJ0O.js +1 -0
  25. package/dist/web/assets/blade-a8OxSdnT.js +1 -0
  26. package/dist/web/assets/bsl-Dgyn0ogV.js +1 -0
  27. package/dist/web/assets/c-C3t2pwGQ.js +1 -0
  28. package/dist/web/assets/cadence-DNquZEk8.js +1 -0
  29. package/dist/web/assets/cairo--RitsXJZ.js +1 -0
  30. package/dist/web/assets/catppuccin-frappe-CD_QflpE.js +1 -0
  31. package/dist/web/assets/catppuccin-latte-DRW-0cLl.js +1 -0
  32. package/dist/web/assets/catppuccin-macchiato-C-_shW-Y.js +1 -0
  33. package/dist/web/assets/catppuccin-mocha-LGGdnPYs.js +1 -0
  34. package/dist/web/assets/chart-8DxAnLoD.js +73 -0
  35. package/dist/web/assets/clarity-BHOwM8T6.js +1 -0
  36. package/dist/web/assets/clojure-DxSadP1t.js +1 -0
  37. package/dist/web/assets/cmake-DbXoA79R.js +1 -0
  38. package/dist/web/assets/cobol-PTqiYgYu.js +1 -0
  39. package/dist/web/assets/code-block-cx7LPXvE.js +13 -0
  40. package/dist/web/assets/codeowners-Bp6g37R7.js +1 -0
  41. package/dist/web/assets/codeql-sacFqUAJ.js +1 -0
  42. package/dist/web/assets/coffee-dyiR41kL.js +1 -0
  43. package/dist/web/assets/common-lisp-C7gG9l05.js +1 -0
  44. package/dist/web/assets/coq-Dsg_Bt_b.js +1 -0
  45. package/dist/web/assets/cpp-BksuvNSY.js +1 -0
  46. package/dist/web/assets/crystal-DtDmRg-F.js +1 -0
  47. package/dist/web/assets/csharp-D9R-vmeu.js +1 -0
  48. package/dist/web/assets/css-BPhBrDlE.js +1 -0
  49. package/dist/web/assets/csv-B0qRVHPH.js +1 -0
  50. package/dist/web/assets/cue-DtFQj3wx.js +1 -0
  51. package/dist/web/assets/cypher-m2LEI-9-.js +1 -0
  52. package/dist/web/assets/d-BoXegm-a.js +1 -0
  53. package/dist/web/assets/dark-plus-C3mMm8J8.js +1 -0
  54. package/dist/web/assets/dart-B9wLZaAG.js +1 -0
  55. package/dist/web/assets/dax-ClGRhx96.js +1 -0
  56. package/dist/web/assets/desktop-DEIpsLCJ.js +1 -0
  57. package/dist/web/assets/diff-BgYniUM_.js +1 -0
  58. package/dist/web/assets/docker-COcR7UxN.js +1 -0
  59. package/dist/web/assets/dotenv-BjQB5zDj.js +1 -0
  60. package/dist/web/assets/dracula-BzJJZx-M.js +1 -0
  61. package/dist/web/assets/dracula-soft-BXkSAIEj.js +1 -0
  62. package/dist/web/assets/dream-maker-C-nORZOA.js +1 -0
  63. package/dist/web/assets/edge-D5gP-w-T.js +1 -0
  64. package/dist/web/assets/elixir-CLiX3zqd.js +1 -0
  65. package/dist/web/assets/elm-CmHSxxaM.js +1 -0
  66. package/dist/web/assets/emacs-lisp-BX77sIaO.js +1 -0
  67. package/dist/web/assets/erb-BYTLMnw6.js +1 -0
  68. package/dist/web/assets/erlang-B-DoSBHF.js +1 -0
  69. package/dist/web/assets/everforest-dark-BgDCqdQA.js +1 -0
  70. package/dist/web/assets/everforest-light-C8M2exoo.js +1 -0
  71. package/dist/web/assets/fennel-bCA53EVm.js +1 -0
  72. package/dist/web/assets/fish-w-ucz2PV.js +1 -0
  73. package/dist/web/assets/fluent-Dayu4EKP.js +1 -0
  74. package/dist/web/assets/fortran-fixed-form-TqA4NnZg.js +1 -0
  75. package/dist/web/assets/fortran-free-form-DKXYxT9g.js +1 -0
  76. package/dist/web/assets/fsharp-XplgxFYe.js +1 -0
  77. package/dist/web/assets/gdresource-BHYsBjWJ.js +1 -0
  78. package/dist/web/assets/gdscript-DfxzS6Rs.js +1 -0
  79. package/dist/web/assets/gdshader-SKMF96pI.js +1 -0
  80. package/dist/web/assets/genie-ajMbGru0.js +1 -0
  81. package/dist/web/assets/gherkin--30QC5Em.js +1 -0
  82. package/dist/web/assets/git-commit-i4q6IMui.js +1 -0
  83. package/dist/web/assets/git-rebase-B-v9cOL2.js +1 -0
  84. package/dist/web/assets/github-dark-DHJKELXO.js +1 -0
  85. package/dist/web/assets/github-dark-default-Cuk6v7N8.js +1 -0
  86. package/dist/web/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  87. package/dist/web/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  88. package/dist/web/assets/github-light-DAi9KRSo.js +1 -0
  89. package/dist/web/assets/github-light-default-D7oLnXFd.js +1 -0
  90. package/dist/web/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  91. package/dist/web/assets/gleam-B430Bg39.js +1 -0
  92. package/dist/web/assets/glimmer-js-D-cwc0-E.js +1 -0
  93. package/dist/web/assets/glimmer-ts-pgjy16dm.js +1 -0
  94. package/dist/web/assets/glsl-DBO2IWDn.js +1 -0
  95. package/dist/web/assets/gnuplot-CM8KxXT1.js +1 -0
  96. package/dist/web/assets/go-B1SYOhNW.js +1 -0
  97. package/dist/web/assets/graphql-cDcHW_If.js +1 -0
  98. package/dist/web/assets/groovy-DkBy-JyN.js +1 -0
  99. package/dist/web/assets/hack-D1yCygmZ.js +1 -0
  100. package/dist/web/assets/haml-B2EZWmdv.js +1 -0
  101. package/dist/web/assets/handlebars-BQGss363.js +1 -0
  102. package/dist/web/assets/haskell-BILxekzW.js +1 -0
  103. package/dist/web/assets/haxe-C5wWYbrZ.js +1 -0
  104. package/dist/web/assets/hcl-HzYwdGDm.js +1 -0
  105. package/dist/web/assets/hjson-T-Tgc4AT.js +1 -0
  106. package/dist/web/assets/hlsl-ifBTmRxC.js +1 -0
  107. package/dist/web/assets/houston-DnULxvSX.js +1 -0
  108. package/dist/web/assets/html-C2L_23MC.js +1 -0
  109. package/dist/web/assets/html-derivative-CSfWNPLT.js +1 -0
  110. package/dist/web/assets/http-FRrOvY1W.js +1 -0
  111. package/dist/web/assets/hxml-TIA70rKU.js +1 -0
  112. package/dist/web/assets/hy-BMj5Y0dO.js +1 -0
  113. package/dist/web/assets/imba-bv_oIlVt.js +1 -0
  114. package/dist/web/assets/index-CeLShda7.css +1 -0
  115. package/dist/web/assets/index-Dp4QwEl0.js +8608 -0
  116. package/dist/web/assets/{index.es-DZEdTXNJ.js → index.es-C-uXEdZB.js} +1 -1
  117. package/dist/web/assets/ini-BjABl1g7.js +1 -0
  118. package/dist/web/assets/java-xI-RfyKK.js +1 -0
  119. package/dist/web/assets/javascript-ySlJ1b_l.js +1 -0
  120. package/dist/web/assets/jinja-DGy0s7-h.js +1 -0
  121. package/dist/web/assets/jison-BqZprYcd.js +1 -0
  122. package/dist/web/assets/json-BQoSv7ci.js +1 -0
  123. package/dist/web/assets/json5-w8dY5SsB.js +1 -0
  124. package/dist/web/assets/jsonc-TU54ms6u.js +1 -0
  125. package/dist/web/assets/jsonl-DREVFZK8.js +1 -0
  126. package/dist/web/assets/jsonnet-BfivnA6A.js +1 -0
  127. package/dist/web/assets/{jspdf.es.min-DT1Li8zz.js → jspdf.es.min-BFQufOcQ.js} +3 -3
  128. package/dist/web/assets/jssm-P4WzXJd0.js +1 -0
  129. package/dist/web/assets/jsx-BAng5TT0.js +1 -0
  130. package/dist/web/assets/julia-BBuGR-5E.js +1 -0
  131. package/dist/web/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  132. package/dist/web/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  133. package/dist/web/assets/kanagawa-wave-DWedfzmr.js +1 -0
  134. package/dist/web/assets/kotlin-B5lbUyaz.js +1 -0
  135. package/dist/web/assets/kusto-mebxcVVE.js +1 -0
  136. package/dist/web/assets/laserwave-DUszq2jm.js +1 -0
  137. package/dist/web/assets/latex-C-cWTeAZ.js +1 -0
  138. package/dist/web/assets/lean-XBlWyCtg.js +1 -0
  139. package/dist/web/assets/less-BfCpw3nA.js +1 -0
  140. package/dist/web/assets/light-plus-B7mTdjB0.js +1 -0
  141. package/dist/web/assets/liquid-D3W5UaiH.js +1 -0
  142. package/dist/web/assets/log-Cc5clBb7.js +1 -0
  143. package/dist/web/assets/logo-IuBKFhSY.js +1 -0
  144. package/dist/web/assets/lua-CvWAzNxB.js +1 -0
  145. package/dist/web/assets/luau-Du5NY7AG.js +1 -0
  146. package/dist/web/assets/make-Bvotw-X0.js +1 -0
  147. package/dist/web/assets/markdown-DK_1WFMa.js +1 -0
  148. package/dist/web/assets/markdown-UIAJJxZW.js +1 -0
  149. package/dist/web/assets/marko-z0MBrx5-.js +1 -0
  150. package/dist/web/assets/material-theme-D5KoaKCx.js +1 -0
  151. package/dist/web/assets/material-theme-darker-BfHTSMKl.js +1 -0
  152. package/dist/web/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  153. package/dist/web/assets/material-theme-ocean-CyktbL80.js +1 -0
  154. package/dist/web/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  155. package/dist/web/assets/matlab-D9-PGadD.js +1 -0
  156. package/dist/web/assets/mdc-DB_EDNY_.js +1 -0
  157. package/dist/web/assets/mdx-sdHcTMYB.js +1 -0
  158. package/dist/web/assets/mermaid-Ci6OQyBP.js +1 -0
  159. package/dist/web/assets/min-dark-CafNBF8u.js +1 -0
  160. package/dist/web/assets/min-light-CTRr51gU.js +1 -0
  161. package/dist/web/assets/mipsasm-BC5c_5Pe.js +1 -0
  162. package/dist/web/assets/mojo-Tz6hzZYG.js +1 -0
  163. package/dist/web/assets/monokai-D4h5O-jR.js +1 -0
  164. package/dist/web/assets/move-DB_GagMm.js +1 -0
  165. package/dist/web/assets/narrat-DLbgOhZU.js +1 -0
  166. package/dist/web/assets/nextflow-B0XVJmRM.js +1 -0
  167. package/dist/web/assets/nginx-D_VnBJ67.js +1 -0
  168. package/dist/web/assets/night-owl-C39BiMTA.js +1 -0
  169. package/dist/web/assets/nim-ZlGxZxc3.js +1 -0
  170. package/dist/web/assets/nix-shcSOmrb.js +1 -0
  171. package/dist/web/assets/nord-Ddv68eIx.js +1 -0
  172. package/dist/web/assets/nushell-D4Tzg5kh.js +1 -0
  173. package/dist/web/assets/objective-c-Deuh7S70.js +1 -0
  174. package/dist/web/assets/objective-cpp-BUEGK8hf.js +1 -0
  175. package/dist/web/assets/ocaml-BNioltXt.js +1 -0
  176. package/dist/web/assets/one-dark-pro-GBQ2dnAY.js +1 -0
  177. package/dist/web/assets/one-light-PoHY5YXO.js +1 -0
  178. package/dist/web/assets/pascal-JqZropPD.js +1 -0
  179. package/dist/web/assets/perl-CHQXSrWU.js +1 -0
  180. package/dist/web/assets/php-B5ebYQev.js +1 -0
  181. package/dist/web/assets/plastic-3e1v2bzS.js +1 -0
  182. package/dist/web/assets/plsql-LKU2TuZ1.js +1 -0
  183. package/dist/web/assets/po-BFLt1xDp.js +1 -0
  184. package/dist/web/assets/poimandres-CS3Unz2-.js +1 -0
  185. package/dist/web/assets/polar-DKykz6zU.js +1 -0
  186. package/dist/web/assets/postcss-B3ZDOciz.js +1 -0
  187. package/dist/web/assets/powerquery-CSHBycmS.js +1 -0
  188. package/dist/web/assets/powershell-BIEUsx6d.js +1 -0
  189. package/dist/web/assets/prisma-B48N-Iqd.js +1 -0
  190. package/dist/web/assets/prolog-BY-TUvya.js +1 -0
  191. package/dist/web/assets/proto-zocC4JxJ.js +1 -0
  192. package/dist/web/assets/pug-CM9l7STV.js +1 -0
  193. package/dist/web/assets/puppet-Cza_XSSt.js +1 -0
  194. package/dist/web/assets/purescript-Bg-kzb6g.js +1 -0
  195. package/dist/web/assets/python-DhUJRlN_.js +1 -0
  196. package/dist/web/assets/qml-D8XfuvdV.js +1 -0
  197. package/dist/web/assets/qmldir-C8lEn-DE.js +1 -0
  198. package/dist/web/assets/qss-DhMKtDLN.js +1 -0
  199. package/dist/web/assets/r-CwjWoCRV.js +1 -0
  200. package/dist/web/assets/racket-CzouJOBO.js +1 -0
  201. package/dist/web/assets/raku-B1bQXN8T.js +1 -0
  202. package/dist/web/assets/razor-CNLDkMZG.js +1 -0
  203. package/dist/web/assets/red-bN70gL4F.js +1 -0
  204. package/dist/web/assets/reg-5LuOXUq_.js +1 -0
  205. package/dist/web/assets/regexp-DWJ3fJO_.js +1 -0
  206. package/dist/web/assets/rel-DJlmqQ1C.js +1 -0
  207. package/dist/web/assets/riscv-QhoSD0DR.js +1 -0
  208. package/dist/web/assets/rose-pine-CmCqftbK.js +1 -0
  209. package/dist/web/assets/rose-pine-dawn-Ds-gbosJ.js +1 -0
  210. package/dist/web/assets/rose-pine-moon-CjDtw9vr.js +1 -0
  211. package/dist/web/assets/rst-4NLicBqY.js +1 -0
  212. package/dist/web/assets/ruby-DeZ3UC14.js +1 -0
  213. package/dist/web/assets/rust-Be6lgOlo.js +1 -0
  214. package/dist/web/assets/sas-BmTFh92c.js +1 -0
  215. package/dist/web/assets/sass-BJ4Li9vH.js +1 -0
  216. package/dist/web/assets/scala-DQVVAn-B.js +1 -0
  217. package/dist/web/assets/scheme-BJGe-b2p.js +1 -0
  218. package/dist/web/assets/scss-C31hgJw-.js +1 -0
  219. package/dist/web/assets/sdbl-BLhTXw86.js +1 -0
  220. package/dist/web/assets/shaderlab-B7qAK45m.js +1 -0
  221. package/dist/web/assets/shellscript-atvbtKCR.js +1 -0
  222. package/dist/web/assets/shellsession-C_rIy8kc.js +1 -0
  223. package/dist/web/assets/slack-dark-BthQWCQV.js +1 -0
  224. package/dist/web/assets/slack-ochin-DqwNpetd.js +1 -0
  225. package/dist/web/assets/smalltalk-DkLiglaE.js +1 -0
  226. package/dist/web/assets/snazzy-light-Bw305WKR.js +1 -0
  227. package/dist/web/assets/solarized-dark-DXbdFlpD.js +1 -0
  228. package/dist/web/assets/solarized-light-L9t79GZl.js +1 -0
  229. package/dist/web/assets/solidity-C1w2a3ep.js +1 -0
  230. package/dist/web/assets/soy-C-lX7w71.js +1 -0
  231. package/dist/web/assets/sparql-bYkjHRlG.js +1 -0
  232. package/dist/web/assets/splunk-Cf8iN4DR.js +1 -0
  233. package/dist/web/assets/sql-COK4E0Yg.js +1 -0
  234. package/dist/web/assets/ssh-config-BknIz3MU.js +1 -0
  235. package/dist/web/assets/stata-DorPZHa4.js +1 -0
  236. package/dist/web/assets/stylus-BeQkCIfX.js +1 -0
  237. package/dist/web/assets/svelte-MSaWC3Je.js +1 -0
  238. package/dist/web/assets/swift-BSxZ-RaX.js +1 -0
  239. package/dist/web/assets/synthwave-84-CbfX1IO0.js +1 -0
  240. package/dist/web/assets/system-verilog-C7L56vO4.js +1 -0
  241. package/dist/web/assets/systemd-CUnW07Te.js +1 -0
  242. package/dist/web/assets/talonscript-C1XDQQGZ.js +1 -0
  243. package/dist/web/assets/tasl-CQjiPCtT.js +1 -0
  244. package/dist/web/assets/tcl-DQ1-QYvQ.js +1 -0
  245. package/dist/web/assets/templ-dwX3ZSMB.js +1 -0
  246. package/dist/web/assets/terraform-BbSNqyBO.js +1 -0
  247. package/dist/web/assets/tex-rYs2v40G.js +1 -0
  248. package/dist/web/assets/tokyo-night-DBQeEorK.js +1 -0
  249. package/dist/web/assets/toml-CB2ApiWb.js +1 -0
  250. package/dist/web/assets/ts-tags-CipyTH0X.js +1 -0
  251. package/dist/web/assets/tsv-B_m7g4N7.js +1 -0
  252. package/dist/web/assets/tsx-B6W0miNI.js +1 -0
  253. package/dist/web/assets/turtle-BMR_PYu6.js +1 -0
  254. package/dist/web/assets/twig-NC5TFiHP.js +1 -0
  255. package/dist/web/assets/typescript-Dj6nwHGl.js +1 -0
  256. package/dist/web/assets/typespec-BpWG_bgh.js +1 -0
  257. package/dist/web/assets/typst-BVUVsWT6.js +1 -0
  258. package/dist/web/assets/v-CAQ2eGtk.js +1 -0
  259. package/dist/web/assets/vala-BFOHcciG.js +1 -0
  260. package/dist/web/assets/vb-CdO5JTpU.js +1 -0
  261. package/dist/web/assets/verilog-CJaU5se_.js +1 -0
  262. package/dist/web/assets/vesper-BEBZ7ncR.js +1 -0
  263. package/dist/web/assets/vhdl-DYoNaHQp.js +1 -0
  264. package/dist/web/assets/viml-m4uW47V2.js +1 -0
  265. package/dist/web/assets/vitesse-black-Bkuqu6BP.js +1 -0
  266. package/dist/web/assets/vitesse-dark-D0r3Knsf.js +1 -0
  267. package/dist/web/assets/vitesse-light-CVO1_9PV.js +1 -0
  268. package/dist/web/assets/vue-BuYVFjOK.js +1 -0
  269. package/dist/web/assets/vue-html-xdeiXROB.js +1 -0
  270. package/dist/web/assets/vyper-nyqBNV6O.js +1 -0
  271. package/dist/web/assets/wasm-C6j12Q_x.js +1 -0
  272. package/dist/web/assets/wasm-CG6Dc4jp.js +1 -0
  273. package/dist/web/assets/wenyan-7A4Fjokl.js +1 -0
  274. package/dist/web/assets/wgsl-CB0Krxn9.js +1 -0
  275. package/dist/web/assets/wikitext-DCE3LsBG.js +1 -0
  276. package/dist/web/assets/wolfram-C3FkfJm5.js +1 -0
  277. package/dist/web/assets/xml-e3z08dGr.js +1 -0
  278. package/dist/web/assets/xsl-Dd0NUgwM.js +1 -0
  279. package/dist/web/assets/yaml-CVw76BM1.js +1 -0
  280. package/dist/web/assets/zenscript-HnGAYVZD.js +1 -0
  281. package/dist/web/assets/zig-BVz_zdnA.js +1 -0
  282. package/dist/web/index.html +2 -2
  283. package/examples/component-showcase/README.md +29 -0
  284. package/examples/component-showcase/flow.json +40 -0
  285. package/examples/component-showcase/nodes/chart/spec.json +66 -0
  286. package/examples/component-showcase/nodes/counter/spec.json +57 -0
  287. package/examples/component-showcase/nodes/fetcher/actions/refresh.ts +35 -0
  288. package/examples/component-showcase/nodes/fetcher/spec.json +68 -0
  289. package/examples/component-showcase/nodes/form/spec.json +87 -0
  290. package/examples/component-showcase/package.json +6 -0
  291. package/examples/component-showcase/style.json +28 -0
  292. package/examples/ecommerce-platform/flow.json +28 -30
  293. package/examples/order-pipeline/flow.json +16 -16
  294. package/package.json +2 -1
  295. package/src/api.ts +73 -23
  296. package/src/cli-e2e.ts +6 -7
  297. package/src/cli-manifest.ts +3 -5
  298. package/src/cli.ts +27 -0
  299. package/src/component-action-runner.ts +188 -0
  300. package/src/component-spec-resolver.ts +60 -0
  301. package/src/diagram.ts +8 -5
  302. package/src/layout.ts +23 -26
  303. package/src/mcp.ts +2 -2
  304. package/src/merge.ts +8 -3
  305. package/src/node-files.ts +1 -1
  306. package/src/operations.ts +109 -52
  307. package/src/runtime.ts +37 -0
  308. package/src/schema-catalog.ts +23 -11
  309. package/src/schema.ts +256 -262
  310. package/src/server.ts +1 -1
  311. package/src/status-runner.ts +2 -1
  312. package/src/watcher.ts +46 -10
  313. package/dist/web/assets/index-BAEA18IR.js +0 -7838
  314. package/dist/web/assets/index-CwfFCUzZ.css +0 -1
package/src/schema.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { COMPONENT_NAMES, componentCatalog } from '@seeflow/canvas/catalog';
1
2
  import { z } from 'zod';
2
3
 
3
4
  const PositionSchema = z.object({
@@ -5,11 +6,8 @@ const PositionSchema = z.object({
5
6
  y: z.number(),
6
7
  });
7
8
 
8
- // US-008: HttpAction was the original shape for both playAction and resetAction
9
- // in pre-script-action releases. After US-001 cut playAction to script-only and
10
- // US-008 cut resetAction the same way, no schema in this file uses HttpAction
11
- // anymore. `HttpMethodSchema` is still used for the optional `method` field on
12
- // connectors (documentation metadata).
9
+ // `HttpMethodSchema` is documentation metadata on connectors. No node schema
10
+ // uses it PlayAction/StatusAction are script-based.
13
11
  const HttpMethodSchema = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
14
12
 
15
13
  // Curated palette tokens. Stored on disk as readable names; the frontend maps
@@ -26,7 +24,8 @@ export const ColorTokenSchema = z.enum([
26
24
  ]);
27
25
 
28
26
  // Visual fields shared by every node type (functional + decorative). All
29
- // optional — existing demo files predate them and must continue to parse.
27
+ // optional — every visual must work without per-field opinions. Live on
28
+ // resolved nodes; the disk-side flow.json strips them into style.json.
30
29
  const NodeVisualBaseShape = {
31
30
  width: z.number().positive().optional(),
32
31
  height: z.number().positive().optional(),
@@ -39,19 +38,20 @@ const NodeVisualBaseShape = {
39
38
  cornerRadius: z.number().min(0).optional(),
40
39
  };
41
40
 
42
- // Consolidated three-field metadata shared by every node variant. `description`
43
- // is the short body text rendered on the canvas under the node header (and as
44
- // light-bold text in the sidebar). `detail` is the long-form free-text body
45
- // rendered only in the sidebar. Both optional so unset fields round-trip
46
- // unchanged. Spread into every node-data schema below since Icon doesn't
47
- // spread NodeVisualBaseShape.
48
- const NodeDescriptionBaseShape = {
41
+ // Semantic-data fields shared by every node type. `name` is optional —
42
+ // every visual works without a label. `icon` is decorative-by-default; the
43
+ // `type:'icon'` variant overrides it to required.
44
+ const NodeSemanticBaseShape = {
45
+ name: z.string().optional(),
49
46
  description: z.string().optional(),
50
47
  detail: z.string().optional(),
48
+ // Decorative header glyph. Lucide icon name (kebab-case) resolved by the
49
+ // canvas <Icon> primitive; falls back to a placeholder when unknown.
50
+ icon: z.string().optional(),
51
51
  };
52
52
 
53
- // US-001: relative-path safety refine (textual). Mirrors the same rule used
54
- // for image/html-node paths further down. Realpath verification is layered on
53
+ // Relative-path safety refine (textual). Mirrors the same rule used for
54
+ // image-node and html-node script paths. Realpath verification is layered on
55
55
  // top by the proxy/status-runner before any spawn (symlink-escape defense).
56
56
  const isCleanRelativePath = (s: string): boolean => {
57
57
  if (s.length === 0) return false;
@@ -68,7 +68,7 @@ const isCleanRelativePath = (s: string): boolean => {
68
68
  // interpreter; `input` (optional) gets JSON-serialized and written to the
69
69
  // child's stdin then closed; `timeoutMs` caps execution (default applied at
70
70
  // the spawn layer, not here).
71
- const ScriptActionSchema = z.object({
71
+ export const ScriptActionSchema = z.object({
72
72
  kind: z.literal('script'),
73
73
  interpreter: z.string().min(1),
74
74
  args: z.array(z.string()).optional(),
@@ -81,11 +81,7 @@ const ScriptActionSchema = z.object({
81
81
 
82
82
  export const PlayActionSchema = ScriptActionSchema;
83
83
 
84
- // US-008: resetAction is a one-shot script action same shape as a play
85
- // script (interpreter + args + scriptPath + optional input/timeoutMs) but
86
- // invoked from the /reset endpoint. The studio kills every live play and
87
- // status script for the demo before running this script, so the running app
88
- // sees a clean baseline when wiping its state.
84
+ // resetAction is a one-shot script with the same shape as a play script.
89
85
  export const ResetActionSchema = ScriptActionSchema;
90
86
 
91
87
  // Long-running status script. Same spawn shape as ScriptAction (interpreter +
@@ -117,54 +113,23 @@ export const StateSourceSchema = z.discriminatedUnion('kind', [
117
113
  z.object({ kind: z.literal('event') }),
118
114
  ]);
119
115
 
120
- const NodeDataBaseSchema = z.object({
121
- name: z.string().min(1),
122
- stateSource: StateSourceSchema,
123
- // Reserved for v2: a module path resolved by future skills runtime.
124
- // Schema-only at v1 never read at runtime.
125
- handlerModule: z.string().optional(),
126
- // Decorative header glyph. Lucide icon name (kebab-case) resolved by the
127
- // canvas <Icon> primitive; falls back to a placeholder when unknown.
128
- icon: z.string().optional(),
129
- ...NodeVisualBaseShape,
130
- ...NodeDescriptionBaseShape,
131
- });
132
-
133
- const PlayNodeDataSchema = NodeDataBaseSchema.extend({
134
- playAction: PlayActionSchema,
135
- statusAction: StatusActionSchema.optional(),
136
- });
137
-
138
- const StateNodeDataSchema = NodeDataBaseSchema.extend({
116
+ // Capabilities any subset of these makes a node Playable / Stateful. All
117
+ // optional, valid on every node type. A node is Playable iff `playAction` is
118
+ // set; Stateful iff `statusAction` is set; Both iff both. `stateSource` is
119
+ // informational metadata that pairs with statusAction. `handlerModule` is
120
+ // reserved for a future skills runtime and is schema-only at v1.
121
+ const NodeCapabilitiesShape = {
139
122
  playAction: PlayActionSchema.optional(),
140
123
  statusAction: StatusActionSchema.optional(),
141
- });
142
-
143
- const NodeBaseShape = {
144
- id: z.string().min(1),
145
- position: PositionSchema,
124
+ stateSource: StateSourceSchema.optional(),
125
+ handlerModule: z.string().optional(),
146
126
  };
147
127
 
148
- const PlayNodeSchema = z.object({
149
- ...NodeBaseShape,
150
- type: z.literal('playNode'),
151
- data: PlayNodeDataSchema,
152
- });
153
-
154
- const StateNodeSchema = z.object({
155
- ...NodeBaseShape,
156
- type: z.literal('stateNode'),
157
- data: StateNodeDataSchema,
158
- });
159
-
160
- // Decorative annotation node — rectangle / ellipse / sticky. No semantic
161
- // payload (no kind/stateSource/playAction); reuses NodeVisualBaseShape so
162
- // users can theme it the same way as functional nodes.
163
- // US-009 added `database` as the first illustrative shape (cylinder rendered
164
- // via inline SVG inside shape-node.tsx). Illustrative shapes share the same
165
- // shapeNode wrapper and color/border fields but own their own visuals via a
166
- // per-shape component under `apps/web/src/components/nodes/shapes/`.
167
- export const ShapeKindSchema = z.enum([
128
+ // 12 flat node types. The first 9 are geometric/illustrative and share
129
+ // GeometricNodeData. `image`, `html`, `icon` carry per-type fields.
130
+ // The renderer picks the SVG / chrome by `type`; the schema treats them
131
+ // (apart from the per-type fields below) as identical.
132
+ export const GEOMETRIC_NODE_TYPES = [
168
133
  'rectangle',
169
134
  'ellipse',
170
135
  'sticky',
@@ -174,148 +139,170 @@ export const ShapeKindSchema = z.enum([
174
139
  'user',
175
140
  'queue',
176
141
  'cloud',
142
+ ] as const;
143
+
144
+ export const NodeTypeSchema = z.enum([
145
+ ...GEOMETRIC_NODE_TYPES,
146
+ 'image',
147
+ 'html',
148
+ 'icon',
149
+ 'component',
177
150
  ]);
178
151
 
179
- const ShapeNodeDataSchema = z.object({
180
- shape: ShapeKindSchema,
181
- name: z.string().optional(),
182
- ...NodeVisualBaseShape,
183
- ...NodeDescriptionBaseShape,
152
+ // --- Component node spec/action schemas --------------------------------------
153
+ // The 'component' node renders a json-render-driven reactive UI on the canvas.
154
+ // `spec` is the source of truth for layout + interactivity; on disk it lives at
155
+ // `<project>/nodes/<id>/spec.json` (the resolver inlines it into data.spec for
156
+ // ResolvedFlowSchema). Element types and props are catalog-validated by a
157
+ // superRefine wired in a later story.
158
+
159
+ export const ComponentSpecElementSchema = z.object({
160
+ type: z.string().min(1),
161
+ props: z.record(z.string(), z.unknown()).optional(),
162
+ children: z.array(z.string()).optional(),
163
+ watch: z.record(z.string(), z.unknown()).optional(),
164
+ });
165
+
166
+ // Declarative state mutation. `path` is a JSON Pointer (starts with '/');
167
+ // `value` may itself carry { $param } / { $state } refs resolved by the
168
+ // runtime at dispatch time.
169
+ const SetActionSchema = z.object({
170
+ kind: z.literal('set'),
171
+ path: z
172
+ .string()
173
+ .min(1)
174
+ .startsWith('/', { message: 'path must be a JSON Pointer (start with /)' }),
175
+ value: z.unknown(),
176
+ });
177
+
178
+ // Script-kind component actions reuse the existing ScriptActionSchema shape
179
+ // (interpreter, scriptPath, timeoutMs, ...). The action runner roots scriptPath
180
+ // under `<projectRoot>/nodes/<nodeId>/`.
181
+ export const ComponentActionSchema = z.discriminatedUnion('kind', [
182
+ SetActionSchema,
183
+ ScriptActionSchema,
184
+ ]);
185
+
186
+ export const ComponentSpecSchema = z.object({
187
+ root: z.string().min(1),
188
+ elements: z.record(z.string(), ComponentSpecElementSchema),
189
+ state: z.record(z.string(), z.unknown()).optional(),
190
+ actions: z.record(z.string(), ComponentActionSchema).optional(),
184
191
  });
185
192
 
186
- const ShapeNodeSchema = z.object({
187
- ...NodeBaseShape,
188
- type: z.literal('shapeNode'),
189
- data: ShapeNodeDataSchema,
193
+ export type ComponentSpec = z.infer<typeof ComponentSpecSchema>;
194
+ export type ComponentAction = z.infer<typeof ComponentActionSchema>;
195
+ export type ComponentSpecElement = z.infer<typeof ComponentSpecElementSchema>;
196
+
197
+ // ---- Resolved (in-memory) per-type data -------------------------------------
198
+
199
+ const ResolvedGeometricNodeData = z.object({
200
+ ...NodeSemanticBaseShape,
201
+ ...NodeVisualBaseShape,
202
+ ...NodeCapabilitiesShape,
190
203
  });
191
204
 
192
- // Decorative image node — references a file under the project root by
193
- // relative path (US-004 hard-cut from base64 data URLs to path-backed files).
194
- // `path` is a relative path under the project root for imageNode uploads:
195
- // no leading slash, no `..` segments. The renderer fetches via
196
- // `GET /api/projects/:id/files/:path`.
197
- const ImageNodeDataSchema = z.object({
205
+ // Image node — references a file under the project root by relative path.
206
+ // `path` is constrained to live under `nodes/<id>/` (post-validate refine on
207
+ // ResolvedFlowSchema below) so the delete_node cascade owns cleanup.
208
+ const ResolvedImageNodeData = z.object({
209
+ ...NodeSemanticBaseShape,
210
+ ...NodeVisualBaseShape,
211
+ ...NodeCapabilitiesShape,
198
212
  path: z.string().min(1).refine(isCleanRelativePath, {
199
213
  message: 'path must be a relative path under the project root (no absolute / traversal)',
200
214
  }),
201
215
  alt: z.string().optional(),
202
- ...NodeVisualBaseShape,
203
- ...NodeDescriptionBaseShape,
204
216
  borderWidth: z.number().min(1).max(8).optional(),
205
217
  });
206
218
 
207
- const ImageNodeSchema = z.object({
208
- ...NodeBaseShape,
209
- type: z.literal('imageNode'),
210
- data: ImageNodeDataSchema,
211
- });
212
-
213
- // US-011 (illustrative-shapes-htmlnode): htmlNode is the escape-hatch node type
214
- // for content the curated nodes don't cover — carries author-written HTML
215
- // inline via `data.html`. The studio externalizes the content to
216
- // `<project>/nodes/<id>/view.html` and stores a `file://` ref in flow.json;
217
- // the resolver inlines the content back on read so consumers see
218
- // the resolved HTML string. The renderer sanitizes before injection
219
- // (US-013/US-014). Spreads NodeVisualBaseShape so authors can theme the
220
- // wrapper (border / background / radius / font) with the same fields
221
- // available on every other visual node.
222
- export const HtmlNodeDataSchema = z.object({
219
+ // Html node — escape-hatch for content the curated visuals don't cover.
220
+ // `html` is externalized to `<project>/nodes/<id>/view.html` on write; the
221
+ // file-ref resolver inlines it back on read. The renderer sanitizes before
222
+ // injection. `autoSize:true` lets the renderer size around the content.
223
+ const ResolvedHtmlNodeData = z.object({
224
+ ...NodeSemanticBaseShape,
225
+ ...NodeVisualBaseShape,
226
+ ...NodeCapabilitiesShape,
223
227
  html: z.string().optional(),
224
- name: z.string().optional(),
225
- // Decorative caption glyph. Lucide icon name (kebab-case) resolved by the
226
- // canvas <Icon> primitive; rendered inline with the caption when set.
227
- icon: z.string().optional(),
228
- // When true (or absent), the renderer measures the HTML content and React
229
- // Flow sizes the wrapper around it (capped at 800×600 by the renderer's
230
- // measuring container styles). The studio adapter (`mergeNodeUpdates`)
231
- // enforces the invariant that `autoSize === true` and persisted
232
- // `width`/`height` never coexist: writing width/height flips autoSize to
233
- // false; writing autoSize: true strips width/height.
234
228
  autoSize: z.boolean().optional(),
235
- ...NodeVisualBaseShape,
236
- ...NodeDescriptionBaseShape,
237
- });
238
-
239
- const HtmlNodeSchema = z.object({
240
- ...NodeBaseShape,
241
- type: z.literal('htmlNode'),
242
- data: HtmlNodeDataSchema,
243
229
  });
244
230
 
245
- // Decorative icon node — renders a Lucide glyph on the canvas. Unboxed
246
- // (no border/cornerRadius/backgroundColor) so it does NOT spread
247
- // NodeVisualBaseShape; only `width` / `height` are reused for resizing.
248
- const IconNodeDataSchema = z.object({
231
+ // Icon node — renders a Lucide glyph as its main visual. `icon` is required
232
+ // here (overrides the optional decorative `icon` from NodeSemanticBaseShape).
233
+ const ResolvedIconNodeData = z.object({
234
+ ...NodeSemanticBaseShape,
235
+ ...NodeVisualBaseShape,
236
+ ...NodeCapabilitiesShape,
249
237
  icon: z.string().min(1),
250
238
  color: ColorTokenSchema.optional(),
251
239
  strokeWidth: z.number().min(0.5).max(4).optional(),
252
- width: z.number().positive().optional(),
253
- height: z.number().positive().optional(),
254
240
  alt: z.string().optional(),
255
- // US-002: optional visible caption rendered below the icon. Distinct from
256
- // `alt` (screen-reader text). Absent / empty → no caption rendered and the
257
- // node's bounding box is byte-identical to the unlabeled layout.
258
- name: z.string().optional(),
259
- ...NodeDescriptionBaseShape,
260
241
  });
261
242
 
262
- const IconNodeSchema = z.object({
263
- ...NodeBaseShape,
264
- type: z.literal('iconNode'),
265
- data: IconNodeDataSchema,
243
+ // Component node — `spec` is the json-render tree. On disk the spec lives in
244
+ // `<project>/nodes/<id>/spec.json`; the resolver inlines it into data.spec
245
+ // before ResolvedFlowSchema validates the merged shape. The on-disk
246
+ // FlowComponentNodeData below has no `spec` field.
247
+ const ResolvedComponentNodeData = z.object({
248
+ ...NodeSemanticBaseShape,
249
+ ...NodeVisualBaseShape,
250
+ ...NodeCapabilitiesShape,
251
+ spec: ComponentSpecSchema,
252
+ autoSize: z.boolean().optional(),
266
253
  });
267
254
 
255
+ const NodeBaseShape = {
256
+ id: z.string().min(1),
257
+ position: PositionSchema,
258
+ };
259
+
260
+ const makeResolvedGeometricSchema = (type: (typeof GEOMETRIC_NODE_TYPES)[number]) =>
261
+ z.object({
262
+ ...NodeBaseShape,
263
+ type: z.literal(type),
264
+ data: ResolvedGeometricNodeData,
265
+ });
266
+
268
267
  const NodeSchema = z.discriminatedUnion('type', [
269
- PlayNodeSchema,
270
- StateNodeSchema,
271
- ShapeNodeSchema,
272
- ImageNodeSchema,
273
- IconNodeSchema,
274
- HtmlNodeSchema,
268
+ makeResolvedGeometricSchema('rectangle'),
269
+ makeResolvedGeometricSchema('ellipse'),
270
+ makeResolvedGeometricSchema('sticky'),
271
+ makeResolvedGeometricSchema('text'),
272
+ makeResolvedGeometricSchema('database'),
273
+ makeResolvedGeometricSchema('server'),
274
+ makeResolvedGeometricSchema('user'),
275
+ makeResolvedGeometricSchema('queue'),
276
+ makeResolvedGeometricSchema('cloud'),
277
+ z.object({ ...NodeBaseShape, type: z.literal('image'), data: ResolvedImageNodeData }),
278
+ z.object({ ...NodeBaseShape, type: z.literal('html'), data: ResolvedHtmlNodeData }),
279
+ z.object({ ...NodeBaseShape, type: z.literal('icon'), data: ResolvedIconNodeData }),
280
+ z.object({
281
+ ...NodeBaseShape,
282
+ type: z.literal('component'),
283
+ data: ResolvedComponentNodeData,
284
+ }),
275
285
  ]);
276
286
 
277
- // Connector is the edge between two nodes. The frontend derives a React Flow
278
- // Edge from each connector at render time (id/source/target are reused;
279
- // `label` becomes the edge label; visual style comes from optional
280
- // `style`/`color` fields). v1 has no separate `edges[]` array — connectors
281
- // are the sole source of truth for inter-node connections. Optional
282
- // metadata fields (`method`/`url`/`eventName`/`queueName`) may be present
283
- // for documentation purposes; the renderer does not branch on them.
287
+ // Connector unchanged by the flat-types refactor.
284
288
  const ConnectorStyleSchema = z.enum(['solid', 'dashed', 'dotted']);
285
289
  const ConnectorDirectionSchema = z.enum(['forward', 'backward', 'both', 'none']);
286
- // Path geometry — orthogonal to `style` (which means the dash pattern). Absent
287
- // → renders as today's smooth bezier curve. 'step' renders as a smoothstep
288
- // (right-angle / zigzag) path. (US-017)
289
290
  const ConnectorPathSchema = z.enum(['curve', 'step']);
290
291
 
291
- // Visual fields shared by every connector. All optional — existing
292
- // demo files predate them and must continue to parse. `direction` defaults
293
- // to 'forward' when absent (the historical behavior).
294
292
  const ConnectorVisualBaseShape = {
295
293
  style: ConnectorStyleSchema.optional(),
296
294
  color: ColorTokenSchema.optional(),
297
295
  direction: ConnectorDirectionSchema.optional(),
298
296
  borderSize: z.number().positive().optional(),
299
297
  path: ConnectorPathSchema.optional(),
300
- // US-018: per-connector label font size in CSS pixels. Absent → fall back to
301
- // the editable-edge default (11px). Mirrors NodeVisualBaseShape.fontSize.
302
298
  fontSize: z.number().positive().optional(),
303
299
  };
304
300
 
305
- // Handle ids — every node type in this codebase uses the same four-handle
306
- // layout: target-only on top + left, source-only on right + bottom (US-013).
307
- // `sourceHandle` MUST be a source-side id and `targetHandle` MUST be a
308
- // target-side id; sending the wrong role leaves a stranded endpoint at render
309
- // time, so the schema rejects it (US-022).
301
+ // Handle ids — every node type uses the same four-handle layout:
302
+ // target-only on top + left, source-only on right + bottom.
310
303
  export const SourceHandleIdSchema = z.enum(['r', 'b']);
311
304
  export const TargetHandleIdSchema = z.enum(['t', 'l']);
312
305
 
313
- // US-006: pinned endpoint position. `side` names one of the four perimeter
314
- // sides of the connected node; `t` is the parameterized position along that
315
- // side, [0, 1], measured from the top-left corner of the side (top/bottom →
316
- // left-to-right; left/right → top-to-bottom). Pins are persisted so they
317
- // survive node moves and resizes without drifting toward the other endpoint's
318
- // center the way floating endpoints do.
319
306
  const EdgePinSideSchema = z.enum(['top', 'right', 'bottom', 'left']);
320
307
  export const EdgePinSchema = z.object({
321
308
  side: EdgePinSideSchema,
@@ -326,21 +313,10 @@ const ConnectorBaseShape = {
326
313
  id: z.string().min(1),
327
314
  source: z.string().min(1),
328
315
  target: z.string().min(1),
329
- // Optional — connectors authored before the four-handle layout omit them and
330
- // React Flow falls back to the first matching handle.
331
316
  sourceHandle: SourceHandleIdSchema.optional(),
332
317
  targetHandle: TargetHandleIdSchema.optional(),
333
- // US-021: tracks whether each endpoint's handle was auto-picked by the
334
- // facing-handle picker (true) or pinned by an explicit user handle drop
335
- // (false / absent). Auto-picked endpoints get re-routed when nodes move so
336
- // the connector keeps facing the other end; user-pinned ones never do.
337
318
  sourceHandleAutoPicked: z.boolean().optional(),
338
319
  targetHandleAutoPicked: z.boolean().optional(),
339
- // US-006: optional explicit perimeter positions for each endpoint. When
340
- // set, the endpoint is computed from `(side, t)` against the connected
341
- // node's current bbox at render time — the position parameterizes with the
342
- // node so the pin survives moves and resizes. Absent → floating /
343
- // handle-based endpoint behavior (back-compat).
344
320
  sourcePin: EdgePinSchema.optional(),
345
321
  targetPin: EdgePinSchema.optional(),
346
322
  label: z.string().optional(),
@@ -363,9 +339,8 @@ export const ResolvedFlowSchema = z
363
339
  nodes: z.array(NodeSchema),
364
340
  connectors: z.array(ConnectorSchema),
365
341
  // Optional one-shot script the studio runs when the user clicks Restart.
366
- // Lets the running app wipe its own in-memory state. The studio kills
367
- // every live play + status script for the flow BEFORE invoking this
368
- // script (US-008), so the script sees no stragglers.
342
+ // The studio kills every live play + status script for the flow BEFORE
343
+ // invoking this script, so the script sees no stragglers.
369
344
  resetAction: ResetActionSchema.optional(),
370
345
  })
371
346
  .superRefine((resolved, ctx) => {
@@ -386,31 +361,54 @@ export const ResolvedFlowSchema = z
386
361
  });
387
362
  }
388
363
  });
389
- // imageNode upload paths must live under the node's own
364
+ // type:'image' upload paths must live under the node's own
390
365
  // `nodes/<id>/` folder so delete_node's removeNodeDir cascade is the
391
366
  // single source of cleanup.
392
367
  resolved.nodes.forEach((node, idx) => {
393
- if (node.type !== 'imageNode') return;
368
+ if (node.type !== 'image') return;
394
369
  const path = (node.data as { path?: string }).path;
395
370
  const expected = `nodes/${node.id}/`;
396
371
  if (typeof path === 'string' && !path.startsWith(expected)) {
397
372
  ctx.addIssue({
398
373
  code: z.ZodIssueCode.custom,
399
374
  path: ['nodes', idx, 'data', 'path'],
400
- message: `imageNode path must start with "${expected}"`,
375
+ message: `image node path must start with "${expected}"`,
401
376
  });
402
377
  }
403
378
  });
379
+ // type:'component' spec.elements entries are catalog-validated here so
380
+ // unknown component names and prop shape mismatches surface at flow-read
381
+ // time with paths pointing into the offending element.
382
+ resolved.nodes.forEach((node, idx) => {
383
+ if (node.type !== 'component') return;
384
+ const elements = node.data.spec.elements;
385
+ for (const [elId, entry] of Object.entries(elements)) {
386
+ const def = componentCatalog.components[entry.type];
387
+ if (!def) {
388
+ ctx.addIssue({
389
+ code: z.ZodIssueCode.custom,
390
+ path: ['nodes', idx, 'data', 'spec', 'elements', elId, 'type'],
391
+ message: `Unknown component type "${entry.type}". Valid names: ${COMPONENT_NAMES.join(', ')}`,
392
+ });
393
+ continue;
394
+ }
395
+ const propsResult = def.props.safeParse(entry.props ?? {});
396
+ if (!propsResult.success) {
397
+ for (const issue of propsResult.error.issues) {
398
+ ctx.addIssue({
399
+ code: z.ZodIssueCode.custom,
400
+ path: ['nodes', idx, 'data', 'spec', 'elements', elId, 'props', ...issue.path],
401
+ message: issue.message,
402
+ });
403
+ }
404
+ }
405
+ }
406
+ });
404
407
  });
405
408
 
406
409
  export type ResolvedFlow = z.infer<typeof ResolvedFlowSchema>;
407
410
  export type ResolvedFlowNode = z.infer<typeof NodeSchema>;
408
- export type ShapeNode = z.infer<typeof ShapeNodeSchema>;
409
- export type ImageNode = z.infer<typeof ImageNodeSchema>;
410
- export type IconNode = z.infer<typeof IconNodeSchema>;
411
- export type HtmlNode = z.infer<typeof HtmlNodeSchema>;
412
- export type HtmlNodeData = z.infer<typeof HtmlNodeDataSchema>;
413
- export type ShapeKind = z.infer<typeof ShapeKindSchema>;
411
+ export type NodeType = z.infer<typeof NodeTypeSchema>;
414
412
  export type ColorToken = z.infer<typeof ColorTokenSchema>;
415
413
  export type Connector = z.infer<typeof ConnectorSchema>;
416
414
  export type ConnectorStyle = z.infer<typeof ConnectorStyleSchema>;
@@ -429,63 +427,50 @@ export type StateSource = z.infer<typeof StateSourceSchema>;
429
427
  // What lives on disk in <project>/flow.json after the split.
430
428
  // =============================================================================
431
429
 
432
- const FlowNodeDataBaseShape = {
433
- name: z.string().min(1),
434
- stateSource: StateSourceSchema,
435
- handlerModule: z.string().optional(),
436
- icon: z.string().optional(),
437
- ...NodeDescriptionBaseShape,
438
- };
439
-
440
- const FlowPlayNodeDataSchema = z
441
- .object({
442
- ...FlowNodeDataBaseShape,
443
- playAction: PlayActionSchema,
444
- statusAction: StatusActionSchema.optional(),
445
- })
446
- .strict();
447
-
448
- const FlowStateNodeDataSchema = z
449
- .object({
450
- ...FlowNodeDataBaseShape,
451
- playAction: PlayActionSchema.optional(),
452
- statusAction: StatusActionSchema.optional(),
453
- })
454
- .strict();
455
-
456
- const FlowShapeNodeDataSchema = z
430
+ const FlowGeometricNodeData = z
457
431
  .object({
458
- shape: ShapeKindSchema,
459
- name: z.string().optional(),
460
- ...NodeDescriptionBaseShape,
432
+ ...NodeSemanticBaseShape,
433
+ ...NodeCapabilitiesShape,
461
434
  })
462
435
  .strict();
463
436
 
464
- const FlowImageNodeDataSchema = z
437
+ const FlowImageNodeData = z
465
438
  .object({
439
+ ...NodeSemanticBaseShape,
440
+ ...NodeCapabilitiesShape,
466
441
  path: z.string().min(1).refine(isCleanRelativePath, {
467
442
  message: 'path must be a relative path under the project root (no absolute / traversal)',
468
443
  }),
469
444
  alt: z.string().optional(),
470
- ...NodeDescriptionBaseShape,
471
445
  })
472
446
  .strict();
473
447
 
474
- const FlowIconNodeDataSchema = z
448
+ const FlowHtmlNodeData = z
449
+ .object({
450
+ ...NodeSemanticBaseShape,
451
+ ...NodeCapabilitiesShape,
452
+ html: z.string().optional(),
453
+ })
454
+ .strict();
455
+
456
+ const FlowIconNodeData = z
475
457
  .object({
458
+ ...NodeSemanticBaseShape,
459
+ ...NodeCapabilitiesShape,
476
460
  icon: z.string().min(1),
477
461
  alt: z.string().optional(),
478
- name: z.string().optional(),
479
- ...NodeDescriptionBaseShape,
480
462
  })
481
463
  .strict();
482
464
 
483
- const FlowHtmlNodeDataSchema = z
465
+ // Component node, on-disk shape. `spec` is intentionally absent — the sidecar
466
+ // `<project>/nodes/<id>/spec.json` is the source of truth. `.strict()` rejects
467
+ // any stray spec field that slips through (the resolver layer is responsible
468
+ // for inlining + the writer for stripping it back out).
469
+ const FlowComponentNodeData = z
484
470
  .object({
485
- html: z.string().optional(),
486
- name: z.string().optional(),
487
- icon: z.string().optional(),
488
- ...NodeDescriptionBaseShape,
471
+ ...NodeSemanticBaseShape,
472
+ ...NodeCapabilitiesShape,
473
+ autoSize: z.boolean().optional(),
489
474
  })
490
475
  .strict();
491
476
 
@@ -493,61 +478,71 @@ const FlowNodeBaseShape = {
493
478
  id: z.string().min(1),
494
479
  };
495
480
 
496
- export const FlowPlayNodeSchema = z
497
- .object({
498
- ...FlowNodeBaseShape,
499
- type: z.literal('playNode'),
500
- data: FlowPlayNodeDataSchema,
501
- })
502
- .strict();
503
-
504
- export const FlowStateNodeSchema = z
505
- .object({
506
- ...FlowNodeBaseShape,
507
- type: z.literal('stateNode'),
508
- data: FlowStateNodeDataSchema,
509
- })
510
- .strict();
481
+ const makeFlowGeometricSchema = (type: (typeof GEOMETRIC_NODE_TYPES)[number]) =>
482
+ z
483
+ .object({
484
+ ...FlowNodeBaseShape,
485
+ type: z.literal(type),
486
+ data: FlowGeometricNodeData,
487
+ })
488
+ .strict();
489
+
490
+ export const FlowRectangleNodeSchema = makeFlowGeometricSchema('rectangle');
491
+ export const FlowEllipseNodeSchema = makeFlowGeometricSchema('ellipse');
492
+ export const FlowStickyNodeSchema = makeFlowGeometricSchema('sticky');
493
+ export const FlowTextNodeSchema = makeFlowGeometricSchema('text');
494
+ export const FlowDatabaseNodeSchema = makeFlowGeometricSchema('database');
495
+ export const FlowServerNodeSchema = makeFlowGeometricSchema('server');
496
+ export const FlowUserNodeSchema = makeFlowGeometricSchema('user');
497
+ export const FlowQueueNodeSchema = makeFlowGeometricSchema('queue');
498
+ export const FlowCloudNodeSchema = makeFlowGeometricSchema('cloud');
511
499
 
512
- export const FlowShapeNodeSchema = z
500
+ export const FlowImageNodeSchema = z
513
501
  .object({
514
502
  ...FlowNodeBaseShape,
515
- type: z.literal('shapeNode'),
516
- data: FlowShapeNodeDataSchema,
503
+ type: z.literal('image'),
504
+ data: FlowImageNodeData,
517
505
  })
518
506
  .strict();
519
507
 
520
- export const FlowImageNodeSchema = z
508
+ export const FlowHtmlNodeSchema = z
521
509
  .object({
522
510
  ...FlowNodeBaseShape,
523
- type: z.literal('imageNode'),
524
- data: FlowImageNodeDataSchema,
511
+ type: z.literal('html'),
512
+ data: FlowHtmlNodeData,
525
513
  })
526
514
  .strict();
527
515
 
528
516
  export const FlowIconNodeSchema = z
529
517
  .object({
530
518
  ...FlowNodeBaseShape,
531
- type: z.literal('iconNode'),
532
- data: FlowIconNodeDataSchema,
519
+ type: z.literal('icon'),
520
+ data: FlowIconNodeData,
533
521
  })
534
522
  .strict();
535
523
 
536
- export const FlowHtmlNodeSchema = z
524
+ export const FlowComponentNodeSchema = z
537
525
  .object({
538
526
  ...FlowNodeBaseShape,
539
- type: z.literal('htmlNode'),
540
- data: FlowHtmlNodeDataSchema,
527
+ type: z.literal('component'),
528
+ data: FlowComponentNodeData,
541
529
  })
542
530
  .strict();
543
531
 
544
532
  const FlowNodeSchema = z.discriminatedUnion('type', [
545
- FlowPlayNodeSchema,
546
- FlowStateNodeSchema,
547
- FlowShapeNodeSchema,
533
+ FlowRectangleNodeSchema,
534
+ FlowEllipseNodeSchema,
535
+ FlowStickyNodeSchema,
536
+ FlowTextNodeSchema,
537
+ FlowDatabaseNodeSchema,
538
+ FlowServerNodeSchema,
539
+ FlowUserNodeSchema,
540
+ FlowQueueNodeSchema,
541
+ FlowCloudNodeSchema,
548
542
  FlowImageNodeSchema,
549
- FlowIconNodeSchema,
550
543
  FlowHtmlNodeSchema,
544
+ FlowIconNodeSchema,
545
+ FlowComponentNodeSchema,
551
546
  ]);
552
547
 
553
548
  const FlowConnectorBaseShape = {
@@ -602,11 +597,10 @@ export type FlowNode = z.infer<typeof FlowNodeSchema>;
602
597
  export type FlowConnector = z.infer<typeof FlowConnectorSchema>;
603
598
 
604
599
  // Envelope-only flow shape for the `seeflow schema flow` surface. The full
605
- // FlowSchema validates the whole graph; this companion schema describes the
606
- // top-level shape without inlining every node variant or the connector
607
- // shape, so the runtime-introspectable JSON Schema stays compact. Authors
608
- // drill into `seeflow schema node` / `seeflow schema connector` for the
609
- // detailed shapes. Not used for validation — only the catalog reads it.
600
+ // FlowSchema validates the whole graph; this companion describes the top-level
601
+ // shape without inlining every node variant or the connector shape, so the
602
+ // runtime-introspectable JSON Schema stays compact. Authors drill into
603
+ // `seeflow schema node` / `seeflow schema connector` for the detailed shapes.
610
604
  export const FlowEnvelopeSchema = z
611
605
  .object({
612
606
  version: z.literal(2),
@@ -635,12 +629,12 @@ const NodeStyleSchema = z
635
629
  fontSize: z.number().positive().optional(),
636
630
  textColor: ColorTokenSchema.optional(),
637
631
  cornerRadius: z.number().min(0).optional(),
638
- // imageNode-specific
632
+ // type:'image'-specific
639
633
  borderWidth: z.number().min(1).max(8).optional(),
640
- // iconNode-specific
634
+ // type:'icon'-specific
641
635
  color: ColorTokenSchema.optional(),
642
636
  strokeWidth: z.number().min(0.5).max(4).optional(),
643
- // htmlNode-specific
637
+ // type:'html'-specific
644
638
  autoSize: z.boolean().optional(),
645
639
  })
646
640
  .strict();