@crvouga/atlas 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (606) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +649 -2
  4. package/dist/bun/src/driver.d.ts +67 -0
  5. package/dist/bun/src/driver.js +90 -0
  6. package/dist/bun/src/index.d.ts +2 -0
  7. package/dist/bun/src/index.js +2 -0
  8. package/dist/core/src/cli.d.ts +1 -0
  9. package/dist/core/src/cli.js +177 -0
  10. package/dist/core/src/config.d.ts +477 -0
  11. package/dist/core/src/config.js +100 -0
  12. package/dist/core/src/events/cloudevents.d.ts +56 -0
  13. package/dist/core/src/events/cloudevents.js +152 -0
  14. package/dist/core/src/graph.d.ts +113 -0
  15. package/dist/core/src/graph.js +221 -0
  16. package/dist/core/src/index.d.ts +17 -0
  17. package/dist/core/src/index.js +17 -0
  18. package/dist/core/src/lint.d.ts +53 -0
  19. package/dist/core/src/lint.js +138 -0
  20. package/dist/core/src/plan.d.ts +109 -0
  21. package/dist/core/src/plan.js +324 -0
  22. package/dist/core/src/privacy.d.ts +8 -0
  23. package/dist/core/src/privacy.js +42 -0
  24. package/dist/core/src/report/formats.d.ts +69 -0
  25. package/dist/core/src/report/formats.js +109 -0
  26. package/dist/core/src/report/markdown.d.ts +8 -0
  27. package/dist/core/src/report/markdown.js +140 -0
  28. package/dist/core/src/run/clients.d.ts +42 -0
  29. package/dist/core/src/run/clients.js +250 -0
  30. package/dist/core/src/run/execute.d.ts +854 -0
  31. package/dist/core/src/run/execute.js +877 -0
  32. package/dist/core/src/run/frames.d.ts +16 -0
  33. package/dist/core/src/run/frames.js +52 -0
  34. package/dist/core/src/run/http.d.ts +41 -0
  35. package/dist/core/src/run/http.js +71 -0
  36. package/dist/core/src/run/types.d.ts +248 -0
  37. package/dist/core/src/run/types.js +39 -0
  38. package/dist/core/src/signals.d.ts +110 -0
  39. package/dist/core/src/signals.js +121 -0
  40. package/dist/core/src/spec/compose.d.ts +28 -0
  41. package/dist/core/src/spec/compose.js +71 -0
  42. package/dist/core/src/spec/index.d.ts +5 -0
  43. package/dist/core/src/spec/index.js +5 -0
  44. package/dist/core/src/spec/load.d.ts +8 -0
  45. package/dist/core/src/spec/load.js +46 -0
  46. package/dist/core/src/spec/parse.d.ts +5 -0
  47. package/dist/core/src/spec/parse.js +32 -0
  48. package/dist/core/src/spec/scxml.d.ts +19 -0
  49. package/dist/core/src/spec/scxml.js +239 -0
  50. package/dist/core/src/spec/types.d.ts +96 -0
  51. package/dist/core/src/spec/types.js +25 -0
  52. package/dist/detox/src/index.d.ts +75 -0
  53. package/dist/detox/src/index.js +110 -0
  54. package/dist/playwright/src/driver.d.ts +44 -0
  55. package/dist/playwright/src/driver.js +82 -0
  56. package/dist/playwright/src/index.d.ts +3 -0
  57. package/dist/playwright/src/index.js +3 -0
  58. package/dist/playwright/src/media.d.ts +68 -0
  59. package/dist/playwright/src/media.js +283 -0
  60. package/dist/playwright/src/signals.d.ts +34 -0
  61. package/dist/playwright/src/signals.js +56 -0
  62. package/dist/reports/src/browser-navigation.d.ts +2 -0
  63. package/dist/reports/src/browser-navigation.js +28 -0
  64. package/dist/reports/src/bundle.d.ts +24 -0
  65. package/dist/reports/src/bundle.js +114 -0
  66. package/dist/reports/src/catalog.d.ts +37 -0
  67. package/dist/reports/src/catalog.js +127 -0
  68. package/dist/reports/src/clock.d.ts +14 -0
  69. package/dist/reports/src/clock.js +4 -0
  70. package/dist/reports/src/completion-order.d.ts +10 -0
  71. package/dist/reports/src/completion-order.js +76 -0
  72. package/dist/reports/src/evidence-live.d.ts +40 -0
  73. package/dist/reports/src/evidence-live.js +118 -0
  74. package/dist/reports/src/evidence-types.d.ts +90 -0
  75. package/dist/reports/src/evidence-types.js +15 -0
  76. package/dist/reports/src/evidence-view.d.ts +68 -0
  77. package/dist/reports/src/evidence-view.js +113 -0
  78. package/dist/reports/src/explorer-types.d.ts +56 -0
  79. package/dist/reports/src/explorer-types.js +0 -0
  80. package/dist/reports/src/explorer-versions.d.ts +4 -0
  81. package/dist/reports/src/explorer-versions.js +21 -0
  82. package/dist/reports/src/export-evidence.d.ts +11 -0
  83. package/dist/reports/src/export-evidence.js +94 -0
  84. package/dist/reports/src/fetcher.d.ts +50 -0
  85. package/dist/reports/src/fetcher.js +205 -0
  86. package/dist/reports/src/format-date.d.ts +1 -0
  87. package/dist/reports/src/format-date.js +10 -0
  88. package/dist/reports/src/format.d.ts +16 -0
  89. package/dist/reports/src/format.js +71 -0
  90. package/dist/reports/src/frames.d.ts +46 -0
  91. package/dist/reports/src/frames.js +245 -0
  92. package/dist/reports/src/hash.d.ts +17 -0
  93. package/dist/reports/src/hash.js +43 -0
  94. package/dist/reports/src/html-frame.d.ts +13 -0
  95. package/dist/reports/src/html-frame.js +63 -0
  96. package/dist/reports/src/http.d.ts +3 -0
  97. package/dist/reports/src/http.js +0 -0
  98. package/dist/reports/src/junit.d.ts +23 -0
  99. package/dist/reports/src/junit.js +119 -0
  100. package/dist/reports/src/keyboard.d.ts +16 -0
  101. package/dist/reports/src/keyboard.js +135 -0
  102. package/dist/reports/src/keys.d.ts +53 -0
  103. package/dist/reports/src/keys.js +122 -0
  104. package/dist/reports/src/layer-catalog.d.ts +5 -0
  105. package/dist/reports/src/layer-catalog.js +39 -0
  106. package/dist/reports/src/layer-merge.d.ts +43 -0
  107. package/dist/reports/src/layer-merge.js +154 -0
  108. package/dist/reports/src/layer-source.d.ts +6 -0
  109. package/dist/reports/src/layer-source.js +32 -0
  110. package/dist/reports/src/layer-store.d.ts +10 -0
  111. package/dist/reports/src/layer-store.js +36 -0
  112. package/dist/reports/src/layer-types.d.ts +32 -0
  113. package/dist/reports/src/layer-types.js +0 -0
  114. package/dist/reports/src/layers-route.d.ts +20 -0
  115. package/dist/reports/src/layers-route.js +259 -0
  116. package/dist/reports/src/namespace-guard.d.ts +11 -0
  117. package/dist/reports/src/namespace-guard.js +40 -0
  118. package/dist/reports/src/navigation.d.ts +8 -0
  119. package/dist/reports/src/navigation.js +0 -0
  120. package/dist/reports/src/object-io.d.ts +9 -0
  121. package/dist/reports/src/object-io.js +41 -0
  122. package/dist/reports/src/object-store.d.ts +67 -0
  123. package/dist/reports/src/object-store.js +32 -0
  124. package/dist/reports/src/performance-part.d.ts +9 -0
  125. package/dist/reports/src/performance-part.js +42 -0
  126. package/dist/reports/src/performance.d.ts +13 -0
  127. package/dist/reports/src/performance.js +45 -0
  128. package/dist/reports/src/persist.d.ts +10 -0
  129. package/dist/reports/src/persist.js +38 -0
  130. package/dist/reports/src/phone.d.ts +5 -0
  131. package/dist/reports/src/phone.js +18 -0
  132. package/dist/reports/src/protocol.d.ts +78 -0
  133. package/dist/reports/src/protocol.js +36 -0
  134. package/dist/reports/src/publication.d.ts +4 -0
  135. package/dist/reports/src/publication.js +35 -0
  136. package/dist/reports/src/publish.d.ts +52 -0
  137. package/dist/reports/src/publish.js +189 -0
  138. package/dist/reports/src/read.d.ts +62 -0
  139. package/dist/reports/src/read.js +214 -0
  140. package/dist/reports/src/recording-bundle.d.ts +22 -0
  141. package/dist/reports/src/recording-bundle.js +4 -0
  142. package/dist/reports/src/recording-keys.d.ts +9 -0
  143. package/dist/reports/src/recording-keys.js +50 -0
  144. package/dist/reports/src/remote-report.d.ts +39 -0
  145. package/dist/reports/src/remote-report.js +304 -0
  146. package/dist/reports/src/report-loader.d.ts +22 -0
  147. package/dist/reports/src/report-loader.js +0 -0
  148. package/dist/reports/src/report-parts.d.ts +10 -0
  149. package/dist/reports/src/report-parts.js +112 -0
  150. package/dist/reports/src/report-types.d.ts +194 -0
  151. package/dist/reports/src/report-types.js +29 -0
  152. package/dist/reports/src/reports-explorer-route.d.ts +90 -0
  153. package/dist/reports/src/reports-explorer-route.js +65 -0
  154. package/dist/reports/src/reports-list-route.d.ts +23 -0
  155. package/dist/reports/src/reports-list-route.js +55 -0
  156. package/dist/reports/src/reports-route.d.ts +267 -0
  157. package/dist/reports/src/reports-route.js +204 -0
  158. package/dist/reports/src/reports-store.d.ts +11 -0
  159. package/dist/reports/src/reports-store.js +28 -0
  160. package/dist/reports/src/resilient-cache.d.ts +3 -0
  161. package/dist/reports/src/resilient-cache.js +33 -0
  162. package/dist/reports/src/retention.d.ts +9 -0
  163. package/dist/reports/src/retention.js +16 -0
  164. package/dist/reports/src/scenario.d.ts +15 -0
  165. package/dist/reports/src/scenario.js +35 -0
  166. package/dist/reports/src/snapshot-bundle.d.ts +18 -0
  167. package/dist/reports/src/snapshot-bundle.js +94 -0
  168. package/dist/reports/src/snapshot-surface.d.ts +29 -0
  169. package/dist/reports/src/snapshot-surface.js +339 -0
  170. package/dist/reports/src/source-content.d.ts +30 -0
  171. package/dist/reports/src/source-content.js +63 -0
  172. package/dist/reports/src/source-entries.d.ts +4 -0
  173. package/dist/reports/src/source-entries.js +34 -0
  174. package/dist/reports/src/source-part.d.ts +4 -0
  175. package/dist/reports/src/source-part.js +66 -0
  176. package/dist/reports/src/source-reader.d.ts +6 -0
  177. package/dist/reports/src/source-reader.js +17 -0
  178. package/dist/reports/src/spec-evidence.d.ts +6 -0
  179. package/dist/reports/src/spec-evidence.js +20 -0
  180. package/dist/reports/src/spec-inventory.d.ts +14 -0
  181. package/dist/reports/src/spec-inventory.js +75 -0
  182. package/dist/reports/src/status.d.ts +4 -0
  183. package/dist/reports/src/status.js +8 -0
  184. package/dist/reports/src/store-prefix.d.ts +21 -0
  185. package/dist/reports/src/store-prefix.js +53 -0
  186. package/dist/reports/src/test-catalog-entity.d.ts +3 -0
  187. package/dist/reports/src/test-catalog-entity.js +34 -0
  188. package/dist/reports/src/types.d.ts +5 -0
  189. package/dist/reports/src/types.js +0 -0
  190. package/dist/reports/src/use-report-nav.d.ts +25 -0
  191. package/dist/reports/src/use-report-nav.js +181 -0
  192. package/dist/schema/src/common.d.ts +57 -0
  193. package/dist/schema/src/common.js +32 -0
  194. package/dist/schema/src/index.d.ts +5 -0
  195. package/dist/schema/src/index.js +5 -0
  196. package/dist/schema/src/manifest.d.ts +457 -0
  197. package/dist/schema/src/manifest.js +247 -0
  198. package/dist/schema/src/report-sources.d.ts +68 -0
  199. package/dist/schema/src/report-sources.js +53 -0
  200. package/dist/schema/src/runs-index.d.ts +52 -0
  201. package/dist/schema/src/runs-index.js +24 -0
  202. package/dist/schema/src/spec.d.ts +245 -0
  203. package/dist/schema/src/spec.js +133 -0
  204. package/dist/vitest/src/index.d.ts +22 -0
  205. package/dist/vitest/src/index.js +33 -0
  206. package/dist/webdriver/src/driver.d.ts +69 -0
  207. package/dist/webdriver/src/driver.js +230 -0
  208. package/dist/webdriver/src/index.d.ts +11 -0
  209. package/dist/webdriver/src/index.js +10 -0
  210. package/dist/webdriver/src/protocol.d.ts +60 -0
  211. package/dist/webdriver/src/protocol.js +146 -0
  212. package/package.json +181 -4
  213. package/packages/bun/README.md +69 -0
  214. package/packages/core/README.md +78 -0
  215. package/packages/core/bin/atlas.mjs +8 -0
  216. package/packages/detox/README.md +73 -0
  217. package/packages/playwright/README.md +77 -0
  218. package/packages/reports/README.md +47 -0
  219. package/packages/reports/STORAGE.md +73 -0
  220. package/packages/schema/README.md +23 -0
  221. package/packages/schema/schemas/atlas-change.schema.json +34 -0
  222. package/packages/schema/schemas/atlas.schema.json +32 -0
  223. package/packages/schema/schemas/cloud-event.schema.json +40 -0
  224. package/packages/schema/schemas/journeys.schema.json +17 -0
  225. package/packages/schema/schemas/manifest.schema.json +160 -0
  226. package/packages/schema/schemas/path-record.schema.json +151 -0
  227. package/packages/schema/schemas/report-sources.schema.json +95 -0
  228. package/packages/schema/schemas/run-info.schema.json +176 -0
  229. package/packages/schema/schemas/run-summary.schema.json +206 -0
  230. package/packages/schema/schemas/runs-index.schema.json +23 -0
  231. package/packages/schema/schemas/state-node.schema.json +238 -0
  232. package/packages/schema/schemas/state-record.schema.json +354 -0
  233. package/packages/schema/schemas/timeline-entry.schema.json +124 -0
  234. package/packages/schema/schemas/transition-record.schema.json +125 -0
  235. package/packages/visualizer/README.md +154 -0
  236. package/packages/visualizer/bin/atlas-visualizer.mjs +176 -0
  237. package/packages/visualizer/example/specs/coffee/ordering/checkout.scxml +82 -0
  238. package/packages/visualizer/example/specs/coffee/ordering/journeys.yaml +129 -0
  239. package/packages/visualizer/example/specs/coffee/ordering/known-issues.md +14 -0
  240. package/packages/visualizer/example/specs/coffee/ordering/machine.yaml +158 -0
  241. package/packages/visualizer/example/specs/coffee/ordering/open-questions.md +25 -0
  242. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/chart.mmd +67 -0
  243. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/ctrf.json +245 -0
  244. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/junit.xml +12 -0
  245. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/manifest.json +2003 -0
  246. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/001.vtt +9 -0
  247. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/002.vtt +9 -0
  248. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/003.vtt +9 -0
  249. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/004.vtt +9 -0
  250. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/005.vtt +17 -0
  251. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/006.vtt +9 -0
  252. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/007.vtt +17 -0
  253. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/008.vtt +17 -0
  254. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/009.vtt +13 -0
  255. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/010.vtt +9 -0
  256. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/011.vtt +9 -0
  257. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/012.vtt +9 -0
  258. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/013.vtt +9 -0
  259. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/01.mp4 +0 -0
  260. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/01.poster.webp +0 -0
  261. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/02.mp4 +0 -0
  262. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/02.poster.webp +0 -0
  263. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/03.mp4 +0 -0
  264. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/03.poster.webp +0 -0
  265. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/04.mp4 +0 -0
  266. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/04.poster.webp +0 -0
  267. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/01.png +0 -0
  268. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/01.webp +0 -0
  269. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/02.png +0 -0
  270. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/02.webp +0 -0
  271. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/03.png +0 -0
  272. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/03.webp +0 -0
  273. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/04.png +0 -0
  274. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/04.webp +0 -0
  275. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/05.png +0 -0
  276. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/05.webp +0 -0
  277. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-2/01.png +0 -0
  278. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-2/01.webp +0 -0
  279. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-3/failed-01.png +0 -0
  280. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-3/failed-01.webp +0 -0
  281. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-4/01.png +0 -0
  282. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-4/01.webp +0 -0
  283. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-5/01.png +0 -0
  284. package/packages/visualizer/fixtures/failures/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-5/01.webp +0 -0
  285. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/checkout.scxml +82 -0
  286. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/journeys.yaml +129 -0
  287. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/known-issues.md +14 -0
  288. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/machine.yaml +158 -0
  289. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/open-questions.md +25 -0
  290. package/packages/visualizer/fixtures/full/runs/2026-10-02T09-10-00-000Z-fast/chart.mmd +67 -0
  291. package/packages/visualizer/fixtures/full/runs/2026-10-02T09-10-00-000Z-fast/ctrf.json +247 -0
  292. package/packages/visualizer/fixtures/full/runs/2026-10-02T09-10-00-000Z-fast/junit.xml +12 -0
  293. package/packages/visualizer/fixtures/full/runs/2026-10-02T09-10-00-000Z-fast/manifest.json +1875 -0
  294. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/chart.mmd +67 -0
  295. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/ctrf.json +244 -0
  296. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/junit.xml +12 -0
  297. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/manifest.json +1977 -0
  298. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/captions/001.vtt +9 -0
  299. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/captions/002.vtt +9 -0
  300. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/captions/003.vtt +9 -0
  301. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/captions/004.vtt +9 -0
  302. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/captions/005.vtt +17 -0
  303. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/captions/006.vtt +9 -0
  304. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/clips/journey-1/01.mp4 +0 -0
  305. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/clips/journey-1/01.poster.webp +0 -0
  306. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/clips/journey-1/02.mp4 +0 -0
  307. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/clips/journey-1/02.poster.webp +0 -0
  308. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/paths/journey-1/01.png +0 -0
  309. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/paths/journey-1/01.webp +0 -0
  310. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/paths/journey-1/02.png +0 -0
  311. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/paths/journey-1/02.webp +0 -0
  312. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/paths/journey-1/03.png +0 -0
  313. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/paths/journey-1/03.webp +0 -0
  314. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/paths/journey-2/01.png +0 -0
  315. package/packages/visualizer/fixtures/full/runs/2026-10-04T16-20-00-000Z-showcase/media/paths/journey-2/01.webp +0 -0
  316. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/chart.mmd +67 -0
  317. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/ctrf.json +472 -0
  318. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/junit.xml +16 -0
  319. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/manifest.json +3701 -0
  320. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/001.vtt +9 -0
  321. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/002.vtt +9 -0
  322. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/003.vtt +9 -0
  323. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/004.vtt +9 -0
  324. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/005.vtt +9 -0
  325. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/006.vtt +13 -0
  326. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/007.vtt +9 -0
  327. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/008.vtt +13 -0
  328. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/009.vtt +17 -0
  329. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/010.vtt +9 -0
  330. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/011.vtt +13 -0
  331. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/012.vtt +9 -0
  332. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/013.vtt +9 -0
  333. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/014.vtt +9 -0
  334. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/015.vtt +9 -0
  335. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/016.vtt +9 -0
  336. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/017.vtt +9 -0
  337. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/018.vtt +17 -0
  338. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/019.vtt +9 -0
  339. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/020.vtt +9 -0
  340. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/021.vtt +9 -0
  341. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/022.vtt +17 -0
  342. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/023.vtt +17 -0
  343. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/024.vtt +13 -0
  344. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/025.vtt +9 -0
  345. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/026.vtt +9 -0
  346. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/027.vtt +9 -0
  347. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/028.vtt +9 -0
  348. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/01.mp4 +0 -0
  349. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/01.poster.webp +0 -0
  350. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/02.mp4 +0 -0
  351. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/02.poster.webp +0 -0
  352. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/03.mp4 +0 -0
  353. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/03.poster.webp +0 -0
  354. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/04.mp4 +0 -0
  355. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/04.poster.webp +0 -0
  356. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/01.png +0 -0
  357. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/01.webp +0 -0
  358. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/02.png +0 -0
  359. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/02.webp +0 -0
  360. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/03.png +0 -0
  361. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/03.webp +0 -0
  362. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/04.png +0 -0
  363. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/04.webp +0 -0
  364. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/05.png +0 -0
  365. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/05.webp +0 -0
  366. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/06.png +0 -0
  367. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/06.webp +0 -0
  368. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/07.png +0 -0
  369. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/07.webp +0 -0
  370. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/08.png +0 -0
  371. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/08.webp +0 -0
  372. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/09.png +0 -0
  373. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/09.webp +0 -0
  374. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/10.png +0 -0
  375. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/10.webp +0 -0
  376. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/11.png +0 -0
  377. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/11.webp +0 -0
  378. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/12.png +0 -0
  379. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/12.webp +0 -0
  380. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/13.png +0 -0
  381. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/13.webp +0 -0
  382. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/14.png +0 -0
  383. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/14.webp +0 -0
  384. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-2/01.png +0 -0
  385. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-2/01.webp +0 -0
  386. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-3/01.png +0 -0
  387. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-3/01.webp +0 -0
  388. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-4/01.png +0 -0
  389. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-4/01.webp +0 -0
  390. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-5/01.png +0 -0
  391. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-5/01.webp +0 -0
  392. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-6/01.png +0 -0
  393. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-6/01.webp +0 -0
  394. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-7/01.png +0 -0
  395. package/packages/visualizer/fixtures/full/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-7/01.webp +0 -0
  396. package/packages/visualizer/fixtures/full/specs/coffee/ordering/checkout.scxml +82 -0
  397. package/packages/visualizer/fixtures/full/specs/coffee/ordering/journeys.yaml +129 -0
  398. package/packages/visualizer/fixtures/full/specs/coffee/ordering/known-issues.md +14 -0
  399. package/packages/visualizer/fixtures/full/specs/coffee/ordering/machine.yaml +158 -0
  400. package/packages/visualizer/fixtures/full/specs/coffee/ordering/open-questions.md +25 -0
  401. package/packages/visualizer/fixtures/large/runs/2026-10-06T14-40-00-000Z-fast/ctrf.json +1280 -0
  402. package/packages/visualizer/fixtures/large/runs/2026-10-06T14-40-00-000Z-fast/junit.xml +26 -0
  403. package/packages/visualizer/fixtures/large/runs/2026-10-06T14-40-00-000Z-fast/manifest.json +28393 -0
  404. package/packages/visualizer/fixtures/large/specs/accounts/sign-up/identity-check.machine.yaml +397 -0
  405. package/packages/visualizer/fixtures/large/specs/accounts/sign-up/journeys.yaml +129 -0
  406. package/packages/visualizer/fixtures/large/specs/accounts/sign-up/machine.yaml +830 -0
  407. package/packages/visualizer/fixtures/large/specs/atlas.yaml +65 -0
  408. package/packages/visualizer/fixtures/large/specs/billing/invoices/journeys.yaml +132 -0
  409. package/packages/visualizer/fixtures/large/specs/billing/invoices/machine.yaml +830 -0
  410. package/packages/visualizer/fixtures/large/specs/billing/invoices/refund-handling.scxml +288 -0
  411. package/packages/visualizer/fixtures/large/specs/catalog/browsing/journeys.yaml +126 -0
  412. package/packages/visualizer/fixtures/large/specs/catalog/browsing/machine.yaml +831 -0
  413. package/packages/visualizer/fixtures/large/specs/catalog/browsing/product-comparison.machine.yaml +395 -0
  414. package/packages/visualizer/fixtures/large/specs/orders/checkout/journeys.yaml +120 -0
  415. package/packages/visualizer/fixtures/large/specs/orders/checkout/machine.yaml +724 -0
  416. package/packages/visualizer/fixtures/large/specs/orders/checkout/open-questions.md +16 -0
  417. package/packages/visualizer/fixtures/large/specs/orders/checkout/payment.scxml +288 -0
  418. package/packages/visualizer/fixtures/large/specs/shipping/deliveries/delivery-scheduling.machine.yaml +397 -0
  419. package/packages/visualizer/fixtures/large/specs/shipping/deliveries/journeys.yaml +132 -0
  420. package/packages/visualizer/fixtures/large/specs/shipping/deliveries/known-issues.md +13 -0
  421. package/packages/visualizer/fixtures/large/specs/shipping/deliveries/machine.yaml +830 -0
  422. package/packages/visualizer/fixtures/large/specs/support/tickets/escalation.machine.yaml +395 -0
  423. package/packages/visualizer/fixtures/large/specs/support/tickets/journeys.yaml +123 -0
  424. package/packages/visualizer/fixtures/large/specs/support/tickets/machine.yaml +720 -0
  425. package/packages/visualizer/fixtures/malformed/runs/2026-10-06T10-00-00-000Z-showcase/manifest.json +68 -0
  426. package/packages/visualizer/fixtures/malformed/runs/2026-10-06T11-00-00-000Z-showcase/ctrf.json +50 -0
  427. package/packages/visualizer/fixtures/malformed/runs/2026-10-06T11-00-00-000Z-showcase/junit.xml +6 -0
  428. package/packages/visualizer/fixtures/malformed/runs/2026-10-06T11-00-00-000Z-showcase/manifest.json +1281 -0
  429. package/packages/visualizer/fixtures/malformed/runs/2026-10-06T12-00-00-000Z-showcase/ctrf.json +50 -0
  430. package/packages/visualizer/fixtures/malformed/runs/2026-10-06T12-00-00-000Z-showcase/junit.xml +6 -0
  431. package/packages/visualizer/fixtures/malformed/runs/2026-10-06T12-00-00-000Z-showcase/manifest.json +1232 -0
  432. package/packages/visualizer/fixtures/malformed/runs/2026-10-06T13-00-00-000Z-showcase/manifest.json +1244 -0
  433. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/checkout.scxml +82 -0
  434. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/journeys.yaml +134 -0
  435. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/known-issues.md +14 -0
  436. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/loyalty.scxml +4 -0
  437. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/machine.yaml +156 -0
  438. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/open-questions.md +25 -0
  439. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/chart.mmd +67 -0
  440. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/ctrf.json +149 -0
  441. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/junit.xml +9 -0
  442. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/manifest.json +1718 -0
  443. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/001.vtt +9 -0
  444. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/002.vtt +17 -0
  445. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/003.vtt +17 -0
  446. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/004.vtt +9 -0
  447. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/005.vtt +9 -0
  448. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/01.mp4 +0 -0
  449. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/01.poster.webp +0 -0
  450. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/02.mp4 +0 -0
  451. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/02.poster.webp +0 -0
  452. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/03.mp4 +0 -0
  453. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/03.poster.webp +0 -0
  454. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/01.png +0 -0
  455. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/01.webp +0 -0
  456. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/02.png +0 -0
  457. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/02.webp +0 -0
  458. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/03.png +0 -0
  459. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/03.webp +0 -0
  460. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/05.png +0 -0
  461. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/05.webp +0 -0
  462. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-3/01.png +0 -0
  463. package/packages/visualizer/fixtures/partial/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-3/01.webp +0 -0
  464. package/packages/visualizer/fixtures/partial/runs/2026-10-06T15-05-00-000Z-showcase/media/paths/journey-1/00-start-01.png +0 -0
  465. package/packages/visualizer/fixtures/partial/runs/2026-10-06T15-05-00-000Z-showcase/media/paths/journey-1/00-start-01.webp +0 -0
  466. package/packages/visualizer/fixtures/partial/runs/2026-10-06T15-05-00-000Z-showcase/media/paths/journey-1/02.png +0 -0
  467. package/packages/visualizer/fixtures/partial/runs/2026-10-06T15-05-00-000Z-showcase/media/paths/journey-1/02.webp +0 -0
  468. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/checkout.scxml +82 -0
  469. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/journeys.yaml +129 -0
  470. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/known-issues.md +14 -0
  471. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/machine.yaml +158 -0
  472. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/open-questions.md +25 -0
  473. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/ctrf.json +79 -0
  474. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/junit.xml +7 -0
  475. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/manifest.json +1504 -0
  476. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/001.vtt +9 -0
  477. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/002.vtt +9 -0
  478. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/003.vtt +9 -0
  479. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/captions/004.vtt +17 -0
  480. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/01.mp4 +0 -0
  481. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/01.poster.webp +0 -0
  482. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/02.mp4 +0 -0
  483. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/clips/journey-1/02.poster.webp +0 -0
  484. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/01.png +0 -0
  485. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/01.webp +0 -0
  486. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/02.png +0 -0
  487. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/02.webp +0 -0
  488. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/03.png +0 -0
  489. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/03.webp +0 -0
  490. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/04.png +0 -0
  491. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-1/04.webp +0 -0
  492. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-2/01.png +0 -0
  493. package/packages/visualizer/fixtures/removed/runs/2026-10-06T14-40-00-000Z-showcase/media/paths/journey-2/01.webp +0 -0
  494. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/checkout.scxml +82 -0
  495. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/journeys.yaml +129 -0
  496. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/known-issues.md +14 -0
  497. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/machine.yaml +158 -0
  498. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/open-questions.md +25 -0
  499. package/packages/visualizer/fixtures/spec-only/runs/.gitkeep +0 -0
  500. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/checkout.scxml +82 -0
  501. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/journeys.yaml +129 -0
  502. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/known-issues.md +14 -0
  503. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/machine.yaml +158 -0
  504. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/open-questions.md +25 -0
  505. package/packages/visualizer/index.html +13 -0
  506. package/packages/visualizer/scripts/make-fixtures.ts +1490 -0
  507. package/packages/visualizer/scripts/publish-data.ts +22 -0
  508. package/packages/visualizer/src/app/AppShell.tsx +56 -0
  509. package/packages/visualizer/src/app/atlas-context.tsx +23 -0
  510. package/packages/visualizer/src/app/gazetteer.module.css +97 -0
  511. package/packages/visualizer/src/app/gazetteer.tsx +105 -0
  512. package/packages/visualizer/src/app/report-sources.tsx +122 -0
  513. package/packages/visualizer/src/app/router.tsx +69 -0
  514. package/packages/visualizer/src/app/search-query.tsx +14 -0
  515. package/packages/visualizer/src/components/DataIssues.module.css +136 -0
  516. package/packages/visualizer/src/components/DataIssues.tsx +70 -0
  517. package/packages/visualizer/src/components/Header.module.css +189 -0
  518. package/packages/visualizer/src/components/Header.tsx +127 -0
  519. package/packages/visualizer/src/components/Legend.module.css +84 -0
  520. package/packages/visualizer/src/components/Legend.tsx +50 -0
  521. package/packages/visualizer/src/components/Screen.module.css +312 -0
  522. package/packages/visualizer/src/components/Screen.tsx +141 -0
  523. package/packages/visualizer/src/components/Status.module.css +116 -0
  524. package/packages/visualizer/src/components/Status.tsx +60 -0
  525. package/packages/visualizer/src/components/icons.tsx +156 -0
  526. package/packages/visualizer/src/components/labels.tsx +22 -0
  527. package/packages/visualizer/src/data/adapters/adapter.ts +112 -0
  528. package/packages/visualizer/src/data/adapters/context.tsx +11 -0
  529. package/packages/visualizer/src/data/adapters/index.ts +29 -0
  530. package/packages/visualizer/src/data/adapters/report-backend.test.ts +96 -0
  531. package/packages/visualizer/src/data/adapters/report-backend.ts +42 -0
  532. package/packages/visualizer/src/data/fixtures.test.ts +407 -0
  533. package/packages/visualizer/src/data/model/build.ts +418 -0
  534. package/packages/visualizer/src/data/model/event-kind.test.ts +59 -0
  535. package/packages/visualizer/src/data/model/event-kind.ts +54 -0
  536. package/packages/visualizer/src/data/model/index.ts +3 -0
  537. package/packages/visualizer/src/data/model/journeys.test.ts +159 -0
  538. package/packages/visualizer/src/data/model/journeys.ts +29 -0
  539. package/packages/visualizer/src/data/model/simulation.test.ts +115 -0
  540. package/packages/visualizer/src/data/model/simulation.ts +187 -0
  541. package/packages/visualizer/src/data/model/status.test.ts +34 -0
  542. package/packages/visualizer/src/data/model/status.ts +43 -0
  543. package/packages/visualizer/src/data/model/types.ts +280 -0
  544. package/packages/visualizer/src/data/parse/issues.test.ts +82 -0
  545. package/packages/visualizer/src/data/parse/issues.ts +114 -0
  546. package/packages/visualizer/src/data/parse/manifest.ts +173 -0
  547. package/packages/visualizer/src/data/parse/markdown.test.ts +119 -0
  548. package/packages/visualizer/src/data/parse/markdown.ts +133 -0
  549. package/packages/visualizer/src/data/parse/spec.test.ts +106 -0
  550. package/packages/visualizer/src/data/parse/spec.ts +557 -0
  551. package/packages/visualizer/src/data/parse/structured.ts +44 -0
  552. package/packages/visualizer/src/data/queries.ts +369 -0
  553. package/packages/visualizer/src/data/testing/load-fixture.ts +63 -0
  554. package/packages/visualizer/src/evidence/api.ts +41 -0
  555. package/packages/visualizer/src/evidence/check-detail.tsx +257 -0
  556. package/packages/visualizer/src/evidence/decision-table.tsx +68 -0
  557. package/packages/visualizer/src/evidence/document-page.tsx +40 -0
  558. package/packages/visualizer/src/evidence/evidence-context.tsx +159 -0
  559. package/packages/visualizer/src/evidence/evidence.module.css +877 -0
  560. package/packages/visualizer/src/evidence/feature-page.tsx +88 -0
  561. package/packages/visualizer/src/evidence/format.ts +56 -0
  562. package/packages/visualizer/src/evidence/gherkin.ts +98 -0
  563. package/packages/visualizer/src/evidence/marks.tsx +118 -0
  564. package/packages/visualizer/src/evidence/model-page.tsx +60 -0
  565. package/packages/visualizer/src/evidence/model-trace.tsx +57 -0
  566. package/packages/visualizer/src/evidence/overview.tsx +134 -0
  567. package/packages/visualizer/src/evidence/quint.ts +45 -0
  568. package/packages/visualizer/src/evidence/recording-page.tsx +69 -0
  569. package/packages/visualizer/src/evidence/recording.module.css +464 -0
  570. package/packages/visualizer/src/evidence/recording.tsx +197 -0
  571. package/packages/visualizer/src/evidence/snapshot.tsx +108 -0
  572. package/packages/visualizer/src/evidence/sources-panel.tsx +134 -0
  573. package/packages/visualizer/src/evidence/sources.module.css +168 -0
  574. package/packages/visualizer/src/evidence/spec-header.tsx +64 -0
  575. package/packages/visualizer/src/evidence/spec-index.tsx +82 -0
  576. package/packages/visualizer/src/evidence/spec-page.tsx +92 -0
  577. package/packages/visualizer/src/evidence/spec-snapshot.tsx +26 -0
  578. package/packages/visualizer/src/layout/graph.test.ts +113 -0
  579. package/packages/visualizer/src/layout/graph.ts +324 -0
  580. package/packages/visualizer/src/layout/layout.ts +237 -0
  581. package/packages/visualizer/src/layout/use-chart-layout.ts +18 -0
  582. package/packages/visualizer/src/main.tsx +31 -0
  583. package/packages/visualizer/src/map/ChartMap.tsx +252 -0
  584. package/packages/visualizer/src/map/EventEdge.tsx +138 -0
  585. package/packages/visualizer/src/map/map.module.css +546 -0
  586. package/packages/visualizer/src/map/nodes.tsx +145 -0
  587. package/packages/visualizer/src/map/path.ts +24 -0
  588. package/packages/visualizer/src/reachable.test.ts +54 -0
  589. package/packages/visualizer/src/spec-kind.test.ts +27 -0
  590. package/packages/visualizer/src/spec-kind.ts +56 -0
  591. package/packages/visualizer/src/state/ui-store.ts +44 -0
  592. package/packages/visualizer/src/styles/global.css +105 -0
  593. package/packages/visualizer/src/styles/tokens.css +80 -0
  594. package/packages/visualizer/src/views/ChartPage.tsx +52 -0
  595. package/packages/visualizer/src/views/ProductPage.tsx +35 -0
  596. package/packages/visualizer/src/views/SidePanel.tsx +339 -0
  597. package/packages/visualizer/src/views/views.module.css +546 -0
  598. package/packages/visualizer/vite/atlas-dev-plugin.ts +169 -0
  599. package/packages/visualizer/vite/atlas-files.test.ts +119 -0
  600. package/packages/visualizer/vite/atlas-files.ts +199 -0
  601. package/packages/visualizer/vite/publish.test.ts +52 -0
  602. package/packages/visualizer/vite/publish.ts +99 -0
  603. package/packages/visualizer/vite/reports-dev-plugin.ts +82 -0
  604. package/packages/visualizer/vite.config.ts +13 -0
  605. package/packages/vitest/README.md +58 -0
  606. package/packages/webdriver/README.md +77 -0
@@ -0,0 +1,88 @@
1
+ import type { CheckSlot, SpecEvidence } from '@crvouga/atlas/reports/evidence-view';
2
+ import { useMemo } from 'react';
3
+
4
+ import type { SpecKind } from '../spec-kind';
5
+ import { CheckDetail, PlainSteps, ResultRow, type CheckSelection, type SelectCheck } from './check-detail';
6
+ import styles from './evidence.module.css';
7
+ import { featureModel, scenarioLabel, slotsByScenario, type FeatureModel, type FeatureScenario } from './gherkin';
8
+ import { Bounded } from './marks';
9
+ import { SourceText, SpecHeader } from './spec-header';
10
+ import { SpecSnapshot } from './spec-snapshot';
11
+
12
+ type Props = { file: string; text: string; kind: SpecKind; entry: SpecEvidence | undefined; selection: CheckSelection; select: SelectCheck };
13
+
14
+ function parse(text: string): { feature: FeatureModel | null; error: string | null } {
15
+ try {
16
+ return { feature: featureModel(text), error: null };
17
+ } catch (error) {
18
+ return { feature: null, error: error instanceof Error ? error.message : String(error) };
19
+ }
20
+ }
21
+
22
+ /** A feature read as its rules and scenarios, each scenario with what was recorded when it ran. */
23
+ export function FeaturePage({ file, text, kind, entry, selection, select }: Props) {
24
+ const { feature, error } = useMemo(() => parse(text), [text]);
25
+ const slots = entry?.slots ?? [];
26
+ const filed = useMemo(() => (feature ? slotsByScenario(feature, slots) : { byScenario: new Map<string, CheckSlot[]>(), leftOver: slots }), [feature, slots]);
27
+ const scenarios = feature?.rules.flatMap((rule) => rule.scenarios.filter((scenario) => !scenario.background)) ?? [];
28
+ const planned = scenarios.reduce((count, scenario) => count + Math.max(1, filed.byScenario.get(scenario.name)?.length ?? 0), 0) + filed.leftOver.length;
29
+ const toggle = (id: string) => select({ check: selection.check === id ? null : id, layer: null });
30
+
31
+ const rows = (scenario: FeatureScenario) => {
32
+ const written = <PlainSteps steps={scenario.steps} examples={scenario.examples} />;
33
+ const recorded = filed.byScenario.get(scenario.name) ?? [];
34
+ if (!recorded.length) {
35
+ const id = `scenario:${scenario.name}`;
36
+ return (
37
+ <ResultRow key={id} label={scenario.name} slot={null} open={selection.check === id} onToggle={() => toggle(id)}>
38
+ <p className={styles.aside}>No completed test report for this scenario yet.</p>
39
+ {written}
40
+ </ResultRow>
41
+ );
42
+ }
43
+ return recorded.map((slot) => (
44
+ <ResultRow key={slot.key} label={scenarioLabel(feature!.name, slot.title)} slot={slot} open={selection.check === slot.id} onToggle={() => toggle(slot.id)}>
45
+ <CheckDetail slot={slot} layerId={selection.check === slot.id ? selection.layer : undefined} select={(patch) => select({ check: slot.id, ...patch })} reads="steps" written={written} />
46
+ </ResultRow>
47
+ ));
48
+ };
49
+
50
+ return (
51
+ <article className={styles.page}>
52
+ <SpecHeader title={feature?.name || file.split('/').at(-1)!} file={file} kind={kind} entry={entry} planned={feature ? planned : undefined}>
53
+ {feature?.description && <p className={styles.lead}>{feature.description}</p>}
54
+ </SpecHeader>
55
+ <SpecSnapshot title={feature?.name || file.split('/').at(-1)!} text={text} kind={kind.one} />
56
+ {error && <p role="alert" className={styles.notice}>This feature could not be parsed: {error}</p>}
57
+ {feature?.rules.map((rule, index) => (
58
+ <section key={index} className={styles.block}>
59
+ {rule.name !== null && <h2 className={styles.blockTitle}>{rule.name}</h2>}
60
+ {rule.description && <p className={styles.blockLead}>{rule.description}</p>}
61
+ {rule.scenarios.filter((scenario) => scenario.background).map((scenario, at) => (
62
+ <div key={at} className={styles.background}>
63
+ <p className={styles.aside}>Before each scenario{scenario.name ? `: ${scenario.name}` : ''}</p>
64
+ <PlainSteps steps={scenario.steps} />
65
+ </div>
66
+ ))}
67
+ <ul className={styles.results}>{rule.scenarios.filter((scenario) => !scenario.background).map(rows)}</ul>
68
+ </section>
69
+ ))}
70
+ {filed.leftOver.length > 0 && (
71
+ <section className={styles.block}>
72
+ <h2 className={styles.blockTitle}>{feature ? 'Other recorded results' : 'Recorded results'}</h2>
73
+ {feature && <p className={styles.blockLead}>Recorded against this file under a name no scenario has now.</p>}
74
+ <ul className={styles.results}>
75
+ <Bounded items={filed.leftOver} size={40} more="More results">
76
+ {(slot) => (
77
+ <ResultRow key={slot.key} label={feature ? scenarioLabel(feature.name, slot.title) : slot.title} slot={slot} open={selection.check === slot.id} onToggle={() => toggle(slot.id)}>
78
+ <CheckDetail slot={slot} layerId={selection.check === slot.id ? selection.layer : undefined} select={(patch) => select({ check: slot.id, ...patch })} reads="steps" />
79
+ </ResultRow>
80
+ )}
81
+ </Bounded>
82
+ </ul>
83
+ </section>
84
+ )}
85
+ <SourceText text={text} label="Feature source" open={!feature} />
86
+ </article>
87
+ );
88
+ }
@@ -0,0 +1,56 @@
1
+ import type { EvidenceStatus } from '@crvouga/atlas/reports/explorer-types';
2
+ import type { SpecStatus } from '@crvouga/atlas/reports/evidence-view';
3
+ import { useEffect, useState } from 'react';
4
+
5
+ export const STATUS_WORD: Record<SpecStatus, string> = {
6
+ passed: 'Passed',
7
+ failed: 'Failed',
8
+ running: 'Running',
9
+ 'not-run': 'Not run',
10
+ none: 'No results'
11
+ };
12
+
13
+ export function statusWord(status: EvidenceStatus | SpecStatus) {
14
+ return STATUS_WORD[status];
15
+ }
16
+
17
+ const UNITS: readonly [number, string][] = [[86_400_000, 'd'], [3_600_000, 'h'], [60_000, 'min']];
18
+
19
+ /** How long ago something was recorded, in the largest whole unit. */
20
+ export function ago(atMs: number, nowMs: number) {
21
+ const elapsed = Math.max(0, nowMs - atMs);
22
+ for (const [size, unit] of UNITS) if (elapsed >= size) return `${Math.floor(elapsed / size)} ${unit} ago`;
23
+ return 'just now';
24
+ }
25
+
26
+ const dateTime = new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' });
27
+
28
+ export function exactTime(atMs: number) {
29
+ return Number.isFinite(atMs) ? dateTime.format(new Date(atMs)) : 'Unknown time';
30
+ }
31
+
32
+ export function duration(ms: number | null | undefined) {
33
+ if (ms === null || ms === undefined || !Number.isFinite(ms)) return '';
34
+ if (ms < 1) return '<1 ms';
35
+ if (ms < 1_000) return `${Math.round(ms)} ms`;
36
+ if (ms < 60_000) return `${(ms / 1_000).toFixed(ms < 10_000 ? 1 : 0)} s`;
37
+ return `${Math.floor(ms / 60_000)} min ${Math.round((ms % 60_000) / 1_000)} s`;
38
+ }
39
+
40
+ export function shortSha(sha: string | null) {
41
+ return sha ? sha.slice(0, 7) : null;
42
+ }
43
+
44
+ export function plural(count: number, one: string, many: string) {
45
+ return `${count} ${count === 1 ? one : many}`;
46
+ }
47
+
48
+ /** The current time, refreshed often enough for "3 min ago" to stay true. */
49
+ export function useNow(everyMs = 30_000) {
50
+ const [now, setNow] = useState(() => Date.now());
51
+ useEffect(() => {
52
+ const timer = setInterval(() => setNow(Date.now()), everyMs);
53
+ return () => clearInterval(timer);
54
+ }, [everyMs]);
55
+ return now;
56
+ }
@@ -0,0 +1,98 @@
1
+ import { AstBuilder, GherkinClassicTokenMatcher, Parser } from '@cucumber/gherkin';
2
+ import { IdGenerator, type Step, type TableRow } from '@cucumber/messages';
3
+ import type { CheckSlot } from '@crvouga/atlas/reports/evidence-view';
4
+
5
+ export function parseFeature(text: string) {
6
+ return new Parser(new AstBuilder(IdGenerator.incrementing()), new GherkinClassicTokenMatcher()).parse(text);
7
+ }
8
+
9
+ export type FeatureStep = { readonly keyword: string; readonly text: string; readonly docString: string | null; readonly table: readonly (readonly string[])[] };
10
+ export type FeatureExamples = { readonly name: string; readonly header: readonly string[]; readonly rows: readonly (readonly string[])[] };
11
+ export type FeatureScenario = {
12
+ readonly keyword: string;
13
+ readonly name: string;
14
+ readonly description: string;
15
+ readonly background: boolean;
16
+ readonly steps: readonly FeatureStep[];
17
+ readonly examples: readonly FeatureExamples[];
18
+ };
19
+ export type FeatureRule = { readonly name: string | null; readonly description: string; readonly scenarios: readonly FeatureScenario[] };
20
+ export type FeatureModel = { readonly name: string; readonly description: string; readonly rules: readonly FeatureRule[] };
21
+
22
+ /** Prose as paragraphs: a feature file wraps its lines by hand, a page wraps them itself. */
23
+ function prose(text: string) {
24
+ return text.trim().split(/\n\s*\n/).map((paragraph) => paragraph.replace(/\s*\n\s*/g, ' ').trim()).filter(Boolean).join('\n\n');
25
+ }
26
+
27
+ function cells(row: TableRow | undefined) {
28
+ return row?.cells.map((cell) => cell.value) ?? [];
29
+ }
30
+
31
+ function steps(list: readonly Step[]): FeatureStep[] {
32
+ return list.map((step) => ({ keyword: step.keyword.trim(), text: step.text, docString: step.docString?.content ?? null, table: step.dataTable?.rows.map(cells) ?? [] }));
33
+ }
34
+
35
+ /** A feature as rules holding scenarios. Scenarios outside a rule share one unnamed group. */
36
+ export function featureModel(text: string): FeatureModel | null {
37
+ const feature = parseFeature(text).feature;
38
+ if (!feature) return null;
39
+ const rules: { name: string | null; description: string; scenarios: FeatureScenario[] }[] = [];
40
+ const loose = () => {
41
+ const last = rules.at(-1);
42
+ if (last && last.name === null) return last;
43
+ const group = { name: null, description: '', scenarios: [] as FeatureScenario[] };
44
+ rules.push(group);
45
+ return group;
46
+ };
47
+ for (const child of feature.children) {
48
+ const group = child.rule ? { name: child.rule.name, description: prose(child.rule.description), scenarios: [] as FeatureScenario[] } : loose();
49
+ if (child.rule) rules.push(group);
50
+ for (const entry of child.rule ? child.rule.children : [child]) {
51
+ const background = entry.background;
52
+ const scenario = entry.scenario;
53
+ if (background) group.scenarios.push({ keyword: background.keyword, name: background.name, description: prose(background.description), background: true, steps: steps(background.steps), examples: [] });
54
+ if (scenario) {
55
+ group.scenarios.push({
56
+ keyword: scenario.keyword,
57
+ name: scenario.name,
58
+ description: prose(scenario.description),
59
+ background: false,
60
+ steps: steps(scenario.steps),
61
+ examples: scenario.examples.map((example) => ({ name: example.name, header: cells(example.tableHeader), rows: example.tableBody.map(cells) }))
62
+ });
63
+ }
64
+ }
65
+ }
66
+ return { name: feature.name, description: prose(feature.description), rules };
67
+ }
68
+
69
+ /** What a recorded check is called without the feature in front: the scenario, then the example. */
70
+ export function scenarioLabel(feature: string, title: string) {
71
+ const head = `${feature}: `;
72
+ return title.startsWith(head) ? title.slice(head.length) : title;
73
+ }
74
+
75
+ /** The example a result ran, when its scenario has several. */
76
+ export function exampleLabel(scenario: string, label: string) {
77
+ return label.startsWith(`${scenario} — `) ? label.slice(scenario.length + 3) : null;
78
+ }
79
+
80
+ /**
81
+ * File each recorded check under the scenario it ran. An outline has one result per example. A
82
+ * result no scenario claims (the scenario was renamed, or the check is not a scenario) is returned
83
+ * as left over, never dropped.
84
+ */
85
+ export function slotsByScenario(feature: FeatureModel, slots: readonly CheckSlot[]) {
86
+ const names = feature.rules.flatMap((rule) => rule.scenarios.filter((scenario) => !scenario.background).map((scenario) => scenario.name));
87
+ // The longest name wins, so "Join" never claims "Join — again" from a scenario called that.
88
+ const longestFirst = [...new Set(names)].sort((a, b) => b.length - a.length);
89
+ const byScenario = new Map<string, CheckSlot[]>();
90
+ const leftOver: CheckSlot[] = [];
91
+ for (const slot of slots) {
92
+ const label = scenarioLabel(feature.name, slot.title);
93
+ const owner = longestFirst.find((name) => label === name || label.startsWith(`${name} — `));
94
+ if (owner === undefined) leftOver.push(slot);
95
+ else byScenario.set(owner, [...(byScenario.get(owner) ?? []), slot]);
96
+ }
97
+ return { byScenario, leftOver };
98
+ }
@@ -0,0 +1,118 @@
1
+ import type { BackendInfo, LayerHeader, StatusCounts } from '@crvouga/atlas/reports/evidence-types';
2
+ import type { SpecStatus, SpecSummary } from '@crvouga/atlas/reports/evidence-view';
3
+ import { useState, type ReactNode } from 'react';
4
+
5
+ import styles from './evidence.module.css';
6
+ import { ago, exactTime, plural, shortSha, statusWord } from './format';
7
+
8
+ /** The outcome as a small mark. Its word is always available to a screen reader. */
9
+ export function StatusMark({ status, size = 16 }: { status: SpecStatus; size?: number }) {
10
+ const common = { width: size, height: size, viewBox: '0 0 16 16', className: styles.mark, 'data-status': status, role: 'img', 'aria-label': statusWord(status) } as const;
11
+ if (status === 'passed') {
12
+ return (
13
+ <svg {...common}>
14
+ <circle cx="8" cy="8" r="7" fill="currentColor" />
15
+ <path d="M4.7 8.3l2.2 2.2 4.4-4.7" fill="none" stroke="#fff" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" />
16
+ </svg>
17
+ );
18
+ }
19
+ if (status === 'failed') {
20
+ return (
21
+ <svg {...common}>
22
+ <circle cx="8" cy="8" r="7" fill="currentColor" />
23
+ <path d="M5.4 5.4l5.2 5.2M10.6 5.4l-5.2 5.2" fill="none" stroke="#fff" strokeWidth="1.7" strokeLinecap="round" />
24
+ </svg>
25
+ );
26
+ }
27
+ if (status === 'running') {
28
+ return (
29
+ <svg {...common}>
30
+ <circle cx="8" cy="8" r="6" fill="none" stroke="currentColor" strokeOpacity="0.25" strokeWidth="2" />
31
+ <path d="M8 2a6 6 0 0 1 6 6" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
32
+ </svg>
33
+ );
34
+ }
35
+ return (
36
+ <svg {...common}>
37
+ <circle cx="8" cy="8" r="6" fill="none" stroke="currentColor" strokeWidth="1.5" strokeDasharray={status === 'none' ? '2.2 2.2' : undefined} />
38
+ </svg>
39
+ );
40
+ }
41
+
42
+ /** What was recorded for a spec, short enough for an index row. */
43
+ export function Tally({ summary }: { summary: SpecSummary }) {
44
+ if (summary.status === 'none') return <span className={styles.tally} data-status="none">No results</span>;
45
+ if (summary.counts.failed) return <span className={styles.tally} data-status="failed">{summary.counts.failed} failed</span>;
46
+ if (summary.counts.running) return <span className={styles.tally} data-status="running">Running</span>;
47
+ if (!summary.counts.passed) return <span className={styles.tally} data-status="not-run">Not run</span>;
48
+ return <span className={styles.tally} data-status="passed">{summary.counts.passed === summary.total ? `${summary.total} passed` : `${summary.counts.passed}/${summary.total}`}</span>;
49
+ }
50
+
51
+ const SEGMENTS = ['failed', 'running', 'passed', 'not-run'] as const;
52
+
53
+ /** Proportions of the recorded outcomes. With nothing recorded it is an empty track. */
54
+ export function Meter({ counts, missing = 0 }: { counts: StatusCounts; missing?: number }) {
55
+ const total = SEGMENTS.reduce((sum, status) => sum + counts[status], 0) + missing;
56
+ return (
57
+ <span className={styles.meter} aria-hidden="true">
58
+ {total > 0 && SEGMENTS.map((status) => counts[status] > 0 && <span key={status} data-status={status} style={{ flexGrow: counts[status] }} />)}
59
+ {total > 0 && missing > 0 && <span data-status="none" style={{ flexGrow: missing }} />}
60
+ </span>
61
+ );
62
+ }
63
+
64
+ export function CountLine({ summary, noun, nouns }: { summary: SpecSummary; noun: string; nouns: string }) {
65
+ if (summary.status === 'none') return <span>No {nouns} recorded yet</span>;
66
+ const parts = [
67
+ summary.counts.failed ? <strong key="failed" className={styles.failedText}>{summary.counts.failed} failed</strong> : null,
68
+ summary.counts.passed ? <span key="passed">{summary.counts.passed} passed</span> : null,
69
+ summary.counts.running ? <span key="running">{summary.counts.running} running</span> : null,
70
+ summary.counts['not-run'] ? <span key="not-run">{summary.counts['not-run']} not run</span> : null
71
+ ].filter(Boolean);
72
+ return (
73
+ <span className={styles.countLine}>
74
+ <span>{plural(summary.total, noun, nouns)}</span>
75
+ {parts}
76
+ </span>
77
+ );
78
+ }
79
+
80
+ export function backendLabel(backends: readonly BackendInfo[], id: string) {
81
+ return backends.find((backend) => backend.id === id)?.label ?? id;
82
+ }
83
+
84
+ /** Where and when a result was recorded. A modified checkout is stated, not raised as an alarm. */
85
+ export function Origin({ layer, backends, now }: { layer: LayerHeader; backends: readonly BackendInfo[]; now: number }) {
86
+ const sha = shortSha(layer.sha);
87
+ return (
88
+ <span className={styles.origin}>
89
+ {layer.live && <span className={styles.liveTag}>Running now</span>}
90
+ <span>{backendLabel(backends, layer.backend)}</span>
91
+ {layer.branch && <span>{layer.branch}</span>}
92
+ {sha && <code>{sha}</code>}
93
+ {layer.freshness === 'modified' && <span>with local changes</span>}
94
+ {layer.freshness === 'unknown' && <span>origin not captured</span>}
95
+ {!layer.live && <time dateTime={new Date(layer.recordedAtMs).toISOString()} title={exactTime(layer.recordedAtMs)}>{ago(layer.recordedAtMs, now)}</time>}
96
+ {Object.entries(layer.parameters).map(([name, value]) => (
97
+ <span key={name} className={styles.param}>{name}: {value}</span>
98
+ ))}
99
+ </span>
100
+ );
101
+ }
102
+
103
+ /** A long list in pages, so the page stays light however much has been recorded. */
104
+ export function Bounded<T>({ items, size, more, children }: { items: readonly T[]; size: number; more: string; children: (item: T, index: number) => ReactNode }) {
105
+ const [shown, setShown] = useState(size);
106
+ return (
107
+ <>
108
+ {items.slice(0, shown).map(children)}
109
+ {items.length > shown && (
110
+ <li className={styles.moreRow}>
111
+ <button type="button" className={styles.quiet} onClick={() => setShown(shown + size)}>
112
+ {more} ({items.length - shown} more)
113
+ </button>
114
+ </li>
115
+ )}
116
+ </>
117
+ );
118
+ }
@@ -0,0 +1,60 @@
1
+ import type { SpecEvidence } from '@crvouga/atlas/reports/evidence-view';
2
+ import { useMemo } from 'react';
3
+
4
+ import type { SpecKind } from '../spec-kind';
5
+ import { CheckDetail, ResultRow, type CheckSelection, type SelectCheck } from './check-detail';
6
+ import styles from './evidence.module.css';
7
+ import { Bounded } from './marks';
8
+ import { QUINT_GROUPS, quintOutline } from './quint';
9
+ import { SourceText, SpecHeader } from './spec-header';
10
+ import { SpecSnapshot } from './spec-snapshot';
11
+
12
+ type Props = { file: string; text: string; kind: SpecKind; entry: SpecEvidence | undefined; selection: CheckSelection; select: SelectCheck };
13
+
14
+ /** A model read as its state and actions, with every trace that was replayed against the app. */
15
+ export function ModelPage({ file, text, kind, entry, selection, select }: Props) {
16
+ const items = useMemo(() => quintOutline(text), [text]);
17
+ const moduleName = items.find((item) => item.kind === 'module')?.name;
18
+ const slots = useMemo(() => [...(entry?.slots ?? [])].sort((a, b) => a.title.localeCompare(b.title, undefined, { numeric: true })), [entry]);
19
+ const toggle = (id: string) => select({ check: selection.check === id ? null : id, layer: null });
20
+ const label = (title: string) => (moduleName && title.startsWith(`${moduleName} · `) ? title.slice(moduleName.length + 3) : title);
21
+ return (
22
+ <article className={styles.page}>
23
+ <SpecHeader title={moduleName ?? file.split('/').at(-1)!} file={file} kind={kind} entry={entry} />
24
+ <SpecSnapshot title={moduleName ?? file.split('/').at(-1)!} text={text} kind={kind.one} />
25
+ {slots.length > 0 && (
26
+ <section className={styles.block}>
27
+ <h2 className={styles.blockTitle}>Traces</h2>
28
+ <ul className={styles.results}>
29
+ <Bounded items={slots} size={40} more="More traces">
30
+ {(slot) => (
31
+ <ResultRow key={slot.key} label={label(slot.title)} slot={slot} open={selection.check === slot.id} onToggle={() => toggle(slot.id)}>
32
+ <CheckDetail slot={slot} layerId={selection.check === slot.id ? selection.layer : undefined} select={(patch) => select({ check: slot.id, ...patch })} reads="trace" />
33
+ </ResultRow>
34
+ )}
35
+ </Bounded>
36
+ </ul>
37
+ </section>
38
+ )}
39
+ {QUINT_GROUPS.map((group) => {
40
+ const declared = items.filter((item) => item.kind === group.kind);
41
+ if (!declared.length) return null;
42
+ return (
43
+ <section key={group.kind} className={styles.block}>
44
+ <h2 className={styles.blockTitle}>{group.label}</h2>
45
+ <ul className={styles.declarations}>
46
+ {declared.map((item) => (
47
+ <li key={item.line}>
48
+ <a href={`#line-${item.line}`} onClick={(event) => { event.preventDefault(); const line = document.getElementById(`line-${item.line}`); line?.closest('details')?.setAttribute('open', ''); line?.scrollIntoView({ block: 'center' }); }}>
49
+ <code>{item.text}</code>
50
+ </a>
51
+ </li>
52
+ ))}
53
+ </ul>
54
+ </section>
55
+ );
56
+ })}
57
+ <SourceText text={text} label="Model source" open={items.length === 0} />
58
+ </article>
59
+ );
60
+ }
@@ -0,0 +1,57 @@
1
+ import { useState } from 'react';
2
+
3
+ import type { ModelState } from './quint';
4
+ import styles from './recording.module.css';
5
+
6
+ function shown(value: unknown) {
7
+ if (value === undefined) return '—';
8
+ return typeof value === 'string' ? value : JSON.stringify(value, null, 2);
9
+ }
10
+
11
+ /** The states a model run went through, one at a time, with what each step changed. */
12
+ export function ModelTrace({ states }: { states: readonly ModelState[] }) {
13
+ const [selected, setSelected] = useState(0);
14
+ if (!states.length) return <p className={styles.quietLine}>No model states were recorded.</p>;
15
+ const at = Math.min(selected, states.length - 1);
16
+ const state = states[at]!;
17
+ const previous = states[at - 1];
18
+ const variables = [...new Set([...Object.keys(previous ?? {}), ...Object.keys(state)])].filter((name) => !name.startsWith('#'));
19
+ const changed = (name: string) => previous !== undefined && JSON.stringify(previous[name]) !== JSON.stringify(state[name]);
20
+ return (
21
+ <div className={styles.trace}>
22
+ <ol className={styles.traceStates} aria-label="Model trace states">
23
+ {states.map((entry, index) => (
24
+ <li key={index}>
25
+ <button type="button" aria-label={`State ${index + 1}`} aria-pressed={at === index} onClick={() => setSelected(index)}>
26
+ <span className={styles.traceNo}>{index + 1}</span>
27
+ <span>{typeof entry['#action'] === 'string' ? entry['#action'] : index === 0 ? 'Start' : 'Step'}</span>
28
+ </button>
29
+ </li>
30
+ ))}
31
+ </ol>
32
+ <div className={styles.tableScroll}>
33
+ <table className={styles.traceTable}>
34
+ <thead>
35
+ <tr>
36
+ <th scope="col">Variable</th>
37
+ <th scope="col">Before</th>
38
+ <th scope="col">State {at + 1}</th>
39
+ </tr>
40
+ </thead>
41
+ <tbody>
42
+ {variables.map((name) => (
43
+ <tr key={name} data-changed={changed(name) || undefined}>
44
+ <th scope="row">
45
+ {name}
46
+ {changed(name) && <span className={styles.changed}>Changed</span>}
47
+ </th>
48
+ <td><pre>{previous ? shown(previous[name]) : '—'}</pre></td>
49
+ <td><pre>{shown(state[name])}</pre></td>
50
+ </tr>
51
+ ))}
52
+ </tbody>
53
+ </table>
54
+ </div>
55
+ </div>
56
+ );
57
+ }
@@ -0,0 +1,134 @@
1
+ import { addSummaries, NO_EVIDENCE, type SpecSummary } from '@crvouga/atlas/reports/evidence-view';
2
+ import { Link } from '@tanstack/react-router';
3
+ import { useMemo } from 'react';
4
+
5
+ import { folderOf, outlinePath, type SpecKind } from '../spec-kind';
6
+ import { useUiStore } from '../state/ui-store';
7
+ import { useEvidence } from './evidence-context';
8
+ import styles from './evidence.module.css';
9
+ import { ago, plural, useNow } from './format';
10
+ import { backendLabel, Bounded, CountLine, Meter, StatusMark, Tally } from './marks';
11
+
12
+ /** A kind's specs at a glance: how many have results, which are failing, and what just ran. */
13
+ export function Overview({ kind }: { kind: SpecKind }) {
14
+ const evidence = useEvidence();
15
+ const now = useNow();
16
+ const setFilter = useUiStore((s) => s.setResultFilter);
17
+ const setSourcesOpen = useUiStore((s) => s.setSourcesOpen);
18
+ const files = useMemo(() => evidence.files.filter(kind.accepts), [evidence.files, kind]);
19
+ const rows = useMemo(() => files.map((file) => ({ file, summary: evidence.view.specs.get(outlinePath(file))?.summary ?? NO_EVIDENCE })), [files, evidence.view]);
20
+ const total = useMemo(() => addSummaries(rows.map((row) => row.summary)), [rows]);
21
+ const withResults = rows.filter((row) => row.summary.status !== 'none');
22
+ const failing = rows.filter((row) => row.summary.counts.failed > 0).sort((a, b) => b.summary.counts.failed - a.summary.counts.failed);
23
+ const recent = [...withResults].sort((a, b) => (b.summary.latestAtMs ?? 0) - (a.summary.latestAtMs ?? 0)).slice(0, 8);
24
+ const empty = rows.length - withResults.length;
25
+ const specRow = (row: { file: string; summary: SpecSummary }) => (
26
+ <li key={row.file}>
27
+ <Link to="/specs/$kind" params={{ kind: kind.id }} search={{ spec: row.file }} className={styles.listRow}>
28
+ <StatusMark status={row.summary.status} />
29
+ <span className={styles.listName}>
30
+ {evidence.titleOf(row.file)}
31
+ <span className={styles.listPath}>{folderOf(row.file)}</span>
32
+ </span>
33
+ <Tally summary={row.summary} />
34
+ {row.summary.latestAtMs !== null && <span className={styles.listTime}>{ago(row.summary.latestAtMs, now)}</span>}
35
+ </Link>
36
+ </li>
37
+ );
38
+ return (
39
+ <article className={styles.page}>
40
+ <header className={styles.specHead}>
41
+ <h1 className={styles.title}>{kind.label}</h1>
42
+ <div className={styles.summary}>
43
+ <Meter counts={total.counts} />
44
+ <p className={styles.summaryLine}>
45
+ <span>{plural(files.length, kind.one, kind.many)}</span>
46
+ {evidence.ready && <span>{withResults.length} with results</span>}
47
+ {evidence.ready && empty > 0 && <span>{empty} without</span>}
48
+ </p>
49
+ {total.status !== 'none' && (
50
+ <p className={styles.summaryLine}>
51
+ <CountLine summary={total} noun={kind.check} nouns={`${kind.check}s`} />
52
+ </p>
53
+ )}
54
+ </div>
55
+ </header>
56
+
57
+ {evidence.unavailable && (
58
+ <p className={styles.notice} role="alert">
59
+ Recorded results are unavailable. {evidence.unavailable}{' '}
60
+ <button type="button" className={styles.link} onClick={evidence.retry}>Try again</button>
61
+ </p>
62
+ )}
63
+ {evidence.notices.map((notice) => (
64
+ <p key={notice} className={styles.notice} role="status">
65
+ {notice}{' '}
66
+ <button type="button" className={styles.link} onClick={evidence.retry}>Try again</button>
67
+ </p>
68
+ ))}
69
+ {!evidence.ready && !evidence.unavailable && <p className={styles.aside} role="status">Loading recorded results…</p>}
70
+ {evidence.ready && evidence.pending > 0 && <p className={styles.aside} role="status">Reading {plural(evidence.pending, 'more run', 'more runs')}…</p>}
71
+
72
+ {evidence.active.length > 0 && (
73
+ <section className={styles.block}>
74
+ <h2 className={styles.blockTitle}>Running now</h2>
75
+ <ul className={styles.list}>
76
+ {evidence.active.map((run) => (
77
+ <li key={run.source} className={styles.listRow}>
78
+ <StatusMark status="running" />
79
+ <span className={styles.listName}>{run.packageDir}</span>
80
+ <span className={styles.tally} data-status={run.failed ? 'failed' : 'running'}>
81
+ {plural(run.completed, 'result', 'results')} so far{run.failed ? `, ${run.failed} failed` : ''}
82
+ </span>
83
+ <span className={styles.listTime}>started {ago(run.startedAtMs, now)}</span>
84
+ </li>
85
+ ))}
86
+ </ul>
87
+ </section>
88
+ )}
89
+
90
+ {failing.length > 0 && (
91
+ <section className={styles.block}>
92
+ <h2 className={styles.blockTitle}>
93
+ Failing <span className={styles.aside}>{plural(failing.length, kind.one, kind.many)}</span>
94
+ </h2>
95
+ <ul className={styles.list}>
96
+ <Bounded items={failing} size={12} more="More failing">
97
+ {specRow}
98
+ </Bounded>
99
+ </ul>
100
+ </section>
101
+ )}
102
+
103
+ {recent.length > 0 && (
104
+ <section className={styles.block}>
105
+ <h2 className={styles.blockTitle}>Latest results</h2>
106
+ <ul className={styles.list}>{recent.map(specRow)}</ul>
107
+ </section>
108
+ )}
109
+
110
+ {evidence.ready && evidence.pending === 0 && withResults.length === 0 && !evidence.unavailable && (
111
+ <p className={styles.aside}>
112
+ {evidence.layers.length === 0
113
+ ? `Nothing has been recorded yet. Results appear here as a spec run finishes each test.`
114
+ : `None of the ${plural(evidence.layers.length, 'run', 'runs')} in the combined report checked a ${kind.one}.`}
115
+ </p>
116
+ )}
117
+
118
+ {evidence.ready && (
119
+ <p className={styles.footLine}>
120
+ {plural(evidence.layers.length, 'run', 'runs')} from {evidence.backends.filter((backend) => backend.available).map((backend) => backendLabel(evidence.backends, backend.id)).join(' and ') || 'no report store'}.{' '}
121
+ <button type="button" className={styles.link} onClick={() => setSourcesOpen(true)}>Choose sources</button>
122
+ {empty > 0 && (
123
+ <>
124
+ {' · '}
125
+ <button type="button" className={styles.link} onClick={() => setFilter('none')}>
126
+ Show the {empty} without results
127
+ </button>
128
+ </>
129
+ )}
130
+ </p>
131
+ )}
132
+ </article>
133
+ );
134
+ }
@@ -0,0 +1,45 @@
1
+ import { flattenSteps } from '@crvouga/atlas/reports/frames';
2
+ import type { ReportStep } from '@crvouga/atlas/reports/report-types';
3
+
4
+ export const QUINT_GROUPS: readonly { readonly kind: string; readonly label: string }[] = [
5
+ { kind: 'var', label: 'State' },
6
+ { kind: 'action', label: 'Actions' },
7
+ { kind: 'val', label: 'Values' },
8
+ { kind: 'pure val', label: 'Pure values' },
9
+ { kind: 'def', label: 'Definitions' },
10
+ { kind: 'pure def', label: 'Pure definitions' },
11
+ { kind: 'type', label: 'Types' },
12
+ { kind: 'const', label: 'Constants' },
13
+ { kind: 'import', label: 'Imports' }
14
+ ];
15
+
16
+ export type QuintItem = { readonly kind: string; readonly name: string; readonly line: number; readonly text: string };
17
+
18
+ /** Declarations in source order. The rest of a line is the declaration, not an execution trace. */
19
+ export function quintOutline(text: string): QuintItem[] {
20
+ return text.split('\n').flatMap((line, index) => {
21
+ const match = /^\s*(module|import|var|const|type|action|val|def|pure val|pure def)\s+(\w+(?:::\w+)*)/.exec(line);
22
+ return match ? [{ kind: match[1]!, name: match[2]!, line: index + 1, text: line.trim() }] : [];
23
+ });
24
+ }
25
+
26
+ export type ModelState = Readonly<Record<string, unknown>>;
27
+
28
+ export function traceStates(value: unknown): ModelState[] | null {
29
+ if (!value || typeof value !== 'object' || !('states' in value) || !Array.isArray(value.states)) return null;
30
+ if (!value.states.every((state) => state !== null && typeof state === 'object' && !Array.isArray(state))) return null;
31
+ return value.states as ModelState[];
32
+ }
33
+
34
+ /** Read recorded assignment text without evaluating Quint or inventing states. */
35
+ export function recordedModelStates(steps: readonly ReportStep[]) {
36
+ return flattenSteps(steps)
37
+ .filter(({ step }) => step.kind === 'quint' && step.detail)
38
+ .map(({ step }) => {
39
+ const state: Record<string, unknown> = { '#action': step.title };
40
+ const assignments = [...step.detail!.matchAll(/^([A-Za-z_]\w*) = ([\s\S]*?)(?=^[A-Za-z_]\w* = |$(?![\s\S]))/gm)];
41
+ for (const match of assignments) state[match[1]!] = match[2]!.trimEnd();
42
+ return { step, state };
43
+ })
44
+ .filter(({ state }) => Object.keys(state).length > 1);
45
+ }