@xdelivered/emberflow 0.2.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 (414) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/bin/commands.test.ts +185 -0
  4. package/bin/commands.ts +306 -0
  5. package/bin/emberflow.mjs +38 -0
  6. package/bin/init.test.ts +165 -0
  7. package/bin/init.ts +297 -0
  8. package/bin/runtime.mjs +105 -0
  9. package/bin/runtime.test.ts +167 -0
  10. package/bin/skillTargets.test.ts +108 -0
  11. package/bin/skillTargets.ts +36 -0
  12. package/bin/skillTemplates.test.ts +37 -0
  13. package/bin/tsconfig.json +23 -0
  14. package/dist/bin/commands.d.ts +24 -0
  15. package/dist/bin/commands.js +252 -0
  16. package/dist/bin/init.d.ts +17 -0
  17. package/dist/bin/init.js +241 -0
  18. package/dist/bin/runtime.d.mts +39 -0
  19. package/dist/bin/runtime.mjs +96 -0
  20. package/dist/bin/skillTargets.d.ts +13 -0
  21. package/dist/bin/skillTargets.js +22 -0
  22. package/dist/server/agents/claudeAdapter.d.ts +8 -0
  23. package/dist/server/agents/claudeAdapter.js +190 -0
  24. package/dist/server/agents/claudeParse.d.ts +2 -0
  25. package/dist/server/agents/claudeParse.js +162 -0
  26. package/dist/server/agents/codexAdapter.d.ts +13 -0
  27. package/dist/server/agents/codexAdapter.js +174 -0
  28. package/dist/server/agents/codexParse.d.ts +2 -0
  29. package/dist/server/agents/codexParse.js +74 -0
  30. package/dist/server/agents/detect.d.ts +23 -0
  31. package/dist/server/agents/detect.js +35 -0
  32. package/dist/server/agents/gitScope.d.ts +24 -0
  33. package/dist/server/agents/gitScope.js +95 -0
  34. package/dist/server/agents/modelRejectionHint.d.ts +8 -0
  35. package/dist/server/agents/modelRejectionHint.js +12 -0
  36. package/dist/server/agents/prompt.d.ts +79 -0
  37. package/dist/server/agents/prompt.js +364 -0
  38. package/dist/server/agents/runManager.d.ts +90 -0
  39. package/dist/server/agents/runManager.js +259 -0
  40. package/dist/server/agents/types.d.ts +15 -0
  41. package/dist/server/agents/types.js +1 -0
  42. package/dist/server/apiStore.d.ts +60 -0
  43. package/dist/server/apiStore.js +249 -0
  44. package/dist/server/auth/enforce.d.ts +1 -0
  45. package/dist/server/auth/enforce.js +6 -0
  46. package/dist/server/auth/types.d.ts +1 -0
  47. package/dist/server/auth/types.js +1 -0
  48. package/dist/server/auth/verifiers.d.ts +1 -0
  49. package/dist/server/auth/verifiers.js +4 -0
  50. package/dist/server/authAttach.d.ts +19 -0
  51. package/dist/server/authAttach.js +79 -0
  52. package/dist/server/cli.d.ts +7 -0
  53. package/dist/server/cli.js +804 -0
  54. package/dist/server/client.d.ts +130 -0
  55. package/dist/server/client.js +247 -0
  56. package/dist/server/environments.d.ts +103 -0
  57. package/dist/server/environments.js +486 -0
  58. package/dist/server/flowStore.d.ts +37 -0
  59. package/dist/server/flowStore.js +117 -0
  60. package/dist/server/httpOperations.d.ts +32 -0
  61. package/dist/server/httpOperations.js +62 -0
  62. package/dist/server/index.d.ts +1 -0
  63. package/dist/server/index.js +1022 -0
  64. package/dist/server/infrastructure.d.ts +51 -0
  65. package/dist/server/infrastructure.js +116 -0
  66. package/dist/server/login.d.ts +21 -0
  67. package/dist/server/login.js +44 -0
  68. package/dist/server/mcp.d.ts +1 -0
  69. package/dist/server/mcp.js +182 -0
  70. package/dist/server/migrateFlows.d.ts +8 -0
  71. package/dist/server/migrateFlows.js +25 -0
  72. package/dist/server/mockHandler.d.ts +15 -0
  73. package/dist/server/mockHandler.js +35 -0
  74. package/dist/server/nodesPayload.d.ts +14 -0
  75. package/dist/server/nodesPayload.js +14 -0
  76. package/dist/server/normalizeFlow.d.ts +21 -0
  77. package/dist/server/normalizeFlow.js +48 -0
  78. package/dist/server/openBrowser.d.ts +8 -0
  79. package/dist/server/openBrowser.js +16 -0
  80. package/dist/server/operationResult.d.ts +12 -0
  81. package/dist/server/operationResult.js +26 -0
  82. package/dist/server/pathGuard.d.ts +1 -0
  83. package/dist/server/pathGuard.js +16 -0
  84. package/dist/server/pathSafety.d.ts +20 -0
  85. package/dist/server/pathSafety.js +35 -0
  86. package/dist/server/projectConfig.d.ts +47 -0
  87. package/dist/server/projectConfig.js +61 -0
  88. package/dist/server/projectMode.d.ts +26 -0
  89. package/dist/server/projectMode.js +44 -0
  90. package/dist/server/redact.d.ts +6 -0
  91. package/dist/server/redact.js +37 -0
  92. package/dist/server/runRegistry.d.ts +84 -0
  93. package/dist/server/runRegistry.js +200 -0
  94. package/dist/server/runtime/RuntimeAdapter.d.ts +22 -0
  95. package/dist/server/runtime/RuntimeAdapter.js +1 -0
  96. package/dist/server/runtime/expressAdapter.d.ts +19 -0
  97. package/dist/server/runtime/expressAdapter.js +56 -0
  98. package/dist/server/scenarioTest.d.ts +14 -0
  99. package/dist/server/scenarioTest.js +66 -0
  100. package/dist/server/subflowRunner.d.ts +32 -0
  101. package/dist/server/subflowRunner.js +72 -0
  102. package/dist/server/testRunner.d.ts +70 -0
  103. package/dist/server/testRunner.js +151 -0
  104. package/dist/src/config.d.ts +3 -0
  105. package/dist/src/config.js +2 -0
  106. package/dist/src/engine/authVerify.d.ts +33 -0
  107. package/dist/src/engine/authVerify.js +63 -0
  108. package/dist/src/engine/diagnostics.d.ts +62 -0
  109. package/dist/src/engine/diagnostics.js +148 -0
  110. package/dist/src/engine/executor.d.ts +151 -0
  111. package/dist/src/engine/executor.js +684 -0
  112. package/dist/src/engine/httpError.d.ts +7 -0
  113. package/dist/src/engine/httpError.js +14 -0
  114. package/dist/src/engine/index.d.ts +11 -0
  115. package/dist/src/engine/index.js +11 -0
  116. package/dist/src/engine/output.d.ts +11 -0
  117. package/dist/src/engine/output.js +20 -0
  118. package/dist/src/engine/publish.d.ts +30 -0
  119. package/dist/src/engine/publish.js +95 -0
  120. package/dist/src/engine/registry.d.ts +34 -0
  121. package/dist/src/engine/registry.js +67 -0
  122. package/dist/src/engine/scenarios.d.ts +12 -0
  123. package/dist/src/engine/scenarios.js +19 -0
  124. package/dist/src/engine/schemaCheck.d.ts +4 -0
  125. package/dist/src/engine/schemaCheck.js +34 -0
  126. package/dist/src/engine/trace.d.ts +12 -0
  127. package/dist/src/engine/trace.js +12 -0
  128. package/dist/src/engine/types.d.ts +308 -0
  129. package/dist/src/engine/types.js +1 -0
  130. package/dist/src/engine/validation.d.ts +26 -0
  131. package/dist/src/engine/validation.js +318 -0
  132. package/dist/src/flows/anomaly-flows.d.ts +23 -0
  133. package/dist/src/flows/anomaly-flows.js +151 -0
  134. package/dist/src/flows/city-sweep-flow.d.ts +10 -0
  135. package/dist/src/flows/city-sweep-flow.js +127 -0
  136. package/dist/src/flows/login-flow.d.ts +13 -0
  137. package/dist/src/flows/login-flow.js +145 -0
  138. package/dist/src/flows/pradar-flows.d.ts +5 -0
  139. package/dist/src/flows/pradar-flows.js +375 -0
  140. package/dist/src/flows/weather-flow.d.ts +12 -0
  141. package/dist/src/flows/weather-flow.js +150 -0
  142. package/dist/src/lib/pathParams.d.ts +3 -0
  143. package/dist/src/lib/pathParams.js +6 -0
  144. package/dist/src/nodes/anomaly.d.ts +2 -0
  145. package/dist/src/nodes/anomaly.js +556 -0
  146. package/dist/src/nodes/flow-control.d.ts +7 -0
  147. package/dist/src/nodes/flow-control.js +270 -0
  148. package/dist/src/nodes/index.d.ts +3 -0
  149. package/dist/src/nodes/index.js +20 -0
  150. package/dist/src/nodes/login.d.ts +7 -0
  151. package/dist/src/nodes/login.js +158 -0
  152. package/dist/src/nodes/loops.d.ts +11 -0
  153. package/dist/src/nodes/loops.js +60 -0
  154. package/dist/src/nodes/pradar/index.d.ts +7 -0
  155. package/dist/src/nodes/pradar/index.js +9 -0
  156. package/dist/src/nodes/pradar/logic.d.ts +189 -0
  157. package/dist/src/nodes/pradar/logic.js +370 -0
  158. package/dist/src/nodes/pradar/nodes.d.ts +42 -0
  159. package/dist/src/nodes/pradar/nodes.js +801 -0
  160. package/dist/src/nodes/requireAuth.d.ts +20 -0
  161. package/dist/src/nodes/requireAuth.js +48 -0
  162. package/dist/src/nodes/response.d.ts +8 -0
  163. package/dist/src/nodes/response.js +33 -0
  164. package/dist/src/nodes/weather.d.ts +33 -0
  165. package/dist/src/nodes/weather.js +260 -0
  166. package/package.json +92 -0
  167. package/server/agentRoute.test.ts +157 -0
  168. package/server/agents/__fixtures__/fake-claude-writer.mjs +23 -0
  169. package/server/agents/__fixtures__/fake-claude.mjs +55 -0
  170. package/server/agents/__fixtures__/fake-codex-writer.mjs +27 -0
  171. package/server/agents/__fixtures__/fake-codex.mjs +52 -0
  172. package/server/agents/__fixtures__/fake-version-empty.mjs +3 -0
  173. package/server/agents/__fixtures__/fake-version-fail.mjs +4 -0
  174. package/server/agents/__fixtures__/fake-version-garbage.mjs +4 -0
  175. package/server/agents/__fixtures__/fake-version-good.mjs +4 -0
  176. package/server/agents/__fixtures__/fake-version-hang.mjs +3 -0
  177. package/server/agents/claudeAdapter.test.ts +127 -0
  178. package/server/agents/claudeAdapter.ts +212 -0
  179. package/server/agents/claudeParse.ts +193 -0
  180. package/server/agents/codexAdapter.test.ts +125 -0
  181. package/server/agents/codexAdapter.ts +198 -0
  182. package/server/agents/codexParse.test.ts +58 -0
  183. package/server/agents/codexParse.ts +126 -0
  184. package/server/agents/detect.test.ts +65 -0
  185. package/server/agents/detect.ts +43 -0
  186. package/server/agents/gitScope.test.ts +129 -0
  187. package/server/agents/gitScope.ts +110 -0
  188. package/server/agents/modelRejectionHint.test.ts +30 -0
  189. package/server/agents/modelRejectionHint.ts +14 -0
  190. package/server/agents/prompt.test.ts +613 -0
  191. package/server/agents/prompt.ts +527 -0
  192. package/server/agents/runManager.test.ts +251 -0
  193. package/server/agents/runManager.ts +286 -0
  194. package/server/agents/types.ts +17 -0
  195. package/server/apiMount.test.ts +591 -0
  196. package/server/apiStore.test.ts +324 -0
  197. package/server/apiStore.ts +241 -0
  198. package/server/auth/enforce.test.ts +61 -0
  199. package/server/auth/enforce.ts +6 -0
  200. package/server/auth/types.ts +5 -0
  201. package/server/auth/verifiers.test.ts +37 -0
  202. package/server/auth/verifiers.ts +9 -0
  203. package/server/authAttach.test.ts +175 -0
  204. package/server/authAttach.ts +99 -0
  205. package/server/cli.ts +847 -0
  206. package/server/cliAuth.test.ts +229 -0
  207. package/server/cliCreate.test.ts +131 -0
  208. package/server/cliDoctor.test.ts +326 -0
  209. package/server/cliTest.test.ts +735 -0
  210. package/server/client.ts +314 -0
  211. package/server/demoProject.test.ts +29 -0
  212. package/server/diagnosticsRoute.test.ts +169 -0
  213. package/server/environments.test.ts +620 -0
  214. package/server/environments.ts +603 -0
  215. package/server/environmentsRoute.test.ts +385 -0
  216. package/server/errorWorkflow.test.ts +205 -0
  217. package/server/flowStore.test.ts +84 -0
  218. package/server/flowStore.ts +125 -0
  219. package/server/httpOperations.test.ts +377 -0
  220. package/server/httpOperations.ts +96 -0
  221. package/server/index.ts +1111 -0
  222. package/server/infrastructure.test.ts +154 -0
  223. package/server/infrastructure.ts +161 -0
  224. package/server/infrastructureRoute.test.ts +105 -0
  225. package/server/login.test.ts +153 -0
  226. package/server/login.ts +67 -0
  227. package/server/mcp.ts +278 -0
  228. package/server/migrateFlows.test.ts +55 -0
  229. package/server/migrateFlows.ts +23 -0
  230. package/server/mockHandler.test.ts +138 -0
  231. package/server/mockHandler.ts +52 -0
  232. package/server/mockRun.test.ts +367 -0
  233. package/server/mockServe.test.ts +467 -0
  234. package/server/nodesPayload.test.ts +29 -0
  235. package/server/nodesPayload.ts +26 -0
  236. package/server/normalizeFlow.test.ts +80 -0
  237. package/server/normalizeFlow.ts +59 -0
  238. package/server/openBrowser.test.ts +20 -0
  239. package/server/openBrowser.ts +22 -0
  240. package/server/operationResult.test.ts +67 -0
  241. package/server/operationResult.ts +27 -0
  242. package/server/pathGuard.test.ts +19 -0
  243. package/server/pathGuard.ts +12 -0
  244. package/server/pathSafety.test.ts +38 -0
  245. package/server/pathSafety.ts +33 -0
  246. package/server/projectConfig.test.ts +67 -0
  247. package/server/projectConfig.ts +101 -0
  248. package/server/projectMode.test.ts +106 -0
  249. package/server/projectMode.ts +59 -0
  250. package/server/redact.test.ts +88 -0
  251. package/server/redact.ts +35 -0
  252. package/server/redactEgress.test.ts +281 -0
  253. package/server/runRegistry.ts +270 -0
  254. package/server/runsAuth.test.ts +119 -0
  255. package/server/runtime/RuntimeAdapter.ts +24 -0
  256. package/server/runtime/expressAdapter.test.ts +38 -0
  257. package/server/runtime/expressAdapter.ts +70 -0
  258. package/server/scenarioTest.test.ts +148 -0
  259. package/server/scenarioTest.ts +88 -0
  260. package/server/setupStatus.test.ts +127 -0
  261. package/server/subflowRunner.ts +104 -0
  262. package/server/testRunner.ts +216 -0
  263. package/server/tsconfig.json +20 -0
  264. package/server/workflowTestRoute.test.ts +297 -0
  265. package/src/App.tsx +233 -0
  266. package/src/components/AgentConsole.tsx +461 -0
  267. package/src/components/BranchRulesEditor.tsx +156 -0
  268. package/src/components/CreateModal.tsx +275 -0
  269. package/src/components/Dock.tsx +487 -0
  270. package/src/components/EnvironmentDialog.tsx +569 -0
  271. package/src/components/EnvironmentPicker.tsx +357 -0
  272. package/src/components/EnvironmentsDialog.tsx +144 -0
  273. package/src/components/ExecutionPager.tsx +67 -0
  274. package/src/components/InfraPanel.test.tsx +103 -0
  275. package/src/components/InfraPanel.tsx +171 -0
  276. package/src/components/Inspector.tsx +1182 -0
  277. package/src/components/Json.tsx +50 -0
  278. package/src/components/JsonModal.tsx +52 -0
  279. package/src/components/LogMessage.tsx +61 -0
  280. package/src/components/NodeRunModal.tsx +213 -0
  281. package/src/components/RunLogPanel.tsx +397 -0
  282. package/src/components/RunbookView.tsx +1085 -0
  283. package/src/components/Scenarios.tsx +476 -0
  284. package/src/components/SettingsDialog.tsx +229 -0
  285. package/src/components/Sidebar.tsx +436 -0
  286. package/src/components/StatusBar.tsx +302 -0
  287. package/src/components/Toolbar.tsx +663 -0
  288. package/src/components/WelcomeDialog.test.tsx +107 -0
  289. package/src/components/WelcomeDialog.tsx +412 -0
  290. package/src/components/agentMarkdown.test.tsx +91 -0
  291. package/src/components/agentMarkdown.tsx +158 -0
  292. package/src/components/ui/badge.tsx +31 -0
  293. package/src/components/ui/button.tsx +46 -0
  294. package/src/components/ui/combobox.tsx +114 -0
  295. package/src/components/ui/command.tsx +82 -0
  296. package/src/components/ui/dialog.tsx +73 -0
  297. package/src/components/ui/input.tsx +20 -0
  298. package/src/components/ui/popover.tsx +32 -0
  299. package/src/components/ui/resizable.tsx +44 -0
  300. package/src/components/ui/tabs.tsx +36 -0
  301. package/src/config.ts +4 -0
  302. package/src/engine/authVerify.ts +81 -0
  303. package/src/engine/diagnostics.test.ts +437 -0
  304. package/src/engine/diagnostics.ts +211 -0
  305. package/src/engine/executor.test.ts +1339 -0
  306. package/src/engine/executor.ts +840 -0
  307. package/src/engine/httpError.test.ts +12 -0
  308. package/src/engine/httpError.ts +16 -0
  309. package/src/engine/index.ts +11 -0
  310. package/src/engine/output.ts +21 -0
  311. package/src/engine/publish.test.ts +176 -0
  312. package/src/engine/publish.ts +127 -0
  313. package/src/engine/registry.test.ts +64 -0
  314. package/src/engine/registry.ts +80 -0
  315. package/src/engine/scenarios.test.ts +55 -0
  316. package/src/engine/scenarios.ts +29 -0
  317. package/src/engine/schemaCheck.test.ts +21 -0
  318. package/src/engine/schemaCheck.ts +35 -0
  319. package/src/engine/trace.test.ts +19 -0
  320. package/src/engine/trace.ts +23 -0
  321. package/src/engine/types.test.ts +22 -0
  322. package/src/engine/types.ts +316 -0
  323. package/src/engine/validation.test.ts +314 -0
  324. package/src/engine/validation.ts +354 -0
  325. package/src/flows/anomaly-flows.test.ts +81 -0
  326. package/src/flows/anomaly-flows.ts +202 -0
  327. package/src/flows/city-sweep-flow.ts +131 -0
  328. package/src/flows/flows.test.ts +30 -0
  329. package/src/flows/login-flow.ts +150 -0
  330. package/src/flows/pradar-flows.test.ts +204 -0
  331. package/src/flows/pradar-flows.ts +419 -0
  332. package/src/flows/weather-flow.ts +154 -0
  333. package/src/globals.css +147 -0
  334. package/src/lib/pathParams.test.ts +24 -0
  335. package/src/lib/pathParams.ts +6 -0
  336. package/src/lib/registerLens.test.ts +79 -0
  337. package/src/lib/registerLens.ts +59 -0
  338. package/src/lib/runbookModel.test.ts +61 -0
  339. package/src/lib/runbookModel.ts +290 -0
  340. package/src/lib/runbookProjection.test.ts +417 -0
  341. package/src/lib/runbookProjection.ts +254 -0
  342. package/src/lib/useTailAnchor.ts +53 -0
  343. package/src/lib/utils.ts +7 -0
  344. package/src/main.tsx +13 -0
  345. package/src/nodes/anomaly.test.ts +98 -0
  346. package/src/nodes/anomaly.ts +637 -0
  347. package/src/nodes/effects-scan.test.ts +44 -0
  348. package/src/nodes/flow-control.test.ts +280 -0
  349. package/src/nodes/flow-control.ts +305 -0
  350. package/src/nodes/index.ts +22 -0
  351. package/src/nodes/login.test.ts +81 -0
  352. package/src/nodes/login.ts +186 -0
  353. package/src/nodes/loops.ts +69 -0
  354. package/src/nodes/pradar/index.ts +11 -0
  355. package/src/nodes/pradar/logic.test.ts +288 -0
  356. package/src/nodes/pradar/logic.ts +582 -0
  357. package/src/nodes/pradar/nodes.test.ts +146 -0
  358. package/src/nodes/pradar/nodes.ts +955 -0
  359. package/src/nodes/requireAuth.test.ts +75 -0
  360. package/src/nodes/requireAuth.ts +55 -0
  361. package/src/nodes/response.test.ts +33 -0
  362. package/src/nodes/response.ts +39 -0
  363. package/src/nodes/traceDetail.test.ts +39 -0
  364. package/src/nodes/weather.test.ts +65 -0
  365. package/src/nodes/weather.ts +306 -0
  366. package/src/store/agentClient.test.ts +103 -0
  367. package/src/store/agentClient.ts +131 -0
  368. package/src/store/apiTree.test.ts +17 -0
  369. package/src/store/apiTree.ts +73 -0
  370. package/src/store/builderStore.test.ts +1234 -0
  371. package/src/store/builderStore.ts +1879 -0
  372. package/src/store/infraClient.ts +63 -0
  373. package/src/store/nodeMeta.test.ts +24 -0
  374. package/src/store/nodeMeta.ts +21 -0
  375. package/src/store/persistence.test.ts +15 -0
  376. package/src/store/persistence.ts +54 -0
  377. package/src/store/serverRunner.ts +381 -0
  378. package/src/store/setupClient.ts +57 -0
  379. package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
  380. package/studio-dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
  381. package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
  382. package/studio-dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
  383. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
  384. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
  385. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
  386. package/studio-dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
  387. package/studio-dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  388. package/studio-dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  389. package/studio-dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  390. package/studio-dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  391. package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
  392. package/studio-dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
  393. package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
  394. package/studio-dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
  395. package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
  396. package/studio-dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
  397. package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
  398. package/studio-dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
  399. package/studio-dist/assets/index-DNJwW-hM.css +1 -0
  400. package/studio-dist/assets/index-O26dKRjW.js +65 -0
  401. package/studio-dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  402. package/studio-dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  403. package/studio-dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  404. package/studio-dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  405. package/studio-dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  406. package/studio-dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  407. package/studio-dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  408. package/studio-dist/favicon.svg +1 -0
  409. package/studio-dist/icons.svg +24 -0
  410. package/studio-dist/index.html +14 -0
  411. package/templates/skills/emberflow-basics/SKILL.md +316 -0
  412. package/templates/skills/emberflow-model-process/SKILL.md +209 -0
  413. package/templates/skills/emberflow-new-workflow/SKILL.md +211 -0
  414. package/templates/skills/emberflow-review-workflow/SKILL.md +148 -0
@@ -0,0 +1,107 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { WelcomeChecklist } from './WelcomeDialog';
4
+ import type { SetupStatus } from '../store/setupClient';
5
+
6
+ /** A pristine, just-initialized project: no agent, no environments, no skills,
7
+ * only the hello example op. */
8
+ const FRESH: SetupStatus = {
9
+ agents: [],
10
+ environments: { configured: false, count: 0, protectedCount: 0, anyAuthConfigured: false },
11
+ skills: { claude: false, codex: false },
12
+ language: 'typescript',
13
+ ops: { count: 1, onlyHello: true },
14
+ servingMode: 'mock',
15
+ infrastructure: { present: false },
16
+ };
17
+
18
+ /** A project that's been set up: agent on PATH, environments + auth, skills
19
+ * installed, infrastructure scouted, several ops. */
20
+ const CONFIGURED: SetupStatus = {
21
+ agents: [
22
+ { kind: 'claude', version: '2.1.0' },
23
+ { kind: 'codex', version: '0.5.3' },
24
+ ],
25
+ environments: { configured: true, count: 2, protectedCount: 1, anyAuthConfigured: true },
26
+ skills: { claude: true, codex: false },
27
+ language: 'typescript',
28
+ ops: { count: 4, onlyHello: false },
29
+ servingMode: 'real',
30
+ infrastructure: { present: true, itemCount: 3 },
31
+ };
32
+
33
+ const html = (status: SetupStatus): string =>
34
+ renderToStaticMarkup(<WelcomeChecklist status={status} />);
35
+
36
+ describe('WelcomeChecklist', () => {
37
+ it('renders every checklist row', () => {
38
+ const out = html(FRESH);
39
+ for (const title of [
40
+ 'Coding agent detected',
41
+ 'Environments configured',
42
+ 'Secrets &amp; auth',
43
+ 'Agent skills installed',
44
+ 'Infrastructure scouted',
45
+ 'First operation',
46
+ ]) {
47
+ expect(out).toContain(title);
48
+ }
49
+ });
50
+
51
+ it('fresh project: shows the install hint and no completed rows', () => {
52
+ const out = html(FRESH);
53
+ // Skills-missing hint is the copyable init command, no action button.
54
+ expect(out).toContain('npx emberflow init --local --no-launch');
55
+ expect(out).toContain('None detected on PATH');
56
+ // Nothing is done yet — no "done" glyph.
57
+ expect(out).not.toContain('aria-label="done"');
58
+ });
59
+
60
+ it('fresh project: the scout button is disabled without an agent', () => {
61
+ const out = html(FRESH);
62
+ // The Scout button carries the "detect an agent first" tooltip and is disabled.
63
+ expect(out).toContain('Detect a coding agent first');
64
+ expect(out).toMatch(/Scout/);
65
+ expect(out).toContain('disabled');
66
+ });
67
+
68
+ it('configured project: shows completed rows and agent versions', () => {
69
+ const out = html(CONFIGURED);
70
+ expect(out).toContain('aria-label="done"');
71
+ expect(out).toContain('claude 2.1.0');
72
+ expect(out).toContain('codex 0.5.3');
73
+ expect(out).toContain('2 environments, 1 protected');
74
+ // Skills present → no install hint.
75
+ expect(out).not.toContain('npx emberflow init');
76
+ });
77
+
78
+ it('configured project: scout is enabled (agent present)', () => {
79
+ const out = html(CONFIGURED);
80
+ expect(out).not.toContain('Detect a coding agent first');
81
+ });
82
+
83
+ it('two agents + onChooseAgent: renders the picker with the chosen agent pressed', () => {
84
+ const out = renderToStaticMarkup(
85
+ <WelcomeChecklist status={CONFIGURED} chosenAgent="codex" onChooseAgent={() => {}} />,
86
+ );
87
+ expect(out).toContain('Choose coding agent');
88
+ expect(out).toContain('Both detected');
89
+ // Codex is the active choice; Claude is not.
90
+ expect(out).toMatch(/aria-pressed="true"[^>]*>Codex/);
91
+ expect(out).toMatch(/aria-pressed="false"[^>]*>Claude/);
92
+ });
93
+
94
+ it('single agent: no picker even when onChooseAgent is provided', () => {
95
+ const oneAgent: SetupStatus = { ...CONFIGURED, agents: [{ kind: 'claude', version: '2.1.0' }] };
96
+ const out = renderToStaticMarkup(
97
+ <WelcomeChecklist status={oneAgent} onChooseAgent={() => {}} />,
98
+ );
99
+ expect(out).not.toContain('Choose coding agent');
100
+ expect(out).toContain('claude 2.1.0');
101
+ });
102
+
103
+ it('shows checklist progress', () => {
104
+ expect(html(FRESH)).toContain('0/6 done');
105
+ expect(html(CONFIGURED)).toContain('6/6 done');
106
+ });
107
+ });
@@ -0,0 +1,412 @@
1
+ import { useEffect, useState } from 'react';
2
+ import {
3
+ CheckCircle2Icon,
4
+ CheckIcon,
5
+ CircleIcon,
6
+ CopyIcon,
7
+ MinusCircleIcon,
8
+ SparklesIcon,
9
+ } from 'lucide-react';
10
+ import { cn } from '@/lib/utils';
11
+ import { Button } from '@/components/ui/button';
12
+ import { Dialog, DialogContent, DialogDescription, DialogTitle } from '@/components/ui/dialog';
13
+ import { EnvironmentsDialog } from './EnvironmentsDialog';
14
+ import { useBuilderStore } from '../store/builderStore';
15
+ import { setupProgress, type SetupStatus } from '../store/setupClient';
16
+ import type { AgentKind } from '../store/agentClient';
17
+
18
+ const AGENT_LABELS: Record<AgentKind, string> = {
19
+ codex: 'Codex',
20
+ claude: 'Claude',
21
+ };
22
+
23
+ const WELCOME_DISMISSED_KEY = 'emberflow.welcome.dismissed';
24
+ /** Re-run this exact command to (re)install the agent skills only. */
25
+ const SKILLS_INSTALL_COMMAND = 'npx emberflow init --local --no-launch';
26
+
27
+ type RowStatus = 'complete' | 'incomplete' | 'unavailable';
28
+
29
+ function StatusGlyph({ status }: { status: RowStatus }) {
30
+ if (status === 'complete') {
31
+ return <CheckCircle2Icon className="mt-0.5 size-4 shrink-0 text-success" aria-label="done" />;
32
+ }
33
+ if (status === 'unavailable') {
34
+ return (
35
+ <MinusCircleIcon
36
+ className="mt-0.5 size-4 shrink-0 text-muted-foreground/30"
37
+ aria-label="not yet available"
38
+ />
39
+ );
40
+ }
41
+ return (
42
+ <CircleIcon
43
+ className="mt-0.5 size-4 shrink-0 text-muted-foreground/50"
44
+ aria-label="to do"
45
+ />
46
+ );
47
+ }
48
+
49
+ function SectionTitle({ children }: { children: React.ReactNode }) {
50
+ return (
51
+ <div className="text-[10px] font-medium uppercase tracking-widest text-muted-foreground">
52
+ {children}
53
+ </div>
54
+ );
55
+ }
56
+
57
+ function ChecklistRow({
58
+ status,
59
+ title,
60
+ detail,
61
+ action,
62
+ children,
63
+ }: {
64
+ status: RowStatus;
65
+ title: string;
66
+ detail: string;
67
+ action?: React.ReactNode;
68
+ children?: React.ReactNode;
69
+ }) {
70
+ return (
71
+ <div className="flex items-start gap-2.5 rounded-md border border-border/70 px-2.5 py-2">
72
+ <StatusGlyph status={status} />
73
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
74
+ <span className="text-[12.5px] font-medium leading-tight">{title}</span>
75
+ <span className="text-[11px] leading-snug text-muted-foreground">{detail}</span>
76
+ {children}
77
+ </div>
78
+ {action && <div className="shrink-0 self-center">{action}</div>}
79
+ </div>
80
+ );
81
+ }
82
+
83
+ function CopyCommand({ command }: { command: string }) {
84
+ const [copied, setCopied] = useState(false);
85
+ return (
86
+ <button
87
+ type="button"
88
+ onClick={() => {
89
+ void navigator.clipboard?.writeText(command);
90
+ setCopied(true);
91
+ setTimeout(() => setCopied(false), 1500);
92
+ }}
93
+ title="Copy to clipboard"
94
+ className="mt-0.5 flex items-center gap-2 self-start rounded-md border border-border bg-secondary/40 px-2 py-1 font-mono text-[11px] text-foreground/90 transition-colors hover:bg-secondary"
95
+ >
96
+ <code>{command}</code>
97
+ {copied ? (
98
+ <CheckIcon className="size-3 text-success" />
99
+ ) : (
100
+ <CopyIcon className="size-3 text-muted-foreground" />
101
+ )}
102
+ </button>
103
+ );
104
+ }
105
+
106
+ export interface WelcomeChecklistActions {
107
+ onOpenSettings: () => void;
108
+ onSetupEnvironments: () => void;
109
+ onOpenEnvironments: () => void;
110
+ onOpenHelloOp: () => void;
111
+ onScoutInfrastructure: () => void;
112
+ }
113
+
114
+ const NOOP_ACTIONS: WelcomeChecklistActions = {
115
+ onOpenSettings: () => {},
116
+ onSetupEnvironments: () => {},
117
+ onOpenEnvironments: () => {},
118
+ onOpenHelloOp: () => {},
119
+ onScoutInfrastructure: () => {},
120
+ };
121
+
122
+ /**
123
+ * Presentational checklist rendered from a resolved `/setup-status`. Split out
124
+ * from the dialog wrapper (which owns fetch/open state) so it renders purely
125
+ * from props — the component test drives it across fresh/configured/no-agent
126
+ * states without a live runner.
127
+ */
128
+ export function WelcomeChecklist({
129
+ status,
130
+ actions = NOOP_ACTIONS,
131
+ chosenAgent,
132
+ onChooseAgent,
133
+ }: {
134
+ status: SetupStatus;
135
+ actions?: WelcomeChecklistActions;
136
+ /** The user's preferred agent (from Settings); defaults to the first detected. */
137
+ chosenAgent?: AgentKind;
138
+ onChooseAgent?: (kind: AgentKind) => void;
139
+ }) {
140
+ const { agents, environments, skills, ops, infrastructure } = status;
141
+ const hasAgent = agents.length > 0;
142
+ const skillsInstalled = skills.claude || skills.codex;
143
+ // With more than one agent CLI on PATH the row becomes a choice, not a fact.
144
+ const multipleAgents = agents.length > 1 && onChooseAgent !== undefined;
145
+ const activeAgent = chosenAgent ?? agents[0]?.kind;
146
+
147
+ const agentDetail = hasAgent
148
+ ? multipleAgents
149
+ ? 'Both detected — pick which one builds for you (changeable in Settings).'
150
+ : agents.map((a) => `${a.kind}${a.version ? ` ${a.version}` : ''}`).join(', ')
151
+ : 'None detected on PATH — install codex or claude to let the agent build for you.';
152
+
153
+ const envDetail = environments.configured
154
+ ? `${environments.count} environment${environments.count === 1 ? '' : 's'}` +
155
+ (environments.protectedCount > 0 ? `, ${environments.protectedCount} protected` : '')
156
+ : "You're in Mock — nothing real is touched. Set up environments to point runs at real systems.";
157
+
158
+ const secretsStatus: RowStatus = !environments.configured
159
+ ? 'unavailable'
160
+ : environments.anyAuthConfigured
161
+ ? 'complete'
162
+ : 'incomplete';
163
+ const secretsDetail = !environments.configured
164
+ ? 'Configure environments first — secrets and auth live per environment.'
165
+ : environments.anyAuthConfigured
166
+ ? 'Auth configured on at least one environment.'
167
+ : 'Add the secrets and login your operations need.';
168
+
169
+ const progress = setupProgress(status);
170
+
171
+ return (
172
+ <div className="space-y-2.5">
173
+ <div className="flex items-baseline justify-between">
174
+ <SectionTitle>Setup checklist</SectionTitle>
175
+ <span className="text-[10.5px] tabular-nums text-muted-foreground">
176
+ {progress.done}/{progress.total} done
177
+ </span>
178
+ </div>
179
+ <div className="flex flex-col gap-1.5">
180
+ <ChecklistRow
181
+ status={hasAgent ? 'complete' : 'incomplete'}
182
+ title="Coding agent detected"
183
+ detail={agentDetail}
184
+ action={
185
+ <Button variant="outline" size="sm" onClick={actions.onOpenSettings}>
186
+ Settings
187
+ </Button>
188
+ }
189
+ >
190
+ {multipleAgents && (
191
+ <div className="mt-0.5 flex gap-1 self-start" role="group" aria-label="Choose coding agent">
192
+ {agents.map(({ kind, version }) => (
193
+ <button
194
+ key={kind}
195
+ type="button"
196
+ onClick={() => onChooseAgent(kind)}
197
+ aria-pressed={activeAgent === kind}
198
+ className={cn(
199
+ 'cursor-pointer rounded-md border px-2 py-1 text-[11.5px] transition-colors',
200
+ activeAgent === kind
201
+ ? 'border-ring bg-secondary/60 font-medium text-foreground'
202
+ : 'border-border text-muted-foreground hover:text-foreground',
203
+ )}
204
+ >
205
+ {AGENT_LABELS[kind]}
206
+ {version ? <span className="ml-1 font-normal text-muted-foreground">{version}</span> : null}
207
+ </button>
208
+ ))}
209
+ </div>
210
+ )}
211
+ </ChecklistRow>
212
+
213
+ <ChecklistRow
214
+ status={environments.configured ? 'complete' : 'incomplete'}
215
+ title="Environments configured"
216
+ detail={envDetail}
217
+ action={
218
+ <div className="flex flex-col items-end gap-1">
219
+ <Button size="sm" onClick={actions.onSetupEnvironments}>
220
+ <SparklesIcon className="size-3.5" />
221
+ Set up with AI
222
+ </Button>
223
+ <button
224
+ type="button"
225
+ onClick={actions.onOpenEnvironments}
226
+ className="text-[10.5px] text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
227
+ >
228
+ Manage manually
229
+ </button>
230
+ </div>
231
+ }
232
+ />
233
+
234
+ <ChecklistRow
235
+ status={secretsStatus}
236
+ title="Secrets & auth"
237
+ detail={secretsDetail}
238
+ action={
239
+ <Button
240
+ variant="outline"
241
+ size="sm"
242
+ disabled={!environments.configured}
243
+ onClick={actions.onOpenEnvironments}
244
+ >
245
+ Open
246
+ </Button>
247
+ }
248
+ />
249
+
250
+ <ChecklistRow
251
+ status={skillsInstalled ? 'complete' : 'incomplete'}
252
+ title="Agent skills installed"
253
+ detail={
254
+ skillsInstalled
255
+ ? 'emberflow-basics is installed for your agent.'
256
+ : 'Installed automatically by emberflow init — this project doesn’t have them yet. Add them (skills only, nothing else changes):'
257
+ }
258
+ >
259
+ {!skillsInstalled && <CopyCommand command={SKILLS_INSTALL_COMMAND} />}
260
+ </ChecklistRow>
261
+
262
+ <ChecklistRow
263
+ status={infrastructure.present ? 'complete' : 'incomplete'}
264
+ title="Infrastructure scouted"
265
+ detail={
266
+ infrastructure.present
267
+ ? `${infrastructure.itemCount ?? 0} item${infrastructure.itemCount === 1 ? '' : 's'} discovered.`
268
+ : 'Have the agent scan this project for databases, APIs and providers it already uses.'
269
+ }
270
+ action={
271
+ <Button
272
+ variant="outline"
273
+ size="sm"
274
+ disabled={!hasAgent}
275
+ title={hasAgent ? undefined : 'Detect a coding agent first'}
276
+ onClick={actions.onScoutInfrastructure}
277
+ >
278
+ <SparklesIcon className="size-3.5" />
279
+ Scout
280
+ </Button>
281
+ }
282
+ />
283
+
284
+ <ChecklistRow
285
+ status={ops.count > 1 ? 'complete' : 'incomplete'}
286
+ title="First operation"
287
+ detail={
288
+ ops.count > 1
289
+ ? "You've built beyond the hello example."
290
+ : 'Only the hello example so far — open it to see how an operation is built.'
291
+ }
292
+ action={
293
+ <Button variant="outline" size="sm" onClick={actions.onOpenHelloOp}>
294
+ Open hello op
295
+ </Button>
296
+ }
297
+ />
298
+ </div>
299
+ </div>
300
+ );
301
+ }
302
+
303
+ /**
304
+ * First-run Welcome/Setup dialog. Mounted once (in the Toolbar). Fetches
305
+ * `/setup-status` on mount to decide whether to auto-open on a fresh project,
306
+ * and again whenever it's opened so the rows reflect the latest state. Always
307
+ * reachable from the Toolbar's Setup button (via the store's `welcomeOpen`).
308
+ */
309
+ export function WelcomeDialog() {
310
+ const welcomeOpen = useBuilderStore((s) => s.welcomeOpen);
311
+ const setWelcomeOpen = useBuilderStore((s) => s.setWelcomeOpen);
312
+ const openSettingsFromWelcome = useBuilderStore((s) => s.openSettingsFromWelcome);
313
+ const beginEnvironmentSetup = useBuilderStore((s) => s.beginEnvironmentSetup);
314
+ const beginInfrastructureScout = useBuilderStore((s) => s.beginInfrastructureScout);
315
+ const switchWorkflow = useBuilderStore((s) => s.switchWorkflow);
316
+ const status = useBuilderStore((s) => s.setupStatus);
317
+ const refreshSetupStatus = useBuilderStore((s) => s.refreshSetupStatus);
318
+ const agentChoice = useBuilderStore((s) => s.agentChoice);
319
+ const setAgentChoice = useBuilderStore((s) => s.setAgentChoice);
320
+ const [envDialogOpen, setEnvDialogOpen] = useState(false);
321
+
322
+ // Mount: fetch once and auto-open on a fresh, undismissed project.
323
+ useEffect(() => {
324
+ let cancelled = false;
325
+ void refreshSetupStatus().then((s) => {
326
+ if (cancelled || !s) return;
327
+ const dismissed =
328
+ typeof localStorage !== 'undefined' && localStorage.getItem(WELCOME_DISMISSED_KEY) === '1';
329
+ if (s.ops.onlyHello && !s.environments.configured && !dismissed) {
330
+ setWelcomeOpen(true);
331
+ }
332
+ });
333
+ return () => {
334
+ cancelled = true;
335
+ };
336
+ // Mount-only: auto-open is a one-shot boot decision.
337
+ // eslint-disable-next-line react-hooks/exhaustive-deps
338
+ }, []);
339
+
340
+ // Refresh the rows every time the dialog opens (e.g. from the StatusBar chip).
341
+ useEffect(() => {
342
+ if (!welcomeOpen) return;
343
+ void refreshSetupStatus();
344
+ }, [welcomeOpen, refreshSetupStatus]);
345
+
346
+ /** A user-driven close (X / overlay / esc / Dismiss) records the dismissal so
347
+ * the dialog never auto-opens again; navigational actions close without it. */
348
+ const dismiss = () => {
349
+ setWelcomeOpen(false);
350
+ if (typeof localStorage !== 'undefined') localStorage.setItem(WELCOME_DISMISSED_KEY, '1');
351
+ };
352
+
353
+ const actions: WelcomeChecklistActions = {
354
+ onOpenSettings: openSettingsFromWelcome,
355
+ onSetupEnvironments: () => {
356
+ setWelcomeOpen(false);
357
+ beginEnvironmentSetup();
358
+ },
359
+ onOpenEnvironments: () => {
360
+ setWelcomeOpen(false);
361
+ setEnvDialogOpen(true);
362
+ },
363
+ onOpenHelloOp: () => {
364
+ setWelcomeOpen(false);
365
+ switchWorkflow('default/hello');
366
+ },
367
+ onScoutInfrastructure: () => {
368
+ setWelcomeOpen(false);
369
+ beginInfrastructureScout();
370
+ },
371
+ };
372
+
373
+ return (
374
+ <>
375
+ <Dialog open={welcomeOpen} onOpenChange={(o) => (o ? setWelcomeOpen(true) : dismiss())}>
376
+ <DialogContent className="max-w-lg">
377
+ <DialogTitle>Welcome to Emberflow</DialogTitle>
378
+ <DialogDescription>
379
+ A few steps to get this project ready to build and run. Everything here is optional —
380
+ you can revisit it any time from the toolbar.
381
+ </DialogDescription>
382
+ {status ? (
383
+ <WelcomeChecklist
384
+ status={status}
385
+ actions={actions}
386
+ chosenAgent={agentChoice.agent}
387
+ onChooseAgent={(kind) => setAgentChoice({ ...agentChoice, agent: kind })}
388
+ />
389
+ ) : (
390
+ <div className="py-6 text-center text-[12px] text-muted-foreground">
391
+ Checking your project…
392
+ </div>
393
+ )}
394
+ <div className="flex justify-end">
395
+ <Button variant="ghost" size="sm" onClick={dismiss}>
396
+ Dismiss
397
+ </Button>
398
+ </div>
399
+ </DialogContent>
400
+ </Dialog>
401
+
402
+ <EnvironmentsDialog
403
+ open={envDialogOpen}
404
+ onOpenChange={setEnvDialogOpen}
405
+ onBack={() => {
406
+ setEnvDialogOpen(false);
407
+ setWelcomeOpen(true);
408
+ }}
409
+ />
410
+ </>
411
+ );
412
+ }
@@ -0,0 +1,91 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { Fragment, type ReactNode } from 'react';
4
+ import { renderAgentMarkdown } from './agentMarkdown';
5
+
6
+ /** Render the block array to static HTML so we can assert on structure/text. */
7
+ function html(source: string): string {
8
+ return renderToStaticMarkup(<Fragment>{renderAgentMarkdown(source) as ReactNode}</Fragment>);
9
+ }
10
+
11
+ describe('renderAgentMarkdown', () => {
12
+ it('renders bold and inline code together inline', () => {
13
+ const out = html('This is **bold** and `code` here.');
14
+ expect(out).toContain('<strong');
15
+ expect(out).toContain('>bold</strong>');
16
+ expect(out).toContain('<code');
17
+ expect(out).toContain('>code</code>');
18
+ // Single paragraph containing both.
19
+ expect((out.match(/<p/g) ?? []).length).toBe(1);
20
+ });
21
+
22
+ it('renders italic', () => {
23
+ const out = html('an *emphasised* word');
24
+ expect(out).toContain('<em');
25
+ expect(out).toContain('>emphasised</em>');
26
+ });
27
+
28
+ it('renders a fenced code block as a <pre> preserving content literally', () => {
29
+ const out = html('before\n\n```\nconst x = **not bold**;\nline2\n```\n\nafter');
30
+ expect(out).toContain('<pre');
31
+ // Markers inside the fence stay literal — no <strong> emitted for them.
32
+ expect(out).toContain('const x = **not bold**;');
33
+ expect(out).toContain('line2');
34
+ // Prose around it still becomes paragraphs.
35
+ expect(out).toContain('>before</p>');
36
+ expect(out).toContain('>after</p>');
37
+ });
38
+
39
+ it('renders headings as slightly-bolder paragraphs, not giant tags', () => {
40
+ const out = html('# Title\n## Sub\n### Small');
41
+ expect(out).not.toContain('<h1');
42
+ expect(out).not.toContain('<h2');
43
+ expect(out).toContain('font-semibold');
44
+ expect(out).toContain('>Title</p>');
45
+ expect(out).toContain('>Sub</p>');
46
+ expect(out).toContain('>Small</p>');
47
+ });
48
+
49
+ it('renders unordered lists (flat)', () => {
50
+ const out = html('- one\n- two\n- three');
51
+ expect((out.match(/<ul/g) ?? []).length).toBe(1);
52
+ expect((out.match(/<li/g) ?? []).length).toBe(3);
53
+ expect(out).toContain('>one</li>');
54
+ });
55
+
56
+ it('renders ordered lists', () => {
57
+ const out = html('1. first\n2. second');
58
+ expect(out).toContain('<ol');
59
+ expect((out.match(/<li/g) ?? []).length).toBe(2);
60
+ expect(out).toContain('>first</li>');
61
+ });
62
+
63
+ it('splits paragraphs on blank lines', () => {
64
+ const out = html('para one\n\npara two');
65
+ expect((out.match(/<p/g) ?? []).length).toBe(2);
66
+ });
67
+
68
+ it('passes plain text through unchanged', () => {
69
+ const out = html('just some plain prose with no markup');
70
+ expect(out).toContain('just some plain prose with no markup');
71
+ expect(out).not.toContain('<strong');
72
+ expect(out).not.toContain('<em');
73
+ expect(out).not.toContain('<code');
74
+ });
75
+
76
+ it('leaves malformed markdown as literal text', () => {
77
+ // Unclosed bold and a single unpaired star have no valid closing marker, so
78
+ // they fall back to literal text rather than emitting <strong>/<em>.
79
+ const out = html('an **unclosed bold marker\n\n5 * 3 = 15');
80
+ expect(out).not.toContain('<strong');
81
+ expect(out).not.toContain('<em');
82
+ expect(out).toContain('**unclosed bold marker');
83
+ expect(out).toContain('5 * 3 = 15');
84
+ });
85
+
86
+ it('does not treat backtick content as bold', () => {
87
+ const out = html('`**literal**`');
88
+ expect(out).toContain('>**literal**</code>');
89
+ expect(out).not.toContain('<strong');
90
+ });
91
+ });