undercity 1.0.0

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 (382) hide show
  1. package/AGENTS.md +26 -0
  2. package/README.md +58 -0
  3. package/actions/AGENTS.md +41 -0
  4. package/actions/_shared/container.js +16 -0
  5. package/actions/auth/ask-login/action.json +15 -0
  6. package/actions/auth/ask-signup/action.json +14 -0
  7. package/actions/display/clear/action.json +18 -0
  8. package/actions/display/clear/action.test.js +32 -0
  9. package/actions/display/markdown/action.json +12 -0
  10. package/actions/display/markdown/action.test.js +32 -0
  11. package/actions/display/rawHtml/action.json +24 -0
  12. package/actions/display/rawHtml/action.test.js +32 -0
  13. package/actions/display/safeHtml/action.json +24 -0
  14. package/actions/display/safeHtml/action.test.js +32 -0
  15. package/actions/display/text/action.js +9 -0
  16. package/actions/display/text/action.json +12 -0
  17. package/actions/display/text/action.test.js +40 -0
  18. package/actions/display/value/action.json +24 -0
  19. package/actions/display/value/action.test.js +32 -0
  20. package/actions/dom/addClass/action.json +23 -0
  21. package/actions/dom/addClass/action.test.js +32 -0
  22. package/actions/dom/focus/action.json +17 -0
  23. package/actions/dom/focus/action.test.js +32 -0
  24. package/actions/dom/hide/action.json +18 -0
  25. package/actions/dom/hide/action.test.js +32 -0
  26. package/actions/dom/removeClass/action.json +22 -0
  27. package/actions/dom/removeClass/action.test.js +32 -0
  28. package/actions/dom/scroll/action.json +29 -0
  29. package/actions/dom/scroll/action.test.js +32 -0
  30. package/actions/dom/setAttr/action.json +29 -0
  31. package/actions/dom/setAttr/action.test.js +32 -0
  32. package/actions/dom/setHtml/action.json +22 -0
  33. package/actions/dom/setHtml/action.test.js +32 -0
  34. package/actions/dom/setStyle/action.json +29 -0
  35. package/actions/dom/setStyle/action.test.js +32 -0
  36. package/actions/dom/setText/action.json +24 -0
  37. package/actions/dom/setText/action.test.js +32 -0
  38. package/actions/dom/show/action.json +18 -0
  39. package/actions/dom/show/action.test.js +32 -0
  40. package/actions/dom/toggle/action.json +17 -0
  41. package/actions/dom/toggle/action.test.js +32 -0
  42. package/actions/dom/toggleClass/action.json +22 -0
  43. package/actions/dom/toggleClass/action.test.js +32 -0
  44. package/actions/event/emit/action.json +24 -0
  45. package/actions/event/emit/action.test.js +32 -0
  46. package/actions/event/on/action.json +23 -0
  47. package/actions/event/on/action.test.js +32 -0
  48. package/actions/event/waitFor/action.json +28 -0
  49. package/actions/event/waitFor/action.test.js +32 -0
  50. package/actions/forms/bindField/action.js +27 -0
  51. package/actions/forms/bindField/action.json +24 -0
  52. package/actions/forms/bindField/action.test.js +20 -0
  53. package/actions/forms/check/action.json +22 -0
  54. package/actions/forms/check/action.test.js +32 -0
  55. package/actions/forms/clearErrors/action.json +11 -0
  56. package/actions/forms/clearErrors/action.test.js +35 -0
  57. package/actions/forms/clearField/action.json +17 -0
  58. package/actions/forms/clearField/action.test.js +32 -0
  59. package/actions/forms/getField/action.json +24 -0
  60. package/actions/forms/getField/action.test.js +32 -0
  61. package/actions/forms/getRange/action.json +22 -0
  62. package/actions/forms/getRange/action.test.js +32 -0
  63. package/actions/forms/getSelect/action.json +22 -0
  64. package/actions/forms/getSelect/action.test.js +32 -0
  65. package/actions/forms/index.js +140 -0
  66. package/actions/forms/serialize/action.json +24 -0
  67. package/actions/forms/serialize/action.test.js +32 -0
  68. package/actions/forms/setCheck/action.json +23 -0
  69. package/actions/forms/setCheck/action.test.js +32 -0
  70. package/actions/forms/setError/action.json +22 -0
  71. package/actions/forms/setError/action.test.js +32 -0
  72. package/actions/forms/setField/action.js +14 -0
  73. package/actions/forms/setField/action.json +24 -0
  74. package/actions/forms/setField/action.test.js +32 -0
  75. package/actions/forms/submit/action.json +18 -0
  76. package/actions/forms/submit/action.test.js +32 -0
  77. package/actions/forms/validate/action.json +24 -0
  78. package/actions/forms/validate/action.test.js +32 -0
  79. package/actions/http/delete/action.json +22 -0
  80. package/actions/http/delete/action.test.js +32 -0
  81. package/actions/http/get/action.json +24 -0
  82. package/actions/http/get/action.test.js +32 -0
  83. package/actions/http/post/action.json +30 -0
  84. package/actions/http/post/action.test.js +32 -0
  85. package/actions/http/put/action.json +27 -0
  86. package/actions/http/put/action.test.js +32 -0
  87. package/actions/http/upload/action.json +35 -0
  88. package/actions/http/upload/action.test.js +32 -0
  89. package/actions/index.js +306 -0
  90. package/actions/index.json +5 -0
  91. package/actions/input/askChoice/action.json +29 -0
  92. package/actions/input/askChoice/action.test.js +32 -0
  93. package/actions/input/askConfirm/action.json +24 -0
  94. package/actions/input/askConfirm/action.test.js +32 -0
  95. package/actions/input/askDate/action.json +24 -0
  96. package/actions/input/askDate/action.test.js +32 -0
  97. package/actions/input/askEmail/action.json +24 -0
  98. package/actions/input/askEmail/action.test.js +32 -0
  99. package/actions/input/askNumber/action.json +35 -0
  100. package/actions/input/askNumber/action.test.js +32 -0
  101. package/actions/input/askPassword/action.json +24 -0
  102. package/actions/input/askPassword/action.test.js +32 -0
  103. package/actions/input/askText/action.json +36 -0
  104. package/actions/input/askText/action.test.js +32 -0
  105. package/actions/logic/delay/action.json +18 -0
  106. package/actions/logic/delay/action.test.js +32 -0
  107. package/actions/logic/if/action.json +28 -0
  108. package/actions/logic/if/action.test.js +32 -0
  109. package/actions/logic/log/action.json +18 -0
  110. package/actions/logic/log/action.test.js +32 -0
  111. package/actions/logic/random/action.json +36 -0
  112. package/actions/logic/random/action.test.js +32 -0
  113. package/actions/logic/transform/action.json +24 -0
  114. package/actions/logic/transform/action.test.js +32 -0
  115. package/actions/media/askAudioUpload/action.json +30 -0
  116. package/actions/media/askAudioUpload/action.test.js +32 -0
  117. package/actions/media/askFileUpload/action.json +36 -0
  118. package/actions/media/askFileUpload/action.test.js +32 -0
  119. package/actions/media/askImageUpload/action.json +37 -0
  120. package/actions/media/askImageUpload/action.test.js +32 -0
  121. package/actions/media/askVideoUpload/action.js +74 -0
  122. package/actions/media/askVideoUpload/action.json +44 -0
  123. package/actions/media/askVideoUpload/action.test.js +51 -0
  124. package/actions/media/captureWebcam/action.json +24 -0
  125. package/actions/media/captureWebcam/action.test.js +32 -0
  126. package/actions/nav/back/action.json +11 -0
  127. package/actions/nav/back/action.test.js +35 -0
  128. package/actions/nav/goto/action.json +18 -0
  129. package/actions/nav/goto/action.test.js +32 -0
  130. package/actions/nav/redirect/action.json +28 -0
  131. package/actions/nav/redirect/action.test.js +32 -0
  132. package/actions/nav/reload/action.json +11 -0
  133. package/actions/nav/reload/action.test.js +35 -0
  134. package/actions/nav/reset/action.json +11 -0
  135. package/actions/nav/reset/action.test.js +35 -0
  136. package/actions/render/alert/action.js +12 -0
  137. package/actions/render/alert/action.json +36 -0
  138. package/actions/render/alert/action.test.js +32 -0
  139. package/actions/render/button/action.js +15 -0
  140. package/actions/render/button/action.json +44 -0
  141. package/actions/render/button/action.test.js +37 -0
  142. package/actions/render/clear/action.js +7 -0
  143. package/actions/render/clear/action.json +11 -0
  144. package/actions/render/clear/action.test.js +35 -0
  145. package/actions/render/divider/action.js +8 -0
  146. package/actions/render/divider/action.json +11 -0
  147. package/actions/render/divider/action.test.js +35 -0
  148. package/actions/render/field/action.js +17 -0
  149. package/actions/render/field/action.json +59 -0
  150. package/actions/render/field/action.test.js +57 -0
  151. package/actions/render/link/action.js +20 -0
  152. package/actions/render/link/action.json +30 -0
  153. package/actions/render/link/action.test.js +32 -0
  154. package/actions/render/markdown/action.json +18 -0
  155. package/actions/render/markdown/action.test.js +32 -0
  156. package/actions/render/paragraph/action.js +9 -0
  157. package/actions/render/paragraph/action.json +32 -0
  158. package/actions/render/paragraph/action.test.js +32 -0
  159. package/actions/render/section/action.js +10 -0
  160. package/actions/render/section/action.json +18 -0
  161. package/actions/render/section/action.test.js +32 -0
  162. package/actions/render/subtitle/action.js +9 -0
  163. package/actions/render/subtitle/action.json +18 -0
  164. package/actions/render/subtitle/action.test.js +32 -0
  165. package/actions/render/title/action.js +9 -0
  166. package/actions/render/title/action.json +30 -0
  167. package/actions/render/title/action.test.js +44 -0
  168. package/actions/session/clear/action.json +11 -0
  169. package/actions/session/clear/action.test.js +35 -0
  170. package/actions/session/load/action.json +22 -0
  171. package/actions/session/load/action.test.js +32 -0
  172. package/actions/session/local/action.json +22 -0
  173. package/actions/session/local/action.test.js +32 -0
  174. package/actions/session/save/action.json +22 -0
  175. package/actions/session/save/action.test.js +32 -0
  176. package/actions/ui/accordion/action.json +23 -0
  177. package/actions/ui/accordion/action.test.js +32 -0
  178. package/actions/ui/badge/action.json +22 -0
  179. package/actions/ui/badge/action.test.js +32 -0
  180. package/actions/ui/collapse/action.json +23 -0
  181. package/actions/ui/collapse/action.test.js +32 -0
  182. package/actions/ui/hideModal/action.json +17 -0
  183. package/actions/ui/hideModal/action.test.js +32 -0
  184. package/actions/ui/loading/action.json +18 -0
  185. package/actions/ui/loading/action.test.js +32 -0
  186. package/actions/ui/modal/action.json +18 -0
  187. package/actions/ui/modal/action.test.js +32 -0
  188. package/actions/ui/progress/action.json +24 -0
  189. package/actions/ui/progress/action.test.js +32 -0
  190. package/actions/ui/toast/action.json +29 -0
  191. package/actions/ui/toast/action.test.js +32 -0
  192. package/actions/ui/tooltip/action.json +17 -0
  193. package/actions/ui/tooltip/action.test.js +32 -0
  194. package/actions/user/carry/action.json +25 -0
  195. package/actions/user/carry/action.test.js +32 -0
  196. package/actions/user/check/action.json +24 -0
  197. package/actions/user/check/action.test.js +32 -0
  198. package/actions/user/clear/action.json +11 -0
  199. package/actions/user/clear/action.test.js +35 -0
  200. package/actions/user/delete/action.json +17 -0
  201. package/actions/user/delete/action.test.js +32 -0
  202. package/actions/user/dump/action.json +11 -0
  203. package/actions/user/dump/action.test.js +35 -0
  204. package/actions/user/get/action.json +24 -0
  205. package/actions/user/get/action.test.js +32 -0
  206. package/actions/user/merge/action.json +18 -0
  207. package/actions/user/merge/action.test.js +32 -0
  208. package/actions/user/set/action.json +24 -0
  209. package/actions/user/set/action.test.js +32 -0
  210. package/generator/base/css/bootstrap.min.css +6 -0
  211. package/generator/base/icons/app-indicator.svg +4 -0
  212. package/generator/base/icons/backpack.svg +4 -0
  213. package/generator/base/icons/broadcast.svg +3 -0
  214. package/generator/base/icons/bullseye.svg +6 -0
  215. package/generator/base/icons/chat-dots.svg +4 -0
  216. package/generator/base/icons/check-circle.svg +4 -0
  217. package/generator/base/icons/clipboard-check.svg +5 -0
  218. package/generator/base/icons/clipboard.svg +4 -0
  219. package/generator/base/icons/copy.svg +3 -0
  220. package/generator/base/icons/cursor.svg +3 -0
  221. package/generator/base/icons/diamond.svg +3 -0
  222. package/generator/base/icons/exclamation-triangle.svg +4 -0
  223. package/generator/base/icons/film.svg +3 -0
  224. package/generator/base/icons/floppy.svg +4 -0
  225. package/generator/base/icons/gear-wide-connected.svg +3 -0
  226. package/generator/base/icons/gear.svg +4 -0
  227. package/generator/base/icons/globe.svg +3 -0
  228. package/generator/base/icons/image.svg +4 -0
  229. package/generator/base/icons/layout-text-window.svg +4 -0
  230. package/generator/base/icons/lightning-charge.svg +3 -0
  231. package/generator/base/icons/magic.svg +3 -0
  232. package/generator/base/icons/pencil-square.svg +4 -0
  233. package/generator/base/icons/record-circle.svg +4 -0
  234. package/generator/base/icons/robot.svg +4 -0
  235. package/generator/base/icons/shield-check.svg +4 -0
  236. package/generator/base/icons/shield-lock.svg +4 -0
  237. package/generator/base/icons/signpost.svg +3 -0
  238. package/generator/base/icons/stars.svg +3 -0
  239. package/generator/base/icons/type.svg +3 -0
  240. package/generator/base/js/bootstrap.bundle.min.js +7 -0
  241. package/package.json +14 -0
  242. package/packages/undercity-http-server/index.js +249 -0
  243. package/packages/undercity-http-server/package.json +10 -0
  244. package/packages/undercity-parser/index.js +323 -0
  245. package/packages/undercity-parser/lexer.js +128 -0
  246. package/packages/undercity-parser/package.json +11 -0
  247. package/plugins/forms.js +397 -0
  248. package/plugins/index.js +83 -0
  249. package/plugins/multipage.js +165 -0
  250. package/plugins/wizard.js +239 -0
  251. package/projects/asd/project.json +1031 -0
  252. package/projects/test-1/project.json +335 -0
  253. package/projects/test-a/project.json +456 -0
  254. package/public/icons/arrows-angle-expand.svg +3 -0
  255. package/public/icons/arrows-fullscreen.svg +3 -0
  256. package/public/icons/bezier2.svg +3 -0
  257. package/public/icons/bootstrap/app-indicator.svg +4 -0
  258. package/public/icons/bootstrap/arrow-clockwise.svg +4 -0
  259. package/public/icons/bootstrap/arrow-counterclockwise.svg +4 -0
  260. package/public/icons/bootstrap/arrow-left.svg +3 -0
  261. package/public/icons/bootstrap/arrows-angle-expand.svg +3 -0
  262. package/public/icons/bootstrap/arrows-fullscreen.svg +3 -0
  263. package/public/icons/bootstrap/backpack.svg +4 -0
  264. package/public/icons/bootstrap/bezier2.svg +3 -0
  265. package/public/icons/bootstrap/bookmark-check.svg +4 -0
  266. package/public/icons/bootstrap/bookmark-plus.svg +4 -0
  267. package/public/icons/bootstrap/box-arrow-right.svg +4 -0
  268. package/public/icons/bootstrap/box-arrow-up.svg +4 -0
  269. package/public/icons/bootstrap/broadcast.svg +3 -0
  270. package/public/icons/bootstrap/bullseye.svg +6 -0
  271. package/public/icons/bootstrap/chat-dots.svg +4 -0
  272. package/public/icons/bootstrap/check-circle.svg +4 -0
  273. package/public/icons/bootstrap/check2.svg +3 -0
  274. package/public/icons/bootstrap/clipboard-check.svg +5 -0
  275. package/public/icons/bootstrap/clipboard.svg +4 -0
  276. package/public/icons/bootstrap/clock-history.svg +5 -0
  277. package/public/icons/bootstrap/command.svg +3 -0
  278. package/public/icons/bootstrap/copy.svg +3 -0
  279. package/public/icons/bootstrap/cursor.svg +3 -0
  280. package/public/icons/bootstrap/diagram-3.svg +3 -0
  281. package/public/icons/bootstrap/diamond.svg +3 -0
  282. package/public/icons/bootstrap/exclamation-triangle.svg +4 -0
  283. package/public/icons/bootstrap/eye.svg +4 -0
  284. package/public/icons/bootstrap/file-earmark-plus.svg +4 -0
  285. package/public/icons/bootstrap/film.svg +3 -0
  286. package/public/icons/bootstrap/floppy.svg +4 -0
  287. package/public/icons/bootstrap/folder2-open.svg +3 -0
  288. package/public/icons/bootstrap/gear-wide-connected.svg +3 -0
  289. package/public/icons/bootstrap/gear.svg +4 -0
  290. package/public/icons/bootstrap/globe.svg +3 -0
  291. package/public/icons/bootstrap/grid-3x3-gap.svg +3 -0
  292. package/public/icons/bootstrap/house-door.svg +3 -0
  293. package/public/icons/bootstrap/image.svg +4 -0
  294. package/public/icons/bootstrap/layout-text-window.svg +4 -0
  295. package/public/icons/bootstrap/lightning-charge.svg +3 -0
  296. package/public/icons/bootstrap/magic.svg +3 -0
  297. package/public/icons/bootstrap/pencil-square.svg +4 -0
  298. package/public/icons/bootstrap/pencil.svg +3 -0
  299. package/public/icons/bootstrap/play.svg +3 -0
  300. package/public/icons/bootstrap/plus-circle.svg +4 -0
  301. package/public/icons/bootstrap/plus-lg.svg +3 -0
  302. package/public/icons/bootstrap/record-circle.svg +4 -0
  303. package/public/icons/bootstrap/robot.svg +4 -0
  304. package/public/icons/bootstrap/save.svg +3 -0
  305. package/public/icons/bootstrap/scissors.svg +3 -0
  306. package/public/icons/bootstrap/shield-check.svg +4 -0
  307. package/public/icons/bootstrap/shield-lock.svg +4 -0
  308. package/public/icons/bootstrap/signpost.svg +3 -0
  309. package/public/icons/bootstrap/stars.svg +3 -0
  310. package/public/icons/bootstrap/stop-circle.svg +4 -0
  311. package/public/icons/bootstrap/terminal.svg +4 -0
  312. package/public/icons/bootstrap/trash3.svg +3 -0
  313. package/public/icons/bootstrap/type.svg +3 -0
  314. package/public/icons/bootstrap/x-circle.svg +4 -0
  315. package/public/icons/bootstrap/x-lg.svg +3 -0
  316. package/public/icons/bootstrap/zoom-in.svg +5 -0
  317. package/public/icons/bootstrap/zoom-out.svg +5 -0
  318. package/public/icons/bullseye.svg +6 -0
  319. package/public/icons/check2.svg +3 -0
  320. package/public/icons/cursor.svg +3 -0
  321. package/public/icons/diamond.svg +3 -0
  322. package/public/icons/eye.svg +4 -0
  323. package/public/icons/file-earmark-plus.svg +4 -0
  324. package/public/icons/floppy.svg +4 -0
  325. package/public/icons/gear.svg +4 -0
  326. package/public/icons/lightning-charge.svg +3 -0
  327. package/public/icons/pencil.svg +3 -0
  328. package/public/icons/play.svg +3 -0
  329. package/public/icons/plus-circle.svg +4 -0
  330. package/public/icons/record-circle.svg +4 -0
  331. package/public/icons/robot.svg +4 -0
  332. package/public/icons/save.svg +3 -0
  333. package/public/icons/stop-circle.svg +4 -0
  334. package/public/icons/terminal.svg +4 -0
  335. package/public/icons/trash3.svg +3 -0
  336. package/public/icons/x-circle.svg +4 -0
  337. package/public/icons/zoom-in.svg +5 -0
  338. package/public/icons/zoom-out.svg +5 -0
  339. package/public/index.html +424 -0
  340. package/public/testbench.html +899 -0
  341. package/scripts/extract-actions.js +128 -0
  342. package/server.js +11 -0
  343. package/src/emitter.js +48 -0
  344. package/src/generator/css.js +135 -0
  345. package/src/generator/index.js +122 -0
  346. package/src/generator/md-renderer-src.js +77 -0
  347. package/src/generator/page.js +300 -0
  348. package/src/generator/runtime.js +1632 -0
  349. package/src/generator/templates.js +508 -0
  350. package/src/ide/action-library.js +856 -0
  351. package/src/ide/af-icons.js +127 -0
  352. package/src/ide/app.js +1375 -0
  353. package/src/ide/command-line/commands.js +242 -0
  354. package/src/ide/command-line/index.js +329 -0
  355. package/src/ide/command-line/parser.js +21 -0
  356. package/src/ide/css/ide.css +1501 -0
  357. package/src/ide/graph.js +282 -0
  358. package/src/ide/history.js +46 -0
  359. package/src/ide/map-builder.js +583 -0
  360. package/src/ide/project-api.js +39 -0
  361. package/src/ide/savant-chat.js +513 -0
  362. package/src/ide/savant.js +1287 -0
  363. package/src/ide/thing-library.js +89 -0
  364. package/src/ide/undercity-map.js +978 -0
  365. package/src/lib/icons.js +72 -0
  366. package/src/lib/scope.js +88 -0
  367. package/src/lib/signal.js +155 -0
  368. package/src/lib/state-machine.js +113 -0
  369. package/src/server/index.js +96 -0
  370. package/src/server/routes/actions.js +144 -0
  371. package/src/server/routes/ai.js +176 -0
  372. package/src/server/routes/generate.js +54 -0
  373. package/src/server/routes/projects.js +106 -0
  374. package/src/server/routes/reset.js +30 -0
  375. package/src/server/routes/submit.js +30 -0
  376. package/src/server/routes/templates.js +139 -0
  377. package/src/server/routes/things.js +33 -0
  378. package/templates/auth-flow.json +335 -0
  379. package/templates/blank.json +39 -0
  380. package/things/auth-server/thing.json +17 -0
  381. package/things/persona-live/thing.json +20 -0
  382. package/things/workflow/thing.json +15 -0
package/AGENTS.md ADDED
@@ -0,0 +1,26 @@
1
+ # Undercity — AGENTS.md
2
+
3
+ ## The MUD Agent Model
4
+
5
+ Undercity's architecture is grounded in the **Multi-User Dungeon (MUD) metaphor**:
6
+
7
+ - **Rooms** have events (incl. user defined events), can contain Things.
8
+ - **The User** moves through rooms carrying an **Inventory** of data.
9
+ - **Things** are objects that inhabit rooms — they react to events, modify inventory, and trigger actions without user input.
10
+ - **Events** are the lifecycle hooks of each room: `onEnter`, `onExit`, `onBack`, `onReset`, `onUnload`.
11
+
12
+ This model gives Undercity a natural, coherent extension path: **any new feature can be framed as a room object, thing, or event type.**
13
+
14
+ ---
15
+
16
+ ## Why MUD?
17
+
18
+ The MUD metaphor was chosen because it provides:
19
+
20
+ - **A coherent vocabulary** for all stakeholders (rooms, users, inventory, things)
21
+ - **A proven extension model** — MUDs have supported objects, things, events, and multiplayer for 40+ years
22
+ - **Natural debugging** — you can "walk" through the flow as an user, inspect inventory at each room, and trace room events
23
+ - **A path to AI** — LLM agents fit naturally as NPCs or guides inhabiting rooms
24
+ - **Compatibility** — new features (auth, persistence, realtime) map to well-understood MUD concepts
25
+
26
+ **The MUD metaphor is the conceptual foundation of Undercity. Preserve it in all architectural decisions.**
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # Undercity IDE
2
+
3
+ Visual flow IDE — MUD-style logic graph builder with Bootstrap code generation.
4
+
5
+ Design multi-screen applications as interconnected rooms on a map, wire up event-driven workflows with a point-and-click action editor, then generate a self-contained static Bootstrap app in one click.
6
+
7
+ ## Quick start
8
+
9
+ ```bash
10
+ npm install
11
+ npm run dev
12
+ ```
13
+
14
+ Open `http://localhost:3000` in your browser.
15
+
16
+ ## Usage
17
+
18
+ 1. **Map** — Place rooms (screens), diamonds (logic branches), and terminals (end states) on the canvas. Connect them with edges.
19
+ 2. **Savant** — Select a node and build its action sequence: display content, read/write inventory, navigate, call APIs, render forms, and more.
20
+ 3. **Generate** — Click **Generate** (or **Preview**) to produce a self-contained Bootstrap app under `generated/<project-id>/`.
21
+
22
+ ## Project structure
23
+
24
+ ```
25
+ src/ide/ IDE client-side JavaScript and CSS
26
+ src/ide/css/ IDE stylesheet
27
+ src/ide/command-line/ Command palette parser and commands
28
+ src/lib/ Shared utilities (signal, scope, state-machine, icons)
29
+ src/generator/ Code generation engine
30
+ src/server/ HTTP server + API routes
31
+ src/server/routes/ API route handlers
32
+ actions/ Action plugins (one directory per category)
33
+ plugins/ Generator plugins (forms, wizard, multipage)
34
+ packages/ Local npm workspace packages (http-server, parser)
35
+ generator/base/ Bootstrap .min files + icon SVGs used during code generation
36
+ public/ HTML entry points (index.html, testbench.html) and static icons
37
+ public/icons/ Custom IDE toolbar icons
38
+ public/icons/bootstrap/ Bootstrap icon subset used by the IDE
39
+ templates/ Starter project templates
40
+ things/ Thing definitions (reusable room objects)
41
+ projects/ Saved user projects (created at runtime)
42
+ generated/ Generated app output (created at runtime)
43
+ ```
44
+
45
+ `tmp/` and `generated/` are excluded from version control.
46
+
47
+ ## Scripts
48
+
49
+ | Command | Description |
50
+ |---------|-------------|
51
+ | `npm start` | Production server |
52
+ | `npm run dev` | Development server with `--watch` hot reload |
53
+
54
+ ## Tech
55
+
56
+ - Node.js (ES Modules, no bundler)
57
+ - Bootstrap 5.3.8
58
+ - Bootstrap Icons 1.13.1
@@ -0,0 +1,41 @@
1
+ # Actions — Plugin Author Guide
2
+
3
+ Each action lives in its own directory:
4
+ actions/<category>/<actionName>/
5
+ action.json — IDE metadata (label, desc, params)
6
+ action.js — Runtime implementation documentation
7
+ action.test.js — Unit tests
8
+
9
+ The category index plugin (actions/<category>/index.js) is the browser-side
10
+ ES module that registers the category with the IDE via App.use().
11
+
12
+ ## action.json schema
13
+
14
+ ```json
15
+ {
16
+ "id": "category.actionName",
17
+ "category": "category",
18
+ "categoryLabel": "Human Label",
19
+ "icon": "bootstrap-icon-name",
20
+ "color": "var(--sol-yellow)",
21
+ "label": "Action Label",
22
+ "desc": "Clear description stating WHEN it runs and WHERE results go.",
23
+ "version": "1.0.0",
24
+ "params": [
25
+ { "name": "fieldName", "label": "Human Label", "type": "text|code|inventory-key|select|boolean|number|textarea|room" }
26
+ ]
27
+ }
28
+ ```
29
+
30
+ ## Creating a new category plugin
31
+
32
+ 1. Create `actions/<category>/index.js` exporting `<Category>Plugin`
33
+ 2. Import it in `actions/index.js` and add to `ActionsPlugin.install()`
34
+ 3. Each action that differs from the server-read action.json should be defined here
35
+
36
+ ## Naming rules
37
+
38
+ - Category IDs: lowercase, e.g. `forms`, `user`, `nav`
39
+ - Action IDs: `<category>.<camelCaseName>`, e.g. `form.bindField`
40
+ - Directory names must match action camelCase: `bindField/`
41
+ - The category/action path in the directory MUST match the id in action.json
@@ -0,0 +1,16 @@
1
+ /**
2
+ * _shared/container.js — Shared helpers for action implementations.
3
+ * Re-exported by action.js files so tests can import behavior directly.
4
+ */
5
+
6
+ export function _container() {
7
+ return document.getElementById('pw-content') ?? document.getElementById('pw-form') ?? document.body;
8
+ }
9
+
10
+ export function _esc(s) {
11
+ return String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
12
+ }
13
+
14
+ export function _escA(s) {
15
+ return String(s ?? '').replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
16
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "id": "auth.askLogin",
3
+ "category": "auth",
4
+ "categoryLabel": "Authentication",
5
+ "label": "Ask — Sign In",
6
+ "desc": "Render the full Sign In form (email + password) into #pw-content. High-level auth action.",
7
+ "version": "1.0.0",
8
+ "params": [
9
+ { "name": "submitTarget", "label": "Submit → room", "type": "room", "placeholder": "auth-check" },
10
+ { "name": "forgotTarget", "label": "Forgot password → room", "type": "room", "placeholder": "forgot" },
11
+ { "name": "signupTarget", "label": "Create account → room", "type": "room", "placeholder": "signup" },
12
+ { "name": "title", "label": "Title", "type": "text", "default": "Sign In" },
13
+ { "name": "subtitle", "label": "Subtitle", "type": "text", "default": "Welcome back. Enter your credentials." }
14
+ ]
15
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "id": "auth.askSignup",
3
+ "category": "auth",
4
+ "categoryLabel": "Authentication",
5
+ "label": "Ask — Create Account",
6
+ "desc": "Render the full Sign Up form (first/last name, email, password) into #pw-content.",
7
+ "version": "1.0.0",
8
+ "params": [
9
+ { "name": "submitTarget", "label": "Submit → room", "type": "room", "placeholder": "register-check" },
10
+ { "name": "loginTarget", "label": "Already have one → room", "type": "room", "placeholder": "login" },
11
+ { "name": "title", "label": "Title", "type": "text", "default": "Create Account" },
12
+ { "name": "subtitle", "label": "Subtitle", "type": "text", "default": "Join us — it only takes a moment." }
13
+ ]
14
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "display.clear",
3
+ "category": "display",
4
+ "categoryLabel": "Display",
5
+ "icon": "type",
6
+ "color": "var(--sol-cyan)",
7
+ "label": "Clear Content",
8
+ "desc": "Empty the innerHTML of a selector.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "selector",
13
+ "label": "Target selector",
14
+ "type": "text",
15
+ "placeholder": "#my-area"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for display.clear
3
+ * Clear Content: Empty the innerHTML of a selector.
4
+ *
5
+ * Each test receives (RT, sandbox) where:
6
+ * RT — the generated runtime module (Inventory, Navigator, Actions, ...)
7
+ * sandbox — a <div> containing #pw-content, #pw-form, #pw-loading
8
+ */
9
+
10
+ export const tests = [
11
+
12
+ {
13
+ name: 'display.clear — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Display ?? RT.display;
16
+ if (!ns) throw new Error('Namespace Display/display not found in runtime');
17
+ if (typeof ns.clear !== 'function') throw new Error('display.clear is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'display.clear — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Display.clear?.("#my-area");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "display.markdown",
3
+ "category": "display",
4
+ "categoryLabel": "Display",
5
+ "label": "Print Markdown",
6
+ "desc": "Render a Markdown string as HTML inside a selector.",
7
+ "version": "1.0.0",
8
+ "params": [
9
+ { "name": "selector", "label": "Target selector", "type": "text", "placeholder": "#my-area" },
10
+ { "name": "content", "label": "Markdown", "type": "textarea", "placeholder": "**Bold** and _italic_" }
11
+ ]
12
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for display.markdown
3
+ * Print Markdown: Render a Markdown string as HTML inside a selector.
4
+ *
5
+ * Each test receives (RT, sandbox) where:
6
+ * RT — the generated runtime module (Inventory, Navigator, Actions, ...)
7
+ * sandbox — a <div> containing #pw-content, #pw-form, #pw-loading
8
+ */
9
+
10
+ export const tests = [
11
+
12
+ {
13
+ name: 'display.markdown — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Display ?? RT.display;
16
+ if (!ns) throw new Error('Namespace Display/display not found in runtime');
17
+ if (typeof ns.markdown !== 'function') throw new Error('display.markdown is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'display.markdown — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Display.markdown?.("#my-area", "**Bold** and _italic_");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,24 @@
1
+ {
2
+ "id": "display.rawHtml",
3
+ "category": "display",
4
+ "categoryLabel": "Display",
5
+ "icon": "type",
6
+ "color": "var(--sol-cyan)",
7
+ "label": "Print Raw HTML",
8
+ "desc": "Inject trusted HTML directly (no sanitization). Use only with controlled content.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "selector",
13
+ "label": "Target selector",
14
+ "type": "text",
15
+ "placeholder": "#my-area"
16
+ },
17
+ {
18
+ "name": "html",
19
+ "label": "HTML",
20
+ "type": "textarea",
21
+ "placeholder": "<b>Trusted HTML</b>"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for display.rawHtml
3
+ * Print Raw HTML: Inject trusted HTML directly (no sanitization). Use only with controlled content.
4
+ *
5
+ * Each test receives (RT, sandbox) where:
6
+ * RT — the generated runtime module (Inventory, Navigator, Actions, ...)
7
+ * sandbox — a <div> containing #pw-content, #pw-form, #pw-loading
8
+ */
9
+
10
+ export const tests = [
11
+
12
+ {
13
+ name: 'display.rawHtml — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Display ?? RT.display;
16
+ if (!ns) throw new Error('Namespace Display/display not found in runtime');
17
+ if (typeof ns.rawHtml !== 'function') throw new Error('display.rawHtml is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'display.rawHtml — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Display.rawHtml?.("#my-area", "<b>Trusted HTML</b>");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,24 @@
1
+ {
2
+ "id": "display.safeHtml",
3
+ "category": "display",
4
+ "categoryLabel": "Display",
5
+ "icon": "type",
6
+ "color": "var(--sol-cyan)",
7
+ "label": "Print Safe HTML",
8
+ "desc": "Sanitize and inject HTML — script/event attributes stripped.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "selector",
13
+ "label": "Target selector",
14
+ "type": "text",
15
+ "placeholder": "#my-area"
16
+ },
17
+ {
18
+ "name": "html",
19
+ "label": "HTML",
20
+ "type": "textarea",
21
+ "placeholder": "<b>Bold</b>"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for display.safeHtml
3
+ * Print Safe HTML: Sanitize and inject HTML — script/event attributes stripped.
4
+ *
5
+ * Each test receives (RT, sandbox) where:
6
+ * RT — the generated runtime module (Inventory, Navigator, Actions, ...)
7
+ * sandbox — a <div> containing #pw-content, #pw-form, #pw-loading
8
+ */
9
+
10
+ export const tests = [
11
+
12
+ {
13
+ name: 'display.safeHtml — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Display ?? RT.display;
16
+ if (!ns) throw new Error('Namespace Display/display not found in runtime');
17
+ if (typeof ns.safeHtml !== 'function') throw new Error('display.safeHtml is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'display.safeHtml — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Display.safeHtml?.("#my-area", "<b>Bold</b>");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,9 @@
1
+ /**
2
+ * display.text — set plain text in a selector.
3
+ * Runtime implementation (injected into generated app via registerNamespace).
4
+ */
5
+ export function text(selector, text) {
6
+ document.querySelectorAll(selector).forEach(el => {
7
+ el.textContent = String(text ?? '');
8
+ });
9
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "display.text",
3
+ "category": "display",
4
+ "categoryLabel": "Display",
5
+ "label": "Print Text",
6
+ "desc": "Set plain text content inside a selector. Safe — no HTML interpretation.",
7
+ "version": "1.0.0",
8
+ "params": [
9
+ { "name": "selector", "label": "Target selector", "type": "text", "placeholder": "#my-label" },
10
+ { "name": "text", "label": "Text", "type": "code", "placeholder": "\"Hello \" + inventory.firstName" }
11
+ ]
12
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * action.test.js — Tests for display.text
3
+ */
4
+ import { text } from './action.js';
5
+
6
+ export const tests = [
7
+
8
+ {
9
+ name: 'display.text — exported from runtime',
10
+ run(RT) {
11
+ if (!RT.Display?.text) throw new Error('RT.Display.text not found');
12
+ },
13
+ },
14
+
15
+ {
16
+ name: 'display.text — sets textContent of matching element',
17
+ run() {
18
+ const el = document.createElement('p');
19
+ el.id = 'test-display-target';
20
+ document.getElementById('pw-content').appendChild(el);
21
+
22
+ text('#test-display-target', 'Hello World');
23
+ if (el.textContent !== 'Hello World') throw new Error(`Got: ${el.textContent}`);
24
+ },
25
+ },
26
+
27
+ {
28
+ name: 'display.text — safe: does not render HTML tags',
29
+ run() {
30
+ const el = document.createElement('p');
31
+ el.id = 'test-display-safe';
32
+ document.getElementById('pw-content').appendChild(el);
33
+
34
+ text('#test-display-safe', '<b>bold</b>');
35
+ if (el.querySelector('b')) throw new Error('Should not render HTML');
36
+ if (!el.textContent.includes('<b>')) throw new Error('Should show raw tag chars');
37
+ },
38
+ },
39
+
40
+ ];
@@ -0,0 +1,24 @@
1
+ {
2
+ "id": "display.value",
3
+ "category": "display",
4
+ "categoryLabel": "Display",
5
+ "icon": "type",
6
+ "color": "var(--sol-cyan)",
7
+ "label": "Show Inventory Value",
8
+ "desc": "Print an inventory key's value as text into a selector.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "selector",
13
+ "label": "Target selector",
14
+ "type": "text",
15
+ "placeholder": "#name-label"
16
+ },
17
+ {
18
+ "name": "key",
19
+ "label": "Inventory key",
20
+ "type": "text",
21
+ "placeholder": "firstName"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for display.value
3
+ * Show Inventory Value: Print an inventory key's value as text into a selector.
4
+ *
5
+ * Each test receives (RT, sandbox) where:
6
+ * RT — the generated runtime module (Inventory, Navigator, Actions, ...)
7
+ * sandbox — a <div> containing #pw-content, #pw-form, #pw-loading
8
+ */
9
+
10
+ export const tests = [
11
+
12
+ {
13
+ name: 'display.value — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Display ?? RT.display;
16
+ if (!ns) throw new Error('Namespace Display/display not found in runtime');
17
+ if (typeof ns.value !== 'function') throw new Error('display.value is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'display.value — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Display.value?.("#name-label", "firstName");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "dom.addClass",
3
+ "category": "dom",
4
+ "categoryLabel": "DOM",
5
+ "icon": "image",
6
+ "color": "var(--sol-green)",
7
+ "label": "Add Class",
8
+ "desc": "Add one or more CSS classes.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "selector",
13
+ "label": "Selector",
14
+ "type": "text"
15
+ },
16
+ {
17
+ "name": "classes",
18
+ "label": "Classes",
19
+ "type": "text",
20
+ "placeholder": "is-active text-info"
21
+ }
22
+ ]
23
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for dom.addClass
3
+ * Add Class: Add one or more CSS classes.
4
+ *
5
+ * Each test receives (RT, sandbox) where:
6
+ * RT — the generated runtime module (Inventory, Navigator, Actions, ...)
7
+ * sandbox — a <div> containing #pw-content, #pw-form, #pw-loading
8
+ */
9
+
10
+ export const tests = [
11
+
12
+ {
13
+ name: 'dom.addClass — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Dom ?? RT.dom;
16
+ if (!ns) throw new Error('Namespace Dom/dom not found in runtime');
17
+ if (typeof ns.addClass !== 'function') throw new Error('dom.addClass is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'dom.addClass — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Dom.addClass?.("", "is-active text-info");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,17 @@
1
+ {
2
+ "id": "dom.focus",
3
+ "category": "dom",
4
+ "categoryLabel": "DOM",
5
+ "icon": "image",
6
+ "color": "var(--sol-green)",
7
+ "label": "Focus Element",
8
+ "desc": "Move keyboard focus to a selector.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "selector",
13
+ "label": "Selector",
14
+ "type": "text"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for dom.focus
3
+ * Focus Element: Move keyboard focus to a selector.
4
+ *
5
+ * Each test receives (RT, sandbox) where:
6
+ * RT — the generated runtime module (Inventory, Navigator, Actions, ...)
7
+ * sandbox — a <div> containing #pw-content, #pw-form, #pw-loading
8
+ */
9
+
10
+ export const tests = [
11
+
12
+ {
13
+ name: 'dom.focus — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Dom ?? RT.dom;
16
+ if (!ns) throw new Error('Namespace Dom/dom not found in runtime');
17
+ if (typeof ns.focus !== 'function') throw new Error('dom.focus is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'dom.focus — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Dom.focus?.("");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "dom.hide",
3
+ "category": "dom",
4
+ "categoryLabel": "DOM",
5
+ "icon": "image",
6
+ "color": "var(--sol-green)",
7
+ "label": "Hide Element",
8
+ "desc": "Add d-none class to matching elements.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "selector",
13
+ "label": "Selector",
14
+ "type": "text",
15
+ "placeholder": "#my-div"
16
+ }
17
+ ]
18
+ }