@tuongaz/seeflow 0.1.65 → 0.1.69

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 (303) 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-DkO2vf4F.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-DU-QlURD.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-Cdou6O-P.css +1 -0
  115. package/dist/web/assets/index-WByZ8Huv.js +8613 -0
  116. package/dist/web/assets/{index.es-CVm3MRo3.js → index.es-BPwqcM9N.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-C06OvDJX.js → jspdf.es.min-BiZ0aMok.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-UIAJJxZW.js +1 -0
  148. package/dist/web/assets/markdown-ZXLU2tih.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 +50 -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/package.json +2 -1
  293. package/src/api.ts +54 -0
  294. package/src/cli.ts +1 -0
  295. package/src/component-action-runner.ts +188 -0
  296. package/src/component-spec-resolver.ts +60 -0
  297. package/src/layout.ts +1 -0
  298. package/src/merge.ts +5 -0
  299. package/src/operations.ts +46 -4
  300. package/src/schema.ts +140 -5
  301. package/src/watcher.ts +39 -3
  302. package/dist/web/assets/index-CwfFCUzZ.css +0 -1
  303. package/dist/web/assets/index-DL6aaddE.js +0 -7838
package/src/api.ts CHANGED
@@ -3,6 +3,7 @@ import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
3
3
  import { Hono } from 'hono';
4
4
  import { streamSSE } from 'hono/streaming';
5
5
  import { z } from 'zod';
6
+ import { runComponentAction } from './component-action-runner.ts';
6
7
  import {
7
8
  AssembleRequestSchema,
8
9
  ProposeScopeRequestSchema,
@@ -38,6 +39,7 @@ import {
38
39
  } from './proxy.ts';
39
40
  import type { Registry } from './registry.ts';
40
41
  import { getSchemaCategory, listSchemaCategories, schemaCategoryNames } from './schema-catalog.ts';
42
+ import type { ComponentAction } from './schema.ts';
41
43
  import { FlowSchema, ResolvedFlowSchema } from './schema.ts';
42
44
  import { type Spawner, defaultSpawner } from './shellout.ts';
43
45
  import { ID_TYPES, MAX_ID_COUNT, generateIds, isIdType } from './short-id.ts';
@@ -850,6 +852,58 @@ export function createApi(options: ApiOptions): Hono {
850
852
  return c.json(result);
851
853
  });
852
854
 
855
+ // POST /api/flows/:id/nodes/:nodeId/actions/:name — dispatch a component
856
+ // node's named action over HTTP. Only `script`-kind actions cross this seam;
857
+ // `set`-kind actions mutate canvas state locally and never round-trip
858
+ // through the API (the runner rejects them with statusHint 400).
859
+ // Payload is the JSON request body (defaults to {} on parse failure) and is
860
+ // piped to the script's stdin by `runComponentAction`. Response is the
861
+ // script's parsed JSON stdout on success.
862
+ api.post('/flows/:id/nodes/:nodeId/actions/:name', async (c) => {
863
+ const id = c.req.param('id');
864
+ const nodeId = c.req.param('nodeId');
865
+ const actionName = c.req.param('name');
866
+
867
+ const entry = registry.getById(id);
868
+ if (!entry) return c.json({ error: 'unknown demo' }, 404);
869
+ if (!events) return c.json({ error: 'events not enabled' }, 500);
870
+
871
+ const fullPath = resolveFilePath(entry.repoPath, entry.flowPath);
872
+ if (!existsSync(fullPath)) {
873
+ return c.json({ error: `Flow file not found: ${fullPath}` }, 404);
874
+ }
875
+ const merged = readMergedFlow(fullPath);
876
+ if (!merged.flow) {
877
+ return c.json({ error: merged.error ?? 'Flow read failed' }, 400);
878
+ }
879
+
880
+ const node = merged.flow.nodes.find((n) => n.id === nodeId);
881
+ if (!node) return c.json({ error: `Unknown nodeId: ${nodeId}` }, 404);
882
+ if (node.type !== 'component') {
883
+ return c.json({ error: `Node ${nodeId} is not a component node` }, 400);
884
+ }
885
+
886
+ const action = (node.data as { spec: { actions?: Record<string, ComponentAction> } }).spec
887
+ .actions?.[actionName];
888
+ if (!action) return c.json({ error: `Unknown action: ${actionName}` }, 404);
889
+
890
+ const payload = await c.req.json().catch(() => ({}));
891
+ const result = await runComponentAction({
892
+ events,
893
+ flowId: id,
894
+ nodeId,
895
+ cwd: entry.repoPath,
896
+ actionName,
897
+ action,
898
+ payload,
899
+ spawner: processSpawner,
900
+ });
901
+ if (!result.ok) {
902
+ return c.json({ error: result.error }, result.statusHint as 400 | 404 | 500 | 504);
903
+ }
904
+ return c.json(result.body);
905
+ });
906
+
853
907
  // POST /api/flows/:id/reset — the "Restart demo" workflow (US-008). Order:
854
908
  // 1. Stop every live play-script + every long-running status-script for
855
909
  // this demo in parallel — both must complete before any reset script
package/src/cli.ts CHANGED
@@ -350,6 +350,7 @@ async function runStart() {
350
350
  async function seedExamples(registry: Registry) {
351
351
  await seedExample(registry, 'order-pipeline');
352
352
  await seedExample(registry, 'ecommerce-platform');
353
+ await seedExample(registry, 'component-showcase');
353
354
  }
354
355
 
355
356
  async function seedExample(registry: Registry, exampleName: string) {
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Dispatches a component node's `script`-kind ComponentAction over HTTP:
3
+ * resolves `scriptPath` under `<cwd>/nodes/<nodeId>/` with the same realpath
4
+ * escape check used by `runPlay` (see proxy.ts), spawns the interpreter via the
5
+ * injectable `ProcessSpawner` seam, pipes the request payload to stdin as JSON,
6
+ * and parses stdout back as JSON (falling back to the raw string).
7
+ *
8
+ * `set`-kind actions are intentionally rejected with statusHint 400: those
9
+ * mutate canvas state locally and never round-trip through the API. The runner
10
+ * is the single seam the API route calls; HTTP status mapping lives in the
11
+ * route handler via `statusHint`.
12
+ */
13
+
14
+ import { realpathSync } from 'node:fs';
15
+ import { join, resolve, sep } from 'node:path';
16
+ import type { EventBus } from './events.ts';
17
+ import { type ProcessSpawner, type SpawnHandle, defaultProcessSpawner } from './process-spawner.ts';
18
+ import type { ComponentAction } from './schema.ts';
19
+ import { shortId } from './short-id.ts';
20
+
21
+ const DEFAULT_TIMEOUT_MS = 5_000;
22
+ const SIGKILL_GRACE_MS = 2_000;
23
+ const SCRIPT_PATH_ESCAPE = 'scriptPath escapes node root';
24
+ const SET_KIND_REJECTION = 'Only script actions are dispatched over HTTP';
25
+
26
+ export interface RunComponentActionOptions {
27
+ events: EventBus;
28
+ flowId: string;
29
+ nodeId: string;
30
+ /** Project root (`<repoPath>`). Script resolves under `<cwd>/nodes/<nodeId>/`. */
31
+ cwd: string;
32
+ actionName: string;
33
+ action: ComponentAction;
34
+ payload: unknown;
35
+ /** Injectable for tests; defaults to `defaultProcessSpawner`. */
36
+ spawner?: ProcessSpawner;
37
+ }
38
+
39
+ export interface ComponentActionResult {
40
+ ok: boolean;
41
+ body?: unknown;
42
+ error?: string;
43
+ /** Suggested HTTP status for the API handler. */
44
+ statusHint: number;
45
+ }
46
+
47
+ type Resolved = { ok: true; absPath: string } | { ok: false };
48
+
49
+ // Resolve `<cwd>/nodes/<nodeId>/<scriptPath>` and verify via realpath it stays
50
+ // inside the node folder. Mirrors proxy.ts:resolveScript — symlink-escape
51
+ // defense in line with `resolveProjectFile` in api.ts.
52
+ function resolveScript(cwd: string, nodeId: string, scriptPath: string): Resolved {
53
+ const nodeRoot = join(cwd, 'nodes', nodeId);
54
+ let realRoot: string;
55
+ try {
56
+ realRoot = realpathSync(nodeRoot);
57
+ } catch {
58
+ return { ok: false };
59
+ }
60
+ const target = resolve(nodeRoot, scriptPath);
61
+ let realTarget: string;
62
+ try {
63
+ realTarget = realpathSync(target);
64
+ } catch {
65
+ return { ok: false };
66
+ }
67
+ const rootWithSep = realRoot.endsWith(sep) ? realRoot : realRoot + sep;
68
+ if (realTarget !== realRoot && !realTarget.startsWith(rootWithSep)) {
69
+ return { ok: false };
70
+ }
71
+ return { ok: true, absPath: realTarget };
72
+ }
73
+
74
+ // Copy `process.env` into a string-only record, then layer the per-run extras.
75
+ // Bun.spawn's env contract is `Record<string, string>` so the undefineds that
76
+ // `process.env` advertises in its type must be filtered out first.
77
+ function buildChildEnv(extra: Record<string, string>): Record<string, string> {
78
+ const env: Record<string, string> = {};
79
+ for (const [k, v] of Object.entries(process.env)) {
80
+ if (typeof v === 'string') env[k] = v;
81
+ }
82
+ return { ...env, ...extra };
83
+ }
84
+
85
+ async function writeStdinPayload(handle: SpawnHandle, payload: unknown): Promise<void> {
86
+ if (!handle.stdin) return;
87
+ const writer = handle.stdin.getWriter();
88
+ try {
89
+ await writer.write(new TextEncoder().encode(JSON.stringify(payload)));
90
+ } finally {
91
+ await writer.close().catch(() => {
92
+ /* stdin already closed by child — not fatal */
93
+ });
94
+ }
95
+ }
96
+
97
+ async function killWithGrace(handle: SpawnHandle): Promise<void> {
98
+ handle.kill('SIGTERM');
99
+ let graceTimer: ReturnType<typeof setTimeout> | undefined;
100
+ const gracePromise = new Promise<'grace'>((res) => {
101
+ graceTimer = setTimeout(() => res('grace'), SIGKILL_GRACE_MS);
102
+ });
103
+ const winner = await Promise.race([handle.exited.then(() => 'exited' as const), gracePromise]);
104
+ if (graceTimer) clearTimeout(graceTimer);
105
+ if (winner === 'grace') {
106
+ handle.kill('SIGKILL');
107
+ await handle.exited;
108
+ }
109
+ }
110
+
111
+ export async function runComponentAction(
112
+ opts: RunComponentActionOptions,
113
+ ): Promise<ComponentActionResult> {
114
+ if (opts.action.kind !== 'script') {
115
+ return { ok: false, error: SET_KIND_REJECTION, statusHint: 400 };
116
+ }
117
+ const spawner = opts.spawner ?? defaultProcessSpawner;
118
+ const resolved = resolveScript(opts.cwd, opts.nodeId, opts.action.scriptPath);
119
+ if (!resolved.ok) {
120
+ return { ok: false, error: SCRIPT_PATH_ESCAPE, statusHint: 400 };
121
+ }
122
+
123
+ const env = buildChildEnv({
124
+ SEEFLOW_DEMO_ID: opts.flowId,
125
+ SEEFLOW_NODE_ID: opts.nodeId,
126
+ SEEFLOW_ACTION_NAME: opts.actionName,
127
+ SEEFLOW_RUN_ID: shortId(),
128
+ });
129
+
130
+ let handle: SpawnHandle;
131
+ try {
132
+ handle = spawner.spawn({
133
+ cmd: [opts.action.interpreter, ...(opts.action.args ?? []), resolved.absPath],
134
+ cwd: opts.cwd,
135
+ env,
136
+ stdin: 'pipe',
137
+ });
138
+ } catch (err) {
139
+ const message = err instanceof Error ? err.message : String(err);
140
+ return { ok: false, error: message, statusHint: 500 };
141
+ }
142
+
143
+ // Drain stdout AND stderr CONCURRENTLY with the process running so OS pipe
144
+ // buffers (~64 KB) don't fill up and deadlock the child.
145
+ const stdoutPromise = new Response(handle.stdout).text();
146
+ const stderrPromise = new Response(handle.stderr).text();
147
+
148
+ // Write stdin and close BEFORE awaiting exit (otherwise a child blocked on
149
+ // `read(stdin)` and a parent blocked on `exited` deadlock each other).
150
+ await writeStdinPayload(handle, opts.payload);
151
+
152
+ const timeoutMs = opts.action.timeoutMs ?? DEFAULT_TIMEOUT_MS;
153
+ let timer: ReturnType<typeof setTimeout> | undefined;
154
+ const timeoutPromise = new Promise<'timeout'>((res) => {
155
+ timer = setTimeout(() => res('timeout'), timeoutMs);
156
+ });
157
+ const exitPromise = handle.exited.then((code) => ({ code }) as const);
158
+
159
+ const race = await Promise.race([exitPromise, timeoutPromise]);
160
+ if (timer) clearTimeout(timer);
161
+
162
+ if (race === 'timeout') {
163
+ await killWithGrace(handle);
164
+ await Promise.allSettled([stdoutPromise, stderrPromise]);
165
+ return {
166
+ ok: false,
167
+ error: `action timed out after ${timeoutMs}ms`,
168
+ statusHint: 504,
169
+ };
170
+ }
171
+
172
+ const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise]);
173
+ if (race.code !== 0) {
174
+ return {
175
+ ok: false,
176
+ error: stderr.trim() || `exit ${race.code}`,
177
+ statusHint: 500,
178
+ };
179
+ }
180
+
181
+ let body: unknown;
182
+ try {
183
+ body = JSON.parse(stdout);
184
+ } catch {
185
+ body = stdout;
186
+ }
187
+ return { ok: true, body, statusHint: 200 };
188
+ }
@@ -0,0 +1,60 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import type { ResolvedFlow } from './schema.ts';
4
+
5
+ export interface SpecInlineError {
6
+ /** Logical path into the merged flow shape, like 'nodes/<id>/data/spec'. */
7
+ path: string;
8
+ message: string;
9
+ }
10
+
11
+ export interface InlineComponentSpecsResult {
12
+ flow: ResolvedFlow;
13
+ errors: SpecInlineError[];
14
+ /** Project-root-relative paths the watcher should track for live reload. */
15
+ refs: string[];
16
+ }
17
+
18
+ /**
19
+ * For every `'component'` node in `flow`, read `nodes/<id>/spec.json`,
20
+ * JSON.parse, and attach the result as `data.spec`. Missing files surface
21
+ * as a SpecInlineError; malformed JSON surfaces likewise. Non-component
22
+ * nodes pass through untouched.
23
+ *
24
+ * Returns a NEW flow object (no mutation of the input) so the watcher's
25
+ * snapshot caching stays safe.
26
+ */
27
+ export function inlineComponentSpecs(
28
+ flow: ResolvedFlow,
29
+ projectRoot: string,
30
+ ): InlineComponentSpecsResult {
31
+ const errors: SpecInlineError[] = [];
32
+ const refs: string[] = [];
33
+
34
+ const nodes = flow.nodes.map((node) => {
35
+ if (node.type !== 'component') return node;
36
+ const relPath = `nodes/${node.id}/spec.json`;
37
+ const absPath = join(projectRoot, relPath);
38
+ if (!existsSync(absPath)) {
39
+ errors.push({
40
+ path: `nodes/${node.id}/data/spec`,
41
+ message: `Missing spec file: ${relPath}`,
42
+ });
43
+ return node;
44
+ }
45
+ let parsed: unknown;
46
+ try {
47
+ parsed = JSON.parse(readFileSync(absPath, 'utf8'));
48
+ } catch (err) {
49
+ errors.push({
50
+ path: `nodes/${node.id}/data/spec`,
51
+ message: `Invalid JSON in ${relPath}: ${err instanceof Error ? err.message : String(err)}`,
52
+ });
53
+ return node;
54
+ }
55
+ refs.push(relPath);
56
+ return { ...node, data: { ...node.data, spec: parsed } } as typeof node;
57
+ });
58
+
59
+ return { flow: { ...flow, nodes } as ResolvedFlow, errors, refs };
60
+ }
package/src/layout.ts CHANGED
@@ -76,6 +76,7 @@ const DEFAULT_DIMENSIONS: Record<FlowNode['type'], { width: number; height: numb
76
76
  image: { width: 200, height: 150 },
77
77
  html: { width: 320, height: 200 },
78
78
  icon: { width: 80, height: 80 },
79
+ component: { width: 320, height: 240 },
79
80
  };
80
81
 
81
82
  // Sticky / text variants are floating annotations. They never participate in
package/src/merge.ts CHANGED
@@ -67,6 +67,7 @@ const NODE_STYLE_KEYS = new Set([
67
67
  'fontSize',
68
68
  'textColor',
69
69
  'cornerRadius',
70
+ 'shadow',
70
71
  'borderWidth',
71
72
  'color',
72
73
  'strokeWidth',
@@ -133,6 +134,10 @@ export function splitFlow(resolved: {
133
134
  const flowData: Record<string, unknown> = {};
134
135
  for (const [k, v] of Object.entries(data)) {
135
136
  if (v === undefined) continue;
137
+ // Component nodes externalize `spec` to <project>/nodes/<id>/spec.json
138
+ // (the sidecar). Drop it from flow.json so the strict on-disk schema
139
+ // doesn't reject it and the spec stays single-sourced on disk.
140
+ if (node.type === 'component' && k === 'spec') continue;
136
141
  if (NODE_DATA_FLOW_KEYS.has(k)) {
137
142
  flowData[k] = v;
138
143
  } else if (NODE_STYLE_KEYS.has(k)) {
package/src/operations.ts CHANGED
@@ -11,6 +11,7 @@ import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSyn
11
11
  import { dirname, isAbsolute, join } from 'node:path';
12
12
  import { type ZodIssue, z } from 'zod';
13
13
  import { writeFileAtomic } from './atomic-write.ts';
14
+ import { inlineComponentSpecs } from './component-spec-resolver.ts';
14
15
  import { type LayoutOptions, computeLayout } from './layout.ts';
15
16
  import { mergeFlowAndStyle, splitFlow } from './merge.ts';
16
17
  import {
@@ -24,6 +25,7 @@ import {
24
25
  import type { Registry } from './registry.ts';
25
26
  import {
26
27
  ColorTokenSchema,
28
+ ComponentSpecSchema,
27
29
  EdgePinSchema,
28
30
  type Flow,
29
31
  FlowSchema,
@@ -103,6 +105,7 @@ export const NodePatchBodySchema = z
103
105
  fontSize: z.number().positive().optional(),
104
106
  textColor: ColorTokenSchema.optional(),
105
107
  cornerRadius: z.number().min(0).optional(),
108
+ shadow: z.number().int().min(0).max(5).optional(),
106
109
  width: z.number().positive().optional(),
107
110
  height: z.number().positive().optional(),
108
111
  // type:'html'-only: when true, the renderer measures content and React Flow
@@ -141,6 +144,12 @@ export const NodePatchBodySchema = z
141
144
  playAction: PlayActionSchema.optional(),
142
145
  statusAction: StatusActionSchema.optional(),
143
146
  stateSource: StateSourceSchema.optional(),
147
+ // type:'component'-only: json-render spec describing the reactive UI.
148
+ // Externalized to `<project>/nodes/<id>/spec.json` by patchNodeImpl; the
149
+ // in-memory ResolvedFlow keeps `data.spec` populated for the post-merge
150
+ // reparse + SSE broadcast, but splitFlow strips it from flow.json so the
151
+ // sidecar is the source of truth on disk.
152
+ spec: ComponentSpecSchema.optional(),
144
153
  })
145
154
  .strict();
146
155
  export type NodePatchBody = z.infer<typeof NodePatchBodySchema>;
@@ -159,6 +168,7 @@ const NODE_DATA_PATCH_KEYS = [
159
168
  'fontSize',
160
169
  'textColor',
161
170
  'cornerRadius',
171
+ 'shadow',
162
172
  'width',
163
173
  'height',
164
174
  'autoSize',
@@ -172,6 +182,7 @@ const NODE_DATA_PATCH_KEYS = [
172
182
  'playAction',
173
183
  'statusAction',
174
184
  'stateSource',
185
+ 'spec',
175
186
  ] as const satisfies ReadonlyArray<keyof NodePatchBody>;
176
187
 
177
188
  const EXTERNALIZED_FIELD_NAMES = new Set<string>(EXTERNALIZED_NODE_FIELDS.map((e) => e.field));
@@ -238,6 +249,10 @@ const SEMANTIC_KEYS_BY_TYPE: Record<z.infer<typeof NodeTypeSchema>, ReadonlySet<
238
249
  'statusAction',
239
250
  'alt',
240
251
  ]),
252
+ // Component nodes externalize `spec` to <project>/nodes/<id>/spec.json; the
253
+ // semantic-key set covers only the universal capability fields so retype
254
+ // never drags `spec` through `data`. (US-007 wires the sidecar writer.)
255
+ component: GEOMETRIC_SEMANTIC_KEYS,
241
256
  };
242
257
 
243
258
  // Visual data keys — routed to style.json on write by splitFlow. Kept here
@@ -253,6 +268,7 @@ const NODE_VISUAL_KEYS = new Set([
253
268
  'fontSize',
254
269
  'textColor',
255
270
  'cornerRadius',
271
+ 'shadow',
256
272
  'borderWidth',
257
273
  'color',
258
274
  'strokeWidth',
@@ -321,12 +337,13 @@ export const mergeNodeUpdates = (node: Record<string, unknown>, updates: NodePat
321
337
  }
322
338
  }
323
339
 
324
- // type:'html'-only invariant enforcement:
340
+ // type:'html' + type:'component' invariant enforcement:
325
341
  // autoSize === true ⊻ (width and height set).
326
342
  // autoSize: true is the dominant signal — it strips width/height even if
327
343
  // the same patch tried to write them. Writing width/height implicitly
328
- // flips autoSize to false.
329
- if (node.type === 'html') {
344
+ // flips autoSize to false. Both node types default to autoSize:true in the
345
+ // renderer and share the same shrink-wrap-to-content mechanism.
346
+ if (node.type === 'html' || node.type === 'component') {
330
347
  // The autoSize invariant requires `width`/`height` to be ABSENT from the
331
348
  // serialized JSON when autoSize is true — not present with value
332
349
  // `undefined` (which would serialize as a stray `"width": null` or get
@@ -757,7 +774,16 @@ export async function mutateMergedFlow<E extends { kind: string }>(
757
774
  const styleParse = StyleSchema.safeParse(read.rawStyle);
758
775
  if (!styleParse.success) return { kind: 'badSchema', issues: styleParse.error.issues };
759
776
 
760
- const merged = mergeFlowAndStyle(flowParse.data, styleParse.data) as unknown as {
777
+ // Inline component spec sidecars (<project>/nodes/<id>/spec.json) before the
778
+ // mutator runs so the post-mutation ResolvedFlowSchema parse sees `data.spec`
779
+ // on every existing component node. splitFlow strips `spec` back out before
780
+ // we write flow.json, keeping the sidecar as the on-disk source of truth.
781
+ const projectRoot = dirname(flowPath);
782
+ const { flow: inlinedFlow } = inlineComponentSpecs(
783
+ mergeFlowAndStyle(flowParse.data, styleParse.data),
784
+ projectRoot,
785
+ );
786
+ const merged = inlinedFlow as unknown as {
761
787
  version: number;
762
788
  name: string;
763
789
  resetAction?: unknown;
@@ -1532,6 +1558,22 @@ export async function patchNodeImpl(
1532
1558
  }
1533
1559
  node.data = data;
1534
1560
  }
1561
+ // Component spec sidecar — write the pretty-printed JSON to
1562
+ // `<project>/nodes/<id>/spec.json` so the on-disk source of truth stays
1563
+ // in sync. mergeNodeUpdates already put data.spec on the merged tree for
1564
+ // the post-mutation ResolvedFlowSchema parse; splitFlow strips it from
1565
+ // flow.json so we don't double-store the spec.
1566
+ if (node.type === 'component' && updates.spec !== undefined) {
1567
+ const specAbs = nodeFileAbsPath(entry.repoPath, nodeId, 'spec.json');
1568
+ try {
1569
+ writeNodeFile(specAbs, `${JSON.stringify(updates.spec, null, 2)}\n`);
1570
+ } catch (err) {
1571
+ return {
1572
+ kind: 'writeFailed',
1573
+ message: err instanceof Error ? err.message : String(err),
1574
+ };
1575
+ }
1576
+ }
1535
1577
  return { kind: 'ok' };
1536
1578
  });
1537
1579
  }