@crvouga/atlas 0.4.0 → 0.6.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 (444) hide show
  1. package/CHANGELOG.md +13 -1
  2. package/README.md +191 -611
  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 +35 -1
  9. package/dist/core/src/config.d.ts +77 -4
  10. package/dist/core/src/config.js +110 -17
  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 +75 -16
  30. package/dist/core/src/run/execute.js +71 -18
  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 +85 -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 +35 -5
  94. package/dist/reports/src/evidence-live.js +18 -2
  95. package/dist/reports/src/evidence-types.d.ts +68 -1
  96. package/dist/reports/src/evidence-types.js +5 -1
  97. package/dist/reports/src/evidence-view.d.ts +43 -4
  98. package/dist/reports/src/evidence-view.js +13 -2
  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 +56 -0
  120. package/dist/reports/src/http-store.js +148 -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 +369 -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 +35 -5
  134. package/dist/reports/src/layer-axes.js +25 -5
  135. package/dist/reports/src/layer-catalog.d.ts +28 -1
  136. package/dist/reports/src/layer-catalog.js +18 -1
  137. package/dist/reports/src/layer-merge.d.ts +98 -9
  138. package/dist/reports/src/layer-merge.js +147 -31
  139. package/dist/reports/src/layer-source.d.ts +18 -0
  140. package/dist/reports/src/layer-source.js +11 -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 +32 -1
  144. package/dist/reports/src/layers-route.d.ts +47 -4
  145. package/dist/reports/src/layers-route.js +86 -7
  146. package/dist/reports/src/live-reporter.d.ts +34 -0
  147. package/dist/reports/src/live-reporter.js +122 -0
  148. package/dist/reports/src/media-types.d.ts +11 -0
  149. package/dist/reports/src/media-types.js +22 -0
  150. package/dist/reports/src/namespace-guard.d.ts +10 -1
  151. package/dist/reports/src/namespace-guard.js +10 -1
  152. package/dist/reports/src/navigation.d.ts +12 -0
  153. package/dist/reports/src/navigation.js +7 -0
  154. package/dist/reports/src/object-io.d.ts +11 -4
  155. package/dist/reports/src/object-io.js +4 -4
  156. package/dist/reports/src/object-store-conformance.d.ts +27 -0
  157. package/dist/reports/src/object-store-conformance.js +87 -0
  158. package/dist/reports/src/object-store.d.ts +33 -24
  159. package/dist/reports/src/object-store.js +17 -24
  160. package/dist/reports/src/parse-with.d.ts +16 -0
  161. package/dist/reports/src/parse-with.js +14 -0
  162. package/dist/reports/src/performance-part.d.ts +25 -1
  163. package/dist/reports/src/performance-part.js +15 -1
  164. package/dist/reports/src/performance.d.ts +22 -2
  165. package/dist/reports/src/performance.js +12 -2
  166. package/dist/reports/src/persist.d.ts +12 -3
  167. package/dist/reports/src/persist.js +12 -3
  168. package/dist/reports/src/phone.d.ts +18 -1
  169. package/dist/reports/src/phone.js +18 -1
  170. package/dist/reports/src/protocol.d.ts +26 -4
  171. package/dist/reports/src/protocol.js +15 -4
  172. package/dist/reports/src/publication.d.ts +21 -1
  173. package/dist/reports/src/publication.js +21 -1
  174. package/dist/reports/src/publish-checks.d.ts +35 -0
  175. package/dist/reports/src/publish-checks.js +122 -0
  176. package/dist/reports/src/publish.d.ts +42 -9
  177. package/dist/reports/src/publish.js +36 -7
  178. package/dist/reports/src/read.d.ts +46 -16
  179. package/dist/reports/src/read.js +38 -14
  180. package/dist/reports/src/recording-bundle.d.ts +13 -2
  181. package/dist/reports/src/recording-bundle.js +4 -2
  182. package/dist/reports/src/recording-keys.d.ts +15 -1
  183. package/dist/reports/src/recording-keys.js +15 -1
  184. package/dist/reports/src/remote-report.d.ts +28 -7
  185. package/dist/reports/src/remote-report.js +15 -7
  186. package/dist/reports/src/report-cli.d.ts +51 -0
  187. package/dist/reports/src/report-cli.js +486 -0
  188. package/dist/reports/src/report-index.d.ts +99 -0
  189. package/dist/reports/src/report-index.js +337 -0
  190. package/dist/reports/src/report-loader.d.ts +10 -0
  191. package/dist/reports/src/report-loader.js +6 -0
  192. package/dist/reports/src/report-parts.d.ts +29 -3
  193. package/dist/reports/src/report-parts.js +37 -3
  194. package/dist/reports/src/report-status.d.ts +48 -0
  195. package/dist/reports/src/report-status.js +156 -0
  196. package/dist/reports/src/report-types.d.ts +73 -0
  197. package/dist/reports/src/report-types.js +3 -0
  198. package/dist/reports/src/reporter.d.ts +67 -0
  199. package/dist/reports/src/reporter.js +128 -0
  200. package/dist/reports/src/reports-explorer-route.d.ts +541 -0
  201. package/dist/reports/src/reports-explorer-route.js +11 -0
  202. package/dist/reports/src/reports-list-route.d.ts +9 -0
  203. package/dist/reports/src/reports-list-route.js +9 -0
  204. package/dist/reports/src/reports-route.d.ts +561 -22
  205. package/dist/reports/src/reports-route.js +11 -3
  206. package/dist/reports/src/reports-store.d.ts +19 -2
  207. package/dist/reports/src/reports-store.js +10 -2
  208. package/dist/reports/src/resilient-cache.d.ts +14 -1
  209. package/dist/reports/src/resilient-cache.js +8 -1
  210. package/dist/reports/src/retention.d.ts +17 -1
  211. package/dist/reports/src/retention.js +5 -1
  212. package/dist/reports/src/run-bundle.d.ts +63 -0
  213. package/dist/reports/src/run-bundle.js +159 -0
  214. package/dist/reports/src/s3-store.d.ts +79 -0
  215. package/dist/reports/src/s3-store.js +295 -0
  216. package/dist/reports/src/scenario.d.ts +14 -4
  217. package/dist/reports/src/scenario.js +5 -4
  218. package/dist/reports/src/sigv4.d.ts +72 -0
  219. package/dist/reports/src/sigv4.js +96 -0
  220. package/dist/reports/src/snapshot-bundle.d.ts +24 -1
  221. package/dist/reports/src/snapshot-bundle.js +27 -2
  222. package/dist/reports/src/snapshot-surface.d.ts +20 -9
  223. package/dist/reports/src/snapshot-surface.js +32 -19
  224. package/dist/reports/src/source-content.d.ts +23 -0
  225. package/dist/reports/src/source-content.js +30 -1
  226. package/dist/reports/src/source-entries.d.ts +13 -0
  227. package/dist/reports/src/source-entries.js +6 -0
  228. package/dist/reports/src/source-part.d.ts +15 -0
  229. package/dist/reports/src/source-part.js +8 -0
  230. package/dist/reports/src/source-reader.d.ts +11 -0
  231. package/dist/reports/src/source-reader.js +5 -0
  232. package/dist/reports/src/spec-evidence.d.ts +12 -2
  233. package/dist/reports/src/spec-evidence.js +6 -2
  234. package/dist/reports/src/spec-inventory.d.ts +15 -1
  235. package/dist/reports/src/spec-inventory.js +22 -1
  236. package/dist/reports/src/spec-link.d.ts +28 -0
  237. package/dist/reports/src/spec-link.js +70 -0
  238. package/dist/reports/src/spec-set.d.ts +63 -0
  239. package/dist/reports/src/spec-set.js +177 -0
  240. package/dist/reports/src/status.d.ts +10 -0
  241. package/dist/reports/src/status.js +5 -0
  242. package/dist/reports/src/store-prefix.d.ts +28 -0
  243. package/dist/reports/src/store-prefix.js +22 -0
  244. package/dist/reports/src/store-url.d.ts +89 -0
  245. package/dist/reports/src/store-url.js +160 -0
  246. package/dist/reports/src/test-catalog-entity.d.ts +11 -0
  247. package/dist/reports/src/test-catalog-entity.js +6 -0
  248. package/dist/reports/src/types.d.ts +6 -1
  249. package/dist/reports/src/use-report-nav.d.ts +35 -0
  250. package/dist/reports/src/use-report-nav.js +12 -0
  251. package/dist/runners/src/codecept.d.ts +14 -0
  252. package/dist/runners/src/codecept.js +16 -0
  253. package/dist/runners/src/jest.d.ts +50 -0
  254. package/dist/runners/src/jest.js +52 -0
  255. package/dist/runners/src/mocha.d.ts +40 -0
  256. package/dist/runners/src/mocha.js +39 -0
  257. package/dist/runners/src/node-test.d.ts +28 -0
  258. package/dist/runners/src/node-test.js +44 -0
  259. package/dist/runners/src/publish.d.ts +30 -0
  260. package/dist/runners/src/publish.js +68 -0
  261. package/dist/schema/src/api.d.ts +800 -0
  262. package/dist/schema/src/api.js +201 -0
  263. package/dist/schema/src/cli-results.d.ts +210 -0
  264. package/dist/schema/src/cli-results.js +124 -0
  265. package/dist/schema/src/common.d.ts +53 -0
  266. package/dist/schema/src/common.js +42 -0
  267. package/dist/schema/src/index.d.ts +16 -0
  268. package/dist/schema/src/index.js +16 -0
  269. package/dist/schema/src/layer.d.ts +2273 -0
  270. package/dist/schema/src/layer.js +462 -0
  271. package/dist/schema/src/live-run.d.ts +123 -0
  272. package/dist/schema/src/live-run.js +30 -0
  273. package/dist/schema/src/manifest.d.ts +97 -2
  274. package/dist/schema/src/manifest.js +84 -0
  275. package/dist/schema/src/project.d.ts +20 -0
  276. package/dist/schema/src/project.js +10 -0
  277. package/dist/schema/src/report-sources.d.ts +29 -0
  278. package/dist/schema/src/report-sources.js +27 -1
  279. package/dist/schema/src/runs-index.d.ts +17 -1
  280. package/dist/schema/src/runs-index.js +15 -0
  281. package/dist/schema/src/server-config.d.ts +177 -0
  282. package/dist/schema/src/server-config.js +85 -0
  283. package/dist/schema/src/spec.d.ts +69 -0
  284. package/dist/schema/src/spec.js +58 -0
  285. package/dist/server/src/app.d.ts +72 -0
  286. package/dist/server/src/app.js +557 -0
  287. package/dist/server/src/auth.d.ts +41 -0
  288. package/dist/server/src/auth.js +59 -0
  289. package/dist/server/src/config.d.ts +83 -0
  290. package/dist/server/src/config.js +204 -0
  291. package/dist/server/src/dev.d.ts +12 -0
  292. package/dist/server/src/dev.js +89 -0
  293. package/dist/server/src/events.d.ts +29 -0
  294. package/dist/server/src/events.js +101 -0
  295. package/dist/server/src/host.d.ts +30 -0
  296. package/dist/server/src/host.js +115 -0
  297. package/dist/server/src/index.d.ts +18 -0
  298. package/dist/server/src/index.js +18 -0
  299. package/dist/server/src/live-runs.d.ts +52 -0
  300. package/dist/server/src/live-runs.js +229 -0
  301. package/dist/server/src/llms.d.ts +19 -0
  302. package/dist/server/src/llms.js +63 -0
  303. package/dist/server/src/main.d.ts +30 -0
  304. package/dist/server/src/main.js +114 -0
  305. package/dist/server/src/openapi.d.ts +56 -0
  306. package/dist/server/src/openapi.js +438 -0
  307. package/dist/server/src/problem.d.ts +8 -0
  308. package/dist/server/src/problem.js +39 -0
  309. package/dist/server/src/projects.d.ts +3 -0
  310. package/dist/server/src/projects.js +154 -0
  311. package/dist/server/src/settings-page.d.ts +9 -0
  312. package/dist/server/src/settings-page.js +93 -0
  313. package/dist/server/src/settings.d.ts +23 -0
  314. package/dist/server/src/settings.js +72 -0
  315. package/dist/server/src/site.d.ts +15 -0
  316. package/dist/server/src/site.js +78 -0
  317. package/dist/server/src/store-api.d.ts +28 -0
  318. package/dist/server/src/store-api.js +180 -0
  319. package/dist/server/src/validate.d.ts +71 -0
  320. package/dist/server/src/validate.js +197 -0
  321. package/dist/vitest/src/index.d.ts +27 -5
  322. package/dist/vitest/src/index.js +18 -5
  323. package/dist/webdriver/src/driver.d.ts +63 -11
  324. package/dist/webdriver/src/driver.js +57 -6
  325. package/dist/webdriver/src/index.d.ts +8 -0
  326. package/dist/webdriver/src/index.js +1 -0
  327. package/dist/webdriver/src/protocol.d.ts +75 -6
  328. package/dist/webdriver/src/protocol.js +79 -6
  329. package/docs/agents.md +137 -0
  330. package/docs/deploying.md +345 -0
  331. package/docs/dogfooding.md +175 -0
  332. package/docs/extending.md +242 -0
  333. package/docs/integrating.md +485 -0
  334. package/docs/reference/bun.md +141 -0
  335. package/docs/reference/cli.md +148 -0
  336. package/docs/reference/core.md +2432 -0
  337. package/docs/reference/detox.md +150 -0
  338. package/docs/reference/http-api.md +799 -0
  339. package/docs/reference/playwright.md +303 -0
  340. package/docs/reference/reports.md +7152 -0
  341. package/docs/reference/runners.md +169 -0
  342. package/docs/reference/schema.md +1762 -0
  343. package/docs/reference/server.md +825 -0
  344. package/docs/reference/vitest.md +45 -0
  345. package/docs/reference/webdriver.md +330 -0
  346. package/docs/report-backends.md +144 -0
  347. package/docs/report-contract.md +400 -0
  348. package/docs/reporting-issues.md +160 -0
  349. package/docs/runners.md +69 -0
  350. package/docs/server.md +321 -0
  351. package/docs/statecharts.md +643 -0
  352. package/llms-full.txt +4114 -0
  353. package/llms.txt +49 -0
  354. package/package.json +61 -11
  355. package/packages/core/README.md +2 -1
  356. package/packages/reports/README.md +65 -0
  357. package/packages/reports/STORAGE.md +4 -0
  358. package/packages/runners/README.md +6 -0
  359. package/packages/runners/compat/codecept.cjs +2 -0
  360. package/packages/runners/compat/jest.cjs +2 -0
  361. package/packages/runners/compat/mocha.cjs +2 -0
  362. package/packages/schema/README.md +14 -0
  363. package/packages/schema/schemas/atlas-change.schema.json +4 -0
  364. package/packages/schema/schemas/check-detail.schema.json +1032 -0
  365. package/packages/schema/schemas/checks-result.schema.json +26 -0
  366. package/packages/schema/schemas/command-error.schema.json +35 -0
  367. package/packages/schema/schemas/doctor-result.schema.json +60 -0
  368. package/packages/schema/schemas/effective-delta.schema.json +490 -0
  369. package/packages/schema/schemas/effective-evidence.schema.json +471 -0
  370. package/packages/schema/schemas/evidence-page.schema.json +271 -0
  371. package/packages/schema/schemas/hello-event.schema.json +26 -0
  372. package/packages/schema/schemas/init-plan.schema.json +347 -0
  373. package/packages/schema/schemas/layer-rows.schema.json +199 -0
  374. package/packages/schema/schemas/layer.schema.json +2888 -0
  375. package/packages/schema/schemas/live-run-start.schema.json +42 -0
  376. package/packages/schema/schemas/live-run-update.schema.json +93 -0
  377. package/packages/schema/schemas/live-run.schema.json +225 -0
  378. package/packages/schema/schemas/merge-result.schema.json +40 -0
  379. package/packages/schema/schemas/outline.schema.json +62 -0
  380. package/packages/schema/schemas/problem.schema.json +37 -0
  381. package/packages/schema/schemas/project-slug.schema.json +8 -0
  382. package/packages/schema/schemas/project.schema.json +22 -0
  383. package/packages/schema/schemas/projects.schema.json +33 -0
  384. package/packages/schema/schemas/publish-checks.schema.json +1413 -0
  385. package/packages/schema/schemas/publish-result.schema.json +168 -0
  386. package/packages/schema/schemas/ready.schema.json +68 -0
  387. package/packages/schema/schemas/report-event.schema.json +100 -0
  388. package/packages/schema/schemas/report-status.schema.json +370 -0
  389. package/packages/schema/schemas/server-config.schema.json +208 -0
  390. package/packages/schema/schemas/server-editable-settings.schema.json +132 -0
  391. package/packages/schema/schemas/server-info.schema.json +114 -0
  392. package/packages/schema/schemas/server-settings-snapshot.schema.json +285 -0
  393. package/packages/schema/schemas/spec-set.schema.json +103 -0
  394. package/packages/schema/schemas/store-listing.schema.json +29 -0
  395. package/packages/schema/schemas/store-write-result.schema.json +28 -0
  396. package/packages/schema/schemas/stream-result.schema.json +59 -0
  397. package/packages/server/README.md +43 -0
  398. package/packages/server/openapi.json +11021 -0
  399. package/packages/visualizer/README.md +32 -11
  400. package/packages/visualizer/bin/atlas-client.mjs +176 -0
  401. package/packages/visualizer/bin/atlas-visualizer.mjs +2 -175
  402. package/packages/visualizer/src/app/AppShell.tsx +32 -16
  403. package/packages/visualizer/src/app/report-sources.tsx +7 -4
  404. package/packages/visualizer/src/app/router.tsx +3 -3
  405. package/packages/visualizer/src/components/ActivityIndicator.module.css +4 -0
  406. package/packages/visualizer/src/components/ActivityIndicator.tsx +6 -0
  407. package/packages/visualizer/src/components/Header.module.css +101 -192
  408. package/packages/visualizer/src/components/Header.tsx +88 -13
  409. package/packages/visualizer/src/components/ProjectPicker.tsx +64 -0
  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/data/adapters/report-backend.test.ts +3 -2
  413. package/packages/visualizer/src/data/adapters/report-backend.ts +1 -1
  414. package/packages/visualizer/src/data/queries.ts +14 -7
  415. package/packages/visualizer/src/evidence/api.ts +16 -3
  416. package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
  417. package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
  418. package/packages/visualizer/src/evidence/check-detail.tsx +21 -4
  419. package/packages/visualizer/src/evidence/document-page.tsx +6 -2
  420. package/packages/visualizer/src/evidence/evidence-context.tsx +79 -12
  421. package/packages/visualizer/src/evidence/evidence.module.css +11 -1
  422. package/packages/visualizer/src/evidence/feature-page.tsx +7 -3
  423. package/packages/visualizer/src/evidence/model-page.tsx +6 -2
  424. package/packages/visualizer/src/evidence/project-home.tsx +45 -0
  425. package/packages/visualizer/src/evidence/recording-page.tsx +4 -1
  426. package/packages/visualizer/src/evidence/spec-index.tsx +2 -1
  427. package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
  428. package/packages/visualizer/src/evidence/test-filter.tsx +21 -0
  429. package/packages/visualizer/src/find/Finder.tsx +1 -7
  430. package/packages/visualizer/src/find/open.ts +4 -0
  431. package/packages/visualizer/src/live/live-query.ts +21 -0
  432. package/packages/visualizer/src/live/live-runs.tsx +69 -0
  433. package/packages/visualizer/src/live/live-stream.ts +225 -0
  434. package/packages/visualizer/src/live/report-progress.ts +62 -0
  435. package/packages/visualizer/src/live/test-progress.tsx +34 -0
  436. package/packages/visualizer/src/runtime.ts +35 -0
  437. package/packages/visualizer/src/state/ui-store.ts +3 -2
  438. package/packages/visualizer/src/views/ChartPage.tsx +2 -0
  439. package/packages/visualizer/src/views/ChartTests.tsx +43 -0
  440. package/packages/visualizer/src/views/SidePanel.tsx +2 -0
  441. package/packages/visualizer/vite/atlas-files.test.ts +4 -4
  442. package/packages/visualizer/vite/atlas-files.ts +1 -5
  443. package/packages/visualizer/vite/atlas-server-proxy.ts +37 -0
  444. package/packages/visualizer/vite.config.ts +11 -3
@@ -1,27 +1,38 @@
1
1
  import type { ReportStepHit } from './report-types.js';
2
+ /** What drawing a press target found: `none` (no target to draw), `found` (target located, overlay drawn) or `missing` (the path does not resolve in the painted snapshot, overlay hidden). */
2
3
  export type HitResult = 'none' | 'found' | 'missing';
3
4
  /**
4
- * Point `html` / `body` / `:root` at the phone viewport, and turn a full
5
- * browser viewport unit into a percentage of that box.
5
+ * Rewrites snapshot CSS so it applies inside the phone surface: the selectors `html` and `:root` become `.sr-snap-doc`,
6
+ * `body` becomes `.sr-snap-body`, and the viewport units `100vh`, `100vw` (and their `d`, `s`, `l` variants) become
7
+ * `100%` of that box. A regex rewrite, so it is approximate for unusual selectors.
6
8
  */
7
9
  export declare function scopeSnapshotCss(css: string): string;
8
- /** Make sure `doc` is our standards-mode shell with `frameCss` installed. */
10
+ /** Makes `doc` the standards-mode shell with `frameCss` installed, replacing its content, unless it already is one. Does nothing when the shell's stylesheet is present, so a later different `frameCss` is not applied. */
9
11
  export declare function ensureShell(doc: Document, frameCss: string): void;
10
- /** Chained first match — mirrors how the UI driver resolves `get(a).get(b)`. */
12
+ /**
13
+ * Finds the element a gesture targeted: the first `[data-testid]` match of `path[0]` inside `scope`, then the first
14
+ * match of `path[1]` inside that, and so on. Returns `null` as soon as a step has no match, and for an empty path
15
+ * returns `null` too. Mirrors how the UI driver resolves `get(a).get(b)`.
16
+ */
11
17
  export declare function resolveHitTarget(scope: ParentNode, path: readonly string[]): Element | null;
12
- /** Draw the one press overlay on `hit`'s target. */
18
+ /** Draws the one press overlay (a box and a dot at `hit.point`, default the centre) on `hit`'s target in a document, scrolling the target into view if it is outside the viewport. Returns what it found; with no hit it hides the overlay. */
13
19
  export declare function drawHit(doc: Document, hit: ReportStepHit | null): HitResult;
14
20
  /**
15
- * Paint one snapshot into `doc` (already a shell) and draw `hit`. `key`
16
- * identifies the snapshot for parse caching.
21
+ * Paints one snapshot into `doc` (already a shell, see `ensureShell`) and draws `hit`. `key` identifies the snapshot for
22
+ * parse caching (256 entries, oldest dropped first): it must change whenever `html` does, or the earlier parse is
23
+ * reused.
17
24
  */
18
25
  export declare function paintSnapshot(doc: Document, snap: {
19
26
  readonly key: string;
20
27
  readonly html: string;
21
28
  }, hit: ReportStepHit | null): HitResult;
22
- /** In-page snapshot surface. No iframe. */
29
+ /** Creates the in-page snapshot surface: a `div.sr-snap` with an open shadow tree, sized to the phone viewport, ready for `paintSnapshotHost`. No iframe. */
23
30
  export declare function createSnapshotHost(): HTMLDivElement;
24
- /** Paint one snapshot into the shadow tree `createSnapshotHost` built. */
31
+ /**
32
+ * Paints one snapshot into the shadow tree `createSnapshotHost` built, installs `snap.css` (the frame stylesheet) and
33
+ * draws `hit`. Painting is skipped, and only the overlay redrawn, when the same `key` and `html` were painted last.
34
+ * Returns `none` when `host` was not built by `createSnapshotHost`.
35
+ */
25
36
  export declare function paintSnapshotHost(host: HTMLElement, snap: {
26
37
  readonly key: string;
27
38
  readonly html: string;
@@ -1,16 +1,19 @@
1
- import { cleanSnapshotHtml, PHONE_H, PHONE_W, } from './phone.js';
2
1
  /**
3
- * One long-lived snapshot surface per client panel. Frames are painted by
4
- * swapping the parsed snapshot DOM into that surface synchronously, so
5
- * scrubbing never re-parses `frameCss` or refetches fonts. The press target
6
- * is resolved against the painted DOM with the driver's chained first-match
7
- * semantics and drawn as a single overlay from real layout.
2
+ * @module Snapshot surface
3
+ *
4
+ * Paints recorded page snapshots (HTML captured at each step of a test) into a phone-sized surface in the viewer and
5
+ * draws the press target of a gesture on top. One long-lived surface per client panel: frames are painted by swapping
6
+ * the parsed snapshot DOM into that surface synchronously, so scrubbing never re-parses `frameCss` or refetches fonts.
7
+ * The press target is resolved against the painted DOM with the driver's chained first-match semantics and drawn as a
8
+ * single overlay from real layout.
8
9
  *
9
- * The phone paints into an open shadow tree, not an iframe: embedded
10
- * browsers drop iframe documents, and a shadow tree stays in the page.
11
- * Snapshot styles sit on the shadow root, and the host is the 390×844
12
- * viewport the app shell fills.
10
+ * The phone paints into an open shadow tree, not an iframe: embedded browsers drop iframe documents, and a shadow tree
11
+ * stays in the page. Snapshot styles sit on the shadow root, and the host is the 390x844 viewport the app shell fills.
12
+ * Browser-only: needs `document`, `DOMParser` and shadow DOM. Snapshot HTML comes from a test run and is untrusted;
13
+ * `<script>` elements are removed (`cleanSnapshotHtml`) but nothing else is sanitised, so show it only in a context you
14
+ * would show that run's pages in.
13
15
  */
16
+ import { cleanSnapshotHtml, PHONE_H, PHONE_W, } from './phone.js';
14
17
  const FRAME_CSS_ID = 'sr-frame-css';
15
18
  const OVERLAY_ID = 'sr-press';
16
19
  const SNAP_ATTR = 'data-sr-snap';
@@ -18,8 +21,9 @@ const PARSE_CACHE_MAX = 256;
18
21
  const OVERLAY_CSS = `#${OVERLAY_ID}{position:absolute;display:none;pointer-events:none;z-index:2147483647;box-sizing:border-box;border:2px solid #ff5a8a;border-radius:6px;box-shadow:0 0 0 4px rgba(255,90,138,.35)}#${OVERLAY_ID} i{position:absolute;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%;background:#ff5a8a;box-shadow:0 0 0 4px rgba(255,90,138,.35)}`;
19
22
  const parseCache = new Map();
20
23
  /**
21
- * Point `html` / `body` / `:root` at the phone viewport, and turn a full
22
- * browser viewport unit into a percentage of that box.
24
+ * Rewrites snapshot CSS so it applies inside the phone surface: the selectors `html` and `:root` become `.sr-snap-doc`,
25
+ * `body` becomes `.sr-snap-body`, and the viewport units `100vh`, `100vw` (and their `d`, `s`, `l` variants) become
26
+ * `100%` of that box. A regex rewrite, so it is approximate for unusual selectors.
23
27
  */
24
28
  export function scopeSnapshotCss(css) {
25
29
  const scoped = css.replace(/(^|[^\w-])(:root|html|body)(?![\w-])/g, (_match, prefix, token) => `${prefix}${token === 'body' ? '.sr-snap-body' : '.sr-snap-doc'}`);
@@ -56,7 +60,7 @@ function shellHtml(frameCss) {
56
60
  const base = `html,body{margin:0}html{position:relative}${OVERLAY_CSS}`;
57
61
  return `<!DOCTYPE html><html><head><meta charset="utf-8"><style id="${FRAME_CSS_ID}">${frameCss}${base}</style></head><body></body></html>`;
58
62
  }
59
- /** Make sure `doc` is our standards-mode shell with `frameCss` installed. */
63
+ /** Makes `doc` the standards-mode shell with `frameCss` installed, replacing its content, unless it already is one. Does nothing when the shell's stylesheet is present, so a later different `frameCss` is not applied. */
60
64
  export function ensureShell(doc, frameCss) {
61
65
  if (doc.getElementById(FRAME_CSS_ID))
62
66
  return;
@@ -148,7 +152,11 @@ function syncHead(root, parsed) {
148
152
  function testIdSelector(id) {
149
153
  return `[data-testid="${id.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"]`;
150
154
  }
151
- /** Chained first match — mirrors how the UI driver resolves `get(a).get(b)`. */
155
+ /**
156
+ * Finds the element a gesture targeted: the first `[data-testid]` match of `path[0]` inside `scope`, then the first
157
+ * match of `path[1]` inside that, and so on. Returns `null` as soon as a step has no match, and for an empty path
158
+ * returns `null` too. Mirrors how the UI driver resolves `get(a).get(b)`.
159
+ */
152
160
  export function resolveHitTarget(scope, path) {
153
161
  let current = scope;
154
162
  let found = null;
@@ -224,7 +232,7 @@ function drawOn(root, hit) {
224
232
  placeOverlay(root, target, hit);
225
233
  return 'found';
226
234
  }
227
- /** Draw the one press overlay on `hit`'s target. */
235
+ /** Draws the one press overlay (a box and a dot at `hit.point`, default the centre) on `hit`'s target in a document, scrolling the target into view if it is outside the viewport. Returns what it found; with no hit it hides the overlay. */
228
236
  export function drawHit(doc, hit) {
229
237
  if (!doc.getElementById('sr-overlay-css')) {
230
238
  const style = doc.createElement('style');
@@ -246,8 +254,9 @@ function paintRoot(root, snap, hit) {
246
254
  return drawOn(root, hit);
247
255
  }
248
256
  /**
249
- * Paint one snapshot into `doc` (already a shell) and draw `hit`. `key`
250
- * identifies the snapshot for parse caching.
257
+ * Paints one snapshot into `doc` (already a shell, see `ensureShell`) and draws `hit`. `key` identifies the snapshot for
258
+ * parse caching (256 entries, oldest dropped first): it must change whenever `html` does, or the earlier parse is
259
+ * reused.
251
260
  */
252
261
  export function paintSnapshot(doc, snap, hit) {
253
262
  return paintRoot(documentRoot(doc), snap, hit);
@@ -272,7 +281,7 @@ const HOST_LAYOUT = [
272
281
  '#root>.themeHost,.dom-root>.themeHost,.dom-root>*>.themeHost',
273
282
  `{${HOST_FILL}}`,
274
283
  ].join('');
275
- /** In-page snapshot surface. No iframe. */
284
+ /** Creates the in-page snapshot surface: a `div.sr-snap` with an open shadow tree, sized to the phone viewport, ready for `paintSnapshotHost`. No iframe. */
276
285
  export function createSnapshotHost() {
277
286
  const host = document.createElement('div');
278
287
  host.className = 'sr-snap';
@@ -322,7 +331,11 @@ function installHostCss(root, frameCss) {
322
331
  if (style.textContent !== next)
323
332
  style.textContent = next;
324
333
  }
325
- /** Paint one snapshot into the shadow tree `createSnapshotHost` built. */
334
+ /**
335
+ * Paints one snapshot into the shadow tree `createSnapshotHost` built, installs `snap.css` (the frame stylesheet) and
336
+ * draws `hit`. Painting is skipped, and only the overlay redrawn, when the same `key` and `html` were painted last.
337
+ * Returns `none` when `host` was not built by `createSnapshotHost`.
338
+ */
326
339
  export function paintSnapshotHost(host, snap, hit) {
327
340
  const root = hostRoot(host);
328
341
  if (!root)
@@ -1,27 +1,50 @@
1
1
  import type { ObjectStore } from './object-store.js';
2
2
  import type { SourceRecord } from './protocol.js';
3
+ /** What makes a recorded source reusable: the exact task that produced it, within a retention window. */
3
4
  export type SourceIdentity = {
5
+ /** The hash of the exact task that produced the source: 16 to 64 lowercase hex digits. */
4
6
  readonly taskHash: string;
7
+ /** The retention window the source belongs to, from `reportGeneration`. A new generation never reuses an older copy, so old content can expire. */
5
8
  readonly generation: number;
6
9
  };
7
10
  type SourceReference = {
8
11
  readonly record: SourceRecord;
9
12
  readonly contentPrefix: string;
10
13
  };
14
+ /** The retention window index for a time: `floor(nowMs / (retentionDays * 86 400 000))`. Throws `Report time must be nonnegative` for a negative or non-finite time and `Report retention must be a positive number of days` for a non-integer or less than 1. */
11
15
  export declare function reportGeneration(nowMs: number, retentionDays: number): number;
16
+ /**
17
+ * The key prefix of a reusable source's content: `content/v2/<generation>/<taskHash>/<source>/`. Throws
18
+ * `Report identity needs an exact task hash`, `Report generation must be a nonnegative integer` or `Invalid report
19
+ * source` when an input is malformed.
20
+ */
12
21
  export declare function sourceContentPrefix(identity: SourceIdentity, source: string): string;
22
+ /** Points a commit's source record at reusable content: writes `commits/<sha>/sources/<source>.json` as `{ record, contentPrefix }`. */
13
23
  export declare function attachSource(store: ObjectStore, args: {
14
24
  readonly sha: string;
15
25
  readonly record: SourceRecord;
16
26
  readonly contentPrefix: string;
17
27
  }): Promise<void>;
28
+ /**
29
+ * Reuses an earlier passing run of the same task: when the content for `identity` is complete in the store, attaches it
30
+ * to `sha` and returns its record; otherwise returns `null` and writes nothing. Returns `null` also when the stored
31
+ * record is for another package or bucket, or did not pass. Throws for a malformed identity or a stored record that is
32
+ * not JSON.
33
+ */
18
34
  export declare function reuseSource(store: ObjectStore, args: {
19
35
  readonly sha: string;
20
36
  readonly packageDir: string;
21
37
  readonly bucket: string;
22
38
  readonly identity: SourceIdentity;
23
39
  }): Promise<SourceRecord | null>;
40
+ /**
41
+ * Reads a commit's record of one source and where its test files live, or `null` when the commit has no such source.
42
+ * A reference written by `attachSource` yields its content prefix; a plain record yields
43
+ * `commits/<sha>/tests/<source>/`. Throws `Invalid report content reference` when a reference's prefix does not match
44
+ * the expected pattern or names another source.
45
+ */
24
46
  export declare function readStoredSource(store: ObjectStore, sha: string, source: string): Promise<SourceReference | null>;
47
+ /** The key prefix of one test's files, `<content prefix><testId>/`, or `null` when the source is not in the commit. Throws `Invalid report test` when `testId` is not a safe key segment. */
25
48
  export declare function sourceTestPrefix(store: ObjectStore, args: {
26
49
  readonly sha: string;
27
50
  readonly source: string;
@@ -1,7 +1,17 @@
1
- /** A source can own commit-local files or reference a reusable complete source. */
1
+ /**
2
+ * @module Source content references
3
+ *
4
+ * A source (one package bucket of recorded tests in a commit) either owns commit-local files under
5
+ * `commits/<sha>/tests/<source>/`, or references a reusable, content-addressed copy under
6
+ * `content/v2/<generation>/<taskHash>/<source>/` that an identical earlier passing run already uploaded. This module
7
+ * names those prefixes, writes and resolves the reference, and decides when a copy is reusable. Only a passing source
8
+ * is ever reused, and a reference whose prefix does not fit the pattern is rejected, so a record cannot point a reader
9
+ * at another path.
10
+ */
2
11
  import { readUtf8, writeUtf8 } from './object-io.js';
3
12
  import { commitPrefix, isKeySegment, sourceId, sourceKey } from './keys.js';
4
13
  import { reportStore } from './publication.js';
14
+ /** The retention window index for a time: `floor(nowMs / (retentionDays * 86 400 000))`. Throws `Report time must be nonnegative` for a negative or non-finite time and `Report retention must be a positive number of days` for a non-integer or less than 1. */
5
15
  export function reportGeneration(nowMs, retentionDays) {
6
16
  if (!Number.isFinite(nowMs) || nowMs < 0)
7
17
  throw new Error('Report time must be nonnegative');
@@ -9,6 +19,11 @@ export function reportGeneration(nowMs, retentionDays) {
9
19
  throw new Error('Report retention must be a positive number of days');
10
20
  return Math.floor(nowMs / (retentionDays * 86_400_000));
11
21
  }
22
+ /**
23
+ * The key prefix of a reusable source's content: `content/v2/<generation>/<taskHash>/<source>/`. Throws
24
+ * `Report identity needs an exact task hash`, `Report generation must be a nonnegative integer` or `Invalid report
25
+ * source` when an input is malformed.
26
+ */
12
27
  export function sourceContentPrefix(identity, source) {
13
28
  if (!/^[0-9a-f]{16,64}$/.test(identity.taskHash))
14
29
  throw new Error('Report identity needs an exact task hash');
@@ -18,9 +33,16 @@ export function sourceContentPrefix(identity, source) {
18
33
  throw new Error('Invalid report source');
19
34
  return `content/v2/${identity.generation}/${identity.taskHash}/${source}/`;
20
35
  }
36
+ /** Points a commit's source record at reusable content: writes `commits/<sha>/sources/<source>.json` as `{ record, contentPrefix }`. */
21
37
  export async function attachSource(store, args) {
22
38
  await writeUtf8(store, sourceKey(args.sha, args.record.source), JSON.stringify({ record: args.record, contentPrefix: args.contentPrefix }), 'application/json; charset=utf-8');
23
39
  }
40
+ /**
41
+ * Reuses an earlier passing run of the same task: when the content for `identity` is complete in the store, attaches it
42
+ * to `sha` and returns its record; otherwise returns `null` and writes nothing. Returns `null` also when the stored
43
+ * record is for another package or bucket, or did not pass. Throws for a malformed identity or a stored record that is
44
+ * not JSON.
45
+ */
24
46
  export async function reuseSource(store, args) {
25
47
  const source = sourceId(args.packageDir, args.bucket);
26
48
  const contentPrefix = sourceContentPrefix(args.identity, source);
@@ -36,6 +58,12 @@ export async function reuseSource(store, args) {
36
58
  await attachSource(store, { sha: args.sha, record, contentPrefix });
37
59
  return record;
38
60
  }
61
+ /**
62
+ * Reads a commit's record of one source and where its test files live, or `null` when the commit has no such source.
63
+ * A reference written by `attachSource` yields its content prefix; a plain record yields
64
+ * `commits/<sha>/tests/<source>/`. Throws `Invalid report content reference` when a reference's prefix does not match
65
+ * the expected pattern or names another source.
66
+ */
39
67
  export async function readStoredSource(store, sha, source) {
40
68
  store = await reportStore(store, sha);
41
69
  const text = await readUtf8(store, sourceKey(sha, source));
@@ -55,6 +83,7 @@ export async function readStoredSource(store, sha, source) {
55
83
  contentPrefix: `${commitPrefix(sha)}tests/${source}/`,
56
84
  };
57
85
  }
86
+ /** The key prefix of one test's files, `<content prefix><testId>/`, or `null` when the source is not in the commit. Throws `Invalid report test` when `testId` is not a safe key segment. */
58
87
  export async function sourceTestPrefix(store, args) {
59
88
  if (!isKeySegment(args.testId))
60
89
  throw new Error('Invalid report test');
@@ -1,4 +1,17 @@
1
+ /**
2
+ * @module Source file entries
3
+ *
4
+ * Streams the files of a recorded source (each test's timeline and its captured frame files) as bundle entries ready to
5
+ * be packed into blobs. Entries come in a fixed order (tests by id, files by name) so the same source always packs into
6
+ * the same blobs.
7
+ */
1
8
  import type { HtmlReportTest } from './recording-bundle.js';
2
9
  import type { BundleEntry } from './bundle.js';
3
10
  import type { PublishSourceArgs } from './publish.js';
11
+ /**
12
+ * Yields, for each test of `tests` in `testId` order, its `report.json` and then each file the report names (sorted by
13
+ * name), keyed by the commit-relative layout `commits/<sha>/tests/<source>/<testId>/...`. Frame files are read from
14
+ * `args.local` under `runId` as the generator is consumed, one at a time. Throws `Missing report snapshot: <file>` when
15
+ * a named file is absent locally, and a `not a ...` error for an unsafe source, test id or file name.
16
+ */
4
17
  export declare function sourceEntries(args: PublishSourceArgs, source: string, tests: readonly HtmlReportTest[], runId: string): AsyncGenerator<BundleEntry>;
@@ -2,6 +2,12 @@ import { testFrameKey } from './recording-keys.js';
2
2
  import { capturedFiles } from './frames.js';
3
3
  import { remoteTestFileKey, remoteTestReportKey } from './keys.js';
4
4
  const JSON_TYPE = 'application/json; charset=utf-8';
5
+ /**
6
+ * Yields, for each test of `tests` in `testId` order, its `report.json` and then each file the report names (sorted by
7
+ * name), keyed by the commit-relative layout `commits/<sha>/tests/<source>/<testId>/...`. Frame files are read from
8
+ * `args.local` under `runId` as the generator is consumed, one at a time. Throws `Missing report snapshot: <file>` when
9
+ * a named file is absent locally, and a `not a ...` error for an unsafe source, test id or file name.
10
+ */
5
11
  export async function* sourceEntries(args, source, tests, runId) {
6
12
  for (const { report } of [...tests].sort((a, b) => a.report.meta.testId < b.report.meta.testId ? -1 : 1)) {
7
13
  const { testId } = report.meta;
@@ -1,4 +1,19 @@
1
+ /**
2
+ * @module Report parts of a recorded source
3
+ *
4
+ * Turns a recorded source (a package bucket's tests with their outcomes) into the report parts (checks grouped by kind)
5
+ * that become a layer's `parts`. Fail-closed: a source that failed as a whole always carries at least one failed check,
6
+ * so recorded passes cannot hide a producer that did not complete.
7
+ */
1
8
  import type { ReportPart } from './explorer-types.js';
2
9
  import type { SourceRecord } from './protocol.js';
10
+ /**
11
+ * The main part of a source: its own evidence part (the first one that is not `performance`) re-keyed to the source,
12
+ * or else one check per test. Check ids become `<source>/<testId>` so two packages can name a test alike. The part's
13
+ * `kind` is `ui-quality` for the `ui-quality` bucket and `behavior` otherwise (unless its evidence says otherwise); a
14
+ * skipped test is `not-run`. When the source failed but no check did, a failing `<source>:producer-failure` check is
15
+ * added.
16
+ */
3
17
  export declare function sourcePart(sha: string, source: SourceRecord): ReportPart;
18
+ /** The parts of a source: `sourcePart`, then each `performance` evidence part as its own part with id `<source>--performance`, re-keyed to the source's time and check ids. */
4
19
  export declare function sourceParts(sha: string, source: SourceRecord): readonly ReportPart[];
@@ -1,4 +1,11 @@
1
1
  import { viewerTestId } from './keys.js';
2
+ /**
3
+ * The main part of a source: its own evidence part (the first one that is not `performance`) re-keyed to the source,
4
+ * or else one check per test. Check ids become `<source>/<testId>` so two packages can name a test alike. The part's
5
+ * `kind` is `ui-quality` for the `ui-quality` bucket and `behavior` otherwise (unless its evidence says otherwise); a
6
+ * skipped test is `not-run`. When the source failed but no check did, a failing `<source>:producer-failure` check is
7
+ * added.
8
+ */
2
9
  export function sourcePart(sha, source) {
3
10
  const kind = source.bucket === 'ui-quality' ? 'ui-quality' : 'behavior';
4
11
  const primary = source.evidence?.find((part) => part.kind !== 'performance');
@@ -47,6 +54,7 @@ function withProducerFailure(part, source) {
47
54
  ],
48
55
  };
49
56
  }
57
+ /** The parts of a source: `sourcePart`, then each `performance` evidence part as its own part with id `<source>--performance`, re-keyed to the source's time and check ids. */
50
58
  export function sourceParts(sha, source) {
51
59
  return [
52
60
  sourcePart(sha, source),
@@ -1,5 +1,16 @@
1
+ /**
2
+ * @module Reading a source's files
3
+ *
4
+ * Chooses how to read the files of one source of a commit: through its bundle index when it has one, otherwise as
5
+ * individual objects (stores written before bundles existed stay readable).
6
+ */
1
7
  import type { ObjectStore } from './object-store.js';
2
8
  import type { BundleEntry } from './bundle.js';
9
+ /**
10
+ * Returns a function that reads one file of a source by key, resolving to its bytes and media type or `null` when it is
11
+ * absent. Reads through the bundle index at `commits/<sha>/bundles/<source>.json` when that has entries, otherwise reads
12
+ * the key directly from `store`. Throws when the index is present but not JSON.
13
+ */
3
14
  export declare function sourceReader(store: ObjectStore, args: {
4
15
  sha: string;
5
16
  source: string;
@@ -1,4 +1,9 @@
1
1
  import { bundleIndexKey, createBundleReader, readBundleIndex } from './bundle.js';
2
+ /**
3
+ * Returns a function that reads one file of a source by key, resolving to its bytes and media type or `null` when it is
4
+ * absent. Reads through the bundle index at `commits/<sha>/bundles/<source>.json` when that has entries, otherwise reads
5
+ * the key directly from `store`. Throws when the index is present but not JSON.
6
+ */
2
7
  export async function sourceReader(store, args) {
3
8
  const index = await readBundleIndex(store, bundleIndexKey(args.sha, args.source));
4
9
  if (Object.keys(index).length > 0)
@@ -1,6 +1,16 @@
1
+ /**
2
+ * @module Matching recorded tests to specs
3
+ *
4
+ * Small lookups from a spec to the recorded tests that ran it, by the source file each test reports. Pure functions
5
+ * over `HtmlReportTest`.
6
+ */
1
7
  import type { HtmlReportTest } from './recording-bundle.js';
2
8
  import type { SpecEntry } from './explorer-types.js';
3
- /** Resolve original spec paths, including runner absolute paths, without matching another document. */
9
+ /** The tests whose reported `sourceFile` is the spec's path or ends with `/<path>` (so a runner's absolute path matches), after turning backslashes into `/`. A longer name that merely ends with the same letters does not match. */
4
10
  export declare function testsForSpec(entry: SpecEntry, tests: readonly HtmlReportTest[]): readonly HtmlReportTest[];
5
- /** Build once for the inventory; large suites never scan all tests per row. */
11
+ /**
12
+ * Counts recorded tests per spec path in one pass, so large suites never scan all tests per row. A test's path is its
13
+ * `sourceFile` (with backslashes turned into `/`) from the first occurrence of `specs/`, wherever that occurs, or the whole
14
+ * path when there is none; tests with no `sourceFile` are not counted.
15
+ */
6
16
  export declare function specEvidenceCounts(tests: readonly HtmlReportTest[]): ReadonlyMap<string, number>;
@@ -1,11 +1,15 @@
1
- /** Resolve original spec paths, including runner absolute paths, without matching another document. */
1
+ /** The tests whose reported `sourceFile` is the spec's path or ends with `/<path>` (so a runner's absolute path matches), after turning backslashes into `/`. A longer name that merely ends with the same letters does not match. */
2
2
  export function testsForSpec(entry, tests) {
3
3
  return tests.filter((test) => {
4
4
  const path = test.report.meta.sourceFile?.replaceAll('\\', '/') ?? '';
5
5
  return path === entry.path || path.endsWith(`/${entry.path}`);
6
6
  });
7
7
  }
8
- /** Build once for the inventory; large suites never scan all tests per row. */
8
+ /**
9
+ * Counts recorded tests per spec path in one pass, so large suites never scan all tests per row. A test's path is its
10
+ * `sourceFile` (with backslashes turned into `/`) from the first occurrence of `specs/`, wherever that occurs, or the whole
11
+ * path when there is none; tests with no `sourceFile` are not counted.
12
+ */
9
13
  export function specEvidenceCounts(tests) {
10
14
  const counts = new Map();
11
15
  for (const test of tests) {
@@ -1,8 +1,20 @@
1
1
  import type { ObjectStore } from './object-store.js';
2
2
  import type { SpecEntry, SpecInventory } from './explorer-types.js';
3
+ /** Whether `path` is a safe spec path: starts with `specs/` and no segment is empty, `.`, `..`, or contains a backslash or control character. */
3
4
  export declare function isSpecPath(path: string): boolean;
5
+ /**
6
+ * Describes one spec document: its `path`, a text `fingerprint`, a `kind` (the file extension, `openslo` for
7
+ * `*.openslo.yaml`, `document` when there is none), a `title` (the first `Feature:` line, else the first `# ` heading,
8
+ * else the file name) and a `domain` (the folder right under `specs/`, or `Overview` for a file directly in it).
9
+ * Throws `Invalid spec path` unless `isSpecPath(path)`.
10
+ */
4
11
  export declare function specEntry(path: string, content: string): SpecEntry;
5
- /** Publish document bodies first; an inventory always names readable content. */
12
+ /**
13
+ * Publishes a commit's spec documents and its inventory, and returns the inventory (entries sorted by path).
14
+ * Publish document bodies first; an inventory always names readable content. Throws `Duplicate spec` for a repeated
15
+ * path (before writing anything), `Invalid spec path` or `Invalid commit` for malformed input. Publishing the same
16
+ * documents again writes the same keys.
17
+ */
6
18
  export declare function publishInventory(store: ObjectStore, args: {
7
19
  readonly sha: string;
8
20
  readonly documents: readonly {
@@ -10,5 +22,7 @@ export declare function publishInventory(store: ObjectStore, args: {
10
22
  readonly content: string;
11
23
  }[];
12
24
  }): Promise<SpecInventory>;
25
+ /** Reads a commit's inventory, or `null` when it has none. Throws `Invalid spec inventory` when the stored one is for another sha, malformed, or names an unsafe path, and `Invalid commit` for a bad sha. */
13
26
  export declare function readInventory(store: ObjectStore, sha: string): Promise<SpecInventory | null>;
27
+ /** Reads one spec document's text, or `null` when the commit has no inventory or the inventory does not list `path`. Throws `Invalid spec path` for an unsafe path. */
14
28
  export declare function readSpecDocument(store: ObjectStore, sha: string, path: string): Promise<string | null>;
@@ -1,8 +1,16 @@
1
+ /**
2
+ * @module Spec inventory of a commit
3
+ *
4
+ * The older, commit-keyed record of which spec documents a commit has: `commits/<sha>/inventory.json` (the entries) and
5
+ * `commits/<sha>/specs/<path>` (their bodies). Document bodies are written first, so an inventory always names readable
6
+ * content. The newer form is the spec set (`spec-set.ts`).
7
+ */
1
8
  import { textFingerprint } from './layer-merge.js';
2
9
  import { readUtf8, writeUtf8 } from './object-io.js';
3
10
  import { eachLimited } from './completion-order.js';
4
11
  import { recordExplorerVersion } from './explorer-versions.js';
5
12
  import { isCommitSha } from './keys.js';
13
+ /** Whether `path` is a safe spec path: starts with `specs/` and no segment is empty, `.`, `..`, or contains a backslash or control character. */
6
14
  export function isSpecPath(path) {
7
15
  return (path.startsWith('specs/') &&
8
16
  path
@@ -12,6 +20,12 @@ export function isSpecPath(path) {
12
20
  part !== '..' &&
13
21
  !/[\\\u0000-\u001f]/.test(part)));
14
22
  }
23
+ /**
24
+ * Describes one spec document: its `path`, a text `fingerprint`, a `kind` (the file extension, `openslo` for
25
+ * `*.openslo.yaml`, `document` when there is none), a `title` (the first `Feature:` line, else the first `# ` heading,
26
+ * else the file name) and a `domain` (the folder right under `specs/`, or `Overview` for a file directly in it).
27
+ * Throws `Invalid spec path` unless `isSpecPath(path)`.
28
+ */
15
29
  export function specEntry(path, content) {
16
30
  if (!isSpecPath(path))
17
31
  throw new Error('Invalid spec path');
@@ -41,7 +55,12 @@ function documentKey(sha, path) {
41
55
  inventoryKey(sha);
42
56
  return `commits/${sha}/specs/${encodeURIComponent(path)}`;
43
57
  }
44
- /** Publish document bodies first; an inventory always names readable content. */
58
+ /**
59
+ * Publishes a commit's spec documents and its inventory, and returns the inventory (entries sorted by path).
60
+ * Publish document bodies first; an inventory always names readable content. Throws `Duplicate spec` for a repeated
61
+ * path (before writing anything), `Invalid spec path` or `Invalid commit` for malformed input. Publishing the same
62
+ * documents again writes the same keys.
63
+ */
45
64
  export async function publishInventory(store, args) {
46
65
  const entries = args.documents
47
66
  .map((doc) => specEntry(doc.path, doc.content))
@@ -56,6 +75,7 @@ export async function publishInventory(store, args) {
56
75
  await recordExplorerVersion(store, args.sha);
57
76
  return inventory;
58
77
  }
78
+ /** Reads a commit's inventory, or `null` when it has none. Throws `Invalid spec inventory` when the stored one is for another sha, malformed, or names an unsafe path, and `Invalid commit` for a bad sha. */
59
79
  export async function readInventory(store, sha) {
60
80
  const raw = await readUtf8(store, inventoryKey(sha));
61
81
  if (raw === null)
@@ -67,6 +87,7 @@ export async function readInventory(store, sha) {
67
87
  throw new Error('Invalid spec inventory');
68
88
  return inventory;
69
89
  }
90
+ /** Reads one spec document's text, or `null` when the commit has no inventory or the inventory does not list `path`. Throws `Invalid spec path` for an unsafe path. */
70
91
  export async function readSpecDocument(store, sha, path) {
71
92
  const inventory = await readInventory(store, sha);
72
93
  if (!inventory?.entries.some((entry) => entry.path === path))
@@ -0,0 +1,28 @@
1
+ import type { EvidenceCheck, SpecEntry } from './explorer-types.js';
2
+ import type { IngestedChecks } from './ingest.js';
3
+ /** Where specs are and how to resolve them for `linkChecksToSpecs`. */
4
+ export type SpecLinks = {
5
+ /** Every file under this directory is a spec named `specs/<its path>`. Relative to `base`. */
6
+ readonly specsDirectory?: string;
7
+ /** Every check in the report belongs to this spec. A path relative to `base`. */
8
+ readonly spec?: string;
9
+ /** The directory that relative paths (`specsDirectory`, `spec`) resolve against, usually the repository root. */
10
+ readonly base: string;
11
+ };
12
+ /**
13
+ * A test report rarely says which spec a test checks. Say so here, in the order of how sure it is:
14
+ *
15
+ * 1. `spec`: the whole report is one spec's checks (a report per feature file).
16
+ * 2. A path the report gives that is one of the specs.
17
+ * 3. A name the report gives for the suite, class or file that is a spec's title or file name
18
+ * (Cucumber writes the feature's title as the suite), or the first part of a check's title before
19
+ * ` > `.
20
+ *
21
+ * What cannot be linked stays unlinked, and is reported under no spec rather than a wrong one.
22
+ * Returns the checks and the specs they now belong to, with fingerprints, so a later reader can warn
23
+ * when a spec changed after the layer was recorded.
24
+ */
25
+ export declare function linkChecksToSpecs(ingested: IngestedChecks, links: SpecLinks): Promise<{
26
+ readonly checks: readonly EvidenceCheck[];
27
+ readonly specs: readonly SpecEntry[];
28
+ }>;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @module Linking checks to specs
3
+ *
4
+ * Decides which spec each imported check belongs to, so results from a JUnit or CTRF file land under the right spec in
5
+ * the report. It reads only the specs directory and the one spec file the caller names explicitly; a report is data and
6
+ * does not choose which files Atlas opens. Uses Node's `fs`.
7
+ */
8
+ import { readFile } from 'node:fs/promises';
9
+ import path from 'node:path';
10
+ import { loadDirectoryOutline, specKindOf } from './directory-outline.js';
11
+ import { specPath, textFingerprint } from './layer-merge.js';
12
+ const words = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
13
+ /** A file's name without the extension of a spec: `user.profile.feature` is `user.profile`, `cart.openslo.yaml` is `cart`. */
14
+ const stem = (file) => path.posix.basename(file).replace(/(\.openslo|\.machine)?\.(feature|qnt|dmn|als|ya?ml|json|scxml|md|txt|xml)$/i, '');
15
+ /**
16
+ * A test report rarely says which spec a test checks. Say so here, in the order of how sure it is:
17
+ *
18
+ * 1. `spec`: the whole report is one spec's checks (a report per feature file).
19
+ * 2. A path the report gives that is one of the specs.
20
+ * 3. A name the report gives for the suite, class or file that is a spec's title or file name
21
+ * (Cucumber writes the feature's title as the suite), or the first part of a check's title before
22
+ * ` > `.
23
+ *
24
+ * What cannot be linked stays unlinked, and is reported under no spec rather than a wrong one.
25
+ * Returns the checks and the specs they now belong to, with fingerprints, so a later reader can warn
26
+ * when a spec changed after the layer was recorded.
27
+ */
28
+ export async function linkChecksToSpecs(ingested, links) {
29
+ const outline = links.specsDirectory ? await loadDirectoryOutline(path.resolve(links.base, links.specsDirectory), { repoRoot: links.base }) : null;
30
+ const known = new Map((outline?.specs ?? []).map(entry => [entry.path, entry]));
31
+ const byName = new Map();
32
+ for (const entry of [...known.values()].sort((a, b) => (a.path < b.path ? -1 : 1))) {
33
+ for (const name of [words(entry.title), words(stem(entry.path))])
34
+ if (name && !byName.has(name))
35
+ byName.set(name, entry.path);
36
+ }
37
+ let explicit = null;
38
+ if (links.spec)
39
+ explicit = specPath(links.spec);
40
+ const checks = ingested.checks.map((check, index) => {
41
+ if (explicit)
42
+ return { ...check, specPath: explicit };
43
+ if (check.specPath && known.has(specPath(check.specPath)))
44
+ return { ...check, specPath: specPath(check.specPath) };
45
+ const first = check.title.split(/ [>›] /)[0] ?? '';
46
+ for (const hint of [...(ingested.hints?.[index] ?? []), first]) {
47
+ const found = byName.get(words(hint)) ?? byName.get(words(stem(hint)));
48
+ if (found)
49
+ return { ...check, specPath: found };
50
+ }
51
+ return check;
52
+ });
53
+ const used = [...new Set(checks.flatMap(check => (check.specPath ? [check.specPath] : [])))].sort();
54
+ const specs = [];
55
+ for (const used_ of used) {
56
+ const entry = known.get(used_);
57
+ if (entry) {
58
+ specs.push(entry);
59
+ continue;
60
+ }
61
+ // An explicit spec outside a specs directory: read it where it is, if it is there. Nothing else
62
+ // a report names is read: a report is data, and does not choose which files Atlas opens.
63
+ if (used_ !== explicit || !links.spec)
64
+ continue;
65
+ const text = await readFile(path.resolve(links.base, links.spec), 'utf8').catch(() => null);
66
+ if (text !== null)
67
+ specs.push({ path: used_, domain: path.basename(links.base) || 'product', kind: specKindOf(used_), title: path.posix.basename(used_), fingerprint: textFingerprint(text) });
68
+ }
69
+ return { checks, specs };
70
+ }