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
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "render.divider",
3
+ "category": "render",
4
+ "categoryLabel": "Page Builder",
5
+ "icon": "layout-text-window",
6
+ "color": "var(--sol-violet)",
7
+ "label": "Divider",
8
+ "desc": "Append a horizontal rule.",
9
+ "version": "1.0.0",
10
+ "params": []
11
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * action.test.js — Unit tests for render.divider
3
+ * Divider: Append a horizontal rule.
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: 'render.divider — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Render ?? RT.render;
16
+ if (!ns) throw new Error('Namespace Render/render not found in runtime');
17
+ if (typeof ns.divider !== 'function') throw new Error('render.divider is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'render.divider — can be called with no arguments',
23
+ async run(RT, sandbox) {
24
+ // Smoke test: calling the action should not throw
25
+ // (may produce DOM changes or be a no-op depending on state)
26
+ try {
27
+ await RT.Render.divider?.();
28
+ } catch (e) {
29
+ // Some actions require specific DOM state — acceptable
30
+ if (e instanceof TypeError) throw e;
31
+ }
32
+ },
33
+ },
34
+
35
+ ];
@@ -0,0 +1,17 @@
1
+ /** render.field — Append a Bootstrap form field (label + input + error div) */
2
+ import { _container, _escA } from '../../_shared/container.js';
3
+
4
+ export function field(name = '', label = '', type = 'text', placeholder = '', autocomplete = '', required = false) {
5
+ const id = `pw-field-${_escA(name)}`;
6
+ const req = Boolean(required) || (String(required) === 'true');
7
+ const div = document.createElement('div');
8
+ div.className = 'mb-3';
9
+ div.innerHTML = `<label for="${id}" class="form-label">${String(label ?? '')}</label>
10
+ <input type="${_escA(type)}" id="${id}" name="${_escA(name)}"
11
+ class="form-control pw-input"
12
+ placeholder="${_escA(placeholder)}"
13
+ ${autocomplete ? `autocomplete="${_escA(autocomplete)}"` : ''}${req ? ' aria-required="true" required' : ''}
14
+ aria-describedby="${id}-error">
15
+ <div id="${id}-error" class="invalid-feedback" data-error="${_escA(name)}"></div>`;
16
+ _container().appendChild(div);
17
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "id": "render.field",
3
+ "category": "render",
4
+ "categoryLabel": "Page Builder",
5
+ "icon": "layout-text-window",
6
+ "color": "var(--sol-violet)",
7
+ "label": "Form Field",
8
+ "desc": "Append a labelled Bootstrap input field.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "name",
13
+ "label": "Field name",
14
+ "type": "text",
15
+ "placeholder": "email"
16
+ },
17
+ {
18
+ "name": "label",
19
+ "label": "Label",
20
+ "type": "text",
21
+ "placeholder": "Email Address"
22
+ },
23
+ {
24
+ "name": "type",
25
+ "label": "Input type",
26
+ "type": "select",
27
+ "options": [
28
+ "text",
29
+ "email",
30
+ "password",
31
+ "tel",
32
+ "url",
33
+ "number",
34
+ "date",
35
+ "search"
36
+ ],
37
+ "default": "text"
38
+ },
39
+ {
40
+ "name": "placeholder",
41
+ "label": "Placeholder",
42
+ "type": "text",
43
+ "placeholder": "you@example.com"
44
+ },
45
+ {
46
+ "name": "autocomplete",
47
+ "label": "Autocomplete",
48
+ "type": "text",
49
+ "placeholder": "email",
50
+ "default": ""
51
+ },
52
+ {
53
+ "name": "required",
54
+ "label": "Required",
55
+ "type": "boolean",
56
+ "default": true
57
+ }
58
+ ]
59
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * action.test.js — Tests for render.field
3
+ */
4
+ import { field } from './action.js';
5
+
6
+ export const tests = [
7
+
8
+ {
9
+ name: 'render.field — exported from runtime',
10
+ run(RT) {
11
+ if (!RT.Render?.field) throw new Error('RT.Render.field not found');
12
+ },
13
+ },
14
+
15
+ {
16
+ name: 'render.field — appends label and input',
17
+ run() {
18
+ field('email', 'Email Address', 'email', 'you@example.com');
19
+ const ct = document.getElementById('pw-content');
20
+ const label = ct.querySelector('label');
21
+ const input = ct.querySelector('input[type="email"]');
22
+ if (!label) throw new Error('No <label>');
23
+ if (!label.textContent.includes('Email Address')) throw new Error(`Label: ${label.textContent}`);
24
+ if (!input) throw new Error('No <input type="email">');
25
+ if (input.name !== 'email') throw new Error(`input.name: ${input.name}`);
26
+ if (!input.placeholder.includes('you@example.com')) throw new Error(`placeholder: ${input.placeholder}`);
27
+ },
28
+ },
29
+
30
+ {
31
+ name: 'render.field — required flag sets required attr',
32
+ run() {
33
+ field('pw', 'Password', 'password', '', '', true);
34
+ const input = document.getElementById('pw-content').querySelector('input[type="password"]');
35
+ if (!input?.required) throw new Error('Expected required');
36
+ },
37
+ },
38
+
39
+ {
40
+ name: 'render.field — has form-control class',
41
+ run() {
42
+ field('username', 'Username');
43
+ const input = document.getElementById('pw-content').querySelector('input');
44
+ if (!input?.classList.contains('form-control')) throw new Error('Missing form-control');
45
+ },
46
+ },
47
+
48
+ {
49
+ name: 'render.field — error div has correct data-error attr',
50
+ run() {
51
+ field('myfield', 'My Field');
52
+ const errDiv = document.getElementById('pw-content').querySelector('[data-error="myfield"]');
53
+ if (!errDiv) throw new Error('No element with data-error="myfield"');
54
+ },
55
+ },
56
+
57
+ ];
@@ -0,0 +1,20 @@
1
+ /** render.link — Append a small centred text link */
2
+ import { _container } from '../../_shared/container.js';
3
+
4
+ export function link(text = '', target = '', prefix = '') {
5
+ const p = document.createElement('p');
6
+ p.className = 'text-center text-muted small mt-3 mb-0';
7
+ const a = document.createElement('a');
8
+ a.href = '#';
9
+ a.className = 'text-info';
10
+ a.textContent = String(text);
11
+ a.addEventListener('click', e => {
12
+ e.preventDefault();
13
+ const fn = 'goTo_' + String(target).replace(/[^a-zA-Z0-9]/g, '_');
14
+ if (typeof window[fn] === 'function') window[fn]();
15
+ else if (target && typeof window.Navigator !== 'undefined') window.Navigator.goto(target);
16
+ });
17
+ if (prefix) p.append(String(prefix) + ' ');
18
+ p.appendChild(a);
19
+ _container().appendChild(p);
20
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "id": "render.link",
3
+ "category": "render",
4
+ "categoryLabel": "Page Builder",
5
+ "icon": "layout-text-window",
6
+ "color": "var(--sol-violet)",
7
+ "label": "Link / Text Link",
8
+ "desc": "Append a small centred text link that navigates to a room.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "text",
13
+ "label": "Link text",
14
+ "type": "text",
15
+ "placeholder": "Forgot password?"
16
+ },
17
+ {
18
+ "name": "target",
19
+ "label": "Room",
20
+ "type": "room",
21
+ "placeholder": "forgot"
22
+ },
23
+ {
24
+ "name": "prefix",
25
+ "label": "Prefix text (optional)",
26
+ "type": "text",
27
+ "default": ""
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for render.link
3
+ * Link / Text Link: Append a small centred text link that navigates to a room.
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: 'render.link — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Render ?? RT.render;
16
+ if (!ns) throw new Error('Namespace Render/render not found in runtime');
17
+ if (typeof ns.link !== 'function') throw new Error('render.link is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'render.link — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Render.link?.("Forgot password?", "forgot", "");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "render.markdown",
3
+ "category": "render",
4
+ "categoryLabel": "Page Builder",
5
+ "icon": "layout-text-window",
6
+ "color": "var(--sol-violet)",
7
+ "label": "Markdown Block",
8
+ "desc": "Append a rendered Markdown block to the page.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "content",
13
+ "label": "Markdown",
14
+ "type": "textarea",
15
+ "placeholder": "**Bold** and _italic_…"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for render.markdown
3
+ * Markdown Block: Append a rendered Markdown block to the page.
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: 'render.markdown — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Render ?? RT.render;
16
+ if (!ns) throw new Error('Namespace Render/render not found in runtime');
17
+ if (typeof ns.markdown !== 'function') throw new Error('render.markdown is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'render.markdown — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Render.markdown?.("**Bold** and _italic_…");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,9 @@
1
+ /** render.paragraph — Append a styled paragraph */
2
+ import { _container } from '../../_shared/container.js';
3
+
4
+ export function paragraph(text, style = 'muted') {
5
+ const p = document.createElement('p');
6
+ p.className = style === 'body' ? 'mb-3' : `text-${style} small mb-2`;
7
+ p.textContent = String(text ?? '');
8
+ _container().appendChild(p);
9
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "id": "render.paragraph",
3
+ "category": "render",
4
+ "categoryLabel": "Page Builder",
5
+ "icon": "layout-text-window",
6
+ "color": "var(--sol-violet)",
7
+ "label": "Paragraph",
8
+ "desc": "Append a styled paragraph of text.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "text",
13
+ "label": "Text",
14
+ "type": "textarea",
15
+ "placeholder": "Body copy…"
16
+ },
17
+ {
18
+ "name": "style",
19
+ "label": "Style",
20
+ "type": "select",
21
+ "options": [
22
+ "muted",
23
+ "info",
24
+ "success",
25
+ "warning",
26
+ "danger",
27
+ "body"
28
+ ],
29
+ "default": "muted"
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for render.paragraph
3
+ * Paragraph: Append a styled paragraph of text.
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: 'render.paragraph — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Render ?? RT.render;
16
+ if (!ns) throw new Error('Namespace Render/render not found in runtime');
17
+ if (typeof ns.paragraph !== 'function') throw new Error('render.paragraph is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'render.paragraph — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Render.paragraph?.("Body copy…", "muted");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,10 @@
1
+ /** render.section — Append an uppercase section label */
2
+ import { _container } from '../../_shared/container.js';
3
+
4
+ export function section(title) {
5
+ const div = document.createElement('div');
6
+ div.className = 'text-uppercase fw-semibold text-muted small mt-3 mb-1';
7
+ div.style.letterSpacing = '.08em';
8
+ div.textContent = String(title ?? '');
9
+ _container().appendChild(div);
10
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "render.section",
3
+ "category": "render",
4
+ "categoryLabel": "Page Builder",
5
+ "icon": "layout-text-window",
6
+ "color": "var(--sol-violet)",
7
+ "label": "Section Header",
8
+ "desc": "Append a small uppercase section divider label.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "title",
13
+ "label": "Title",
14
+ "type": "text",
15
+ "placeholder": "Personal Details"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for render.section
3
+ * Section Header: Append a small uppercase section divider label.
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: 'render.section — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Render ?? RT.render;
16
+ if (!ns) throw new Error('Namespace Render/render not found in runtime');
17
+ if (typeof ns.section !== 'function') throw new Error('render.section is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'render.section — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Render.section?.("Personal Details");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,9 @@
1
+ /** render.subtitle — Append a muted subtitle paragraph */
2
+ import { _container } from '../../_shared/container.js';
3
+
4
+ export function subtitle(text) {
5
+ const p = document.createElement('p');
6
+ p.className = 'text-muted small mb-4';
7
+ p.textContent = String(text ?? '');
8
+ _container().appendChild(p);
9
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "render.subtitle",
3
+ "category": "render",
4
+ "categoryLabel": "Page Builder",
5
+ "icon": "layout-text-window",
6
+ "color": "var(--sol-violet)",
7
+ "label": "Subtitle",
8
+ "desc": "Append a muted subtitle paragraph.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "text",
13
+ "label": "Text",
14
+ "type": "text",
15
+ "placeholder": "Welcome back. Enter your credentials."
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for render.subtitle
3
+ * Subtitle: Append a muted subtitle paragraph.
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: 'render.subtitle — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Render ?? RT.render;
16
+ if (!ns) throw new Error('Namespace Render/render not found in runtime');
17
+ if (typeof ns.subtitle !== 'function') throw new Error('render.subtitle is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'render.subtitle — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Render.subtitle?.("Welcome back. Enter your credentials.");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,9 @@
1
+ /** render.title — Append a heading to #pw-content */
2
+ import { _container } from '../../_shared/container.js';
3
+
4
+ export function title(text, size = 'h2') {
5
+ const el = document.createElement(size);
6
+ el.className = 'pw-heading mb-1';
7
+ el.textContent = String(text ?? '');
8
+ _container().appendChild(el);
9
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "id": "render.title",
3
+ "category": "render",
4
+ "categoryLabel": "Page Builder",
5
+ "icon": "layout-text-window",
6
+ "color": "var(--sol-violet)",
7
+ "label": "Page Title",
8
+ "desc": "Append a heading to the page content area.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "text",
13
+ "label": "Title text",
14
+ "type": "text",
15
+ "placeholder": "Sign In"
16
+ },
17
+ {
18
+ "name": "size",
19
+ "label": "Size",
20
+ "type": "select",
21
+ "options": [
22
+ "h1",
23
+ "h2",
24
+ "h3",
25
+ "h4"
26
+ ],
27
+ "default": "h2"
28
+ }
29
+ ]
30
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * action.test.js — Tests for render.title
3
+ * Imports action.js directly for self-contained testing.
4
+ */
5
+ import { title } from './action.js';
6
+
7
+ export const tests = [
8
+
9
+ {
10
+ name: 'render.title — is exported from runtime',
11
+ run(RT) {
12
+ if (!RT.Render?.title) throw new Error('RT.Render.title not found');
13
+ },
14
+ },
15
+
16
+ {
17
+ name: 'render.title — appends <h2> with correct text',
18
+ run() {
19
+ title('Hello World');
20
+ const el = document.getElementById('pw-content').querySelector('h2');
21
+ if (!el) throw new Error('Expected <h2> in #pw-content');
22
+ if (!el.textContent.includes('Hello World')) throw new Error(`Got: ${el.textContent}`);
23
+ },
24
+ },
25
+
26
+ {
27
+ name: 'render.title — respects custom heading size',
28
+ run() {
29
+ title('Big', 'h1');
30
+ if (!document.getElementById('pw-content').querySelector('h1'))
31
+ throw new Error('Expected <h1>');
32
+ },
33
+ },
34
+
35
+ {
36
+ name: 'render.title — has pw-heading class',
37
+ run() {
38
+ title('Styled');
39
+ const el = document.getElementById('pw-content').querySelector('h2');
40
+ if (!el?.classList.contains('pw-heading')) throw new Error('Missing pw-heading class');
41
+ },
42
+ },
43
+
44
+ ];
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "session.clear",
3
+ "category": "session",
4
+ "categoryLabel": "Session",
5
+ "icon": "floppy",
6
+ "color": "var(--sol-base1)",
7
+ "label": "Clear Session",
8
+ "desc": "Remove all Undercity keys from sessionStorage.",
9
+ "version": "1.0.0",
10
+ "params": []
11
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * action.test.js — Unit tests for session.clear
3
+ * Clear Session: Remove all Undercity keys from sessionStorage.
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: 'session.clear — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Session ?? RT.session;
16
+ if (!ns) throw new Error('Namespace Session/session not found in runtime');
17
+ if (typeof ns.clear !== 'function') throw new Error('session.clear is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'session.clear — can be called with no arguments',
23
+ async run(RT, sandbox) {
24
+ // Smoke test: calling the action should not throw
25
+ // (may produce DOM changes or be a no-op depending on state)
26
+ try {
27
+ await RT.Session.clear?.();
28
+ } catch (e) {
29
+ // Some actions require specific DOM state — acceptable
30
+ if (e instanceof TypeError) throw e;
31
+ }
32
+ },
33
+ },
34
+
35
+ ];