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,44 @@
1
+ {
2
+ "id": "media.askVideoUpload",
3
+ "category": "media",
4
+ "categoryLabel": "Media & Upload",
5
+ "icon": "film",
6
+ "color": "var(--sol-magenta)",
7
+ "label": "Ask for Video Upload",
8
+ "desc": "Open file picker for a video. Optionally extract a preview thumbnail frame.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "label",
13
+ "label": "Prompt label",
14
+ "type": "text",
15
+ "placeholder": "Select a video"
16
+ },
17
+ {
18
+ "name": "accept",
19
+ "label": "Accept types",
20
+ "type": "text",
21
+ "placeholder": "video/*",
22
+ "default": "video/*"
23
+ },
24
+ {
25
+ "name": "thumbnailPrompt",
26
+ "label": "Thumbnail prompt",
27
+ "type": "text",
28
+ "placeholder": "Select a frame as thumbnail",
29
+ "default": "Select a frame that serves as a good preview thumbnail"
30
+ },
31
+ {
32
+ "name": "extractThumb",
33
+ "label": "Extract thumbnail",
34
+ "type": "boolean",
35
+ "default": true
36
+ },
37
+ {
38
+ "name": "into",
39
+ "label": "Store in var",
40
+ "type": "text",
41
+ "placeholder": "uploadedVideo"
42
+ }
43
+ ]
44
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * action.test.js — Tests for media.askVideoUpload
3
+ * Tests verify that the upload UI is rendered correctly in the DOM.
4
+ */
5
+ import { askVideoUpload } from './action.js';
6
+
7
+ export const tests = [
8
+
9
+ {
10
+ name: 'media.askVideoUpload — exported from runtime',
11
+ run(RT) {
12
+ if (!RT.Media?.askVideoUpload) throw new Error('RT.Media.askVideoUpload not found');
13
+ },
14
+ },
15
+
16
+ {
17
+ name: 'media.askVideoUpload — renders upload card with file input',
18
+ run() {
19
+ const promise = askVideoUpload('Upload your video', 'video/*', '', false);
20
+
21
+ const ct = document.getElementById('pw-content');
22
+ const card = ct.querySelector('.pw-media-wrap');
23
+ if (!card) throw new Error('Expected .pw-media-wrap in #pw-content');
24
+
25
+ const input = card.querySelector('input[type="file"]');
26
+ if (!input) throw new Error('Expected file <input>');
27
+ if (input.accept !== 'video/*') throw new Error(`accept="${input.accept}", expected "video/*"`);
28
+
29
+ const prompt = card.querySelector('p');
30
+ if (!prompt?.textContent.includes('Upload your video'))
31
+ throw new Error(`Prompt text: "${prompt?.textContent}"`);
32
+
33
+ card.remove();
34
+ promise.catch(() => {});
35
+ },
36
+ },
37
+
38
+ {
39
+ name: 'media.askVideoUpload — renders prompt label',
40
+ run() {
41
+ const promise = askVideoUpload('Choose a video file');
42
+ const card = document.getElementById('pw-content').querySelector('.pw-media-wrap');
43
+ const p = card?.querySelector('p');
44
+ if (!p?.textContent.includes('Choose a video file'))
45
+ throw new Error(`Prompt: "${p?.textContent}"`);
46
+ card?.remove();
47
+ promise.catch(() => {});
48
+ },
49
+ },
50
+
51
+ ];
@@ -0,0 +1,24 @@
1
+ {
2
+ "id": "media.captureWebcam",
3
+ "category": "media",
4
+ "categoryLabel": "Media & Upload",
5
+ "icon": "film",
6
+ "color": "var(--sol-magenta)",
7
+ "label": "Capture from Webcam",
8
+ "desc": "Ask camera permission and capture a still photo.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "label",
13
+ "label": "Prompt label",
14
+ "type": "text",
15
+ "placeholder": "Take a photo"
16
+ },
17
+ {
18
+ "name": "into",
19
+ "label": "Store in var",
20
+ "type": "text",
21
+ "placeholder": "photo"
22
+ }
23
+ ]
24
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for media.captureWebcam
3
+ * Capture from Webcam: Ask camera permission and capture a still photo.
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: 'media.captureWebcam — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Media ?? RT.media;
16
+ if (!ns) throw new Error('Namespace Media/media not found in runtime');
17
+ if (typeof ns.captureWebcam !== 'function') throw new Error('media.captureWebcam is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'media.captureWebcam — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Media.captureWebcam?.("Take a photo");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "nav.back",
3
+ "category": "navigation",
4
+ "categoryLabel": "Navigation",
5
+ "icon": "signpost",
6
+ "color": "var(--sol-blue)",
7
+ "label": "Go Back",
8
+ "desc": "Return to the previous room (browser history).",
9
+ "version": "1.0.0",
10
+ "params": []
11
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * action.test.js — Unit tests for nav.back
3
+ * Go Back: Return to the previous room (browser history).
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: 'nav.back — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Nav ?? RT.nav;
16
+ if (!ns) throw new Error('Namespace Nav/nav not found in runtime');
17
+ if (typeof ns.back !== 'function') throw new Error('nav.back is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'nav.back — 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.Nav.back?.();
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,18 @@
1
+ {
2
+ "id": "nav.goto",
3
+ "category": "navigation",
4
+ "categoryLabel": "Navigation",
5
+ "icon": "signpost",
6
+ "color": "var(--sol-blue)",
7
+ "label": "Go To Room",
8
+ "desc": "Navigate the user to another room in the flow.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "target",
13
+ "label": "Room",
14
+ "type": "room",
15
+ "placeholder": "room-id"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for nav.goto
3
+ * Go To Room: Navigate the user to another room in the flow.
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: 'nav.goto — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Nav ?? RT.nav;
16
+ if (!ns) throw new Error('Namespace Nav/nav not found in runtime');
17
+ if (typeof ns.goto !== 'function') throw new Error('nav.goto is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'nav.goto — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Nav.goto?.("room-id");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,28 @@
1
+ {
2
+ "id": "nav.redirect",
3
+ "category": "navigation",
4
+ "categoryLabel": "Navigation",
5
+ "icon": "signpost",
6
+ "color": "var(--sol-blue)",
7
+ "label": "Redirect URL",
8
+ "desc": "Navigate to an absolute or relative URL.",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "url",
13
+ "label": "URL",
14
+ "type": "text",
15
+ "placeholder": "https://…"
16
+ },
17
+ {
18
+ "name": "target",
19
+ "label": "Open in",
20
+ "type": "select",
21
+ "options": [
22
+ "_self",
23
+ "_blank"
24
+ ],
25
+ "default": "_self"
26
+ }
27
+ ]
28
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for nav.redirect
3
+ * Redirect URL: Navigate to an absolute or relative URL.
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: 'nav.redirect — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Nav ?? RT.nav;
16
+ if (!ns) throw new Error('Namespace Nav/nav not found in runtime');
17
+ if (typeof ns.redirect !== 'function') throw new Error('nav.redirect is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'nav.redirect — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Nav.redirect?.("https://…", "_self");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "nav.reload",
3
+ "category": "navigation",
4
+ "categoryLabel": "Navigation",
5
+ "icon": "signpost",
6
+ "color": "var(--sol-blue)",
7
+ "label": "Reload Page",
8
+ "desc": "Hard-reload the current room.",
9
+ "version": "1.0.0",
10
+ "params": []
11
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * action.test.js — Unit tests for nav.reload
3
+ * Reload Page: Hard-reload the current 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: 'nav.reload — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Nav ?? RT.nav;
16
+ if (!ns) throw new Error('Namespace Nav/nav not found in runtime');
17
+ if (typeof ns.reload !== 'function') throw new Error('nav.reload is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'nav.reload — 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.Nav.reload?.();
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,11 @@
1
+ {
2
+ "id": "nav.reset",
3
+ "category": "navigation",
4
+ "categoryLabel": "Navigation",
5
+ "icon": "signpost",
6
+ "color": "var(--sol-blue)",
7
+ "label": "Reset Flow",
8
+ "desc": "Clear inventory and return the user to the Lobby.",
9
+ "version": "1.0.0",
10
+ "params": []
11
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * action.test.js — Unit tests for nav.reset
3
+ * Reset Flow: Clear inventory and return the user to the Lobby.
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: 'nav.reset — is exported from runtime',
14
+ run(RT) {
15
+ const ns = RT.Nav ?? RT.nav;
16
+ if (!ns) throw new Error('Namespace Nav/nav not found in runtime');
17
+ if (typeof ns.reset !== 'function') throw new Error('nav.reset is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'nav.reset — 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.Nav.reset?.();
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,12 @@
1
+ /** render.alert — Append a Bootstrap alert box */
2
+ import { _container, _escA } from '../../_shared/container.js';
3
+
4
+ export function alert(id = 'pw-alert', type = 'danger', text = '') {
5
+ const div = document.createElement('div');
6
+ div.id = id;
7
+ div.className = `alert alert-${_escA(type)} py-2${text ? '' : ' d-none'}`;
8
+ div.setAttribute('role', 'alert');
9
+ div.setAttribute('aria-live', 'polite');
10
+ if (text) div.textContent = String(text);
11
+ _container().appendChild(div);
12
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "id": "render.alert",
3
+ "category": "render",
4
+ "categoryLabel": "Page Builder",
5
+ "icon": "layout-text-window",
6
+ "color": "var(--sol-violet)",
7
+ "label": "Alert Box",
8
+ "desc": "Append a hidden Bootstrap alert (show via display.text or form errors).",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "id",
13
+ "label": "Element ID",
14
+ "type": "text",
15
+ "placeholder": "login-alert"
16
+ },
17
+ {
18
+ "name": "type",
19
+ "label": "Type",
20
+ "type": "select",
21
+ "options": [
22
+ "danger",
23
+ "warning",
24
+ "info",
25
+ "success"
26
+ ],
27
+ "default": "danger"
28
+ },
29
+ {
30
+ "name": "text",
31
+ "label": "Initial text (leave blank to hide)",
32
+ "type": "text",
33
+ "default": ""
34
+ }
35
+ ]
36
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * action.test.js — Unit tests for render.alert
3
+ * Alert Box: Append a hidden Bootstrap alert (show via display.text or form errors).
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.alert — 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.alert !== 'function') throw new Error('render.alert is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'render.alert — can be called with default params',
23
+ async run(RT, sandbox) {
24
+ try {
25
+ await RT.Render.alert?.("login-alert", "danger", "");
26
+ } catch (e) {
27
+ if (e instanceof TypeError) throw e;
28
+ }
29
+ },
30
+ },
31
+
32
+ ];
@@ -0,0 +1,15 @@
1
+ /** render.button — Append a Bootstrap button that navigates to a target room */
2
+ import { _container, _escA } from '../../_shared/container.js';
3
+
4
+ export function button(label = '', target = '', variant = 'primary', full = true) {
5
+ const btn = document.createElement('button');
6
+ btn.type = 'button';
7
+ btn.className = `btn btn-${_escA(variant)} fw-semibold${(full === true || String(full) === 'true') ? ' w-100 mt-3' : ' mt-2'}`;
8
+ btn.textContent = String(label);
9
+ btn.addEventListener('click', () => {
10
+ const fn = 'goTo_' + String(target).replace(/[^a-zA-Z0-9]/g, '_');
11
+ if (typeof window[fn] === 'function') window[fn]();
12
+ else if (target && typeof window.Navigator !== 'undefined') window.Navigator.goto(target);
13
+ });
14
+ _container().appendChild(btn);
15
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "id": "render.button",
3
+ "category": "render",
4
+ "categoryLabel": "Page Builder",
5
+ "icon": "layout-text-window",
6
+ "color": "var(--sol-violet)",
7
+ "label": "Button",
8
+ "desc": "Append a button that navigates to a room (runs onExit first).",
9
+ "version": "1.0.0",
10
+ "params": [
11
+ {
12
+ "name": "label",
13
+ "label": "Label",
14
+ "type": "text",
15
+ "placeholder": "Sign In"
16
+ },
17
+ {
18
+ "name": "target",
19
+ "label": "Room",
20
+ "type": "room",
21
+ "placeholder": "auth-check"
22
+ },
23
+ {
24
+ "name": "variant",
25
+ "label": "Style",
26
+ "type": "select",
27
+ "options": [
28
+ "primary",
29
+ "secondary",
30
+ "outline-secondary",
31
+ "outline-info",
32
+ "danger",
33
+ "success"
34
+ ],
35
+ "default": "primary"
36
+ },
37
+ {
38
+ "name": "full",
39
+ "label": "Full width",
40
+ "type": "boolean",
41
+ "default": true
42
+ }
43
+ ]
44
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * action.test.js — Tests for render.button
3
+ */
4
+ import { button } from './action.js';
5
+
6
+ export const tests = [
7
+
8
+ {
9
+ name: 'render.button — exported from runtime',
10
+ run(RT) {
11
+ if (!RT.Render?.button) throw new Error('RT.Render.button not found');
12
+ },
13
+ },
14
+
15
+ {
16
+ name: 'render.button — appends <button> with label and primary class',
17
+ run() {
18
+ button('Sign In', 'auth-check', 'primary', true);
19
+ const btn = document.getElementById('pw-content').querySelector('button');
20
+ if (!btn) throw new Error('No button');
21
+ if (!btn.textContent.includes('Sign In')) throw new Error(`Label: ${btn.textContent}`);
22
+ if (!btn.classList.contains('btn-primary')) throw new Error('Missing btn-primary');
23
+ if (!btn.classList.contains('w-100')) throw new Error('Missing w-100 (full=true)');
24
+ },
25
+ },
26
+
27
+ {
28
+ name: 'render.button — full=false omits w-100',
29
+ run() {
30
+ button('Back', 'lobby', 'outline-secondary', false);
31
+ const btn = document.getElementById('pw-content').querySelector('button');
32
+ if (!btn) throw new Error('No button');
33
+ if (btn.classList.contains('w-100')) throw new Error('Should not have w-100 when full=false');
34
+ },
35
+ },
36
+
37
+ ];
@@ -0,0 +1,7 @@
1
+ /** render.clear — Clear #pw-content */
2
+ import { _container } from '../../_shared/container.js';
3
+
4
+ export function clear() {
5
+ const c = _container();
6
+ if (c) c.innerHTML = '';
7
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "render.clear",
3
+ "category": "render",
4
+ "categoryLabel": "Page Builder",
5
+ "icon": "layout-text-window",
6
+ "color": "var(--sol-violet)",
7
+ "label": "Clear Page",
8
+ "desc": "Empty #pw-content. Put this first in onEnter to rebuild on each visit.",
9
+ "version": "1.0.0",
10
+ "params": []
11
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * action.test.js — Unit tests for render.clear
3
+ * Clear Page: Empty #pw-content. Put this first in onEnter to rebuild on each visit.
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.clear — 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.clear !== 'function') throw new Error('render.clear is not a function');
18
+ },
19
+ },
20
+
21
+ {
22
+ name: 'render.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.Render.clear?.();
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,8 @@
1
+ /** render.divider — Append a horizontal divider */
2
+ import { _container } from '../../_shared/container.js';
3
+
4
+ export function divider() {
5
+ const hr = document.createElement('hr');
6
+ hr.className = 'my-3 border-secondary';
7
+ _container().appendChild(hr);
8
+ }