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,165 @@
1
+ /**
2
+ * plugins/multipage.js — Multi-page transition plugin.
3
+ *
4
+ * Wraps all page-to-page navigation in CSS animations so the generated
5
+ * app feels like a native SPA. The transition style is set on the project:
6
+ * proj.meta.transition = 'fade' | 'slide' | 'push' | 'zoom' | 'none'
7
+ *
8
+ * How it works:
9
+ * 1. Replaces Navigator.goto() with a version that applies an exit class,
10
+ * waits for its CSS animation, then navigates.
11
+ * 2. Adds an enter animation that plays automatically when each page loads
12
+ * (via a CSS animation on body.pw-page).
13
+ * 3. Writes a transitions.css alongside flow.css.
14
+ *
15
+ * Usage: set `proj.meta.transition = 'fade'` in your project and enable the plugin.
16
+ */
17
+
18
+ // ── CSS bundles per transition type ──────────────────────────────────────────
19
+
20
+ const TRANSITIONS = {
21
+ fade: `
22
+ /* ── Fade transition ─────────────────────────────────────────────────────── */
23
+ body.pw-page {
24
+ animation: pw-enter-fade 0.28s ease-out;
25
+ }
26
+ body.pw-exit {
27
+ animation: pw-exit-fade 0.22s ease-in forwards;
28
+ pointer-events: none;
29
+ }
30
+ @keyframes pw-enter-fade {
31
+ from { opacity: 0; }
32
+ to { opacity: 1; }
33
+ }
34
+ @keyframes pw-exit-fade {
35
+ from { opacity: 1; }
36
+ to { opacity: 0; }
37
+ }`,
38
+
39
+ slide: `
40
+ /* ── Slide transition (right→left) ────────────────────────────────────────── */
41
+ body.pw-page {
42
+ animation: pw-enter-slide 0.3s cubic-bezier(.25,.46,.45,.94);
43
+ }
44
+ body.pw-exit {
45
+ animation: pw-exit-slide 0.25s cubic-bezier(.55,.06,.68,.19) forwards;
46
+ pointer-events: none;
47
+ }
48
+ @keyframes pw-enter-slide {
49
+ from { opacity: 0; transform: translateX(40px); }
50
+ to { opacity: 1; transform: translateX(0); }
51
+ }
52
+ @keyframes pw-exit-slide {
53
+ from { opacity: 1; transform: translateX(0); }
54
+ to { opacity: 0; transform: translateX(-40px); }
55
+ }`,
56
+
57
+ push: `
58
+ /* ── Push transition (scale + slide) ────────────────────────────────────── */
59
+ body.pw-page {
60
+ animation: pw-enter-push 0.32s cubic-bezier(.22,.61,.36,1);
61
+ }
62
+ body.pw-exit {
63
+ animation: pw-exit-push 0.24s ease-in forwards;
64
+ pointer-events: none;
65
+ }
66
+ @keyframes pw-enter-push {
67
+ from { opacity: 0; transform: scale(.96) translateY(16px); }
68
+ to { opacity: 1; transform: scale(1) translateY(0); }
69
+ }
70
+ @keyframes pw-exit-push {
71
+ from { opacity: 1; transform: scale(1) translateY(0); }
72
+ to { opacity: 0; transform: scale(1.03) translateY(-8px); }
73
+ }`,
74
+
75
+ zoom: `
76
+ /* ── Zoom transition ────────────────────────────────────────────────────── */
77
+ body.pw-page {
78
+ animation: pw-enter-zoom 0.28s ease-out;
79
+ }
80
+ body.pw-exit {
81
+ animation: pw-exit-zoom 0.22s ease-in forwards;
82
+ pointer-events: none;
83
+ }
84
+ @keyframes pw-enter-zoom {
85
+ from { opacity: 0; transform: scale(.94); }
86
+ to { opacity: 1; transform: scale(1); }
87
+ }
88
+ @keyframes pw-exit-zoom {
89
+ from { opacity: 1; transform: scale(1); }
90
+ to { opacity: 0; transform: scale(1.05); }
91
+ }`,
92
+
93
+ none: '',
94
+ };
95
+
96
+ // ── Runtime extension ─────────────────────────────────────────────────────────
97
+
98
+ function buildRuntimeExtension(proj) {
99
+ const type = proj.meta?.transition ?? 'fade';
100
+ const duration = proj.meta?.transitionDuration ?? 240;
101
+
102
+ return `
103
+ // ── Transitions (injected by multipage plugin) ────────────────────────────
104
+ export const Transition = (() => {
105
+ const TYPE = '${type}';
106
+ const DURATION = ${duration};
107
+
108
+ async function go(room) {
109
+ if (TYPE === 'none') { _origGoto(room); return; }
110
+ document.body.classList.add('pw-exit');
111
+ await new Promise(r => setTimeout(r, DURATION));
112
+ _origGoto(room);
113
+ }
114
+
115
+ // Override Navigator — capture original first, then replace
116
+ const _origGoto = Navigator.goto;
117
+ Navigator.goto = go;
118
+
119
+ return { type: TYPE, duration: DURATION, goto: go };
120
+ })();`;
121
+ }
122
+
123
+ // ── afterGenerate hook: write transitions.css ─────────────────────────────────
124
+
125
+ async function afterGenerate(proj, outDir, _files) {
126
+ const type = proj.meta?.transition ?? 'fade';
127
+ const css = TRANSITIONS[type] ?? TRANSITIONS.fade;
128
+ if (!css.trim()) return; // no transitions for 'none'
129
+
130
+ const { writeFile } = await import('fs/promises');
131
+ const { join } = await import('path');
132
+ await writeFile(join(outDir, 'css', 'transitions.css'), css.trimStart(), 'utf8');
133
+ }
134
+
135
+ // ── CSS injection into flow.css ───────────────────────────────────────────────
136
+
137
+ function buildCSSExtension(proj) {
138
+ const type = proj.meta?.transition ?? 'fade';
139
+ return TRANSITIONS[type] ?? '';
140
+ }
141
+
142
+ // ── Plugin manifest ───────────────────────────────────────────────────────────
143
+
144
+ const MultipagePlugin = {
145
+ name: 'multipage',
146
+ version: '1.0.0',
147
+ description: 'Animated page transitions for multi-page generated apps',
148
+
149
+ install(registry) {
150
+ registry.addRuntimeExtension(buildRuntimeExtension);
151
+ registry.addCSSExtension(buildCSSExtension);
152
+ registry.addAfterGenerate(afterGenerate);
153
+ },
154
+
155
+ commands: [
156
+ {
157
+ name: 'set transition',
158
+ category: 'Project',
159
+ description: 'Configure page transition style for generated apps',
160
+ usage: 'set transition <fade|slide|push|zoom|none>',
161
+ },
162
+ ],
163
+ };
164
+
165
+ export default MultipagePlugin;
@@ -0,0 +1,239 @@
1
+ /**
2
+ * plugins/wizard.js — Multi-step wizard generation plugin.
3
+ *
4
+ * Registers the `wizard` template type. A wizard room renders all
5
+ * its steps on a single page and navigates between them client-side,
6
+ * storing progress in Inventory so the user can resume where they left off.
7
+ *
8
+ * Node meta shape:
9
+ * {
10
+ * "wizard": {
11
+ * "title": "Account Setup",
12
+ * "steps": [
13
+ * {
14
+ * "id": "profile",
15
+ * "title": "Your Profile",
16
+ * "fields": [
17
+ * { "name": "displayName", "type": "text", "label": "Display Name", "required": true },
18
+ * { "name": "bio", "type": "textarea", "label": "Short Bio" }
19
+ * ]
20
+ * },
21
+ * {
22
+ * "id": "billing",
23
+ * "title": "Billing",
24
+ * "fields": [
25
+ * { "name": "plan", "type": "select", "label": "Plan",
26
+ * "options": ["Free", "Pro", "Enterprise"] }
27
+ * ]
28
+ * }
29
+ * ],
30
+ * "finishTarget": "dashboard"
31
+ * }
32
+ * }
33
+ */
34
+
35
+ import { escHtml, escAttr } from '../src/generator/templates.js';
36
+
37
+ // ── Field renderer (shared with forms plugin logic) ───────────────────────────
38
+
39
+ function renderField(field) {
40
+ const { name, type = 'text', label, placeholder = '', required, rows, options } = field;
41
+ const id = `wz-field-${escAttr(name)}`;
42
+ const base = `name="${escAttr(name)}" id="${id}"${required ? ' required' : ''}${placeholder ? ` placeholder="${escAttr(placeholder)}"` : ''}`;
43
+
44
+ if (type === 'textarea') {
45
+ return `
46
+ <div class="mb-3">
47
+ <label class="form-label" for="${id}">${escHtml(label ?? name)}${required ? ' <span class="text-danger">*</span>' : ''}</label>
48
+ <textarea class="form-control pw-input" ${base} rows="${rows ?? 3}"></textarea>
49
+ <div class="d-none text-danger small mt-1" data-error="${escAttr(name)}"></div>
50
+ </div>`;
51
+ }
52
+ if (type === 'select') {
53
+ const opts = (options ?? []).map(o => {
54
+ const v = typeof o === 'object' ? o.value : o;
55
+ const l = typeof o === 'object' ? o.label : o;
56
+ return `<option value="${escAttr(v)}">${escHtml(l)}</option>`;
57
+ }).join('');
58
+ return `
59
+ <div class="mb-3">
60
+ <label class="form-label" for="${id}">${escHtml(label ?? name)}</label>
61
+ <select class="form-select pw-input" ${base}><option value="">— choose —</option>${opts}</select>
62
+ <div class="d-none text-danger small mt-1" data-error="${escAttr(name)}"></div>
63
+ </div>`;
64
+ }
65
+ return `
66
+ <div class="mb-3">
67
+ <label class="form-label" for="${id}">${escHtml(label ?? name)}${required ? ' <span class="text-danger">*</span>' : ''}</label>
68
+ <input type="${escAttr(type)}" class="form-control pw-input" ${base}>
69
+ <div class="d-none text-danger small mt-1" data-error="${escAttr(name)}"></div>
70
+ </div>`;
71
+ }
72
+
73
+ // ── Template builder ──────────────────────────────────────────────────────────
74
+
75
+ function buildWizardPage(proj, node, outEdges) {
76
+ const wz = node.meta?.wizard ?? {};
77
+ const title = wz.title ?? node.label ?? 'Setup Wizard';
78
+ const steps = wz.steps ?? [];
79
+ const finishTarget = wz.finishTarget ?? outEdges[0]?.toId ?? null;
80
+ const onExitJSON = JSON.stringify(node.payload?.onExit ?? [], null, 6);
81
+
82
+ // Progress indicators
83
+ const progressItems = steps.map((step, i) => `
84
+ <div class="pw-wz-step${i === 0 ? ' active' : ''}" data-wz-step="${i}">
85
+ <span class="pw-wz-num">${i + 1}</span>
86
+ <span class="pw-wz-title">${escHtml(step.title ?? step.id)}</span>
87
+ </div>`).join('');
88
+
89
+ // Step panels
90
+ const panels = steps.map((step, i) => {
91
+ const fieldsHTML = (step.fields ?? []).map(renderField).join('');
92
+ return `
93
+ <div class="pw-wz-panel${i === 0 ? '' : ' d-none'}" data-wz-panel="${i}">
94
+ <h5 class="pw-heading mb-4">${escHtml(step.title ?? step.id)}</h5>
95
+ ${fieldsHTML || '<p class="text-muted">No fields defined for this step.</p>'}
96
+ </div>`;
97
+ }).join('');
98
+
99
+ const stepDefs = JSON.stringify(steps.map(s => ({
100
+ id: s.id,
101
+ fields: (s.fields ?? []).map(f => ({ name: f.name, required: !!f.required, type: f.type ?? 'text' })),
102
+ })), null, 6);
103
+
104
+ return `
105
+ <!-- Wizard: ${escHtml(title)} -->
106
+ <div class="pw-card col-md-7 mx-auto">
107
+ <h2 class="pw-heading mb-4">${escHtml(title)}</h2>
108
+
109
+ <!-- Progress bar -->
110
+ <div class="pw-wz-progress mb-4">${progressItems}
111
+ </div>
112
+
113
+ <!-- Step content -->
114
+ <form id="pw-wz-form" novalidate>
115
+ ${panels}
116
+ <div id="pw-wz-error" class="d-none alert alert-danger py-2 mb-3"></div>
117
+ </form>
118
+
119
+ <!-- Navigation -->
120
+ <div class="d-flex justify-content-between mt-4">
121
+ <button id="pw-wz-back" class="btn btn-outline-secondary d-none">← Back</button>
122
+ <div class="ms-auto d-flex gap-2">
123
+ <button id="pw-wz-next" class="btn btn-primary">Next →</button>
124
+ <button id="pw-wz-finish" class="btn btn-success d-none">Finish ✓</button>
125
+ </div>
126
+ </div>
127
+ </div>
128
+
129
+ <style>
130
+ .pw-wz-progress { display:flex; gap:0; }
131
+ .pw-wz-step { display:flex; align-items:center; gap:8px; padding:8px 16px; font-size:12px;
132
+ color:var(--sol-text); border-bottom:2px solid transparent; }
133
+ .pw-wz-step.active { color:var(--sol-cyan); border-color:var(--sol-cyan); }
134
+ .pw-wz-step.done { color:var(--sol-green); border-color:var(--sol-green); }
135
+ .pw-wz-num { display:inline-flex; align-items:center; justify-content:center;
136
+ width:22px; height:22px; border-radius:50%; font-size:11px; font-weight:700;
137
+ background:rgba(38,139,210,.2); color:var(--sol-blue); }
138
+ .pw-wz-step.active .pw-wz-num { background:var(--sol-cyan); color:#002b36; }
139
+ .pw-wz-step.done .pw-wz-num { background:var(--sol-green); color:#002b36; }
140
+ </style>
141
+
142
+ <script type="module">
143
+ import { Inventory, Navigator, runPayload } from './js/runtime.js';
144
+
145
+ const STEPS = ${stepDefs};
146
+ const ON_EXIT = ${onExitJSON};
147
+ const FINISH_TO = ${finishTarget ? `'${finishTarget}'` : 'null'};
148
+ const STEP_KEY = '__wz_step_${node.id}__';
149
+ let current = Inventory.get(STEP_KEY) ?? 0;
150
+
151
+ const panels = document.querySelectorAll('[data-wz-panel]');
152
+ const progSteps= document.querySelectorAll('[data-wz-step]');
153
+ const btnBack = document.getElementById('pw-wz-back');
154
+ const btnNext = document.getElementById('pw-wz-next');
155
+ const btnFin = document.getElementById('pw-wz-finish');
156
+
157
+ function showStep(n) {
158
+ current = Math.max(0, Math.min(n, STEPS.length - 1));
159
+ Inventory.set(STEP_KEY, current);
160
+ panels.forEach((p, i) => p.classList.toggle('d-none', i !== current));
161
+ progSteps.forEach((s, i) => {
162
+ s.classList.toggle('active', i === current);
163
+ s.classList.toggle('done', i < current);
164
+ });
165
+ btnBack.classList.toggle('d-none', current === 0);
166
+ btnNext.classList.toggle('d-none', current === STEPS.length - 1);
167
+ btnFin.classList.toggle('d-none', current !== STEPS.length - 1);
168
+ }
169
+
170
+ function validateStep(idx) {
171
+ const step = STEPS[idx] ?? {};
172
+ const errors = {};
173
+ for (const f of step.fields ?? []) {
174
+ if (!f.required) continue;
175
+ const el = document.querySelector(\`[name="\${f.name}"]\`);
176
+ const val = f.type === 'checkbox' ? el?.checked : el?.value?.trim();
177
+ if (!val) errors[f.name] = \`\${f.name} is required\`;
178
+ }
179
+ return errors;
180
+ }
181
+
182
+ function saveStep(idx) {
183
+ for (const f of (STEPS[idx]?.fields ?? [])) {
184
+ const el = document.querySelector(\`[name="\${f.name}"]\`);
185
+ if (el) Inventory.set(f.name, f.type === 'checkbox' ? el.checked : el.value);
186
+ }
187
+ }
188
+
189
+ btnBack.addEventListener('click', () => showStep(current - 1));
190
+
191
+ btnNext.addEventListener('click', () => {
192
+ const errors = validateStep(current);
193
+ document.querySelectorAll('[data-error]').forEach(e => { e.textContent=''; e.classList.add('d-none'); });
194
+ if (Object.keys(errors).length) {
195
+ for (const [name, msg] of Object.entries(errors)) {
196
+ const el = document.querySelector(\`[data-error="\${name}"]\`);
197
+ if (el) { el.textContent = msg; el.classList.remove('d-none'); }
198
+ }
199
+ return;
200
+ }
201
+ saveStep(current);
202
+ showStep(current + 1);
203
+ });
204
+
205
+ btnFin.addEventListener('click', async () => {
206
+ const errors = validateStep(current);
207
+ if (Object.keys(errors).length) { btnNext.click(); return; }
208
+ saveStep(current);
209
+ Inventory.delete(STEP_KEY);
210
+ await runPayload(ON_EXIT);
211
+ if (FINISH_TO) Navigator.goto(FINISH_TO);
212
+ });
213
+
214
+ showStep(current);
215
+ </script>`;
216
+ }
217
+
218
+ // ── Plugin manifest ───────────────────────────────────────────────────────────
219
+
220
+ const WizardPlugin = {
221
+ name: 'wizard',
222
+ version: '1.0.0',
223
+ description: 'Multi-step wizard pages with progress indicator and inventory persistence',
224
+
225
+ install(registry) {
226
+ registry.addTemplate('wizard', buildWizardPage);
227
+ },
228
+
229
+ commands: [
230
+ {
231
+ name: 'scaffold wizard',
232
+ category: 'Generator',
233
+ description: 'Add a wizard room with multiple steps',
234
+ usage: 'scaffold wizard <room-id> [--steps <step1,step2,...>] [--title <title>]',
235
+ },
236
+ ],
237
+ };
238
+
239
+ export default WizardPlugin;