plum-e2e 2.10.0 → 2.12.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 (650) hide show
  1. package/.env +1 -1
  2. package/CLAUDE.md +1 -1
  3. package/README.md +7 -7
  4. package/backend/app.js +1 -1
  5. package/backend/lib/csv.js +21 -0
  6. package/backend/lib/exportResponse.js +37 -0
  7. package/backend/lib/serverBootstrap.js +2 -2
  8. package/backend/lib/serverConfig.js +5 -3
  9. package/backend/mcp/server.js +44 -0
  10. package/backend/middleware/runnerOrAdmin.js +1 -1
  11. package/backend/package.json +1 -1
  12. package/backend/routes/reports.routes.js +19 -0
  13. package/backend/routes/runners.routes.js +1 -1
  14. package/backend/routes/test-cases.routes.js +12 -0
  15. package/backend/routes/test-runs.routes.js +17 -0
  16. package/backend/routes/test-suites.routes.js +34 -0
  17. package/backend/scripts/{manage-runners.mjs → manage-nodes.mjs} +67 -52
  18. package/backend/services/exportService.js +339 -0
  19. package/backend/services/testImportService.js +87 -0
  20. package/bin/plum.js +174 -68
  21. package/docker-compose.yml +1 -1
  22. package/frontend/.svelte-kit/adapter-node/.vite/manifest.json +48 -40
  23. package/frontend/.svelte-kit/{output/server/_app/immutable/assets/Toast.DENwYn3g.css → adapter-node/_app/immutable/assets/Button.C1zsJAUG.css} +1 -1
  24. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/ExportMenu.DsJQ1Qua.css +6 -0
  25. package/frontend/{build/client/_app/immutable/assets/_layout.HkPnpRl9.css → .svelte-kit/adapter-node/_app/immutable/assets/_layout.B4TT3qNx.css} +1 -1
  26. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.4bPj83tb.css +6 -0
  27. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.BwgtEi_a.css +6 -0
  28. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.DOC8fPSI.css +6 -0
  29. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.Dm1nSEgE.css +6 -0
  30. package/frontend/.svelte-kit/adapter-node/chunks/{Toast.js → Button.js} +2 -17
  31. package/frontend/.svelte-kit/adapter-node/chunks/ConfirmModal.js +1 -1
  32. package/frontend/.svelte-kit/adapter-node/chunks/EmptyState.js +1 -1
  33. package/frontend/.svelte-kit/adapter-node/chunks/common.js +11 -4
  34. package/frontend/.svelte-kit/adapter-node/chunks/constants.js +1 -1
  35. package/frontend/.svelte-kit/adapter-node/chunks/internal.js +1 -1
  36. package/frontend/.svelte-kit/adapter-node/chunks/repository.js +21 -17
  37. package/frontend/.svelte-kit/adapter-node/entries/pages/_layout.svelte.js +2 -0
  38. package/frontend/.svelte-kit/adapter-node/entries/pages/reports/_id_/_page.svelte.js +11 -2
  39. package/frontend/.svelte-kit/adapter-node/entries/pages/scheduled-tests/_page.svelte.js +2 -5
  40. package/frontend/.svelte-kit/adapter-node/entries/pages/settings/_page.svelte.js +300 -253
  41. package/frontend/.svelte-kit/adapter-node/entries/pages/test-repository/_page.svelte.js +55 -36
  42. package/frontend/.svelte-kit/adapter-node/entries/pages/test-repository/runs/_id_/_page.svelte.js +3 -6
  43. package/frontend/.svelte-kit/adapter-node/entries/pages/test-repository/suites/_id_/_page.svelte.js +15 -17
  44. package/frontend/.svelte-kit/adapter-node/manifest-full.js +1 -1
  45. package/frontend/.svelte-kit/adapter-node/manifest.js +1 -1
  46. package/frontend/.svelte-kit/adapter-node/nodes/0.js +2 -2
  47. package/frontend/.svelte-kit/adapter-node/nodes/1.js +1 -1
  48. package/frontend/.svelte-kit/adapter-node/nodes/10.js +2 -2
  49. package/frontend/.svelte-kit/adapter-node/nodes/11.js +2 -2
  50. package/frontend/.svelte-kit/adapter-node/nodes/12.js +2 -2
  51. package/frontend/.svelte-kit/adapter-node/nodes/2.js +1 -1
  52. package/frontend/.svelte-kit/adapter-node/nodes/3.js +1 -1
  53. package/frontend/.svelte-kit/adapter-node/nodes/4.js +1 -1
  54. package/frontend/.svelte-kit/adapter-node/nodes/5.js +1 -1
  55. package/frontend/.svelte-kit/adapter-node/nodes/6.js +2 -2
  56. package/frontend/.svelte-kit/adapter-node/nodes/7.js +2 -2
  57. package/frontend/.svelte-kit/adapter-node/nodes/8.js +2 -2
  58. package/frontend/.svelte-kit/adapter-node/nodes/9.js +1 -1
  59. package/frontend/.svelte-kit/generated/server/internal.js +1 -1
  60. package/frontend/.svelte-kit/output/client/.vite/manifest.json +459 -412
  61. package/frontend/.svelte-kit/output/client/_app/immutable/assets/{0.CmJTG5mP.css → 0.BEqlRAtD.css} +1 -1
  62. package/frontend/.svelte-kit/output/client/_app/immutable/assets/10.DC0wD6w_.css +6 -0
  63. package/frontend/.svelte-kit/output/client/_app/immutable/assets/12.Dm1nSEgE.css +6 -0
  64. package/frontend/.svelte-kit/output/client/_app/immutable/assets/6.DdQVxyZK.css +6 -0
  65. package/frontend/.svelte-kit/output/client/_app/immutable/assets/8.DOC8fPSI.css +6 -0
  66. package/frontend/.svelte-kit/{adapter-node/_app/immutable/assets/Toast.DENwYn3g.css → output/client/_app/immutable/assets/Button.C1zsJAUG.css} +1 -1
  67. package/frontend/.svelte-kit/output/client/_app/immutable/assets/ExportMenu.DsJQ1Qua.css +6 -0
  68. package/frontend/.svelte-kit/output/client/_app/immutable/assets/{_layout.HkPnpRl9.css → _layout.B4TT3qNx.css} +1 -1
  69. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.4bPj83tb.css +6 -0
  70. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.BwgtEi_a.css +6 -0
  71. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.DOC8fPSI.css +6 -0
  72. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.Dm1nSEgE.css +6 -0
  73. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/{C2_rQ4aY.js → 3MBVX4Yb.js} +1 -1
  74. package/frontend/{build/client/_app/immutable/chunks/CfkUW1vA.js → .svelte-kit/output/client/_app/immutable/chunks/6VDMVB5U.js} +1 -1
  75. package/frontend/{build/client/_app/immutable/chunks/B0_HKFXV.js → .svelte-kit/output/client/_app/immutable/chunks/B2lVGNml.js} +1 -1
  76. package/frontend/{build/client/_app/immutable/chunks/CboYYxrf.js → .svelte-kit/output/client/_app/immutable/chunks/BCh31ecr.js} +1 -1
  77. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BDb8hZHu.js +6 -0
  78. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/{CeH-RgMb.js → BIf7lHIm.js} +1 -1
  79. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BIjh3jiU.js +8 -0
  80. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BTV7EXZb.js +11 -0
  81. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/{DAmOMhaf.js → BWGoOYU0.js} +1 -1
  82. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BdBJfKUi.js +6 -0
  83. package/frontend/{build/client/_app/immutable/chunks/BAvTKHxT.js → .svelte-kit/output/client/_app/immutable/chunks/BnXsGCy3.js} +1 -1
  84. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BtTNggoY.js +8 -0
  85. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/C4Z3p6jE.js +6 -0
  86. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CNpD-yNH.js +6 -0
  87. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CUPWZLIN.js +6 -0
  88. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/{Ck6C3Fl0.js → CXQhSK1T.js} +1 -1
  89. package/frontend/{build/client/_app/immutable/chunks/C00jnSU2.js → .svelte-kit/output/client/_app/immutable/chunks/CtgCYzu5.js} +1 -1
  90. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/D72giPTu.js +6 -0
  91. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/{GDjpV3r7.js → DDx3no2z.js} +1 -1
  92. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/D_thT6pZ.js +6 -0
  93. package/frontend/{build/client/_app/immutable/chunks/C8S7qQjQ.js → .svelte-kit/output/client/_app/immutable/chunks/DeiVAbyn.js} +1 -1
  94. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DiexacSB.js +6 -0
  95. package/frontend/{build/client/_app/immutable/chunks/d2K3c8Kx.js → .svelte-kit/output/client/_app/immutable/chunks/Dlymnwnf.js} +1 -1
  96. package/frontend/{build/client/_app/immutable/chunks/Cgw8TOOy.js → .svelte-kit/output/client/_app/immutable/chunks/gCgJ3Cwu.js} +1 -1
  97. package/frontend/{build/client/_app/immutable/chunks/BJ_ypKl3.js → .svelte-kit/output/client/_app/immutable/chunks/htkwR9b-.js} +1 -1
  98. package/frontend/{build/client/_app/immutable/chunks/C7uEQ-LH.js → .svelte-kit/output/client/_app/immutable/chunks/iL4SCrvY.js} +1 -1
  99. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/{CoTEzvLa.js → kFFd6Oo5.js} +1 -1
  100. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/pNZogwkM.js +6 -0
  101. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/{DWzEJYIF.js → prk8PJfA.js} +1 -1
  102. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/qvxlSGpD.js +6 -0
  103. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/r_d1Iu0W.js +6 -0
  104. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/{B00vxw57.js → tsOANt55.js} +1 -1
  105. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/zXSLr47H.js +6 -0
  106. package/frontend/.svelte-kit/output/client/_app/immutable/entry/app.DgdSwPuN.js +7 -0
  107. package/frontend/{build/client/_app/immutable/entry/start.BXOLyf9R.js → .svelte-kit/output/client/_app/immutable/entry/start.D_lhI48n.js} +1 -1
  108. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/0.D2F-i1I5.js +6 -0
  109. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/{1.BI97il3r.js → 1.JIF2GfHq.js} +1 -1
  110. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/10.A0AHLfOt.js +6 -0
  111. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/11.CVSPxlqY.js +6 -0
  112. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/12.CnAR0AQb.js +6 -0
  113. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/{2.Dr6yugXz.js → 2.Bno00fr8.js} +1 -1
  114. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/3.B6KwaCsM.js +6 -0
  115. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/{4.DDADylfF.js → 4.BX70m3v6.js} +1 -1
  116. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/5.UIFh06yr.js +6 -0
  117. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/6.DZ1OV24V.js +10 -0
  118. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/7.B_XntBZ1.js +6 -0
  119. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/8.CVHgJw-E.js +9 -0
  120. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/{9.BOK0GNF_.js → 9.DDDQPmMj.js} +1 -1
  121. package/frontend/.svelte-kit/output/client/_app/version.json +1 -1
  122. package/frontend/.svelte-kit/output/server/.vite/manifest.json +48 -40
  123. package/frontend/{build/client/_app/immutable/assets/Toast.DENwYn3g.css → .svelte-kit/output/server/_app/immutable/assets/Button.C1zsJAUG.css} +1 -1
  124. package/frontend/.svelte-kit/output/server/_app/immutable/assets/ExportMenu.DsJQ1Qua.css +6 -0
  125. package/frontend/.svelte-kit/{adapter-node/_app/immutable/assets/_layout.HkPnpRl9.css → output/server/_app/immutable/assets/_layout.B4TT3qNx.css} +1 -1
  126. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.4bPj83tb.css +6 -0
  127. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.BwgtEi_a.css +6 -0
  128. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.DOC8fPSI.css +6 -0
  129. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.Dm1nSEgE.css +6 -0
  130. package/frontend/.svelte-kit/output/server/chunks/{Toast.js → Button.js} +2 -17
  131. package/frontend/.svelte-kit/output/server/chunks/ConfirmModal.js +1 -1
  132. package/frontend/.svelte-kit/output/server/chunks/EmptyState.js +1 -1
  133. package/frontend/.svelte-kit/output/server/chunks/common.js +11 -4
  134. package/frontend/.svelte-kit/output/server/chunks/constants.js +1 -1
  135. package/frontend/.svelte-kit/output/server/chunks/internal.js +1 -1
  136. package/frontend/.svelte-kit/output/server/chunks/repository.js +21 -17
  137. package/frontend/.svelte-kit/output/server/entries/pages/_layout.svelte.js +2 -0
  138. package/frontend/.svelte-kit/output/server/entries/pages/reports/_id_/_page.svelte.js +11 -2
  139. package/frontend/.svelte-kit/output/server/entries/pages/scheduled-tests/_page.svelte.js +2 -5
  140. package/frontend/.svelte-kit/output/server/entries/pages/settings/_page.svelte.js +300 -253
  141. package/frontend/.svelte-kit/output/server/entries/pages/test-repository/_page.svelte.js +55 -36
  142. package/frontend/.svelte-kit/output/server/entries/pages/test-repository/runs/_id_/_page.svelte.js +3 -6
  143. package/frontend/.svelte-kit/output/server/entries/pages/test-repository/suites/_id_/_page.svelte.js +15 -17
  144. package/frontend/.svelte-kit/output/server/manifest-full.js +1 -1
  145. package/frontend/.svelte-kit/output/server/manifest.js +1 -1
  146. package/frontend/.svelte-kit/output/server/nodes/0.js +2 -2
  147. package/frontend/.svelte-kit/output/server/nodes/1.js +1 -1
  148. package/frontend/.svelte-kit/output/server/nodes/10.js +2 -2
  149. package/frontend/.svelte-kit/output/server/nodes/11.js +2 -2
  150. package/frontend/.svelte-kit/output/server/nodes/12.js +2 -2
  151. package/frontend/.svelte-kit/output/server/nodes/2.js +1 -1
  152. package/frontend/.svelte-kit/output/server/nodes/3.js +1 -1
  153. package/frontend/.svelte-kit/output/server/nodes/4.js +1 -1
  154. package/frontend/.svelte-kit/output/server/nodes/5.js +1 -1
  155. package/frontend/.svelte-kit/output/server/nodes/6.js +2 -2
  156. package/frontend/.svelte-kit/output/server/nodes/7.js +2 -2
  157. package/frontend/.svelte-kit/output/server/nodes/8.js +2 -2
  158. package/frontend/.svelte-kit/output/server/nodes/9.js +1 -1
  159. package/frontend/build/client/_app/immutable/assets/{0.CmJTG5mP.css → 0.BEqlRAtD.css} +1 -1
  160. package/frontend/build/client/_app/immutable/assets/0.BEqlRAtD.css.br +0 -0
  161. package/frontend/build/client/_app/immutable/assets/0.BEqlRAtD.css.gz +0 -0
  162. package/frontend/build/client/_app/immutable/assets/10.DC0wD6w_.css +6 -0
  163. package/frontend/build/client/_app/immutable/assets/10.DC0wD6w_.css.br +0 -0
  164. package/frontend/build/client/_app/immutable/assets/10.DC0wD6w_.css.gz +0 -0
  165. package/frontend/build/client/_app/immutable/assets/12.Dm1nSEgE.css +6 -0
  166. package/frontend/build/client/_app/immutable/assets/12.Dm1nSEgE.css.br +0 -0
  167. package/frontend/build/client/_app/immutable/assets/12.Dm1nSEgE.css.gz +0 -0
  168. package/frontend/build/client/_app/immutable/assets/6.DdQVxyZK.css +6 -0
  169. package/frontend/build/client/_app/immutable/assets/6.DdQVxyZK.css.br +0 -0
  170. package/frontend/build/client/_app/immutable/assets/6.DdQVxyZK.css.gz +0 -0
  171. package/frontend/build/client/_app/immutable/assets/8.DOC8fPSI.css +6 -0
  172. package/frontend/build/client/_app/immutable/assets/8.DOC8fPSI.css.br +0 -0
  173. package/frontend/build/client/_app/immutable/assets/8.DOC8fPSI.css.gz +0 -0
  174. package/frontend/{.svelte-kit/output/client/_app/immutable/assets/Toast.DENwYn3g.css → build/client/_app/immutable/assets/Button.C1zsJAUG.css} +1 -1
  175. package/frontend/build/client/_app/immutable/assets/Button.C1zsJAUG.css.br +0 -0
  176. package/frontend/build/client/_app/immutable/assets/Button.C1zsJAUG.css.gz +0 -0
  177. package/frontend/build/client/_app/immutable/assets/ExportMenu.DsJQ1Qua.css +6 -0
  178. package/frontend/build/client/_app/immutable/assets/ExportMenu.DsJQ1Qua.css.br +0 -0
  179. package/frontend/build/client/_app/immutable/assets/ExportMenu.DsJQ1Qua.css.gz +0 -0
  180. package/frontend/{.svelte-kit/output/server/_app/immutable/assets/_layout.HkPnpRl9.css → build/client/_app/immutable/assets/_layout.B4TT3qNx.css} +1 -1
  181. package/frontend/build/client/_app/immutable/assets/_layout.B4TT3qNx.css.br +0 -0
  182. package/frontend/build/client/_app/immutable/assets/_layout.B4TT3qNx.css.gz +0 -0
  183. package/frontend/build/client/_app/immutable/assets/_page.4bPj83tb.css +6 -0
  184. package/frontend/build/client/_app/immutable/assets/_page.4bPj83tb.css.br +0 -0
  185. package/frontend/build/client/_app/immutable/assets/_page.4bPj83tb.css.gz +0 -0
  186. package/frontend/build/client/_app/immutable/assets/_page.BwgtEi_a.css +6 -0
  187. package/frontend/build/client/_app/immutable/assets/_page.BwgtEi_a.css.br +0 -0
  188. package/frontend/build/client/_app/immutable/assets/_page.BwgtEi_a.css.gz +0 -0
  189. package/frontend/build/client/_app/immutable/assets/_page.DOC8fPSI.css +6 -0
  190. package/frontend/build/client/_app/immutable/assets/_page.DOC8fPSI.css.br +0 -0
  191. package/frontend/build/client/_app/immutable/assets/_page.DOC8fPSI.css.gz +0 -0
  192. package/frontend/build/client/_app/immutable/assets/_page.Dm1nSEgE.css +6 -0
  193. package/frontend/build/client/_app/immutable/assets/_page.Dm1nSEgE.css.br +0 -0
  194. package/frontend/build/client/_app/immutable/assets/_page.Dm1nSEgE.css.gz +0 -0
  195. package/frontend/build/client/_app/immutable/chunks/{C2_rQ4aY.js → 3MBVX4Yb.js} +1 -1
  196. package/frontend/build/client/_app/immutable/chunks/3MBVX4Yb.js.br +0 -0
  197. package/frontend/build/client/_app/immutable/chunks/3MBVX4Yb.js.gz +0 -0
  198. package/frontend/{.svelte-kit/output/client/_app/immutable/chunks/CfkUW1vA.js → build/client/_app/immutable/chunks/6VDMVB5U.js} +1 -1
  199. package/frontend/build/client/_app/immutable/chunks/6VDMVB5U.js.br +0 -0
  200. package/frontend/build/client/_app/immutable/chunks/6VDMVB5U.js.gz +0 -0
  201. package/frontend/{.svelte-kit/output/client/_app/immutable/chunks/B0_HKFXV.js → build/client/_app/immutable/chunks/B2lVGNml.js} +1 -1
  202. package/frontend/build/client/_app/immutable/chunks/B2lVGNml.js.br +0 -0
  203. package/frontend/build/client/_app/immutable/chunks/B2lVGNml.js.gz +0 -0
  204. package/frontend/{.svelte-kit/output/client/_app/immutable/chunks/CboYYxrf.js → build/client/_app/immutable/chunks/BCh31ecr.js} +1 -1
  205. package/frontend/build/client/_app/immutable/chunks/BCh31ecr.js.br +6 -0
  206. package/frontend/build/client/_app/immutable/chunks/BCh31ecr.js.gz +0 -0
  207. package/frontend/build/client/_app/immutable/chunks/BDb8hZHu.js +6 -0
  208. package/frontend/build/client/_app/immutable/chunks/BDb8hZHu.js.br +0 -0
  209. package/frontend/build/client/_app/immutable/chunks/BDb8hZHu.js.gz +0 -0
  210. package/frontend/build/client/_app/immutable/chunks/{CeH-RgMb.js → BIf7lHIm.js} +1 -1
  211. package/frontend/build/client/_app/immutable/chunks/BIf7lHIm.js.br +0 -0
  212. package/frontend/build/client/_app/immutable/chunks/{CeH-RgMb.js.gz → BIf7lHIm.js.gz} +0 -0
  213. package/frontend/build/client/_app/immutable/chunks/BIjh3jiU.js +8 -0
  214. package/frontend/build/client/_app/immutable/chunks/BIjh3jiU.js.br +0 -0
  215. package/frontend/build/client/_app/immutable/chunks/BIjh3jiU.js.gz +0 -0
  216. package/frontend/build/client/_app/immutable/chunks/BTV7EXZb.js +11 -0
  217. package/frontend/build/client/_app/immutable/chunks/BTV7EXZb.js.br +0 -0
  218. package/frontend/build/client/_app/immutable/chunks/BTV7EXZb.js.gz +0 -0
  219. package/frontend/build/client/_app/immutable/chunks/{DAmOMhaf.js → BWGoOYU0.js} +1 -1
  220. package/frontend/build/client/_app/immutable/chunks/BWGoOYU0.js.br +0 -0
  221. package/frontend/build/client/_app/immutable/chunks/BWGoOYU0.js.gz +0 -0
  222. package/frontend/build/client/_app/immutable/chunks/BdBJfKUi.js +6 -0
  223. package/frontend/build/client/_app/immutable/chunks/BdBJfKUi.js.br +0 -0
  224. package/frontend/build/client/_app/immutable/chunks/BdBJfKUi.js.gz +0 -0
  225. package/frontend/{.svelte-kit/output/client/_app/immutable/chunks/BAvTKHxT.js → build/client/_app/immutable/chunks/BnXsGCy3.js} +1 -1
  226. package/frontend/build/client/_app/immutable/chunks/BnXsGCy3.js.br +0 -0
  227. package/frontend/build/client/_app/immutable/chunks/BnXsGCy3.js.gz +0 -0
  228. package/frontend/build/client/_app/immutable/chunks/BtTNggoY.js +8 -0
  229. package/frontend/build/client/_app/immutable/chunks/BtTNggoY.js.br +0 -0
  230. package/frontend/build/client/_app/immutable/chunks/BtTNggoY.js.gz +0 -0
  231. package/frontend/build/client/_app/immutable/chunks/C4Z3p6jE.js +6 -0
  232. package/frontend/build/client/_app/immutable/chunks/C4Z3p6jE.js.br +0 -0
  233. package/frontend/build/client/_app/immutable/chunks/C4Z3p6jE.js.gz +0 -0
  234. package/frontend/build/client/_app/immutable/chunks/CNpD-yNH.js +6 -0
  235. package/frontend/build/client/_app/immutable/chunks/CNpD-yNH.js.br +0 -0
  236. package/frontend/build/client/_app/immutable/chunks/CNpD-yNH.js.gz +0 -0
  237. package/frontend/build/client/_app/immutable/chunks/CUPWZLIN.js +6 -0
  238. package/frontend/build/client/_app/immutable/chunks/CUPWZLIN.js.br +0 -0
  239. package/frontend/build/client/_app/immutable/chunks/CUPWZLIN.js.gz +0 -0
  240. package/frontend/build/client/_app/immutable/chunks/{Ck6C3Fl0.js → CXQhSK1T.js} +1 -1
  241. package/frontend/build/client/_app/immutable/chunks/CXQhSK1T.js.br +0 -0
  242. package/frontend/build/client/_app/immutable/chunks/CXQhSK1T.js.gz +0 -0
  243. package/frontend/{.svelte-kit/output/client/_app/immutable/chunks/C00jnSU2.js → build/client/_app/immutable/chunks/CtgCYzu5.js} +1 -1
  244. package/frontend/build/client/_app/immutable/chunks/CtgCYzu5.js.br +0 -0
  245. package/frontend/build/client/_app/immutable/chunks/CtgCYzu5.js.gz +0 -0
  246. package/frontend/build/client/_app/immutable/chunks/D72giPTu.js +6 -0
  247. package/frontend/build/client/_app/immutable/chunks/D72giPTu.js.br +0 -0
  248. package/frontend/build/client/_app/immutable/chunks/D72giPTu.js.gz +0 -0
  249. package/frontend/build/client/_app/immutable/chunks/{GDjpV3r7.js → DDx3no2z.js} +1 -1
  250. package/frontend/build/client/_app/immutable/chunks/DDx3no2z.js.br +0 -0
  251. package/frontend/build/client/_app/immutable/chunks/DDx3no2z.js.gz +0 -0
  252. package/frontend/build/client/_app/immutable/chunks/D_thT6pZ.js +6 -0
  253. package/frontend/build/client/_app/immutable/chunks/D_thT6pZ.js.br +0 -0
  254. package/frontend/build/client/_app/immutable/chunks/D_thT6pZ.js.gz +0 -0
  255. package/frontend/{.svelte-kit/output/client/_app/immutable/chunks/C8S7qQjQ.js → build/client/_app/immutable/chunks/DeiVAbyn.js} +1 -1
  256. package/frontend/build/client/_app/immutable/chunks/DeiVAbyn.js.br +6 -0
  257. package/frontend/build/client/_app/immutable/chunks/DeiVAbyn.js.gz +0 -0
  258. package/frontend/build/client/_app/immutable/chunks/DiexacSB.js +6 -0
  259. package/frontend/build/client/_app/immutable/chunks/DiexacSB.js.br +0 -0
  260. package/frontend/build/client/_app/immutable/chunks/DiexacSB.js.gz +0 -0
  261. package/frontend/{.svelte-kit/output/client/_app/immutable/chunks/d2K3c8Kx.js → build/client/_app/immutable/chunks/Dlymnwnf.js} +1 -1
  262. package/frontend/build/client/_app/immutable/chunks/Dlymnwnf.js.br +0 -0
  263. package/frontend/build/client/_app/immutable/chunks/Dlymnwnf.js.gz +0 -0
  264. package/frontend/{.svelte-kit/output/client/_app/immutable/chunks/Cgw8TOOy.js → build/client/_app/immutable/chunks/gCgJ3Cwu.js} +1 -1
  265. package/frontend/build/client/_app/immutable/chunks/gCgJ3Cwu.js.br +0 -0
  266. package/frontend/build/client/_app/immutable/chunks/gCgJ3Cwu.js.gz +0 -0
  267. package/frontend/{.svelte-kit/output/client/_app/immutable/chunks/BJ_ypKl3.js → build/client/_app/immutable/chunks/htkwR9b-.js} +1 -1
  268. package/frontend/build/client/_app/immutable/chunks/htkwR9b-.js.br +0 -0
  269. package/frontend/build/client/_app/immutable/chunks/htkwR9b-.js.gz +0 -0
  270. package/frontend/{.svelte-kit/output/client/_app/immutable/chunks/C7uEQ-LH.js → build/client/_app/immutable/chunks/iL4SCrvY.js} +1 -1
  271. package/frontend/build/client/_app/immutable/chunks/iL4SCrvY.js.br +0 -0
  272. package/frontend/build/client/_app/immutable/chunks/iL4SCrvY.js.gz +0 -0
  273. package/frontend/build/client/_app/immutable/chunks/{CoTEzvLa.js → kFFd6Oo5.js} +1 -1
  274. package/frontend/build/client/_app/immutable/chunks/kFFd6Oo5.js.br +0 -0
  275. package/frontend/build/client/_app/immutable/chunks/kFFd6Oo5.js.gz +0 -0
  276. package/frontend/build/client/_app/immutable/chunks/pNZogwkM.js +6 -0
  277. package/frontend/build/client/_app/immutable/chunks/pNZogwkM.js.br +0 -0
  278. package/frontend/build/client/_app/immutable/chunks/pNZogwkM.js.gz +0 -0
  279. package/frontend/build/client/_app/immutable/chunks/{DWzEJYIF.js → prk8PJfA.js} +1 -1
  280. package/frontend/build/client/_app/immutable/chunks/prk8PJfA.js.br +0 -0
  281. package/frontend/build/client/_app/immutable/chunks/prk8PJfA.js.gz +0 -0
  282. package/frontend/build/client/_app/immutable/chunks/qvxlSGpD.js +6 -0
  283. package/frontend/build/client/_app/immutable/chunks/qvxlSGpD.js.br +0 -0
  284. package/frontend/build/client/_app/immutable/chunks/qvxlSGpD.js.gz +0 -0
  285. package/frontend/build/client/_app/immutable/chunks/r_d1Iu0W.js +6 -0
  286. package/frontend/build/client/_app/immutable/chunks/r_d1Iu0W.js.br +0 -0
  287. package/frontend/build/client/_app/immutable/chunks/r_d1Iu0W.js.gz +0 -0
  288. package/frontend/build/client/_app/immutable/chunks/{B00vxw57.js → tsOANt55.js} +1 -1
  289. package/frontend/build/client/_app/immutable/chunks/tsOANt55.js.br +0 -0
  290. package/frontend/build/client/_app/immutable/chunks/tsOANt55.js.gz +0 -0
  291. package/frontend/build/client/_app/immutable/chunks/zXSLr47H.js +6 -0
  292. package/frontend/build/client/_app/immutable/chunks/zXSLr47H.js.br +0 -0
  293. package/frontend/build/client/_app/immutable/chunks/zXSLr47H.js.gz +0 -0
  294. package/frontend/build/client/_app/immutable/entry/app.DgdSwPuN.js +7 -0
  295. package/frontend/build/client/_app/immutable/entry/app.DgdSwPuN.js.br +0 -0
  296. package/frontend/build/client/_app/immutable/entry/app.DgdSwPuN.js.gz +0 -0
  297. package/frontend/{.svelte-kit/output/client/_app/immutable/entry/start.BXOLyf9R.js → build/client/_app/immutable/entry/start.D_lhI48n.js} +1 -1
  298. package/frontend/build/client/_app/immutable/entry/{start.BXOLyf9R.js.br → start.D_lhI48n.js.br} +1 -1
  299. package/frontend/build/client/_app/immutable/entry/{start.BXOLyf9R.js.gz → start.D_lhI48n.js.gz} +0 -0
  300. package/frontend/build/client/_app/immutable/nodes/0.D2F-i1I5.js +6 -0
  301. package/frontend/build/client/_app/immutable/nodes/0.D2F-i1I5.js.br +0 -0
  302. package/frontend/build/client/_app/immutable/nodes/0.D2F-i1I5.js.gz +0 -0
  303. package/frontend/build/client/_app/immutable/nodes/{1.BI97il3r.js → 1.JIF2GfHq.js} +1 -1
  304. package/frontend/build/client/_app/immutable/nodes/1.JIF2GfHq.js.br +5 -0
  305. package/frontend/build/client/_app/immutable/nodes/1.JIF2GfHq.js.gz +0 -0
  306. package/frontend/build/client/_app/immutable/nodes/10.A0AHLfOt.js +6 -0
  307. package/frontend/build/client/_app/immutable/nodes/10.A0AHLfOt.js.br +0 -0
  308. package/frontend/build/client/_app/immutable/nodes/10.A0AHLfOt.js.gz +0 -0
  309. package/frontend/build/client/_app/immutable/nodes/11.CVSPxlqY.js +6 -0
  310. package/frontend/build/client/_app/immutable/nodes/11.CVSPxlqY.js.br +0 -0
  311. package/frontend/build/client/_app/immutable/nodes/11.CVSPxlqY.js.gz +0 -0
  312. package/frontend/build/client/_app/immutable/nodes/12.CnAR0AQb.js +6 -0
  313. package/frontend/build/client/_app/immutable/nodes/12.CnAR0AQb.js.br +0 -0
  314. package/frontend/build/client/_app/immutable/nodes/12.CnAR0AQb.js.gz +0 -0
  315. package/frontend/build/client/_app/immutable/nodes/{2.Dr6yugXz.js → 2.Bno00fr8.js} +1 -1
  316. package/frontend/build/client/_app/immutable/nodes/2.Bno00fr8.js.br +0 -0
  317. package/frontend/build/client/_app/immutable/nodes/2.Bno00fr8.js.gz +0 -0
  318. package/frontend/build/client/_app/immutable/nodes/3.B6KwaCsM.js +6 -0
  319. package/frontend/build/client/_app/immutable/nodes/3.B6KwaCsM.js.br +0 -0
  320. package/frontend/build/client/_app/immutable/nodes/3.B6KwaCsM.js.gz +0 -0
  321. package/frontend/build/client/_app/immutable/nodes/{4.DDADylfF.js → 4.BX70m3v6.js} +1 -1
  322. package/frontend/build/client/_app/immutable/nodes/4.BX70m3v6.js.br +0 -0
  323. package/frontend/build/client/_app/immutable/nodes/4.BX70m3v6.js.gz +0 -0
  324. package/frontend/build/client/_app/immutable/nodes/5.UIFh06yr.js +6 -0
  325. package/frontend/build/client/_app/immutable/nodes/5.UIFh06yr.js.br +0 -0
  326. package/frontend/build/client/_app/immutable/nodes/5.UIFh06yr.js.gz +0 -0
  327. package/frontend/build/client/_app/immutable/nodes/6.DZ1OV24V.js +10 -0
  328. package/frontend/build/client/_app/immutable/nodes/6.DZ1OV24V.js.br +0 -0
  329. package/frontend/build/client/_app/immutable/nodes/6.DZ1OV24V.js.gz +0 -0
  330. package/frontend/build/client/_app/immutable/nodes/7.B_XntBZ1.js +6 -0
  331. package/frontend/build/client/_app/immutable/nodes/7.B_XntBZ1.js.br +0 -0
  332. package/frontend/build/client/_app/immutable/nodes/7.B_XntBZ1.js.gz +0 -0
  333. package/frontend/build/client/_app/immutable/nodes/8.CVHgJw-E.js +9 -0
  334. package/frontend/build/client/_app/immutable/nodes/8.CVHgJw-E.js.br +0 -0
  335. package/frontend/build/client/_app/immutable/nodes/8.CVHgJw-E.js.gz +0 -0
  336. package/frontend/build/client/_app/immutable/nodes/{9.BOK0GNF_.js → 9.DDDQPmMj.js} +1 -1
  337. package/frontend/build/client/_app/immutable/nodes/9.DDDQPmMj.js.br +0 -0
  338. package/frontend/build/client/_app/immutable/nodes/9.DDDQPmMj.js.gz +0 -0
  339. package/frontend/build/client/_app/version.json +1 -1
  340. package/frontend/build/client/_app/version.json.br +0 -0
  341. package/frontend/build/client/_app/version.json.gz +0 -0
  342. package/frontend/build/server/chunks/0-38cvTs44.js +22 -0
  343. package/frontend/build/server/chunks/0-38cvTs44.js.map +5 -0
  344. package/frontend/build/server/chunks/{1-B3HpigzL.js → 1-CcYsajvR.js} +2 -2
  345. package/frontend/build/server/chunks/{1-B3HpigzL.js.map → 1-CcYsajvR.js.map} +1 -1
  346. package/frontend/build/server/chunks/10-CFR2MUJt.js +14 -0
  347. package/frontend/build/server/chunks/10-CFR2MUJt.js.map +5 -0
  348. package/frontend/build/server/chunks/11-BmkiRQ9D.js +14 -0
  349. package/frontend/build/server/chunks/11-BmkiRQ9D.js.map +5 -0
  350. package/frontend/build/server/chunks/12-BphJAwnL.js +14 -0
  351. package/frontend/build/server/chunks/12-BphJAwnL.js.map +5 -0
  352. package/frontend/build/server/chunks/2-Dalgd2D4.js +14 -0
  353. package/frontend/build/server/chunks/{2-DioMbKbC.js.map → 2-Dalgd2D4.js.map} +1 -1
  354. package/frontend/build/server/chunks/3-BvsmdXCj.js +14 -0
  355. package/frontend/build/server/chunks/{3-CgiZg_9v.js.map → 3-BvsmdXCj.js.map} +1 -1
  356. package/frontend/build/server/chunks/4-CN18WCEn.js +14 -0
  357. package/frontend/build/server/chunks/{4-BM-49dbE.js.map → 4-CN18WCEn.js.map} +1 -1
  358. package/frontend/build/server/chunks/5-BwLYF3xL.js +14 -0
  359. package/frontend/build/server/chunks/{5-Brx_KOzy.js.map → 5-BwLYF3xL.js.map} +1 -1
  360. package/frontend/build/server/chunks/6-DTM2N_k3.js +14 -0
  361. package/frontend/build/server/chunks/6-DTM2N_k3.js.map +5 -0
  362. package/frontend/build/server/chunks/7-DTNbErSe.js +14 -0
  363. package/frontend/build/server/chunks/7-DTNbErSe.js.map +5 -0
  364. package/frontend/build/server/chunks/8-BfCbb7pl.js +14 -0
  365. package/frontend/build/server/chunks/8-BfCbb7pl.js.map +5 -0
  366. package/frontend/build/server/chunks/9-CQKepnMw.js +14 -0
  367. package/frontend/build/server/chunks/{9-BMAKiAem.js.map → 9-CQKepnMw.js.map} +1 -1
  368. package/frontend/build/server/chunks/{Toast-CHB87F8k.js → Button-B9h9viZX.js} +3 -17
  369. package/frontend/build/server/chunks/Button-B9h9viZX.js.map +5 -0
  370. package/frontend/build/server/chunks/{ConfirmModal-CtGWHl0v.js → ConfirmModal-C6Cz898n.js} +2 -2
  371. package/frontend/build/server/chunks/{ConfirmModal-CtGWHl0v.js.map → ConfirmModal-C6Cz898n.js.map} +1 -1
  372. package/frontend/build/server/chunks/{EmptyState-Zpm9tJe1.js → EmptyState-CRtsJIv6.js} +2 -2
  373. package/frontend/build/server/chunks/{EmptyState-Zpm9tJe1.js.map → EmptyState-CRtsJIv6.js.map} +1 -1
  374. package/frontend/build/server/chunks/{_layout.svelte-C_6nsIpT.js → _layout.svelte-B8vZGefg.js} +6 -7
  375. package/frontend/build/server/chunks/_layout.svelte-B8vZGefg.js.map +5 -0
  376. package/frontend/build/server/chunks/{_page.svelte-CTebwzwy.js → _page.svelte-BrnXHxJ4.js} +14 -7
  377. package/frontend/build/server/chunks/_page.svelte-BrnXHxJ4.js.map +5 -0
  378. package/frontend/build/server/chunks/_page.svelte-C2WqvInJ.js +898 -0
  379. package/frontend/build/server/chunks/_page.svelte-C2WqvInJ.js.map +5 -0
  380. package/frontend/build/server/chunks/{_page.svelte-Bamoq11m.js → _page.svelte-CP6jA1ex.js} +6 -6
  381. package/frontend/build/server/chunks/{_page.svelte-Bamoq11m.js.map → _page.svelte-CP6jA1ex.js.map} +1 -1
  382. package/frontend/build/server/chunks/{_page.svelte-CbTrCeeX.js → _page.svelte-D6b-klLK.js} +9 -9
  383. package/frontend/build/server/chunks/{_page.svelte-CbTrCeeX.js.map → _page.svelte-D6b-klLK.js.map} +1 -1
  384. package/frontend/build/server/chunks/{_page.svelte-CmJKt3-H.js → _page.svelte-DBjTpquQ.js} +18 -21
  385. package/frontend/build/server/chunks/_page.svelte-DBjTpquQ.js.map +5 -0
  386. package/frontend/build/server/chunks/{_page.svelte-DHZhNTPg.js → _page.svelte-DM1_y4Oq.js} +60 -42
  387. package/frontend/build/server/chunks/_page.svelte-DM1_y4Oq.js.map +5 -0
  388. package/frontend/build/server/chunks/{_page.svelte-DObTHQe2.js → _page.svelte-Mo-O3HJ5.js} +7 -7
  389. package/frontend/build/server/chunks/{_page.svelte-DObTHQe2.js.map → _page.svelte-Mo-O3HJ5.js.map} +1 -1
  390. package/frontend/build/server/chunks/{_page.svelte-CAlhaCzS.js → _page.svelte-mZF4NrfU.js} +9 -12
  391. package/frontend/build/server/chunks/_page.svelte-mZF4NrfU.js.map +5 -0
  392. package/frontend/build/server/chunks/{_page.svelte-BNwU9zza.js → _page.svelte-nrlgIFaU.js} +8 -11
  393. package/frontend/build/server/chunks/_page.svelte-nrlgIFaU.js.map +5 -0
  394. package/frontend/build/server/chunks/{common-DYYTVHPw.js → common-DS-Z0UFe.js} +7 -2
  395. package/frontend/build/server/chunks/common-DS-Z0UFe.js.map +5 -0
  396. package/frontend/build/server/chunks/{constants-BYVkTZ-O.js → constants-OmHysD51.js} +2 -2
  397. package/frontend/build/server/chunks/{constants-BYVkTZ-O.js.map → constants-OmHysD51.js.map} +1 -1
  398. package/frontend/build/server/chunks/{format-2pqy61MU.js → format-Bqb0as15.js} +2 -2
  399. package/frontend/build/server/chunks/{format-2pqy61MU.js.map → format-Bqb0as15.js.map} +1 -1
  400. package/frontend/build/server/chunks/{reports-WuXG6tj3.js → reports-chCHCDzB.js} +2 -2
  401. package/frontend/build/server/chunks/{reports-WuXG6tj3.js.map → reports-chCHCDzB.js.map} +1 -1
  402. package/frontend/build/server/chunks/{reports2-CCpI0xkQ.js → reports2-Dr3zu9CD.js} +2 -2
  403. package/frontend/build/server/chunks/{reports2-CCpI0xkQ.js.map → reports2-Dr3zu9CD.js.map} +1 -1
  404. package/frontend/build/server/chunks/{repository-TYWcFuWP.js → repository-DSsWVKEb.js} +5 -3
  405. package/frontend/build/server/chunks/repository-DSsWVKEb.js.map +5 -0
  406. package/frontend/build/server/chunks/{runner-D5yCWOsy.js → runner-aaUtOqck.js} +3 -3
  407. package/frontend/build/server/chunks/{runner-D5yCWOsy.js.map → runner-aaUtOqck.js.map} +1 -1
  408. package/frontend/build/server/index.js +1 -1
  409. package/frontend/build/server/index.js.map +1 -1
  410. package/frontend/build/server/manifest.js +14 -14
  411. package/frontend/build/server/manifest.js.map +1 -1
  412. package/frontend/package-lock.json +14 -0
  413. package/frontend/package.json +2 -0
  414. package/frontend/src/lib/api/reports.js +7 -0
  415. package/frontend/src/lib/api/repository.js +28 -0
  416. package/frontend/src/lib/components/layout/RunnerPanel.svelte +2 -1
  417. package/frontend/src/lib/components/reports/RecordingPlayer.svelte +172 -2
  418. package/frontend/src/lib/components/ui/ExportMenu.svelte +154 -0
  419. package/frontend/src/lib/components/ui/NotificationStack.svelte +177 -0
  420. package/frontend/src/lib/constants.js +1 -1
  421. package/frontend/src/lib/copy/common.js +9 -0
  422. package/frontend/src/lib/copy/reports.js +20 -0
  423. package/frontend/src/lib/copy/repository.js +4 -0
  424. package/frontend/src/lib/copy/settings.js +15 -0
  425. package/frontend/src/lib/stores/notifications.js +23 -0
  426. package/frontend/src/lib/stores/replayVideo.js +51 -0
  427. package/frontend/src/lib/utils/download.js +28 -0
  428. package/frontend/src/lib/utils/replayCapture.js +91 -0
  429. package/frontend/src/lib/utils/replayVideo.js +111 -0
  430. package/frontend/src/routes/+layout.svelte +2 -0
  431. package/frontend/src/routes/reports/[id]/+page.svelte +38 -3
  432. package/frontend/src/routes/scheduled-tests/+page.svelte +9 -17
  433. package/frontend/src/routes/settings/+page.svelte +140 -56
  434. package/frontend/src/routes/test-repository/+page.svelte +44 -16
  435. package/frontend/src/routes/test-repository/runs/[id]/+page.svelte +14 -23
  436. package/frontend/src/routes/test-repository/suites/[id]/+page.svelte +38 -22
  437. package/package.json +1 -1
  438. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.5IJAEAjc.css +0 -6
  439. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.B9H_IpSU.css +0 -6
  440. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.COwSGmmy.css +0 -6
  441. package/frontend/.svelte-kit/adapter-node/_app/immutable/assets/_page.Dp6vaGRc.css +0 -6
  442. package/frontend/.svelte-kit/generated/client/nodes/13.js +0 -6
  443. package/frontend/.svelte-kit/output/client/_app/immutable/assets/10.CdaQUS6U.css +0 -6
  444. package/frontend/.svelte-kit/output/client/_app/immutable/assets/12.B9H_IpSU.css +0 -6
  445. package/frontend/.svelte-kit/output/client/_app/immutable/assets/6.BN5XbLUX.css +0 -6
  446. package/frontend/.svelte-kit/output/client/_app/immutable/assets/8.COwSGmmy.css +0 -6
  447. package/frontend/.svelte-kit/output/client/_app/immutable/assets/Button.FOWXxvc7.css +0 -6
  448. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.5IJAEAjc.css +0 -6
  449. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.B9H_IpSU.css +0 -6
  450. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.COwSGmmy.css +0 -6
  451. package/frontend/.svelte-kit/output/client/_app/immutable/assets/_page.Dp6vaGRc.css +0 -6
  452. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/2-KCkWZO.js +0 -6
  453. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/BbYY_TzB.js +0 -6
  454. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/Beula-TH.js +0 -11
  455. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CZWchtW8.js +0 -6
  456. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/CegSvdD-.js +0 -6
  457. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/Cfmt4_tJ.js +0 -6
  458. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DB3YXVFP.js +0 -6
  459. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DgQy0SB2.js +0 -6
  460. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DiVmSEJa.js +0 -8
  461. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/Dl-F6VCo.js +0 -6
  462. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/DuAVKaL3.js +0 -6
  463. package/frontend/.svelte-kit/output/client/_app/immutable/chunks/K1h4wprj.js +0 -6
  464. package/frontend/.svelte-kit/output/client/_app/immutable/entry/app.DRjRA71t.js +0 -7
  465. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/0.CRb8Nyce.js +0 -6
  466. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/10.kyazROhw.js +0 -6
  467. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/11.B3vMgT2v.js +0 -6
  468. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/12.DkPcIrgW.js +0 -6
  469. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/3.DUiL7fb5.js +0 -6
  470. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/5.N4CeDeoD.js +0 -6
  471. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/6.DWd93Bv5.js +0 -10
  472. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/7.c8d1cQQM.js +0 -6
  473. package/frontend/.svelte-kit/output/client/_app/immutable/nodes/8.Cvd3MauU.js +0 -9
  474. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.5IJAEAjc.css +0 -6
  475. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.B9H_IpSU.css +0 -6
  476. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.COwSGmmy.css +0 -6
  477. package/frontend/.svelte-kit/output/server/_app/immutable/assets/_page.Dp6vaGRc.css +0 -6
  478. package/frontend/build/client/_app/immutable/assets/0.CmJTG5mP.css.br +0 -0
  479. package/frontend/build/client/_app/immutable/assets/0.CmJTG5mP.css.gz +0 -0
  480. package/frontend/build/client/_app/immutable/assets/10.CdaQUS6U.css +0 -6
  481. package/frontend/build/client/_app/immutable/assets/10.CdaQUS6U.css.br +0 -0
  482. package/frontend/build/client/_app/immutable/assets/10.CdaQUS6U.css.gz +0 -0
  483. package/frontend/build/client/_app/immutable/assets/12.B9H_IpSU.css +0 -6
  484. package/frontend/build/client/_app/immutable/assets/12.B9H_IpSU.css.br +0 -0
  485. package/frontend/build/client/_app/immutable/assets/12.B9H_IpSU.css.gz +0 -0
  486. package/frontend/build/client/_app/immutable/assets/6.BN5XbLUX.css +0 -6
  487. package/frontend/build/client/_app/immutable/assets/6.BN5XbLUX.css.br +0 -0
  488. package/frontend/build/client/_app/immutable/assets/6.BN5XbLUX.css.gz +0 -0
  489. package/frontend/build/client/_app/immutable/assets/8.COwSGmmy.css +0 -6
  490. package/frontend/build/client/_app/immutable/assets/8.COwSGmmy.css.br +0 -0
  491. package/frontend/build/client/_app/immutable/assets/8.COwSGmmy.css.gz +0 -0
  492. package/frontend/build/client/_app/immutable/assets/Button.FOWXxvc7.css +0 -6
  493. package/frontend/build/client/_app/immutable/assets/Button.FOWXxvc7.css.br +0 -0
  494. package/frontend/build/client/_app/immutable/assets/Button.FOWXxvc7.css.gz +0 -0
  495. package/frontend/build/client/_app/immutable/assets/Toast.DENwYn3g.css.br +0 -0
  496. package/frontend/build/client/_app/immutable/assets/Toast.DENwYn3g.css.gz +0 -0
  497. package/frontend/build/client/_app/immutable/assets/_layout.HkPnpRl9.css.br +0 -0
  498. package/frontend/build/client/_app/immutable/assets/_layout.HkPnpRl9.css.gz +0 -0
  499. package/frontend/build/client/_app/immutable/assets/_page.5IJAEAjc.css +0 -6
  500. package/frontend/build/client/_app/immutable/assets/_page.5IJAEAjc.css.br +0 -0
  501. package/frontend/build/client/_app/immutable/assets/_page.5IJAEAjc.css.gz +0 -0
  502. package/frontend/build/client/_app/immutable/assets/_page.B9H_IpSU.css +0 -6
  503. package/frontend/build/client/_app/immutable/assets/_page.B9H_IpSU.css.br +0 -0
  504. package/frontend/build/client/_app/immutable/assets/_page.B9H_IpSU.css.gz +0 -0
  505. package/frontend/build/client/_app/immutable/assets/_page.COwSGmmy.css +0 -6
  506. package/frontend/build/client/_app/immutable/assets/_page.COwSGmmy.css.br +0 -0
  507. package/frontend/build/client/_app/immutable/assets/_page.COwSGmmy.css.gz +0 -0
  508. package/frontend/build/client/_app/immutable/assets/_page.Dp6vaGRc.css +0 -6
  509. package/frontend/build/client/_app/immutable/assets/_page.Dp6vaGRc.css.br +0 -0
  510. package/frontend/build/client/_app/immutable/assets/_page.Dp6vaGRc.css.gz +0 -0
  511. package/frontend/build/client/_app/immutable/chunks/2-KCkWZO.js +0 -6
  512. package/frontend/build/client/_app/immutable/chunks/2-KCkWZO.js.br +0 -0
  513. package/frontend/build/client/_app/immutable/chunks/2-KCkWZO.js.gz +0 -0
  514. package/frontend/build/client/_app/immutable/chunks/B00vxw57.js.br +0 -0
  515. package/frontend/build/client/_app/immutable/chunks/B00vxw57.js.gz +0 -0
  516. package/frontend/build/client/_app/immutable/chunks/B0_HKFXV.js.br +0 -0
  517. package/frontend/build/client/_app/immutable/chunks/B0_HKFXV.js.gz +0 -0
  518. package/frontend/build/client/_app/immutable/chunks/BAvTKHxT.js.br +0 -0
  519. package/frontend/build/client/_app/immutable/chunks/BAvTKHxT.js.gz +0 -0
  520. package/frontend/build/client/_app/immutable/chunks/BJ_ypKl3.js.br +0 -0
  521. package/frontend/build/client/_app/immutable/chunks/BJ_ypKl3.js.gz +0 -0
  522. package/frontend/build/client/_app/immutable/chunks/BbYY_TzB.js +0 -6
  523. package/frontend/build/client/_app/immutable/chunks/BbYY_TzB.js.br +0 -0
  524. package/frontend/build/client/_app/immutable/chunks/BbYY_TzB.js.gz +0 -0
  525. package/frontend/build/client/_app/immutable/chunks/Beula-TH.js +0 -11
  526. package/frontend/build/client/_app/immutable/chunks/Beula-TH.js.br +0 -0
  527. package/frontend/build/client/_app/immutable/chunks/Beula-TH.js.gz +0 -0
  528. package/frontend/build/client/_app/immutable/chunks/C00jnSU2.js.br +0 -0
  529. package/frontend/build/client/_app/immutable/chunks/C00jnSU2.js.gz +0 -0
  530. package/frontend/build/client/_app/immutable/chunks/C2_rQ4aY.js.br +0 -0
  531. package/frontend/build/client/_app/immutable/chunks/C2_rQ4aY.js.gz +0 -0
  532. package/frontend/build/client/_app/immutable/chunks/C7uEQ-LH.js.br +0 -0
  533. package/frontend/build/client/_app/immutable/chunks/C7uEQ-LH.js.gz +0 -0
  534. package/frontend/build/client/_app/immutable/chunks/C8S7qQjQ.js.br +0 -0
  535. package/frontend/build/client/_app/immutable/chunks/C8S7qQjQ.js.gz +0 -0
  536. package/frontend/build/client/_app/immutable/chunks/CZWchtW8.js +0 -6
  537. package/frontend/build/client/_app/immutable/chunks/CZWchtW8.js.br +0 -0
  538. package/frontend/build/client/_app/immutable/chunks/CZWchtW8.js.gz +0 -0
  539. package/frontend/build/client/_app/immutable/chunks/CboYYxrf.js.br +0 -6
  540. package/frontend/build/client/_app/immutable/chunks/CboYYxrf.js.gz +0 -0
  541. package/frontend/build/client/_app/immutable/chunks/CeH-RgMb.js.br +0 -0
  542. package/frontend/build/client/_app/immutable/chunks/CegSvdD-.js +0 -6
  543. package/frontend/build/client/_app/immutable/chunks/CegSvdD-.js.br +0 -0
  544. package/frontend/build/client/_app/immutable/chunks/CegSvdD-.js.gz +0 -0
  545. package/frontend/build/client/_app/immutable/chunks/CfkUW1vA.js.br +0 -0
  546. package/frontend/build/client/_app/immutable/chunks/CfkUW1vA.js.gz +0 -0
  547. package/frontend/build/client/_app/immutable/chunks/Cfmt4_tJ.js +0 -6
  548. package/frontend/build/client/_app/immutable/chunks/Cfmt4_tJ.js.br +0 -0
  549. package/frontend/build/client/_app/immutable/chunks/Cfmt4_tJ.js.gz +0 -0
  550. package/frontend/build/client/_app/immutable/chunks/Cgw8TOOy.js.br +0 -0
  551. package/frontend/build/client/_app/immutable/chunks/Cgw8TOOy.js.gz +0 -0
  552. package/frontend/build/client/_app/immutable/chunks/Ck6C3Fl0.js.br +0 -0
  553. package/frontend/build/client/_app/immutable/chunks/Ck6C3Fl0.js.gz +0 -0
  554. package/frontend/build/client/_app/immutable/chunks/CoTEzvLa.js.br +0 -0
  555. package/frontend/build/client/_app/immutable/chunks/CoTEzvLa.js.gz +0 -0
  556. package/frontend/build/client/_app/immutable/chunks/DAmOMhaf.js.br +0 -0
  557. package/frontend/build/client/_app/immutable/chunks/DAmOMhaf.js.gz +0 -0
  558. package/frontend/build/client/_app/immutable/chunks/DB3YXVFP.js +0 -6
  559. package/frontend/build/client/_app/immutable/chunks/DB3YXVFP.js.br +0 -0
  560. package/frontend/build/client/_app/immutable/chunks/DB3YXVFP.js.gz +0 -0
  561. package/frontend/build/client/_app/immutable/chunks/DWzEJYIF.js.br +0 -7
  562. package/frontend/build/client/_app/immutable/chunks/DWzEJYIF.js.gz +0 -0
  563. package/frontend/build/client/_app/immutable/chunks/DgQy0SB2.js +0 -6
  564. package/frontend/build/client/_app/immutable/chunks/DgQy0SB2.js.br +0 -0
  565. package/frontend/build/client/_app/immutable/chunks/DgQy0SB2.js.gz +0 -0
  566. package/frontend/build/client/_app/immutable/chunks/DiVmSEJa.js +0 -8
  567. package/frontend/build/client/_app/immutable/chunks/DiVmSEJa.js.br +0 -0
  568. package/frontend/build/client/_app/immutable/chunks/DiVmSEJa.js.gz +0 -0
  569. package/frontend/build/client/_app/immutable/chunks/Dl-F6VCo.js +0 -6
  570. package/frontend/build/client/_app/immutable/chunks/Dl-F6VCo.js.br +0 -0
  571. package/frontend/build/client/_app/immutable/chunks/Dl-F6VCo.js.gz +0 -0
  572. package/frontend/build/client/_app/immutable/chunks/DuAVKaL3.js +0 -6
  573. package/frontend/build/client/_app/immutable/chunks/DuAVKaL3.js.br +0 -0
  574. package/frontend/build/client/_app/immutable/chunks/DuAVKaL3.js.gz +0 -0
  575. package/frontend/build/client/_app/immutable/chunks/GDjpV3r7.js.br +0 -0
  576. package/frontend/build/client/_app/immutable/chunks/GDjpV3r7.js.gz +0 -0
  577. package/frontend/build/client/_app/immutable/chunks/K1h4wprj.js +0 -6
  578. package/frontend/build/client/_app/immutable/chunks/K1h4wprj.js.br +0 -0
  579. package/frontend/build/client/_app/immutable/chunks/K1h4wprj.js.gz +0 -0
  580. package/frontend/build/client/_app/immutable/chunks/d2K3c8Kx.js.br +0 -0
  581. package/frontend/build/client/_app/immutable/chunks/d2K3c8Kx.js.gz +0 -0
  582. package/frontend/build/client/_app/immutable/entry/app.DRjRA71t.js +0 -7
  583. package/frontend/build/client/_app/immutable/entry/app.DRjRA71t.js.br +0 -0
  584. package/frontend/build/client/_app/immutable/entry/app.DRjRA71t.js.gz +0 -0
  585. package/frontend/build/client/_app/immutable/nodes/0.CRb8Nyce.js +0 -6
  586. package/frontend/build/client/_app/immutable/nodes/0.CRb8Nyce.js.br +0 -0
  587. package/frontend/build/client/_app/immutable/nodes/0.CRb8Nyce.js.gz +0 -0
  588. package/frontend/build/client/_app/immutable/nodes/1.BI97il3r.js.br +0 -0
  589. package/frontend/build/client/_app/immutable/nodes/1.BI97il3r.js.gz +0 -0
  590. package/frontend/build/client/_app/immutable/nodes/10.kyazROhw.js +0 -6
  591. package/frontend/build/client/_app/immutable/nodes/10.kyazROhw.js.br +0 -0
  592. package/frontend/build/client/_app/immutable/nodes/10.kyazROhw.js.gz +0 -0
  593. package/frontend/build/client/_app/immutable/nodes/11.B3vMgT2v.js +0 -6
  594. package/frontend/build/client/_app/immutable/nodes/11.B3vMgT2v.js.br +0 -0
  595. package/frontend/build/client/_app/immutable/nodes/11.B3vMgT2v.js.gz +0 -0
  596. package/frontend/build/client/_app/immutable/nodes/12.DkPcIrgW.js +0 -6
  597. package/frontend/build/client/_app/immutable/nodes/12.DkPcIrgW.js.br +0 -0
  598. package/frontend/build/client/_app/immutable/nodes/12.DkPcIrgW.js.gz +0 -0
  599. package/frontend/build/client/_app/immutable/nodes/2.Dr6yugXz.js.br +0 -0
  600. package/frontend/build/client/_app/immutable/nodes/2.Dr6yugXz.js.gz +0 -0
  601. package/frontend/build/client/_app/immutable/nodes/3.DUiL7fb5.js +0 -6
  602. package/frontend/build/client/_app/immutable/nodes/3.DUiL7fb5.js.br +0 -0
  603. package/frontend/build/client/_app/immutable/nodes/3.DUiL7fb5.js.gz +0 -0
  604. package/frontend/build/client/_app/immutable/nodes/4.DDADylfF.js.br +0 -0
  605. package/frontend/build/client/_app/immutable/nodes/4.DDADylfF.js.gz +0 -0
  606. package/frontend/build/client/_app/immutable/nodes/5.N4CeDeoD.js +0 -6
  607. package/frontend/build/client/_app/immutable/nodes/5.N4CeDeoD.js.br +0 -0
  608. package/frontend/build/client/_app/immutable/nodes/5.N4CeDeoD.js.gz +0 -0
  609. package/frontend/build/client/_app/immutable/nodes/6.DWd93Bv5.js +0 -10
  610. package/frontend/build/client/_app/immutable/nodes/6.DWd93Bv5.js.br +0 -0
  611. package/frontend/build/client/_app/immutable/nodes/6.DWd93Bv5.js.gz +0 -0
  612. package/frontend/build/client/_app/immutable/nodes/7.c8d1cQQM.js +0 -6
  613. package/frontend/build/client/_app/immutable/nodes/7.c8d1cQQM.js.br +0 -0
  614. package/frontend/build/client/_app/immutable/nodes/7.c8d1cQQM.js.gz +0 -0
  615. package/frontend/build/client/_app/immutable/nodes/8.Cvd3MauU.js +0 -9
  616. package/frontend/build/client/_app/immutable/nodes/8.Cvd3MauU.js.br +0 -0
  617. package/frontend/build/client/_app/immutable/nodes/8.Cvd3MauU.js.gz +0 -0
  618. package/frontend/build/client/_app/immutable/nodes/9.BOK0GNF_.js.br +0 -0
  619. package/frontend/build/client/_app/immutable/nodes/9.BOK0GNF_.js.gz +0 -0
  620. package/frontend/build/server/chunks/0-_rljpEay.js +0 -22
  621. package/frontend/build/server/chunks/0-_rljpEay.js.map +0 -5
  622. package/frontend/build/server/chunks/10-BQBBYpxV.js +0 -14
  623. package/frontend/build/server/chunks/10-BQBBYpxV.js.map +0 -5
  624. package/frontend/build/server/chunks/11-10xw0TIP.js +0 -14
  625. package/frontend/build/server/chunks/11-10xw0TIP.js.map +0 -5
  626. package/frontend/build/server/chunks/12-CZ3_Uw3c.js +0 -14
  627. package/frontend/build/server/chunks/12-CZ3_Uw3c.js.map +0 -5
  628. package/frontend/build/server/chunks/2-DioMbKbC.js +0 -14
  629. package/frontend/build/server/chunks/3-CgiZg_9v.js +0 -14
  630. package/frontend/build/server/chunks/4-BM-49dbE.js +0 -14
  631. package/frontend/build/server/chunks/5-Brx_KOzy.js +0 -14
  632. package/frontend/build/server/chunks/6-C5swINND.js +0 -14
  633. package/frontend/build/server/chunks/6-C5swINND.js.map +0 -5
  634. package/frontend/build/server/chunks/7-B8e4y3CB.js +0 -14
  635. package/frontend/build/server/chunks/7-B8e4y3CB.js.map +0 -5
  636. package/frontend/build/server/chunks/8-BZtkZMIZ.js +0 -14
  637. package/frontend/build/server/chunks/8-BZtkZMIZ.js.map +0 -5
  638. package/frontend/build/server/chunks/9-BMAKiAem.js +0 -14
  639. package/frontend/build/server/chunks/Toast-CHB87F8k.js.map +0 -5
  640. package/frontend/build/server/chunks/_layout.svelte-C_6nsIpT.js.map +0 -5
  641. package/frontend/build/server/chunks/_page.svelte-BNwU9zza.js.map +0 -5
  642. package/frontend/build/server/chunks/_page.svelte-CAlhaCzS.js.map +0 -5
  643. package/frontend/build/server/chunks/_page.svelte-CTebwzwy.js.map +0 -5
  644. package/frontend/build/server/chunks/_page.svelte-CmJKt3-H.js.map +0 -5
  645. package/frontend/build/server/chunks/_page.svelte-DHZhNTPg.js.map +0 -5
  646. package/frontend/build/server/chunks/_page.svelte-ywHy9KCh.js +0 -852
  647. package/frontend/build/server/chunks/_page.svelte-ywHy9KCh.js.map +0 -5
  648. package/frontend/build/server/chunks/common-DYYTVHPw.js.map +0 -5
  649. package/frontend/build/server/chunks/repository-TYWcFuWP.js.map +0 -5
  650. package/frontend/src/lib/components/ui/Toast.svelte +0 -24
@@ -0,0 +1,177 @@
1
+ <!--
2
+ * This file is part of Plum.
3
+ * Licensed under the MIT License. See LICENSE file in the project root for details.
4
+ -->
5
+
6
+ <script>
7
+ import { fly } from 'svelte/transition';
8
+ import { flip } from 'svelte/animate';
9
+ import { notifications, dismissNotification } from '$lib/stores/notifications';
10
+ import { replayVideoJob, cancelReplayVideo, dismissReplayVideo } from '$lib/stores/replayVideo';
11
+ import { CANCEL_LABEL } from '$lib/copy/common';
12
+ import {
13
+ DISMISS_LABEL,
14
+ VIDEO_TASK_RENDERING,
15
+ VIDEO_TASK_DONE,
16
+ VIDEO_TASK_FAILED,
17
+ VIDEO_TASK_EXPERIMENTAL
18
+ } from '$lib/copy/reports';
19
+
20
+ $: job = $replayVideoJob;
21
+ $: pct = Math.round((job?.progress ?? 0) * 100);
22
+ </script>
23
+
24
+ <div class="stack">
25
+ {#if job}
26
+ <div class="card" transition:fly={{ y: 12, duration: 200 }}>
27
+ {#if job.status === 'rendering'}
28
+ <div class="row">
29
+ <span class="spinner" aria-hidden="true"></span>
30
+ <span class="msg">{VIDEO_TASK_RENDERING} {pct}%</span>
31
+ <button class="link text" on:click={cancelReplayVideo}>{CANCEL_LABEL}</button>
32
+ </div>
33
+ <div class="bar"><span class="fill" style="width:{pct}%"></span></div>
34
+ <p class="sub">{VIDEO_TASK_EXPERIMENTAL}</p>
35
+ {:else if job.status === 'done'}
36
+ <div class="row">
37
+ <span class="dot success"></span>
38
+ <span class="msg">{VIDEO_TASK_DONE}</span>
39
+ </div>
40
+ {:else}
41
+ <div class="row">
42
+ <span class="dot error"></span>
43
+ <span class="msg">{VIDEO_TASK_FAILED}</span>
44
+ <button class="link text" on:click={dismissReplayVideo}>{DISMISS_LABEL}</button>
45
+ </div>
46
+ {#if job.error}<p class="sub">{job.error}</p>{/if}
47
+ {/if}
48
+ </div>
49
+ {/if}
50
+
51
+ {#each $notifications as n (n.id)}
52
+ <div class="card" animate:flip={{ duration: 180 }} transition:fly={{ y: 12, duration: 200 }}>
53
+ <div class="row">
54
+ <span class="dot {n.type}"></span>
55
+ <span class="msg">{n.message}</span>
56
+ <button class="link" on:click={() => dismissNotification(n.id)} aria-label={DISMISS_LABEL}>
57
+ <svg width="12" height="12" viewBox="0 0 14 14" fill="none">
58
+ <path
59
+ d="M1 1l12 12M13 1L1 13"
60
+ stroke="currentColor"
61
+ stroke-width="1.6"
62
+ stroke-linecap="round"
63
+ />
64
+ </svg>
65
+ </button>
66
+ </div>
67
+ </div>
68
+ {/each}
69
+ </div>
70
+
71
+ <style>
72
+ .stack {
73
+ position: fixed;
74
+ right: 1rem;
75
+ bottom: 3.25rem;
76
+ /* Above the replay modal (500) so feedback stays visible over it. */
77
+ z-index: 600;
78
+ display: flex;
79
+ flex-direction: column-reverse;
80
+ gap: 0.5rem;
81
+ max-width: min(20rem, calc(100vw - 2rem));
82
+ pointer-events: none;
83
+ }
84
+
85
+ .card {
86
+ pointer-events: auto;
87
+ padding: 0.7rem 0.85rem;
88
+ background: var(--bg-elevated);
89
+ border: 1px solid var(--border);
90
+ border-radius: var(--radius-md);
91
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.16);
92
+ font-size: 0.82rem;
93
+ }
94
+
95
+ .row {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 0.55rem;
99
+ }
100
+ .msg {
101
+ flex: 1;
102
+ color: var(--text);
103
+ line-height: 1.35;
104
+ }
105
+ .sub {
106
+ margin-top: 0.4rem;
107
+ color: var(--text-muted);
108
+ font-size: 0.72rem;
109
+ }
110
+
111
+ .dot {
112
+ width: 7px;
113
+ height: 7px;
114
+ border-radius: 50%;
115
+ flex-shrink: 0;
116
+ background: var(--text-muted);
117
+ }
118
+ .dot.success {
119
+ background: var(--pass);
120
+ }
121
+ .dot.error {
122
+ background: var(--fail);
123
+ }
124
+ .dot.info {
125
+ background: var(--accent);
126
+ }
127
+
128
+ .link {
129
+ display: inline-flex;
130
+ align-items: center;
131
+ background: none;
132
+ border: none;
133
+ color: var(--text-muted);
134
+ cursor: pointer;
135
+ padding: 0;
136
+ flex-shrink: 0;
137
+ }
138
+ .link.text {
139
+ font-size: 0.75rem;
140
+ }
141
+ .link:hover {
142
+ color: var(--accent);
143
+ }
144
+
145
+ .bar {
146
+ margin-top: 0.55rem;
147
+ height: 4px;
148
+ border-radius: var(--radius-pill);
149
+ background: var(--bg-subtle);
150
+ overflow: hidden;
151
+ }
152
+ .fill {
153
+ display: block;
154
+ height: 100%;
155
+ background: var(--accent);
156
+ transition: width 0.2s var(--ease-out);
157
+ }
158
+ .spinner {
159
+ width: 12px;
160
+ height: 12px;
161
+ flex-shrink: 0;
162
+ border: 2px solid var(--border);
163
+ border-top-color: var(--accent);
164
+ border-radius: 50%;
165
+ animation: spin 0.7s linear infinite;
166
+ }
167
+ @keyframes spin {
168
+ to {
169
+ transform: rotate(360deg);
170
+ }
171
+ }
172
+ @media (prefers-reduced-motion: reduce) {
173
+ .spinner {
174
+ animation-duration: 2s;
175
+ }
176
+ }
177
+ </style>
@@ -25,7 +25,7 @@ export const REPO_PAGE_SIZE = 20;
25
25
  export const SUITE_CASES_PER_PAGE = 20;
26
26
 
27
27
  export const COPY_TIMEOUT_MS = 1400;
28
- export const TOAST_TIMEOUT_MS = 4000;
28
+ export const TOAST_TIMEOUT_MS = 8000;
29
29
 
30
30
  export const REDIRECT_DELAY_MS = 3000;
31
31
 
@@ -26,6 +26,15 @@ export const SLACK_LABEL = 'Slack';
26
26
  export const EMAIL_LABEL = 'Email';
27
27
  export const PASSWORD_LABEL = 'Password';
28
28
 
29
+ export const EXPORT_LABEL = 'Export';
30
+ export const EXPORTING_LABEL = 'Exporting…';
31
+ export const EXPORT_MENU_ITEMS = [
32
+ { id: 'csv', label: 'CSV', hint: 'Download report' },
33
+ { id: 'json', label: 'JSON', hint: 'Download JSON. Re-importable' }
34
+ ];
35
+ export const exportFailedToast = (what) => `Could not export ${what}.`;
36
+ export const exportedToast = (what) => `${what} exported.`;
37
+
29
38
  export const CLEAR_SEARCH_LABEL = 'Clear search';
30
39
  export const SORT_BY_LABEL = 'Sort by';
31
40
  export const LOADING_LABEL = 'Loading…';
@@ -55,6 +55,26 @@ export const caseLabel = (index) => `Case ${index}`;
55
55
  // ── Recording replay ──
56
56
  export const PLAYER_LOAD_ERROR = 'Could not load this recording.';
57
57
  export const INSPECT_TOGGLE_LABEL = 'Inspect element';
58
+
59
+ export const REPORT_EXPORT_MENU_ITEMS = [
60
+ { id: 'csv', label: 'CSV', hint: 'Download report' },
61
+ { id: 'json', label: 'JSON', hint: 'Structured summary' }
62
+ ];
63
+
64
+ // ── Replay download (screenshot / video) ──
65
+ export const REPLAY_DOWNLOAD_LABEL = 'Download';
66
+ export const SCREENSHOT_LABEL = 'Screenshot';
67
+ export const SCREENSHOT_HINT = 'PNG of the current frame';
68
+ export const RECORD_VIDEO_LABEL = 'Record video';
69
+ export const RECORD_VIDEO_HINT = 'WebM — experimental, stays in your browser';
70
+ export const VIDEO_MULTITAB_UNSUPPORTED = 'Video export is only available for single-tab replays.';
71
+ export const SCREENSHOT_SAVED_TOAST = 'Screenshot saved.';
72
+ export const SCREENSHOT_FAILED_TOAST = 'Could not capture this frame.';
73
+ export const VIDEO_TASK_RENDERING = 'Rendering replay video…';
74
+ export const VIDEO_TASK_DONE = 'Replay video downloaded';
75
+ export const VIDEO_TASK_FAILED = 'Replay video render failed';
76
+ export const VIDEO_TASK_EXPERIMENTAL = 'Experimental — quality varies. Nothing is uploaded.';
77
+ export const DISMISS_LABEL = 'Dismiss';
58
78
  export const RESTART_LABEL = 'Restart replay';
59
79
  export const STEPS_RAIL_HEADING = 'Steps';
60
80
  export const INSPECTOR_HEADING = 'Inspector';
@@ -19,6 +19,10 @@ export const HEADING = 'Test Repository';
19
19
  export const HEADER_DESC = 'Manage test suites, cases, and track manual test runs.';
20
20
  export const NEW_SUITE_LABEL = '+ New Suite';
21
21
  export const NEW_RUN_LABEL = '+ New Run';
22
+ export const IMPORT_TEST_CASES_LINK = 'Import from file';
23
+ export const IMPORT_TEST_CASES_HREF = '/settings?section=testcases';
24
+ export const EXPORT_TEST_CASES_WHAT = 'Test cases';
25
+ export const EXPORT_SUITE_WHAT = 'Suite';
22
26
  export const SUITES_TAB_LABEL = 'Suites';
23
27
  export const RUNS_TAB_LABEL = 'Test Runs';
24
28
  export const SEARCH_PLACEHOLDER = 'Search by ID or name…';
@@ -19,6 +19,21 @@ export const PROJECT_LABEL = 'Project';
19
19
  export const RUNNERS_LABEL = 'Runners';
20
20
  export const REPOSITORY_NAV_LABEL = 'Repository';
21
21
  export const REPOSITORY_HEADING = 'Test Repository';
22
+ export const TEST_CASES_NAV_LABEL = 'Test Cases';
23
+ export const TEST_CASES_HEADING = 'Test Cases';
24
+ export const TEST_CASES_DESC = 'Bring test cases in from a Plum export file.';
25
+ export const TC_IMPORT_CARD_TITLE = 'Import';
26
+ export const TC_IMPORT_DESC =
27
+ 'Upload a JSON file exported from Test Repository. Suites and cases are added as new records.';
28
+ export const TC_IMPORT_HINT =
29
+ 'A case is skipped when its ID already exists here. Cases with a different prefix or no ID are imported with a fresh ID.';
30
+ export const TC_IMPORT_FAILED_FALLBACK = 'Could not read that file.';
31
+ export const tcImportLabel = (importing) => (importing ? 'Importing…' : 'Import');
32
+ export const tcImportSummary = ({ importedCases, importedSuites, skippedCases, skippedSuites }) => {
33
+ const imported = `Imported ${importedCases} ${importedCases === 1 ? 'case' : 'cases'} in ${importedSuites} ${importedSuites === 1 ? 'suite' : 'suites'}`;
34
+ const skipped = skippedCases + skippedSuites;
35
+ return skipped > 0 ? `${imported} · skipped ${skippedCases} already here` : imported + '.';
36
+ };
22
37
  export const INTEGRATIONS_LABEL = 'Integrations';
23
38
  export const MCP_NAV_LABEL = 'MCP';
24
39
  export const MCP_HEADING = 'MCP Integration';
@@ -0,0 +1,23 @@
1
+ /*
2
+ * This file is part of Plum.
3
+ * Licensed under the MIT License. See LICENSE file in the project root for details.
4
+ */
5
+
6
+ import { writable } from 'svelte/store';
7
+ import { TOAST_TIMEOUT_MS } from '$lib/constants';
8
+
9
+ // { id, type: 'success' | 'error' | 'info', message, sticky }
10
+ export const notifications = writable([]);
11
+
12
+ let seq = 0;
13
+
14
+ export function notify(type, message, { sticky = false, timeout = TOAST_TIMEOUT_MS } = {}) {
15
+ const id = ++seq;
16
+ notifications.update((list) => [...list, { id, type, message, sticky }]);
17
+ if (!sticky) setTimeout(() => dismissNotification(id), timeout);
18
+ return id;
19
+ }
20
+
21
+ export function dismissNotification(id) {
22
+ notifications.update((list) => list.filter((n) => n.id !== id));
23
+ }
@@ -0,0 +1,51 @@
1
+ /*
2
+ * This file is part of Plum.
3
+ * Licensed under the MIT License. See LICENSE file in the project root for details.
4
+ */
5
+
6
+ import { writable, get } from 'svelte/store';
7
+ import { renderReplayToWebM } from '$lib/utils/replayVideo';
8
+ import { downloadBlob } from '$lib/utils/download';
9
+
10
+ // null when idle; otherwise { status, progress, filename, error }.
11
+ // status: 'rendering' | 'done' | 'error'
12
+ export const replayVideoJob = writable(null);
13
+
14
+ let cancelled = false;
15
+
16
+ export function cancelReplayVideo() {
17
+ cancelled = true;
18
+ }
19
+
20
+ export function dismissReplayVideo() {
21
+ replayVideoJob.set(null);
22
+ }
23
+
24
+ /** `replayer` is the live rrweb replayer to record; it is not retained. */
25
+ export async function startReplayVideo({ replayer, durationMs, filename }) {
26
+ if (get(replayVideoJob)?.status === 'rendering') return;
27
+ cancelled = false;
28
+ replayVideoJob.set({ status: 'rendering', progress: 0, filename });
29
+
30
+ try {
31
+ const blob = await renderReplayToWebM({
32
+ replayer,
33
+ durationMs,
34
+ onProgress: (p) => replayVideoJob.update((j) => (j ? { ...j, progress: p } : j)),
35
+ shouldCancel: () => cancelled
36
+ });
37
+ if (cancelled) {
38
+ replayVideoJob.set(null);
39
+ return;
40
+ }
41
+ downloadBlob(blob, filename);
42
+ replayVideoJob.set({ status: 'done', progress: 1, filename });
43
+ setTimeout(() => replayVideoJob.update((j) => (j?.status === 'done' ? null : j)), 6000);
44
+ } catch (e) {
45
+ if (cancelled || e.message === 'Cancelled.') {
46
+ replayVideoJob.set(null);
47
+ return;
48
+ }
49
+ replayVideoJob.set({ status: 'error', progress: 0, filename, error: e.message });
50
+ }
51
+ }
@@ -0,0 +1,28 @@
1
+ /*
2
+ * This file is part of Plum.
3
+ * Licensed under the MIT License. See LICENSE file in the project root for details.
4
+ */
5
+
6
+ export function downloadBlob(blob, filename) {
7
+ const url = URL.createObjectURL(blob);
8
+ const a = document.createElement('a');
9
+ a.href = url;
10
+ a.download = filename;
11
+ document.body.appendChild(a);
12
+ a.click();
13
+ a.remove();
14
+ // Revoking synchronously can cancel the download in some browsers.
15
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
16
+ }
17
+
18
+ function filenameFromDisposition(header, fallback) {
19
+ const match = /filename="?([^"]+)"?/.exec(header ?? '');
20
+ return match ? match[1] : fallback;
21
+ }
22
+
23
+ export async function downloadFromEndpoint(url, { headers = {}, fallbackName = 'export' } = {}) {
24
+ const res = await fetch(url, { headers });
25
+ if (!res.ok) throw new Error('Export failed');
26
+ const blob = await res.blob();
27
+ downloadBlob(blob, filenameFromDisposition(res.headers.get('Content-Disposition'), fallbackName));
28
+ }
@@ -0,0 +1,91 @@
1
+ /*
2
+ * This file is part of Plum.
3
+ * Licensed under the MIT License. See LICENSE file in the project root for details.
4
+ */
5
+
6
+ import * as htmlToImage from 'html-to-image';
7
+
8
+ function toBase64(buffer) {
9
+ const bytes = new Uint8Array(buffer);
10
+ let binary = '';
11
+ for (let i = 0; i < bytes.length; i += 0x8000) {
12
+ binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
13
+ }
14
+ return btoa(binary);
15
+ }
16
+
17
+ const fontMime = (url) =>
18
+ url.includes('woff2') ? 'font/woff2' : url.includes('woff') ? 'font/woff' : 'font/ttf';
19
+
20
+ const FONT_FACE_RE = /@font-face\s*{[^}]*}/g;
21
+
22
+ async function faceTextsFromHref(href) {
23
+ if (!href) return [];
24
+ try {
25
+ const css = await fetch(href).then((r) => r.text());
26
+ return css.match(FONT_FACE_RE) ?? [];
27
+ } catch {
28
+ return [];
29
+ }
30
+ }
31
+
32
+ // html-to-image's own font embedding misses @font-face rules from a cross-origin
33
+ // stylesheet (the tested page's Google Fonts <link>) — the capture then falls
34
+ // back to a default face. Re-fetch the CORS-blocked sheets by URL ourselves.
35
+ export async function collectFontEmbedCSS(doc) {
36
+ const rules = [];
37
+ for (const sheet of doc.styleSheets) {
38
+ let cssRules;
39
+ try {
40
+ cssRules = sheet.cssRules;
41
+ } catch {
42
+ rules.push(...(await faceTextsFromHref(sheet.href)));
43
+ continue;
44
+ }
45
+ for (const rule of cssRules) {
46
+ if (rule.type === 5 || rule.constructor?.name === 'CSSFontFaceRule') {
47
+ rules.push(rule.cssText);
48
+ } else if (rule.type === 3 && rule.href) {
49
+ let reachable = true;
50
+ try {
51
+ void rule.styleSheet.cssRules;
52
+ } catch {
53
+ reachable = false;
54
+ }
55
+ if (!reachable) rules.push(...(await faceTextsFromHref(rule.href)));
56
+ }
57
+ }
58
+ }
59
+
60
+ const cache = new Map();
61
+ const out = [];
62
+ for (let text of rules) {
63
+ const urls = [...text.matchAll(/url\((['"]?)(https?:\/\/[^'")]+)\1\)/g)].map((m) => m[2]);
64
+ for (const url of urls) {
65
+ try {
66
+ if (!cache.has(url)) {
67
+ const buf = await fetch(url).then((r) => r.arrayBuffer());
68
+ cache.set(url, `data:${fontMime(url)};base64,${toBase64(buf)}`);
69
+ }
70
+ text = text.split(url).join(cache.get(url));
71
+ } catch {
72
+ // unreachable font — leave the original url, the face just won't embed
73
+ }
74
+ }
75
+ out.push(text);
76
+ }
77
+ return out.join('\n');
78
+ }
79
+
80
+ /** PNG blob of whatever the given rrweb replayer is currently showing. */
81
+ export async function captureReplayFrame(replayer) {
82
+ const doc = replayer?.iframe?.contentDocument;
83
+ if (!doc?.documentElement) throw new Error('Nothing to capture yet — let the replay load first.');
84
+ const fontEmbedCSS = await collectFontEmbedCSS(doc).catch(() => '');
85
+ const blob = await htmlToImage.toBlob(doc.documentElement, {
86
+ backgroundColor: '#ffffff',
87
+ fontEmbedCSS
88
+ });
89
+ if (!blob) throw new Error('Could not capture this frame.');
90
+ return blob;
91
+ }
@@ -0,0 +1,111 @@
1
+ /*
2
+ * This file is part of Plum.
3
+ * Licensed under the MIT License. See LICENSE file in the project root for details.
4
+ */
5
+
6
+ import * as htmlToImage from 'html-to-image';
7
+ import fixWebmDuration from 'fix-webm-duration';
8
+ import { collectFontEmbedCSS } from './replayCapture';
9
+
10
+ const FPS = 12;
11
+ const TAIL_MS = 400;
12
+
13
+ const supportedMime = () =>
14
+ ['video/webm;codecs=vp9', 'video/webm;codecs=vp8', 'video/webm'].find((m) =>
15
+ MediaRecorder.isTypeSupported(m)
16
+ );
17
+
18
+ /**
19
+ * Records the already-loaded replayer to a WebM blob in the browser — nothing is
20
+ * uploaded. Plays it through in real time (so a mid-recording navigation applies
21
+ * the same as normal playback) while html-to-image snapshots each frame onto a
22
+ * canvas the MediaRecorder samples at FPS; a slow snapshot just repeats the
23
+ * previous frame, so the clip stays the right length.
24
+ *
25
+ * A second rrweb player can't be spun up off-screen for this (two instances
26
+ * fight over shared module state and the hidden one renders blank), so it drives
27
+ * the visible replayer and the caller restores its position afterwards.
28
+ */
29
+ export async function renderReplayToWebM({ replayer, durationMs, onProgress, shouldCancel }) {
30
+ const iframe = replayer?.iframe;
31
+ if (!iframe?.contentDocument?.documentElement) throw new Error('The replay is not ready yet.');
32
+ if (!durationMs || durationMs < 200) throw new Error('This recording is too short to record.');
33
+ if (typeof MediaRecorder === 'undefined') throw new Error("This browser can't record video.");
34
+ const mime = supportedMime();
35
+ if (!mime) throw new Error("This browser can't record WebM video.");
36
+
37
+ const w = Math.max(320, iframe.offsetWidth || 1280);
38
+ const h = Math.max(240, iframe.offsetHeight || 720);
39
+ const canvas = document.createElement('canvas');
40
+ canvas.width = w;
41
+ canvas.height = h;
42
+ const ctx = canvas.getContext('2d');
43
+ const paintWhite = () => {
44
+ ctx.fillStyle = '#ffffff';
45
+ ctx.fillRect(0, 0, w, h);
46
+ };
47
+ paintWhite();
48
+
49
+ const fontEmbedCSS = await collectFontEmbedCSS(iframe.contentDocument).catch(() => '');
50
+
51
+ const recorder = new MediaRecorder(canvas.captureStream(FPS), {
52
+ mimeType: mime,
53
+ videoBitsPerSecond: 3_500_000
54
+ });
55
+ const chunks = [];
56
+ recorder.ondataavailable = (e) => e.data.size && chunks.push(e.data);
57
+ const stopped = new Promise((res) => (recorder.onstop = res));
58
+ recorder.start(250);
59
+
60
+ replayer.play(0);
61
+ const start = performance.now();
62
+ let capturing = false;
63
+ let renderedMs = 0;
64
+
65
+ try {
66
+ await new Promise((resolve, reject) => {
67
+ function tick() {
68
+ if (shouldCancel?.()) return reject(new Error('Cancelled.'));
69
+ renderedMs = performance.now() - start;
70
+ onProgress?.(Math.min(1, renderedMs / durationMs));
71
+
72
+ if (!capturing) {
73
+ capturing = true;
74
+ htmlToImage
75
+ .toCanvas(iframe.contentDocument.documentElement, {
76
+ width: w,
77
+ height: h,
78
+ backgroundColor: '#ffffff',
79
+ fontEmbedCSS
80
+ })
81
+ .then((frame) => {
82
+ paintWhite();
83
+ ctx.drawImage(frame, 0, 0, w, h);
84
+ })
85
+ .catch(() => {})
86
+ .finally(() => {
87
+ capturing = false;
88
+ });
89
+ }
90
+
91
+ if (renderedMs >= durationMs + TAIL_MS) resolve();
92
+ else requestAnimationFrame(tick);
93
+ }
94
+ requestAnimationFrame(tick);
95
+ });
96
+ } finally {
97
+ recorder.stop();
98
+ await stopped;
99
+ try {
100
+ replayer.pause(0);
101
+ } catch {
102
+ // caller rebuilds the player anyway
103
+ }
104
+ }
105
+
106
+ onProgress?.(1);
107
+ const raw = new Blob(chunks, { type: mime });
108
+ // MediaRecorder WebM carries no duration in its header — players show it as
109
+ // unseekable / 0:00 until playback ends. Patch the EBML so the file behaves.
110
+ return fixWebmDuration(raw, Math.round(renderedMs), { logger: false }).catch(() => raw);
111
+ }
@@ -11,6 +11,7 @@
11
11
  import Nav from '$lib/components/layout/Nav.svelte';
12
12
  import PageShell from '$lib/components/layout/PageShell.svelte';
13
13
  import RunnerPanel from '$lib/components/layout/RunnerPanel.svelte';
14
+ import NotificationStack from '$lib/components/ui/NotificationStack.svelte';
14
15
  import { auth } from '$lib/stores/auth';
15
16
  import { checkNeedsSetup } from '$lib/api/auth';
16
17
 
@@ -51,6 +52,7 @@
51
52
  <slot />
52
53
  </PageShell>
53
54
  <RunnerPanel />
55
+ <NotificationStack />
54
56
  {/if}
55
57
  {:else}
56
58
  <div class="boot-loading">Loading…</div>
@@ -7,7 +7,7 @@
7
7
  import { page } from '$app/stores';
8
8
  import { onMount } from 'svelte';
9
9
  import { slide } from 'svelte/transition';
10
- import { fetchReportDetail, fetchRecordings } from '$lib/api/reports';
10
+ import { fetchReportDetail, fetchRecordings, downloadReportExport } from '$lib/api/reports';
11
11
  import {
12
12
  isScheduled,
13
13
  triggerLabel,
@@ -39,10 +39,14 @@
39
39
  attemptsLabel,
40
40
  caseLabel,
41
41
  UNKNOWN_RUNNER_LABEL,
42
- workerLabel
42
+ workerLabel,
43
+ REPORT_EXPORT_MENU_ITEMS
43
44
  } from '$lib/copy/reports';
45
+ import { exportFailedToast, exportedToast } from '$lib/copy/common';
46
+ import { notify } from '$lib/stores/notifications';
44
47
  import Badge from '$lib/components/ui/Badge.svelte';
45
48
  import BackLink from '$lib/components/ui/BackLink.svelte';
49
+ import ExportMenu from '$lib/components/ui/ExportMenu.svelte';
46
50
  import StatusDot from '$lib/components/ui/StatusDot.svelte';
47
51
  import TagChip from '$lib/components/ui/TagChip.svelte';
48
52
  import StepKeyword from '$lib/components/ui/StepKeyword.svelte';
@@ -63,6 +67,19 @@
63
67
  let scenarioRecordings = [];
64
68
  let replayInspecting = false;
65
69
 
70
+ let exporting = false;
71
+ async function handleExport(format) {
72
+ exporting = true;
73
+ try {
74
+ await downloadReportExport(reportId, format);
75
+ notify('success', exportedToast('Report'));
76
+ } catch {
77
+ notify('error', exportFailedToast('this report'));
78
+ } finally {
79
+ exporting = false;
80
+ }
81
+ }
82
+
66
83
  function scenarioHasRecording(scenario) {
67
84
  return allRecordings.some((r) => r.scenarioId === scenario.id);
68
85
  }
@@ -128,7 +145,16 @@
128
145
 
129
146
  <svelte:head><title>{DETAIL_PAGE_TITLE}</title></svelte:head>
130
147
 
131
- <BackLink href="/reports" label={REPORTS_BACK_LABEL} />
148
+ <div class="detail-top">
149
+ <BackLink href="/reports" label={REPORTS_BACK_LABEL} />
150
+ {#if detail}
151
+ <ExportMenu
152
+ items={REPORT_EXPORT_MENU_ITEMS}
153
+ busy={exporting}
154
+ on:select={(e) => handleExport(e.detail)}
155
+ />
156
+ {/if}
157
+ </div>
132
158
 
133
159
  {#if error}
134
160
  <div class="error-state">{error}</div>
@@ -612,6 +638,7 @@
612
638
  {reportId}
613
639
  recordings={scenarioRecordings}
614
640
  steps={replayScenario.steps}
641
+ scenarioName={replayScenario.name}
615
642
  bind:inspecting={replayInspecting}
616
643
  />
617
644
  {/if}
@@ -621,6 +648,14 @@
621
648
  {/if}
622
649
 
623
650
  <style>
651
+ .detail-top {
652
+ display: flex;
653
+ align-items: center;
654
+ justify-content: space-between;
655
+ gap: 1rem;
656
+ margin-bottom: 1rem;
657
+ }
658
+
624
659
  .report-header {
625
660
  border-radius: var(--radius-lg);
626
661
  border: 1px solid var(--border);