@crvouga/atlas 0.3.0 → 0.5.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 (429) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +178 -604
  3. package/dist/bun/src/driver.d.ts +41 -7
  4. package/dist/bun/src/driver.js +47 -6
  5. package/dist/bun/src/index.d.ts +8 -0
  6. package/dist/bun/src/index.js +8 -0
  7. package/dist/core/src/cli.d.ts +14 -1
  8. package/dist/core/src/cli.js +30 -1
  9. package/dist/core/src/config.d.ts +70 -4
  10. package/dist/core/src/config.js +62 -3
  11. package/dist/core/src/events/cloudevents.d.ts +54 -9
  12. package/dist/core/src/events/cloudevents.js +60 -9
  13. package/dist/core/src/graph.d.ts +21 -0
  14. package/dist/core/src/graph.js +26 -0
  15. package/dist/core/src/index.d.ts +11 -0
  16. package/dist/core/src/index.js +11 -0
  17. package/dist/core/src/lint.d.ts +30 -0
  18. package/dist/core/src/lint.js +13 -0
  19. package/dist/core/src/plan.d.ts +39 -1
  20. package/dist/core/src/plan.js +28 -1
  21. package/dist/core/src/privacy.d.ts +29 -2
  22. package/dist/core/src/privacy.js +19 -2
  23. package/dist/core/src/report/formats.d.ts +34 -4
  24. package/dist/core/src/report/formats.js +24 -4
  25. package/dist/core/src/report/markdown.d.ts +15 -0
  26. package/dist/core/src/report/markdown.js +8 -0
  27. package/dist/core/src/run/clients.d.ts +44 -5
  28. package/dist/core/src/run/clients.js +46 -2
  29. package/dist/core/src/run/execute.d.ts +69 -16
  30. package/dist/core/src/run/execute.js +67 -16
  31. package/dist/core/src/run/frames.d.ts +13 -4
  32. package/dist/core/src/run/frames.js +19 -4
  33. package/dist/core/src/run/http.d.ts +34 -1
  34. package/dist/core/src/run/http.js +14 -1
  35. package/dist/core/src/run/types.d.ts +76 -3
  36. package/dist/core/src/run/types.js +19 -3
  37. package/dist/core/src/signals.d.ts +49 -5
  38. package/dist/core/src/signals.js +22 -5
  39. package/dist/core/src/spec/compose.d.ts +23 -1
  40. package/dist/core/src/spec/compose.js +10 -1
  41. package/dist/core/src/spec/index.d.ts +9 -1
  42. package/dist/core/src/spec/index.js +9 -1
  43. package/dist/core/src/spec/load.d.ts +22 -1
  44. package/dist/core/src/spec/load.js +31 -1
  45. package/dist/core/src/spec/parse.d.ts +16 -2
  46. package/dist/core/src/spec/parse.js +25 -2
  47. package/dist/core/src/spec/scxml.d.ts +25 -4
  48. package/dist/core/src/spec/scxml.js +36 -4
  49. package/dist/core/src/spec/types.d.ts +52 -1
  50. package/dist/core/src/spec/types.js +24 -0
  51. package/dist/detox/src/index.d.ts +54 -11
  52. package/dist/detox/src/index.js +45 -7
  53. package/dist/playwright/src/driver.d.ts +39 -3
  54. package/dist/playwright/src/driver.js +27 -3
  55. package/dist/playwright/src/index.d.ts +10 -0
  56. package/dist/playwright/src/index.js +10 -0
  57. package/dist/playwright/src/media.d.ts +50 -4
  58. package/dist/playwright/src/media.js +55 -4
  59. package/dist/playwright/src/signals.d.ts +44 -5
  60. package/dist/playwright/src/signals.js +29 -5
  61. package/dist/reports/src/attachment-files.d.ts +44 -0
  62. package/dist/reports/src/attachment-files.js +121 -0
  63. package/dist/reports/src/attachment-response.d.ts +31 -0
  64. package/dist/reports/src/attachment-response.js +55 -0
  65. package/dist/reports/src/aws-credentials.d.ts +48 -0
  66. package/dist/reports/src/aws-credentials.js +221 -0
  67. package/dist/reports/src/browser-navigation.d.ts +14 -0
  68. package/dist/reports/src/browser-navigation.js +7 -0
  69. package/dist/reports/src/bundle.d.ts +34 -2
  70. package/dist/reports/src/bundle.js +38 -2
  71. package/dist/reports/src/catalog.d.ts +34 -3
  72. package/dist/reports/src/catalog.js +17 -3
  73. package/dist/reports/src/cli-stores.d.ts +36 -0
  74. package/dist/reports/src/cli-stores.js +73 -0
  75. package/dist/reports/src/clock.d.ts +7 -2
  76. package/dist/reports/src/clock.js +5 -2
  77. package/dist/reports/src/completion-order.d.ts +18 -5
  78. package/dist/reports/src/completion-order.js +18 -5
  79. package/dist/reports/src/compose.d.ts +80 -0
  80. package/dist/reports/src/compose.js +117 -0
  81. package/dist/reports/src/copy-store.d.ts +29 -0
  82. package/dist/reports/src/copy-store.js +52 -0
  83. package/dist/reports/src/directory-outline.d.ts +38 -0
  84. package/dist/reports/src/directory-outline.js +103 -0
  85. package/dist/reports/src/directory-store.d.ts +20 -0
  86. package/dist/reports/src/directory-store.js +166 -0
  87. package/dist/reports/src/doctor.d.ts +29 -0
  88. package/dist/reports/src/doctor.js +83 -0
  89. package/dist/reports/src/effective-delta.d.ts +34 -0
  90. package/dist/reports/src/effective-delta.js +25 -0
  91. package/dist/reports/src/evidence-detail.d.ts +64 -0
  92. package/dist/reports/src/evidence-detail.js +92 -0
  93. package/dist/reports/src/evidence-live.d.ts +40 -4
  94. package/dist/reports/src/evidence-live.js +52 -4
  95. package/dist/reports/src/evidence-types.d.ts +70 -1
  96. package/dist/reports/src/evidence-types.js +5 -1
  97. package/dist/reports/src/evidence-view.d.ts +58 -6
  98. package/dist/reports/src/evidence-view.js +44 -5
  99. package/dist/reports/src/explorer-types.d.ts +40 -3
  100. package/dist/reports/src/explorer-types.js +8 -0
  101. package/dist/reports/src/explorer-versions.d.ts +9 -1
  102. package/dist/reports/src/explorer-versions.js +18 -1
  103. package/dist/reports/src/export-evidence.d.ts +16 -1
  104. package/dist/reports/src/export-evidence.js +52 -1
  105. package/dist/reports/src/fetcher.d.ts +27 -3
  106. package/dist/reports/src/fetcher.js +11 -3
  107. package/dist/reports/src/format-date.d.ts +6 -0
  108. package/dist/reports/src/format-date.js +6 -0
  109. package/dist/reports/src/format.d.ts +16 -0
  110. package/dist/reports/src/format.js +10 -0
  111. package/dist/reports/src/frames.d.ts +36 -3
  112. package/dist/reports/src/frames.js +22 -3
  113. package/dist/reports/src/hash.d.ts +18 -0
  114. package/dist/reports/src/hash.js +13 -0
  115. package/dist/reports/src/host.d.ts +29 -0
  116. package/dist/reports/src/host.js +64 -0
  117. package/dist/reports/src/html-frame.d.ts +5 -6
  118. package/dist/reports/src/html-frame.js +12 -1
  119. package/dist/reports/src/http-store.d.ts +52 -0
  120. package/dist/reports/src/http-store.js +145 -0
  121. package/dist/reports/src/http.d.ts +7 -0
  122. package/dist/reports/src/http.js +6 -0
  123. package/dist/reports/src/ingest.d.ts +54 -0
  124. package/dist/reports/src/ingest.js +210 -0
  125. package/dist/reports/src/init.d.ts +40 -0
  126. package/dist/reports/src/init.js +364 -0
  127. package/dist/reports/src/junit.d.ts +33 -6
  128. package/dist/reports/src/junit.js +87 -21
  129. package/dist/reports/src/keyboard.d.ts +20 -0
  130. package/dist/reports/src/keyboard.js +11 -0
  131. package/dist/reports/src/keys.d.ts +21 -3
  132. package/dist/reports/src/keys.js +21 -3
  133. package/dist/reports/src/layer-axes.d.ts +48 -0
  134. package/dist/reports/src/layer-axes.js +71 -0
  135. package/dist/reports/src/layer-catalog.d.ts +28 -1
  136. package/dist/reports/src/layer-catalog.js +19 -2
  137. package/dist/reports/src/layer-merge.d.ts +98 -9
  138. package/dist/reports/src/layer-merge.js +154 -33
  139. package/dist/reports/src/layer-source.d.ts +18 -0
  140. package/dist/reports/src/layer-source.js +14 -0
  141. package/dist/reports/src/layer-store.d.ts +42 -0
  142. package/dist/reports/src/layer-store.js +63 -5
  143. package/dist/reports/src/layer-types.d.ts +34 -1
  144. package/dist/reports/src/layers-route.d.ts +47 -4
  145. package/dist/reports/src/layers-route.js +88 -12
  146. package/dist/reports/src/media-types.d.ts +11 -0
  147. package/dist/reports/src/media-types.js +22 -0
  148. package/dist/reports/src/namespace-guard.d.ts +10 -1
  149. package/dist/reports/src/namespace-guard.js +10 -1
  150. package/dist/reports/src/navigation.d.ts +12 -0
  151. package/dist/reports/src/navigation.js +7 -0
  152. package/dist/reports/src/object-io.d.ts +11 -4
  153. package/dist/reports/src/object-io.js +4 -4
  154. package/dist/reports/src/object-store-conformance.d.ts +27 -0
  155. package/dist/reports/src/object-store-conformance.js +87 -0
  156. package/dist/reports/src/object-store.d.ts +33 -24
  157. package/dist/reports/src/object-store.js +17 -24
  158. package/dist/reports/src/parse-with.d.ts +16 -0
  159. package/dist/reports/src/parse-with.js +14 -0
  160. package/dist/reports/src/performance-part.d.ts +25 -1
  161. package/dist/reports/src/performance-part.js +15 -1
  162. package/dist/reports/src/performance.d.ts +22 -2
  163. package/dist/reports/src/performance.js +12 -2
  164. package/dist/reports/src/persist.d.ts +12 -3
  165. package/dist/reports/src/persist.js +12 -3
  166. package/dist/reports/src/phone.d.ts +18 -1
  167. package/dist/reports/src/phone.js +18 -1
  168. package/dist/reports/src/protocol.d.ts +30 -4
  169. package/dist/reports/src/protocol.js +15 -4
  170. package/dist/reports/src/publication.d.ts +21 -1
  171. package/dist/reports/src/publication.js +21 -1
  172. package/dist/reports/src/publish-checks.d.ts +35 -0
  173. package/dist/reports/src/publish-checks.js +122 -0
  174. package/dist/reports/src/publish.d.ts +44 -9
  175. package/dist/reports/src/publish.js +40 -7
  176. package/dist/reports/src/read.d.ts +46 -16
  177. package/dist/reports/src/read.js +38 -14
  178. package/dist/reports/src/recording-bundle.d.ts +13 -2
  179. package/dist/reports/src/recording-bundle.js +4 -2
  180. package/dist/reports/src/recording-keys.d.ts +15 -1
  181. package/dist/reports/src/recording-keys.js +15 -1
  182. package/dist/reports/src/remote-report.d.ts +28 -7
  183. package/dist/reports/src/remote-report.js +15 -7
  184. package/dist/reports/src/report-cli.d.ts +51 -0
  185. package/dist/reports/src/report-cli.js +485 -0
  186. package/dist/reports/src/report-index.d.ts +99 -0
  187. package/dist/reports/src/report-index.js +337 -0
  188. package/dist/reports/src/report-loader.d.ts +10 -0
  189. package/dist/reports/src/report-loader.js +6 -0
  190. package/dist/reports/src/report-parts.d.ts +29 -3
  191. package/dist/reports/src/report-parts.js +40 -3
  192. package/dist/reports/src/report-status.d.ts +48 -0
  193. package/dist/reports/src/report-status.js +156 -0
  194. package/dist/reports/src/report-types.d.ts +73 -0
  195. package/dist/reports/src/report-types.js +3 -0
  196. package/dist/reports/src/reporter.d.ts +67 -0
  197. package/dist/reports/src/reporter.js +128 -0
  198. package/dist/reports/src/reports-explorer-route.d.ts +541 -0
  199. package/dist/reports/src/reports-explorer-route.js +11 -0
  200. package/dist/reports/src/reports-list-route.d.ts +9 -0
  201. package/dist/reports/src/reports-list-route.js +9 -0
  202. package/dist/reports/src/reports-route.d.ts +561 -22
  203. package/dist/reports/src/reports-route.js +11 -3
  204. package/dist/reports/src/reports-store.d.ts +19 -2
  205. package/dist/reports/src/reports-store.js +10 -2
  206. package/dist/reports/src/resilient-cache.d.ts +14 -1
  207. package/dist/reports/src/resilient-cache.js +8 -1
  208. package/dist/reports/src/retention.d.ts +17 -1
  209. package/dist/reports/src/retention.js +5 -1
  210. package/dist/reports/src/run-bundle.d.ts +63 -0
  211. package/dist/reports/src/run-bundle.js +159 -0
  212. package/dist/reports/src/s3-store.d.ts +79 -0
  213. package/dist/reports/src/s3-store.js +295 -0
  214. package/dist/reports/src/scenario.d.ts +14 -4
  215. package/dist/reports/src/scenario.js +5 -4
  216. package/dist/reports/src/sigv4.d.ts +72 -0
  217. package/dist/reports/src/sigv4.js +96 -0
  218. package/dist/reports/src/snapshot-bundle.d.ts +24 -1
  219. package/dist/reports/src/snapshot-bundle.js +27 -2
  220. package/dist/reports/src/snapshot-surface.d.ts +20 -9
  221. package/dist/reports/src/snapshot-surface.js +32 -19
  222. package/dist/reports/src/source-content.d.ts +23 -0
  223. package/dist/reports/src/source-content.js +30 -1
  224. package/dist/reports/src/source-entries.d.ts +13 -0
  225. package/dist/reports/src/source-entries.js +6 -0
  226. package/dist/reports/src/source-part.d.ts +15 -0
  227. package/dist/reports/src/source-part.js +8 -0
  228. package/dist/reports/src/source-reader.d.ts +11 -0
  229. package/dist/reports/src/source-reader.js +5 -0
  230. package/dist/reports/src/spec-evidence.d.ts +12 -2
  231. package/dist/reports/src/spec-evidence.js +6 -2
  232. package/dist/reports/src/spec-inventory.d.ts +15 -1
  233. package/dist/reports/src/spec-inventory.js +22 -1
  234. package/dist/reports/src/spec-link.d.ts +28 -0
  235. package/dist/reports/src/spec-link.js +70 -0
  236. package/dist/reports/src/spec-set.d.ts +63 -0
  237. package/dist/reports/src/spec-set.js +177 -0
  238. package/dist/reports/src/status.d.ts +10 -0
  239. package/dist/reports/src/status.js +5 -0
  240. package/dist/reports/src/store-prefix.d.ts +28 -0
  241. package/dist/reports/src/store-prefix.js +22 -0
  242. package/dist/reports/src/store-url.d.ts +89 -0
  243. package/dist/reports/src/store-url.js +160 -0
  244. package/dist/reports/src/test-catalog-entity.d.ts +11 -0
  245. package/dist/reports/src/test-catalog-entity.js +6 -0
  246. package/dist/reports/src/types.d.ts +6 -1
  247. package/dist/reports/src/use-report-nav.d.ts +35 -0
  248. package/dist/reports/src/use-report-nav.js +12 -0
  249. package/dist/schema/src/api.d.ts +799 -0
  250. package/dist/schema/src/api.js +201 -0
  251. package/dist/schema/src/cli-results.d.ts +210 -0
  252. package/dist/schema/src/cli-results.js +124 -0
  253. package/dist/schema/src/common.d.ts +53 -0
  254. package/dist/schema/src/common.js +42 -0
  255. package/dist/schema/src/index.d.ts +14 -0
  256. package/dist/schema/src/index.js +14 -0
  257. package/dist/schema/src/layer.d.ts +2273 -0
  258. package/dist/schema/src/layer.js +462 -0
  259. package/dist/schema/src/manifest.d.ts +95 -0
  260. package/dist/schema/src/manifest.js +84 -0
  261. package/dist/schema/src/report-sources.d.ts +28 -0
  262. package/dist/schema/src/report-sources.js +25 -0
  263. package/dist/schema/src/runs-index.d.ts +16 -0
  264. package/dist/schema/src/runs-index.js +15 -0
  265. package/dist/schema/src/server-config.d.ts +93 -0
  266. package/dist/schema/src/server-config.js +73 -0
  267. package/dist/schema/src/spec.d.ts +69 -0
  268. package/dist/schema/src/spec.js +58 -0
  269. package/dist/server/src/app.d.ts +62 -0
  270. package/dist/server/src/app.js +419 -0
  271. package/dist/server/src/auth.d.ts +41 -0
  272. package/dist/server/src/auth.js +59 -0
  273. package/dist/server/src/config.d.ts +83 -0
  274. package/dist/server/src/config.js +187 -0
  275. package/dist/server/src/dev.d.ts +12 -0
  276. package/dist/server/src/dev.js +80 -0
  277. package/dist/server/src/events.d.ts +29 -0
  278. package/dist/server/src/events.js +101 -0
  279. package/dist/server/src/host.d.ts +30 -0
  280. package/dist/server/src/host.js +115 -0
  281. package/dist/server/src/index.d.ts +16 -0
  282. package/dist/server/src/index.js +16 -0
  283. package/dist/server/src/llms.d.ts +19 -0
  284. package/dist/server/src/llms.js +63 -0
  285. package/dist/server/src/main.d.ts +30 -0
  286. package/dist/server/src/main.js +112 -0
  287. package/dist/server/src/openapi.d.ts +59 -0
  288. package/dist/server/src/openapi.js +374 -0
  289. package/dist/server/src/problem.d.ts +8 -0
  290. package/dist/server/src/problem.js +39 -0
  291. package/dist/server/src/site.d.ts +13 -0
  292. package/dist/server/src/site.js +43 -0
  293. package/dist/server/src/store-api.d.ts +28 -0
  294. package/dist/server/src/store-api.js +159 -0
  295. package/dist/server/src/validate.d.ts +71 -0
  296. package/dist/server/src/validate.js +197 -0
  297. package/dist/vitest/src/index.d.ts +27 -5
  298. package/dist/vitest/src/index.js +18 -5
  299. package/dist/webdriver/src/driver.d.ts +63 -11
  300. package/dist/webdriver/src/driver.js +57 -6
  301. package/dist/webdriver/src/index.d.ts +8 -0
  302. package/dist/webdriver/src/index.js +1 -0
  303. package/dist/webdriver/src/protocol.d.ts +75 -6
  304. package/dist/webdriver/src/protocol.js +79 -6
  305. package/docs/agents.md +137 -0
  306. package/docs/deploying.md +345 -0
  307. package/docs/dogfooding.md +175 -0
  308. package/docs/extending.md +242 -0
  309. package/docs/integrating.md +485 -0
  310. package/docs/reference/bun.md +141 -0
  311. package/docs/reference/cli.md +146 -0
  312. package/docs/reference/core.md +2426 -0
  313. package/docs/reference/detox.md +150 -0
  314. package/docs/reference/http-api.md +303 -0
  315. package/docs/reference/playwright.md +303 -0
  316. package/docs/reference/reports.md +7098 -0
  317. package/docs/reference/schema.md +1651 -0
  318. package/docs/reference/server.md +685 -0
  319. package/docs/reference/vitest.md +45 -0
  320. package/docs/reference/webdriver.md +330 -0
  321. package/docs/report-backends.md +144 -0
  322. package/docs/report-contract.md +400 -0
  323. package/docs/reporting-issues.md +160 -0
  324. package/docs/server.md +285 -0
  325. package/docs/statecharts.md +641 -0
  326. package/llms-full.txt +3506 -0
  327. package/llms.txt +47 -0
  328. package/package.json +33 -5
  329. package/packages/core/README.md +2 -1
  330. package/packages/reports/README.md +69 -2
  331. package/packages/reports/STORAGE.md +5 -1
  332. package/packages/schema/README.md +14 -0
  333. package/packages/schema/schemas/check-detail.schema.json +1032 -0
  334. package/packages/schema/schemas/checks-result.schema.json +26 -0
  335. package/packages/schema/schemas/command-error.schema.json +35 -0
  336. package/packages/schema/schemas/doctor-result.schema.json +60 -0
  337. package/packages/schema/schemas/effective-delta.schema.json +490 -0
  338. package/packages/schema/schemas/effective-evidence.schema.json +471 -0
  339. package/packages/schema/schemas/evidence-page.schema.json +271 -0
  340. package/packages/schema/schemas/hello-event.schema.json +22 -0
  341. package/packages/schema/schemas/init-plan.schema.json +347 -0
  342. package/packages/schema/schemas/layer-rows.schema.json +199 -0
  343. package/packages/schema/schemas/layer.schema.json +2888 -0
  344. package/packages/schema/schemas/merge-result.schema.json +40 -0
  345. package/packages/schema/schemas/outline.schema.json +62 -0
  346. package/packages/schema/schemas/problem.schema.json +37 -0
  347. package/packages/schema/schemas/publish-checks.schema.json +1413 -0
  348. package/packages/schema/schemas/publish-result.schema.json +168 -0
  349. package/packages/schema/schemas/ready.schema.json +68 -0
  350. package/packages/schema/schemas/report-event.schema.json +100 -0
  351. package/packages/schema/schemas/report-status.schema.json +370 -0
  352. package/packages/schema/schemas/server-config.schema.json +194 -0
  353. package/packages/schema/schemas/server-info.schema.json +114 -0
  354. package/packages/schema/schemas/spec-set.schema.json +103 -0
  355. package/packages/schema/schemas/store-listing.schema.json +29 -0
  356. package/packages/schema/schemas/store-write-result.schema.json +28 -0
  357. package/packages/schema/schemas/stream-result.schema.json +59 -0
  358. package/packages/server/README.md +43 -0
  359. package/packages/server/openapi.json +6699 -0
  360. package/packages/visualizer/README.md +27 -5
  361. package/packages/visualizer/example/specs/coffee/ordering/cart.qnt +7 -0
  362. package/packages/visualizer/example/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  363. package/packages/visualizer/example/specs/coffee/ordering/drink-price.dmn +24 -0
  364. package/packages/visualizer/example/specs/coffee/ordering/loyalty.feature +7 -0
  365. package/packages/visualizer/example/specs/coffee/ordering/ordering.feature +36 -0
  366. package/packages/visualizer/example/specs/coffee/ordering/payment.als +4 -0
  367. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/cart.qnt +7 -0
  368. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  369. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/drink-price.dmn +24 -0
  370. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/loyalty.feature +7 -0
  371. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/ordering.feature +36 -0
  372. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/payment.als +4 -0
  373. package/packages/visualizer/fixtures/full/specs/coffee/ordering/cart.qnt +7 -0
  374. package/packages/visualizer/fixtures/full/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  375. package/packages/visualizer/fixtures/full/specs/coffee/ordering/drink-price.dmn +24 -0
  376. package/packages/visualizer/fixtures/full/specs/coffee/ordering/loyalty.feature +7 -0
  377. package/packages/visualizer/fixtures/full/specs/coffee/ordering/ordering.feature +36 -0
  378. package/packages/visualizer/fixtures/full/specs/coffee/ordering/payment.als +4 -0
  379. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/cart.qnt +7 -0
  380. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  381. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/drink-price.dmn +24 -0
  382. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/loyalty.feature +7 -0
  383. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/ordering.feature +36 -0
  384. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/payment.als +4 -0
  385. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/cart.qnt +7 -0
  386. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  387. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/drink-price.dmn +24 -0
  388. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/loyalty.feature +7 -0
  389. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/ordering.feature +36 -0
  390. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/payment.als +4 -0
  391. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/cart.qnt +7 -0
  392. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  393. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/drink-price.dmn +24 -0
  394. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/loyalty.feature +7 -0
  395. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/ordering.feature +36 -0
  396. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/payment.als +4 -0
  397. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/cart.qnt +7 -0
  398. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  399. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/drink-price.dmn +24 -0
  400. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/loyalty.feature +7 -0
  401. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/ordering.feature +36 -0
  402. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/payment.als +4 -0
  403. package/packages/visualizer/scripts/make-fixtures.ts +2 -0
  404. package/packages/visualizer/scripts/publish-data.ts +1 -2
  405. package/packages/visualizer/src/app/AppShell.tsx +9 -2
  406. package/packages/visualizer/src/app/report-sources.tsx +6 -4
  407. package/packages/visualizer/src/app/router.tsx +3 -3
  408. package/packages/visualizer/src/components/Header.module.css +35 -0
  409. package/packages/visualizer/src/components/Header.tsx +41 -4
  410. package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
  411. package/packages/visualizer/src/data/adapters/index.ts +10 -1
  412. package/packages/visualizer/src/evidence/api.ts +14 -2
  413. package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
  414. package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
  415. package/packages/visualizer/src/evidence/check-detail.tsx +38 -4
  416. package/packages/visualizer/src/evidence/evidence-context.tsx +74 -12
  417. package/packages/visualizer/src/evidence/evidence.module.css +20 -0
  418. package/packages/visualizer/src/evidence/marks.tsx +2 -0
  419. package/packages/visualizer/src/evidence/overview.tsx +3 -3
  420. package/packages/visualizer/src/evidence/sources-panel.tsx +88 -34
  421. package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
  422. package/packages/visualizer/src/find/Finder.tsx +1 -7
  423. package/packages/visualizer/src/find/open.ts +4 -0
  424. package/packages/visualizer/src/live/live-stream.ts +204 -0
  425. package/packages/visualizer/src/runtime.ts +19 -0
  426. package/packages/visualizer/src/state/ui-store.ts +6 -2
  427. package/packages/visualizer/vite/atlas-server-proxy.ts +36 -0
  428. package/packages/visualizer/vite/example-reports.ts +475 -0
  429. package/packages/visualizer/vite.config.ts +18 -2
@@ -0,0 +1,475 @@
1
+ // @io-wall-ok — example dev server reads the specs it is already serving
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ import type { EvidenceCheck } from '@crvouga/atlas/reports/explorer-types';
6
+ import { specEntry } from '@crvouga/atlas/reports/spec-inventory';
7
+ import { publishLayer } from '@crvouga/atlas/reports/layer-store';
8
+ import type { LayerSummary } from '@crvouga/atlas/reports/layer-types';
9
+ import type { ObjectStore } from '@crvouga/atlas/reports/object-store';
10
+ import type { ReportFrame, ReportStep, RunReport } from '@crvouga/atlas/reports/report-types';
11
+ import { createReportsApp, type ReportsAppDeps } from '@crvouga/atlas/reports/reports-route';
12
+ import { ObjectStoreWithPrefix } from '@crvouga/atlas/reports/store-prefix';
13
+
14
+ import { listSpecFiles, resolveRoots } from './atlas-files';
15
+
16
+ /** The version the example calls current. Older layers stay in the report beside it. */
17
+ export const EXAMPLE_CURRENT = `c0ffee${'1'.repeat(34)}`;
18
+ export const EXAMPLE_PREVIOUS = `a11ce0${'0'.repeat(34)}`;
19
+ export const EXAMPLE_BRANCH = `b1a4c0${'2'.repeat(34)}`;
20
+
21
+ const EVIDENCE_FILE = /\.(feature|qnt|dmn|als)$/i;
22
+ const OPENSLO_FILE = /\.openslo\.ya?ml$/i;
23
+
24
+ const COFFEE_FEATURE = 'coffee/ordering/ordering.feature';
25
+ const TODO_FEATURE = 'share-a-list.feature';
26
+
27
+ const CSS = 'main { font-family: sans-serif; padding: 24px; } h1 { font-size: 20px; margin: 0 0 12px; } button { font: 16px sans-serif; padding: 8px 12px; }';
28
+
29
+ function memoryStore(): ObjectStore {
30
+ const objects = new Map<string, { bytes: Uint8Array; contentType: string }>();
31
+ const store: ObjectStore = {
32
+ async get(key) {
33
+ const found = objects.get(key);
34
+ if (!found) return null;
35
+ return { body: new Blob([new Uint8Array(found.bytes)]).stream(), contentType: found.contentType, size: found.bytes.length };
36
+ },
37
+ async put(key, bytes, contentType) {
38
+ objects.set(key, { bytes: new Uint8Array(bytes), contentType });
39
+ },
40
+ async head(key) {
41
+ return objects.has(key);
42
+ },
43
+ async delete(key) {
44
+ objects.delete(key);
45
+ },
46
+ async deleteMany(keys) {
47
+ for (const key of keys) objects.delete(key);
48
+ },
49
+ async getUri() {
50
+ return null;
51
+ },
52
+ async listKeys(prefix = '') {
53
+ return [...objects.keys()].filter((key) => key.startsWith(prefix)).sort();
54
+ },
55
+ withPrefix(prefix) {
56
+ return new ObjectStoreWithPrefix(store, prefix);
57
+ }
58
+ };
59
+ return store;
60
+ }
61
+
62
+ /** Spec documents the evidence tabs read, relative to the specs root. */
63
+ export function readExampleSpecs(root: string): Record<string, string> {
64
+ const specs: Record<string, string> = {};
65
+ for (const file of listSpecFiles(root)) {
66
+ if (!EVIDENCE_FILE.test(file) && !OPENSLO_FILE.test(file)) continue;
67
+ specs[file] = readFileSync(path.join(root, file), 'utf8');
68
+ }
69
+ return specs;
70
+ }
71
+
72
+ /** The coffee fixture and the todo example are the specs this dev server can fill with layers. */
73
+ export function servesExampleReports(specsRoot: string): boolean {
74
+ return existsSync(path.join(specsRoot, COFFEE_FEATURE)) || existsSync(path.join(specsRoot, TODO_FEATURE));
75
+ }
76
+
77
+ type Status = 'passed' | 'failed';
78
+ type BackendName = 'local' | 'shared' | 'device-lab';
79
+
80
+ type CheckSpec = {
81
+ readonly id: string;
82
+ readonly title: string;
83
+ readonly spec: string;
84
+ readonly status: Status;
85
+ readonly detail?: string;
86
+ readonly kind?: 'behavior' | 'performance';
87
+ readonly runtime?: string;
88
+ readonly steps?: readonly ReportStep[];
89
+ readonly files?: readonly { readonly path: string; readonly body: string }[];
90
+ readonly measurements?: EvidenceCheck['measurements'];
91
+ };
92
+
93
+ type LayerSpec = {
94
+ readonly at: number;
95
+ readonly sha: string;
96
+ readonly branch: string;
97
+ readonly environment?: string;
98
+ readonly parameters?: Record<string, string>;
99
+ readonly backend: BackendName;
100
+ readonly packageDir: string;
101
+ readonly checks: readonly CheckSpec[];
102
+ };
103
+
104
+ function at(iso: string) {
105
+ return Date.parse(iso);
106
+ }
107
+
108
+ function picture(title: string, body: string) {
109
+ return `<!doctype html><html><body><main><h1>${title}</h1>${body}</main></body></html>`;
110
+ }
111
+
112
+ function frame(index: number, file: string | null): ReportFrame {
113
+ return {
114
+ index,
115
+ capturedAtMs: index * 1000,
116
+ snapshots: file ? [{ name: 'Customer', nodeId: 'customer', media: 'html', file }] : []
117
+ };
118
+ }
119
+
120
+ /** One moment per line. A picture is kept so the recording has something to show. */
121
+ function timeline(lines: readonly { readonly title: string; readonly status?: Status; readonly error?: string; readonly picture?: string }[]) {
122
+ const files: { path: string; body: string }[] = [];
123
+ const steps: ReportStep[] = lines.map((line, index) => {
124
+ const n = index + 1;
125
+ const shot = line.picture ? `frames/m-${String(n).padStart(4, '0')}.html` : null;
126
+ if (shot && line.picture) files.push({ path: shot, body: line.picture });
127
+ return {
128
+ index: n,
129
+ title: line.title,
130
+ kind: 'when',
131
+ status: line.status ?? 'passed',
132
+ startedAtMs: n * 1000,
133
+ endedAtMs: n * 1000 + 400,
134
+ durationMs: 400,
135
+ frames: [frame(n, shot)],
136
+ steps: [],
137
+ ...(line.error ? { error: line.error } : {})
138
+ };
139
+ });
140
+ return { steps, files };
141
+ }
142
+
143
+ function quintSteps(lines: readonly { readonly title: string; readonly detail: string }[]): ReportStep[] {
144
+ return lines.map((line, index) => {
145
+ const n = index + 1;
146
+ return { index: n, title: line.title, kind: 'quint', status: 'passed', detail: line.detail, startedAtMs: n * 1000, endedAtMs: n * 1000 + 200, durationMs: 200, frames: [], steps: [] };
147
+ });
148
+ }
149
+
150
+ function named(feature: string, scenario: string) {
151
+ return `${feature}: ${scenario}`;
152
+ }
153
+
154
+ /** Five layers. Each one ran only its own checks, on one environment, from one version and backend. */
155
+ function coffeeLayers(): readonly LayerSpec[] {
156
+ const feature = 'Order a drink';
157
+ const spec = COFFEE_FEATURE;
158
+ const quint = 'coffee/ordering/cart.qnt';
159
+ const dmn = 'coffee/ordering/drink-price.dmn';
160
+ const slo = 'coffee/ordering/checkout.openslo.yaml';
161
+ const alloy = 'coffee/ordering/payment.als';
162
+ const dir = 'examples/coffee';
163
+ const card = timeline([
164
+ { title: 'Given a drink in the cart' },
165
+ { title: 'When the customer pays by card', picture: picture('Order', '<p>Oat latte, large</p><button data-testid="pay">Pay by card</button>') },
166
+ { title: 'Then the barista can start the drink' }
167
+ ]);
168
+ const declined = timeline([
169
+ { title: 'Given a drink in the cart' },
170
+ { title: 'When the card is declined', status: 'failed', error: 'The wallet never confirmed the payment', picture: picture('Checkout', '<p>The card was declined.</p><button data-testid="wallet">Pay with wallet</button>') },
171
+ { title: 'And the customer pays with a wallet' }
172
+ ]);
173
+ const iosCard = timeline([
174
+ { title: 'Given a drink in the cart' },
175
+ { title: 'When the customer pays by card', status: 'failed', error: 'The card form never appeared' },
176
+ { title: 'Then the barista can start the drink' }
177
+ ]);
178
+ const scenario = (id: string, name: string, status: Status, extra: Partial<CheckSpec> = {}): CheckSpec => ({ id, title: named(feature, name), spec, status, ...extra });
179
+ return [
180
+ {
181
+ at: at('2026-10-01T15:00:00.000Z'),
182
+ sha: EXAMPLE_PREVIOUS,
183
+ branch: 'main',
184
+ environment: 'desktop-web',
185
+ backend: 'shared',
186
+ packageDir: dir,
187
+ checks: [
188
+ scenario('pays-by-card', 'Pays by card', 'passed', { steps: card.steps, files: card.files }),
189
+ scenario('pays-with-wallet', 'Pays with a wallet', 'passed'),
190
+ scenario('card-declined', 'Card declined, then a wallet works', 'passed'),
191
+ scenario('slow-payment', 'Slow payment', 'passed'),
192
+ scenario('leaves-checkout', 'Leaves checkout and comes back', 'passed'),
193
+ scenario('drink-left', 'Drink left too long', 'passed'),
194
+ { id: 'slow-payment', title: named(feature, 'Slow payment'), spec, status: 'passed', kind: 'performance', measurements: [{ name: 'Payment', value: 74, unit: 's', limit: 60 }] },
195
+ { id: 'trace-1', title: 'cart · trace 1', spec: quint, status: 'passed', runtime: 'quint', steps: quintSteps([{ title: 'add(0)', detail: 'items = 1' }, { title: 'pay', detail: 'paid = true' }]) },
196
+ { id: 'price-large', title: 'Drink price: large oat latte', spec: dmn, status: 'passed', runtime: 'dmn' },
197
+ { id: 'checkout-latency', title: 'Checkout answers within a second', spec: slo, status: 'passed', runtime: 'openslo' },
198
+ { id: 'one-payment', title: 'An order has one payment', spec: alloy, status: 'passed', runtime: 'alloy' }
199
+ ]
200
+ },
201
+ {
202
+ at: at('2026-10-06T15:00:00.000Z'),
203
+ sha: EXAMPLE_PREVIOUS,
204
+ branch: 'main',
205
+ parameters: { environment: 'Android', device: 'phone' },
206
+ backend: 'device-lab',
207
+ packageDir: dir,
208
+ checks: [
209
+ scenario('pays-by-card', 'Pays by card', 'passed'),
210
+ scenario('pays-with-wallet', 'Pays with a wallet', 'passed'),
211
+ { id: 'checkout-latency', title: 'Checkout answers within a second', spec: slo, status: 'failed', runtime: 'openslo', detail: 'Checkout took 1.8 seconds' }
212
+ ]
213
+ },
214
+ {
215
+ at: at('2026-10-08T15:00:00.000Z'),
216
+ sha: EXAMPLE_BRANCH,
217
+ branch: 'feat/wallet',
218
+ environment: 'mobile-web',
219
+ backend: 'local',
220
+ packageDir: dir,
221
+ checks: [
222
+ scenario('pays-by-card', 'Pays by card', 'passed'),
223
+ scenario('pays-with-wallet', 'Pays with a wallet', 'failed', { detail: 'The wallet sheet did not open' }),
224
+ scenario('card-declined', 'Card declined, then a wallet works', 'passed'),
225
+ scenario('slow-payment', 'Slow payment', 'passed'),
226
+ { id: 'price-large', title: 'Drink price: large oat latte', spec: dmn, status: 'failed', runtime: 'dmn', detail: 'Large oat latte was priced at 4' }
227
+ ]
228
+ },
229
+ {
230
+ at: at('2026-10-11T15:00:00.000Z'),
231
+ sha: EXAMPLE_CURRENT,
232
+ branch: 'main',
233
+ environment: 'ios',
234
+ backend: 'device-lab',
235
+ packageDir: dir,
236
+ checks: [scenario('pays-by-card', 'Pays by card', 'failed', { detail: 'The card form never appeared', steps: iosCard.steps, files: iosCard.files })]
237
+ },
238
+ {
239
+ at: at('2026-10-12T16:00:00.000Z'),
240
+ sha: EXAMPLE_CURRENT,
241
+ branch: 'main',
242
+ environment: 'desktop-web',
243
+ backend: 'local',
244
+ packageDir: dir,
245
+ checks: [scenario('card-declined', 'Card declined, then a wallet works', 'failed', { detail: 'The wallet never confirmed the payment', steps: declined.steps, files: declined.files })]
246
+ }
247
+ ];
248
+ }
249
+
250
+ function todoLayers(): readonly LayerSpec[] {
251
+ const feature = 'Share a list';
252
+ const spec = TODO_FEATURE;
253
+ // These match the ids `examples/todo/documents.ts` records, so a desktop web run replaces that slot.
254
+ const quint = 'list.qnt';
255
+ const dmn = 'retry-delay.dmn';
256
+ const slo = 'sync.openslo.yaml';
257
+ const alloy = 'list.als';
258
+ const dir = 'examples/todo';
259
+ const weekend = timeline([
260
+ { title: 'Given no list yet' },
261
+ { title: 'When the customer names the list', picture: picture('Weekend chores', '<p>Water the plants</p><button data-testid="add">Add</button>') },
262
+ { title: 'Then the list is clear and synced' }
263
+ ]);
264
+ const outage = timeline([
265
+ { title: 'Given a list' },
266
+ { title: 'When sync fails', status: 'failed', error: 'The retry never caught up', picture: picture('Weekend chores', '<p>Sync failed. The list is still here.</p>') },
267
+ { title: 'Then the list is synced' }
268
+ ]);
269
+ const iosWeekend = timeline([
270
+ { title: 'Given no list yet' },
271
+ { title: 'When the customer names the list', status: 'failed', error: 'The list name field never appeared' },
272
+ { title: 'Then the list is clear and synced' }
273
+ ]);
274
+ const scenario = (id: string, name: string, status: Status, extra: Partial<CheckSpec> = {}): CheckSpec => ({ id, title: named(feature, name), spec, status, ...extra });
275
+ return [
276
+ {
277
+ at: at('2026-10-01T15:00:00.000Z'),
278
+ sha: EXAMPLE_PREVIOUS,
279
+ branch: 'main',
280
+ environment: 'desktop-web',
281
+ backend: 'shared',
282
+ packageDir: dir,
283
+ checks: [
284
+ scenario('gherkin.share-a-list.plans-a-weekend', 'Plans a weekend', 'passed', { steps: weekend.steps, files: weekend.files }),
285
+ scenario('gherkin.share-a-list.keeps-working-through-a-sync-outage', 'Keeps working through a sync outage', 'passed'),
286
+ scenario('gherkin.share-a-list.starts-over', 'Starts over', 'passed'),
287
+ scenario('gherkin.share-a-list.starts-over-while-offline', 'Starts over while offline', 'passed'),
288
+ { id: 'gherkin.share-a-list.starts-over-while-offline', title: named(feature, 'Starts over while offline'), spec, status: 'passed', kind: 'performance', measurements: [{ name: 'Sync', value: 2.4, unit: 's', limit: 1 }] },
289
+ { id: 'trace-1', title: 'list · trace 1', spec: quint, status: 'passed', runtime: 'quint', steps: quintSteps([{ title: 'add(0)', detail: 'items = 1' }, { title: 'sync', detail: 'synced = true' }]) },
290
+ { id: 'retry-second', title: 'Retry delay: second attempt', spec: dmn, status: 'passed', runtime: 'dmn' },
291
+ { id: 'sync-latency', title: 'Sync finishes within a second', spec: slo, status: 'passed', runtime: 'openslo' },
292
+ { id: 'list-owns-todos', title: 'A list has its todos', spec: alloy, status: 'passed', runtime: 'alloy' }
293
+ ]
294
+ },
295
+ {
296
+ at: at('2026-10-06T15:00:00.000Z'),
297
+ sha: EXAMPLE_PREVIOUS,
298
+ branch: 'main',
299
+ parameters: { environment: 'Android', device: 'phone' },
300
+ backend: 'device-lab',
301
+ packageDir: dir,
302
+ checks: [
303
+ scenario('gherkin.share-a-list.plans-a-weekend', 'Plans a weekend', 'passed'),
304
+ scenario('gherkin.share-a-list.starts-over', 'Starts over', 'passed'),
305
+ { id: 'sync-latency', title: 'Sync finishes within a second', spec: slo, status: 'failed', runtime: 'openslo', detail: 'Sync took 1.8 seconds' }
306
+ ]
307
+ },
308
+ {
309
+ at: at('2026-10-08T15:00:00.000Z'),
310
+ sha: EXAMPLE_BRANCH,
311
+ branch: 'feat/offline',
312
+ environment: 'mobile-web',
313
+ backend: 'local',
314
+ packageDir: dir,
315
+ checks: [
316
+ scenario('gherkin.share-a-list.plans-a-weekend', 'Plans a weekend', 'passed'),
317
+ scenario('gherkin.share-a-list.keeps-working-through-a-sync-outage', 'Keeps working through a sync outage', 'passed'),
318
+ scenario('gherkin.share-a-list.starts-over', 'Starts over', 'failed', { detail: 'The list was still there' }),
319
+ { id: 'retry-second', title: 'Retry delay: second attempt', spec: dmn, status: 'failed', runtime: 'dmn', detail: 'The second retry waited 10 seconds' }
320
+ ]
321
+ },
322
+ {
323
+ at: at('2026-10-11T15:00:00.000Z'),
324
+ sha: EXAMPLE_CURRENT,
325
+ branch: 'main',
326
+ environment: 'ios',
327
+ backend: 'device-lab',
328
+ packageDir: dir,
329
+ checks: [scenario('gherkin.share-a-list.plans-a-weekend', 'Plans a weekend', 'failed', { detail: 'The list name field never appeared', steps: iosWeekend.steps, files: iosWeekend.files })]
330
+ },
331
+ {
332
+ at: at('2026-10-12T16:00:00.000Z'),
333
+ sha: EXAMPLE_CURRENT,
334
+ branch: 'main',
335
+ environment: 'desktop-web',
336
+ backend: 'local',
337
+ packageDir: dir,
338
+ checks: [scenario('gherkin.share-a-list.keeps-working-through-a-sync-outage', 'Keeps working through a sync outage', 'failed', { detail: 'The retry never caught up', steps: outage.steps, files: outage.files })]
339
+ }
340
+ ];
341
+ }
342
+
343
+ function layersFor(specs: Readonly<Record<string, string>>): readonly LayerSpec[] | null {
344
+ if (COFFEE_FEATURE in specs) return coffeeLayers();
345
+ if (TODO_FEATURE in specs) return todoLayers();
346
+ return null;
347
+ }
348
+
349
+ function evidence(producer: string, check: CheckSpec): EvidenceCheck {
350
+ return {
351
+ id: `${producer}/${check.id}`,
352
+ title: check.title,
353
+ status: check.status,
354
+ detail: check.detail ?? '',
355
+ specPath: `specs/${check.spec}`,
356
+ measurements: check.measurements ?? []
357
+ };
358
+ }
359
+
360
+ function recorded(producer: string, check: CheckSpec): RunReport {
361
+ const ended = check.steps?.at(-1)?.endedAtMs ?? 1;
362
+ return {
363
+ meta: {
364
+ testId: check.id,
365
+ runId: 'example',
366
+ title: check.title,
367
+ system: 'example',
368
+ runtime: check.runtime ?? 'gherkin',
369
+ seed: check.id,
370
+ status: check.status,
371
+ actorIds: ['customer'],
372
+ startedAtMs: 0,
373
+ endedAtMs: ended,
374
+ durationMs: Math.max(1, ended),
375
+ sourceFile: `specs/${check.spec}`,
376
+ ...(check.detail ? { error: check.detail } : {})
377
+ },
378
+ steps: check.steps ?? []
379
+ };
380
+ }
381
+
382
+ async function publishExampleLayer(store: ObjectStore, producer: string, layer: LayerSpec, specs: Readonly<Record<string, string>>) {
383
+ const behavior = layer.checks.filter((check) => check.kind !== 'performance');
384
+ const timings = layer.checks.filter((check) => check.kind === 'performance');
385
+ const reports = behavior.filter((check) => check.steps);
386
+ const referenced = [...new Set(layer.checks.map((check) => check.spec))];
387
+ const summary: Omit<LayerSummary, 'id'> = {
388
+ producer,
389
+ recordedAtMs: layer.at,
390
+ sha: layer.sha,
391
+ branch: layer.branch,
392
+ ...(layer.environment ? { environment: layer.environment } : {}),
393
+ runUrl: null,
394
+ parameters: layer.parameters ?? {},
395
+ specs: referenced.map((file) => {
396
+ const text = specs[file];
397
+ if (text === undefined) throw new Error(`Example layer names a spec that was not loaded: ${file}`);
398
+ return specEntry(`specs/${file}`, text);
399
+ }),
400
+ recording: { type: 'layer' },
401
+ source: {
402
+ source: producer,
403
+ packageDir: layer.packageDir,
404
+ bucket: 'all',
405
+ status: behavior.some((check) => check.status === 'failed') ? 'failed' : 'passed',
406
+ updatedAtMs: layer.at,
407
+ ...(layer.environment ? { environment: layer.environment } : {}),
408
+ ...(layer.parameters ? { parameters: layer.parameters } : {}),
409
+ tests: reports.map((check) => recorded(producer, check).meta)
410
+ },
411
+ parts: [
412
+ { sha: layer.sha, id: producer, kind: 'behavior', title: producer, recordedAtMs: layer.at, checks: behavior.map((check) => evidence(producer, check)) },
413
+ ...(timings.length ? [{ sha: layer.sha, id: `${producer}-timing`, kind: 'performance', title: 'Timings', recordedAtMs: layer.at, checks: timings.map((check) => evidence(producer, check)) }] : [])
414
+ ]
415
+ };
416
+ async function* entries() {
417
+ if (reports.some((check) => check.steps?.some((step) => step.frames.length > 0))) yield { key: 'frame.css', bytes: new TextEncoder().encode(CSS), contentType: 'text/css; charset=utf-8' };
418
+ for (const check of reports) {
419
+ yield { key: `tests/${check.id}/report.json`, bytes: new TextEncoder().encode(JSON.stringify(recorded(producer, check))), contentType: 'application/json' };
420
+ for (const file of check.files ?? []) yield { key: `tests/${check.id}/files/${file.path}`, bytes: new TextEncoder().encode(file.body), contentType: 'text/html; charset=utf-8' };
421
+ }
422
+ }
423
+ await publishLayer(store, summary, entries());
424
+ }
425
+
426
+ /**
427
+ * Layers for the coffee example or the todo example, already written into three stores.
428
+ * Any other spec set returns null, so a project's own reports are not replaced by this one.
429
+ */
430
+ export async function exampleReports(specs: Readonly<Record<string, string>>): Promise<ReportsAppDeps | null> {
431
+ const layers = layersFor(specs);
432
+ if (!layers) return null;
433
+ const producer = COFFEE_FEATURE in specs ? 'ordering' : 'todo';
434
+ const localStore = memoryStore();
435
+ const store = memoryStore();
436
+ const lab = memoryStore();
437
+ const backends = { local: localStore, shared: store, 'device-lab': lab } as const;
438
+ for (const layer of layers) await publishExampleLayer(backends[layer.backend], producer, layer, specs);
439
+ return {
440
+ store,
441
+ localStore,
442
+ backends: [{ id: 'device-lab', label: 'Device lab', store: lab }],
443
+ clock: { now: () => Date.now() },
444
+ timers: {
445
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
446
+ clearTimeout: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
447
+ setInterval: (fn, ms) => setInterval(fn, ms),
448
+ clearInterval: (handle) => clearInterval(handle as ReturnType<typeof setInterval>)
449
+ },
450
+ outline: async () => ({
451
+ specs: Object.keys(specs).sort().map((file) => specEntry(`specs/${file}`, specs[file] ?? '')),
452
+ sha: EXAMPLE_CURRENT,
453
+ changedFiles: [],
454
+ readSpec: async (specPath) => specs[specPath.replace(/^specs\//, '')] ?? null
455
+ })
456
+ };
457
+ }
458
+
459
+ let exampleApp: ReturnType<typeof appForProcess> | undefined;
460
+
461
+ async function appForProcess() {
462
+ const specs = readExampleSpecs(resolveRoots().specs);
463
+ const deps = await exampleReports(specs);
464
+ return deps ? createReportsApp(deps) : null;
465
+ }
466
+
467
+ /** `GET /api/reports/...` for the visualizer dev server, when no host module is configured. */
468
+ export async function reportsDevFetch(request: Request, _repoRoot: string): Promise<Response> {
469
+ exampleApp ??= appForProcess();
470
+ const app = await exampleApp;
471
+ if (!app) return Response.json({ error: 'not_found' }, { status: 404 });
472
+ const url = new URL(request.url);
473
+ const pathname = url.pathname.replace(/^\/api\/reports/, '') || '/';
474
+ return app.request(pathname + url.search);
475
+ }
@@ -1,13 +1,29 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+
1
4
  import react from '@vitejs/plugin-react';
2
5
  import { defineConfig } from 'vite';
3
6
 
7
+ import { resolveRoots } from './vite/atlas-files';
8
+ import { servesExampleReports } from './vite/example-reports';
4
9
  import { reportsDevPlugin } from './vite/reports-dev-plugin';
5
10
  import { atlasDevPlugin } from './vite/atlas-dev-plugin';
11
+ import { atlasServerPage, atlasServerProxy } from './vite/atlas-server-proxy';
12
+
13
+ const appRoot = path.dirname(fileURLToPath(import.meta.url));
14
+
15
+ // `atlas dev` runs the Atlas server on a port of its own and tells this config where. The browser then talks to
16
+ // this dev server alone: `/api` is proxied to the server, and the page gets the same address book a served
17
+ // build gets, so the viewer cannot tell the two apart. Without a server, the viewer reads the working tree.
18
+ const serverUrl = process.env.ATLAS_API_URL || '';
19
+ const reportsModule = serverUrl ? '' : process.env.ATLAS_REPORTS_MODULE || (servesExampleReports(resolveRoots().specs) ? path.join(appRoot, 'vite/example-reports.ts') : '');
6
20
 
7
21
  export default defineConfig({
8
22
  base: './',
9
- plugins: [react(), atlasDevPlugin(), ...(process.env.ATLAS_REPORTS_MODULE ? [reportsDevPlugin({ repoRoot: process.env.INIT_CWD || process.cwd(), appModule: process.env.ATLAS_REPORTS_MODULE })] : [])],
23
+ plugins: serverUrl
24
+ ? [react(), atlasServerPage()]
25
+ : [react(), atlasDevPlugin(), ...(reportsModule ? [reportsDevPlugin({ repoRoot: process.env.INIT_CWD || process.cwd(), appModule: reportsModule })] : [])],
10
26
  worker: { format: 'es' },
11
- server: { port: Number(process.env.PORT ?? 5180), strictPort: false },
27
+ server: { port: Number(process.env.PORT ?? 5180), strictPort: false, ...(serverUrl ? atlasServerProxy(serverUrl) : {}) },
12
28
  build: { outDir: 'dist', emptyOutDir: true, chunkSizeWarningLimit: 2_500 }
13
29
  });