@storyshelf/app 0.6.2 → 0.7.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 (361) hide show
  1. package/README.md +42 -4
  2. package/dist/app-types.d.mts +1 -3
  3. package/dist/asset-manifest.mjs +13 -5
  4. package/dist/asset-manifest.mjs.map +1 -1
  5. package/dist/assets/{htmx.min.js → htmx.mjs} +10 -1
  6. package/dist/assets/htmx.mjs.map +1 -0
  7. package/dist/assets/mark.mjs +10 -0
  8. package/dist/assets/mark.mjs.map +1 -0
  9. package/dist/boot-tasks.mjs +80 -0
  10. package/dist/boot-tasks.mjs.map +1 -0
  11. package/dist/build-state-flags.mjs +18 -0
  12. package/dist/build-state-flags.mjs.map +1 -0
  13. package/dist/build-state-setup.mjs +75 -0
  14. package/dist/build-state-setup.mjs.map +1 -0
  15. package/dist/capture-alerts.mjs +46 -0
  16. package/dist/capture-alerts.mjs.map +1 -0
  17. package/dist/capture-process.mjs +59 -0
  18. package/dist/capture-process.mjs.map +1 -0
  19. package/dist/capture-setup.mjs +95 -117
  20. package/dist/capture-setup.mjs.map +1 -1
  21. package/dist/connections.mjs +34 -0
  22. package/dist/connections.mjs.map +1 -0
  23. package/dist/consumer-setup.mjs +58 -0
  24. package/dist/consumer-setup.mjs.map +1 -0
  25. package/dist/git-status.mjs +46 -0
  26. package/dist/git-status.mjs.map +1 -0
  27. package/dist/hidden-data.mjs +24 -0
  28. package/dist/hidden-data.mjs.map +1 -0
  29. package/dist/index.d.mts +3 -3
  30. package/dist/index.mjs +203 -125
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/insights/budget.mjs +49 -0
  33. package/dist/insights/budget.mjs.map +1 -0
  34. package/dist/insights/deps.mjs +21 -0
  35. package/dist/insights/deps.mjs.map +1 -0
  36. package/dist/insights/errors.mjs +22 -0
  37. package/dist/insights/errors.mjs.map +1 -0
  38. package/dist/insights/health-panel.mjs +40 -0
  39. package/dist/insights/health-panel.mjs.map +1 -0
  40. package/dist/insights/job.mjs +103 -0
  41. package/dist/insights/job.mjs.map +1 -0
  42. package/dist/insights/load-health.mjs +59 -0
  43. package/dist/insights/load-health.mjs.map +1 -0
  44. package/dist/insights/load-triage.mjs +98 -0
  45. package/dist/insights/load-triage.mjs.map +1 -0
  46. package/dist/insights/panel.mjs +28 -0
  47. package/dist/insights/panel.mjs.map +1 -0
  48. package/dist/insights/request.mjs +113 -0
  49. package/dist/insights/request.mjs.map +1 -0
  50. package/dist/insights/stale.mjs +21 -0
  51. package/dist/insights/stale.mjs.map +1 -0
  52. package/dist/insights/usage-report.mjs +60 -0
  53. package/dist/insights/usage-report.mjs.map +1 -0
  54. package/dist/insights/view.mjs +48 -0
  55. package/dist/insights/view.mjs.map +1 -0
  56. package/dist/job-canceller.mjs +14 -0
  57. package/dist/job-canceller.mjs.map +1 -0
  58. package/dist/job-queue.mjs +51 -0
  59. package/dist/job-queue.mjs.map +1 -0
  60. package/dist/jobs-setup.mjs +132 -0
  61. package/dist/jobs-setup.mjs.map +1 -0
  62. package/dist/lifecycle.mjs +91 -19
  63. package/dist/lifecycle.mjs.map +1 -1
  64. package/dist/middleware/auth-gate.mjs +4 -2
  65. package/dist/middleware/auth-gate.mjs.map +1 -1
  66. package/dist/middleware/rate-limit.mjs +41 -33
  67. package/dist/middleware/rate-limit.mjs.map +1 -1
  68. package/dist/middleware/request-log.mjs +3 -2
  69. package/dist/middleware/request-log.mjs.map +1 -1
  70. package/dist/middleware/require-review.mjs +51 -0
  71. package/dist/middleware/require-review.mjs.map +1 -0
  72. package/dist/middleware/require-testing.mjs +41 -0
  73. package/dist/middleware/require-testing.mjs.map +1 -0
  74. package/dist/middleware/setup-gate.mjs.map +1 -1
  75. package/dist/middleware/store-scope.mjs +12 -2
  76. package/dist/middleware/store-scope.mjs.map +1 -1
  77. package/dist/notifications-pause.mjs +33 -0
  78. package/dist/notifications-pause.mjs.map +1 -0
  79. package/dist/notify.d.mts +2 -1
  80. package/dist/notify.mjs +78 -39
  81. package/dist/notify.mjs.map +1 -1
  82. package/dist/pages/a11y-violations.mjs +116 -0
  83. package/dist/pages/a11y-violations.mjs.map +1 -0
  84. package/dist/pages/admin-ai.mjs +78 -0
  85. package/dist/pages/admin-ai.mjs.map +1 -0
  86. package/dist/pages/admin-system-origin.mjs +123 -0
  87. package/dist/pages/admin-system-origin.mjs.map +1 -0
  88. package/dist/pages/admin-system-targets.mjs +53 -0
  89. package/dist/pages/admin-system-targets.mjs.map +1 -0
  90. package/dist/pages/admin-system.mjs +63 -13
  91. package/dist/pages/admin-system.mjs.map +1 -1
  92. package/dist/pages/build-detail.mjs +104 -76
  93. package/dist/pages/build-detail.mjs.map +1 -1
  94. package/dist/pages/build-diff-actions.mjs +87 -33
  95. package/dist/pages/build-diff-actions.mjs.map +1 -1
  96. package/dist/pages/build-diff-comments.mjs +16 -14
  97. package/dist/pages/build-diff-comments.mjs.map +1 -1
  98. package/dist/pages/build-diff-environment.mjs +82 -0
  99. package/dist/pages/build-diff-environment.mjs.map +1 -0
  100. package/dist/pages/build-diff-header.mjs +22 -4
  101. package/dist/pages/build-diff-header.mjs.map +1 -1
  102. package/dist/pages/build-diff-insight.mjs +113 -0
  103. package/dist/pages/build-diff-insight.mjs.map +1 -0
  104. package/dist/pages/build-diff-model.mjs +55 -10
  105. package/dist/pages/build-diff-model.mjs.map +1 -1
  106. package/dist/pages/build-diff-nav.mjs +28 -12
  107. package/dist/pages/build-diff-nav.mjs.map +1 -1
  108. package/dist/pages/build-diff-shortcuts.mjs +5 -1
  109. package/dist/pages/build-diff-shortcuts.mjs.map +1 -1
  110. package/dist/pages/build-diff-usage.mjs +63 -0
  111. package/dist/pages/build-diff-usage.mjs.map +1 -0
  112. package/dist/pages/build-diff-viewer.mjs +62 -5
  113. package/dist/pages/build-diff-viewer.mjs.map +1 -1
  114. package/dist/pages/build-diff.mjs +34 -13
  115. package/dist/pages/build-diff.mjs.map +1 -1
  116. package/dist/pages/build-tests.mjs +74 -0
  117. package/dist/pages/build-tests.mjs.map +1 -0
  118. package/dist/pages/component-detail.mjs +249 -0
  119. package/dist/pages/component-detail.mjs.map +1 -0
  120. package/dist/pages/component-stories.mjs +168 -0
  121. package/dist/pages/component-stories.mjs.map +1 -0
  122. package/dist/pages/compute-jobs.mjs +13 -26
  123. package/dist/pages/compute-jobs.mjs.map +1 -1
  124. package/dist/pages/design-links.mjs +182 -0
  125. package/dist/pages/design-links.mjs.map +1 -0
  126. package/dist/pages/environment-change.mjs +53 -0
  127. package/dist/pages/environment-change.mjs.map +1 -0
  128. package/dist/pages/health-panel.mjs +126 -0
  129. package/dist/pages/health-panel.mjs.map +1 -0
  130. package/dist/pages/library-grid.mjs +105 -0
  131. package/dist/pages/library-grid.mjs.map +1 -0
  132. package/dist/pages/library-model.mjs +47 -0
  133. package/dist/pages/library-model.mjs.map +1 -0
  134. package/dist/pages/library-snapshots.mjs +130 -0
  135. package/dist/pages/library-snapshots.mjs.map +1 -0
  136. package/dist/pages/library.mjs +112 -218
  137. package/dist/pages/library.mjs.map +1 -1
  138. package/dist/pages/notifications-paused.mjs +22 -0
  139. package/dist/pages/notifications-paused.mjs.map +1 -0
  140. package/dist/pages/profile.mjs +53 -48
  141. package/dist/pages/profile.mjs.map +1 -1
  142. package/dist/pages/project-builds-table.mjs +2 -2
  143. package/dist/pages/project-builds-table.mjs.map +1 -1
  144. package/dist/pages/project-builds.mjs +51 -21
  145. package/dist/pages/project-builds.mjs.map +1 -1
  146. package/dist/pages/project-settings.mjs +51 -12
  147. package/dist/pages/project-settings.mjs.map +1 -1
  148. package/dist/pages/projects-card.mjs +14 -1
  149. package/dist/pages/projects-card.mjs.map +1 -1
  150. package/dist/pages/projects.mjs +6 -2
  151. package/dist/pages/projects.mjs.map +1 -1
  152. package/dist/pages/review-changeset.mjs +116 -0
  153. package/dist/pages/review-changeset.mjs.map +1 -0
  154. package/dist/pages/review-comments.mjs +103 -0
  155. package/dist/pages/review-comments.mjs.map +1 -0
  156. package/dist/pages/review-verdict.mjs +102 -0
  157. package/dist/pages/review-verdict.mjs.map +1 -0
  158. package/dist/pages/settings-ai.mjs +69 -0
  159. package/dist/pages/settings-ai.mjs.map +1 -0
  160. package/dist/pages/settings-capture.mjs +124 -0
  161. package/dist/pages/settings-capture.mjs.map +1 -0
  162. package/dist/pages/settings-design-credential.mjs +99 -0
  163. package/dist/pages/settings-design-credential.mjs.map +1 -0
  164. package/dist/pages/settings-design-links.mjs +179 -0
  165. package/dist/pages/settings-design-links.mjs.map +1 -0
  166. package/dist/pages/settings-design.mjs +48 -0
  167. package/dist/pages/settings-design.mjs.map +1 -0
  168. package/dist/pages/settings-general.mjs +46 -21
  169. package/dist/pages/settings-general.mjs.map +1 -1
  170. package/dist/pages/settings-labels.mjs +43 -2
  171. package/dist/pages/settings-labels.mjs.map +1 -1
  172. package/dist/pages/settings-links.mjs +112 -0
  173. package/dist/pages/settings-links.mjs.map +1 -0
  174. package/dist/pages/settings-notifications-git.mjs +128 -0
  175. package/dist/pages/settings-notifications-git.mjs.map +1 -0
  176. package/dist/pages/settings-notifications.mjs +79 -42
  177. package/dist/pages/settings-notifications.mjs.map +1 -1
  178. package/dist/pages/settings-tests.mjs +33 -2
  179. package/dist/pages/settings-tests.mjs.map +1 -1
  180. package/dist/pages/target-choices.mjs +39 -0
  181. package/dist/pages/target-choices.mjs.map +1 -0
  182. package/dist/publish-setup.mjs +89 -0
  183. package/dist/publish-setup.mjs.map +1 -0
  184. package/dist/retention-timer.mjs +2 -1
  185. package/dist/retention-timer.mjs.map +1 -1
  186. package/dist/review-events.mjs +48 -0
  187. package/dist/review-events.mjs.map +1 -0
  188. package/dist/routers/admin-pages.mjs +45 -3
  189. package/dist/routers/admin-pages.mjs.map +1 -1
  190. package/dist/routers/admin-public-origin.mjs +85 -0
  191. package/dist/routers/admin-public-origin.mjs.map +1 -0
  192. package/dist/routers/assets.mjs +9 -2
  193. package/dist/routers/assets.mjs.map +1 -1
  194. package/dist/routers/auth.mjs +1 -1
  195. package/dist/routers/baseline-guard.mjs +2 -2
  196. package/dist/routers/baseline-guard.mjs.map +1 -1
  197. package/dist/routers/baselines.handlers.mjs +64 -0
  198. package/dist/routers/baselines.handlers.mjs.map +1 -0
  199. package/dist/routers/baselines.mjs +125 -0
  200. package/dist/routers/baselines.mjs.map +1 -0
  201. package/dist/routers/build-cancel.mjs +68 -0
  202. package/dist/routers/build-cancel.mjs.map +1 -0
  203. package/dist/routers/build-changeset.mjs +70 -0
  204. package/dist/routers/build-changeset.mjs.map +1 -0
  205. package/dist/routers/build-reviews.handlers.mjs +86 -0
  206. package/dist/routers/build-reviews.handlers.mjs.map +1 -0
  207. package/dist/routers/build-reviews.mjs +74 -0
  208. package/dist/routers/build-reviews.mjs.map +1 -0
  209. package/dist/routers/builds.handlers.mjs +46 -55
  210. package/dist/routers/builds.handlers.mjs.map +1 -1
  211. package/dist/routers/builds.mjs +71 -17
  212. package/dist/routers/builds.mjs.map +1 -1
  213. package/dist/routers/capture-config.mjs +35 -0
  214. package/dist/routers/capture-config.mjs.map +1 -0
  215. package/dist/routers/comments.mjs +41 -15
  216. package/dist/routers/comments.mjs.map +1 -1
  217. package/dist/routers/component-catalog.handlers.mjs +71 -0
  218. package/dist/routers/component-catalog.handlers.mjs.map +1 -0
  219. package/dist/routers/component.handlers.mjs +121 -0
  220. package/dist/routers/component.handlers.mjs.map +1 -0
  221. package/dist/routers/connections.mjs +118 -0
  222. package/dist/routers/connections.mjs.map +1 -0
  223. package/dist/routers/design-connect.handlers.mjs +44 -0
  224. package/dist/routers/design-connect.handlers.mjs.map +1 -0
  225. package/dist/routers/design.handlers.mjs +114 -0
  226. package/dist/routers/design.handlers.mjs.map +1 -0
  227. package/dist/routers/health-report.mjs +2 -3
  228. package/dist/routers/health-report.mjs.map +1 -1
  229. package/dist/routers/health.mjs +4 -3
  230. package/dist/routers/health.mjs.map +1 -1
  231. package/dist/routers/helpers.mjs +19 -3
  232. package/dist/routers/helpers.mjs.map +1 -1
  233. package/dist/routers/insights-ui.mjs +64 -0
  234. package/dist/routers/insights-ui.mjs.map +1 -0
  235. package/dist/routers/insights.handlers.mjs +45 -0
  236. package/dist/routers/insights.handlers.mjs.map +1 -0
  237. package/dist/routers/insights.mjs +189 -0
  238. package/dist/routers/insights.mjs.map +1 -0
  239. package/dist/routers/links.handlers.mjs +80 -0
  240. package/dist/routers/links.handlers.mjs.map +1 -0
  241. package/dist/routers/links.mjs +139 -0
  242. package/dist/routers/links.mjs.map +1 -0
  243. package/dist/routers/media.mjs +18 -4
  244. package/dist/routers/media.mjs.map +1 -1
  245. package/dist/routers/members.mjs +2 -1
  246. package/dist/routers/members.mjs.map +1 -1
  247. package/dist/routers/notification-channels.mjs +17 -23
  248. package/dist/routers/notification-channels.mjs.map +1 -1
  249. package/dist/routers/oauth-state.mjs +45 -0
  250. package/dist/routers/oauth-state.mjs.map +1 -0
  251. package/dist/routers/preview-base.mjs +38 -0
  252. package/dist/routers/preview-base.mjs.map +1 -0
  253. package/dist/routers/preview-token.mjs +51 -0
  254. package/dist/routers/preview-token.mjs.map +1 -0
  255. package/dist/routers/profile.mjs +3 -1
  256. package/dist/routers/profile.mjs.map +1 -1
  257. package/dist/routers/projects.mjs +17 -1
  258. package/dist/routers/projects.mjs.map +1 -1
  259. package/dist/routers/publish-upload.mjs +78 -0
  260. package/dist/routers/publish-upload.mjs.map +1 -0
  261. package/dist/routers/published-access.mjs +52 -0
  262. package/dist/routers/published-access.mjs.map +1 -0
  263. package/dist/routers/published-files.mjs +52 -0
  264. package/dist/routers/published-files.mjs.map +1 -0
  265. package/dist/routers/review-forms.mjs +72 -0
  266. package/dist/routers/review-forms.mjs.map +1 -0
  267. package/dist/routers/review-section.handlers.mjs +85 -0
  268. package/dist/routers/review-section.handlers.mjs.map +1 -0
  269. package/dist/routers/review-switch-off.mjs +29 -0
  270. package/dist/routers/review-switch-off.mjs.map +1 -0
  271. package/dist/routers/schemas.mjs +140 -22
  272. package/dist/routers/schemas.mjs.map +1 -1
  273. package/dist/routers/scoped-models.mjs +22 -0
  274. package/dist/routers/scoped-models.mjs.map +1 -0
  275. package/dist/routers/settings-ai.mjs +35 -0
  276. package/dist/routers/settings-ai.mjs.map +1 -0
  277. package/dist/routers/settings-capture.mjs +77 -0
  278. package/dist/routers/settings-capture.mjs.map +1 -0
  279. package/dist/routers/settings-design.mjs +103 -0
  280. package/dist/routers/settings-design.mjs.map +1 -0
  281. package/dist/routers/settings-general.mjs +55 -10
  282. package/dist/routers/settings-general.mjs.map +1 -1
  283. package/dist/routers/settings-git-channel.mjs +77 -0
  284. package/dist/routers/settings-git-channel.mjs.map +1 -0
  285. package/dist/routers/settings-labels.mjs +27 -1
  286. package/dist/routers/settings-labels.mjs.map +1 -1
  287. package/dist/routers/settings-links.mjs +63 -0
  288. package/dist/routers/settings-links.mjs.map +1 -0
  289. package/dist/routers/settings-members.mjs +4 -7
  290. package/dist/routers/settings-members.mjs.map +1 -1
  291. package/dist/routers/settings-notifications.mjs +15 -10
  292. package/dist/routers/settings-notifications.mjs.map +1 -1
  293. package/dist/routers/settings-tokens.mjs +2 -5
  294. package/dist/routers/settings-tokens.mjs.map +1 -1
  295. package/dist/routers/settings-webhooks.mjs +7 -24
  296. package/dist/routers/settings-webhooks.mjs.map +1 -1
  297. package/dist/routers/settings.handlers.mjs +107 -26
  298. package/dist/routers/settings.handlers.mjs.map +1 -1
  299. package/dist/routers/settings.mjs +16 -4
  300. package/dist/routers/settings.mjs.map +1 -1
  301. package/dist/routers/signed-storybook.mjs +53 -0
  302. package/dist/routers/signed-storybook.mjs.map +1 -0
  303. package/dist/routers/snapshot-groups.mjs +63 -0
  304. package/dist/routers/snapshot-groups.mjs.map +1 -0
  305. package/dist/routers/snapshots.mjs +103 -5
  306. package/dist/routers/snapshots.mjs.map +1 -1
  307. package/dist/routers/storybook.mjs +37 -88
  308. package/dist/routers/storybook.mjs.map +1 -1
  309. package/dist/routers/tokens.mjs +2 -5
  310. package/dist/routers/tokens.mjs.map +1 -1
  311. package/dist/routers/ui.mjs +34 -5
  312. package/dist/routers/ui.mjs.map +1 -1
  313. package/dist/routers/webhooks.mjs +5 -11
  314. package/dist/routers/webhooks.mjs.map +1 -1
  315. package/dist/routers/well-known.mjs +11 -4
  316. package/dist/routers/well-known.mjs.map +1 -1
  317. package/dist/routes/api-routes.mjs +93 -0
  318. package/dist/routes/api-routes.mjs.map +1 -0
  319. package/dist/routes/classification.mjs +38 -0
  320. package/dist/routes/classification.mjs.map +1 -0
  321. package/dist/routes/openapi-classes.mjs +22 -0
  322. package/dist/routes/openapi-classes.mjs.map +1 -0
  323. package/dist/routes/page-routes.mjs +104 -0
  324. package/dist/routes/page-routes.mjs.map +1 -0
  325. package/dist/runtime.mjs +18 -13
  326. package/dist/runtime.mjs.map +1 -1
  327. package/dist/serverless.mjs +5 -0
  328. package/dist/store.mjs +19 -1
  329. package/dist/store.mjs.map +1 -1
  330. package/dist/targets.mjs +46 -0
  331. package/dist/targets.mjs.map +1 -0
  332. package/dist/ui/buttons.mjs +16 -3
  333. package/dist/ui/buttons.mjs.map +1 -1
  334. package/dist/ui/design-reference.mjs +97 -0
  335. package/dist/ui/design-reference.mjs.map +1 -0
  336. package/dist/ui/document.mjs +1 -2
  337. package/dist/ui/document.mjs.map +1 -1
  338. package/dist/ui/forms.mjs +6 -5
  339. package/dist/ui/forms.mjs.map +1 -1
  340. package/dist/ui/icons/paths.mjs +1 -0
  341. package/dist/ui/icons/paths.mjs.map +1 -1
  342. package/dist/ui/preview-embed.mjs +82 -0
  343. package/dist/ui/preview-embed.mjs.map +1 -0
  344. package/dist/ui/shell/client-script.mjs +85 -0
  345. package/dist/ui/shell/client-script.mjs.map +1 -1
  346. package/dist/ui/shell/nav.mjs +12 -8
  347. package/dist/ui/shell/nav.mjs.map +1 -1
  348. package/dist/ui/shell/review-script.mjs +8 -0
  349. package/dist/ui/shell/review-script.mjs.map +1 -1
  350. package/dist/ui/shell/sidebar.mjs +2 -2
  351. package/dist/ui/shell/sidebar.mjs.map +1 -1
  352. package/package.json +11 -9
  353. package/dist/asset-paths.mjs +0 -23
  354. package/dist/asset-paths.mjs.map +0 -1
  355. package/dist/assets/mark.svg +0 -1
  356. package/dist/pages/settings-status.mjs +0 -137
  357. package/dist/pages/settings-status.mjs.map +0 -1
  358. package/dist/routers/settings-status.mjs +0 -77
  359. package/dist/routers/settings-status.mjs.map +0 -1
  360. package/dist/routers/status-configs.mjs +0 -108
  361. package/dist/routers/status-configs.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"admin-system.mjs","names":[],"sources":["../../src/pages/admin-system.tsx"],"sourcesContent":["import type { ShelfConfig } from \"@storyshelf/core/config\";\nimport type { CredentialProbe } from \"@storyshelf/core/models\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { HealthReport } from \"../routers/health-report.ts\";\nimport {\n Alert,\n Badge,\n Button,\n Card,\n EmptyState,\n HStack,\n Meta,\n PageHeader,\n SectionTitle,\n Stat,\n Table,\n} from \"../ui/components.tsx\";\nimport { csrfField } from \"../ui/csrf-field.tsx\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Data for the site-admin System page (adapters, health, safe config). */\nexport interface AdminSystemData {\n report: HealthReport;\n authEnabled: boolean;\n config: ShelfConfig;\n /** Active login method labels (read-only badges; empty when auth is off). */\n authMethods?: string[];\n /** Stored-credential state; null/undefined when no secret is configured. */\n credentials?: CredentialProbe | null;\n}\n\nfunction healthTone(state: string): \"success\" | \"info\" | \"danger\" | \"neutral\" {\n if (state === \"ok\") {\n return \"success\";\n }\n if (state === \"starting\") {\n return \"info\";\n }\n if (state === \"failed\" || state === \"degraded\") {\n return \"danger\";\n }\n return \"neutral\";\n}\n\nfunction formatUptime(uptimeSecs: number): string {\n if (uptimeSecs < 60) {\n return `${uptimeSecs}s`;\n }\n const minutes = Math.floor(uptimeSecs / 60);\n if (minutes < 60) {\n return `${minutes}m ${uptimeSecs % 60}s`;\n }\n return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;\n}\n\nfunction formatLatency(latencyMs: number | undefined): string {\n return latencyMs === undefined ? \"—\" : `${latencyMs} ms`;\n}\n\nfunction formatOptional(value: string | number | null | undefined): string {\n if (value === undefined || value === null || value === \"\") {\n return \"—\";\n }\n return String(value);\n}\n\n/** One adapter row: identity, version, live probe state, latency, detail. */\nfunction AdapterRow(props: {\n adapter: HealthReport[\"adapters\"][number];\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { adapter } = props;\n return (\n <tr key={`${adapter.category}/${adapter.kind}`}>\n <td>\n <strong>{adapter.name}</strong>\n {adapter.description ? <Meta as=\"div\">{adapter.description}</Meta> : null}\n {adapter.hasLifecycle ? null : <Meta as=\"div\">No lifecycle hooks</Meta>}\n </td>\n <td>\n <Meta as=\"span\">{adapter.category}</Meta>\n </td>\n <td>\n <Meta as=\"span\" mono>\n {adapter.kind}\n </Meta>\n </td>\n <td>\n <Meta as=\"span\" mono>\n {adapter.version}\n </Meta>\n </td>\n <td>\n <Badge tone={healthTone(adapter.state)} icon>\n {adapter.state}\n </Badge>\n </td>\n <td class=\"nowrap\">\n <Meta as=\"span\">{formatLatency(adapter.latencyMs)}</Meta>\n </td>\n <td>\n <Meta as=\"span\">{adapter.detail ?? \"—\"}</Meta>\n </td>\n </tr>\n );\n}\n\ninterface AdapterTableProps {\n report: HealthReport;\n}\n\n/** Adapter inventory with live health states. */\nfunction AdapterTable(props: AdapterTableProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { report } = props;\n if (report.adapters.length === 0) {\n return <EmptyState description=\"No adapters wired.\" />;\n }\n return (\n <Table>\n <table>\n <thead>\n <tr>\n <th>Adapter</th>\n <th>Category</th>\n <th>Kind</th>\n <th>Version</th>\n <th>Health</th>\n <th>Latency</th>\n <th>Detail</th>\n </tr>\n </thead>\n <tbody>\n {report.adapters.map((adapter) => (\n <AdapterRow key={`${adapter.category}/${adapter.kind}`} adapter={adapter} />\n ))}\n </tbody>\n </table>\n </Table>\n );\n}\n\ninterface ServerFactsProps {\n report: HealthReport;\n authEnabled: boolean;\n config: ShelfConfig;\n authMethods?: string[];\n}\n\n/** Non-secret server facts (never secret, admin token, or credentials). */\nfunction ServerFacts(props: ServerFactsProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { report, authEnabled, config, authMethods } = props;\n return (\n <Card>\n <SectionTitle>Server</SectionTitle>\n <div class=\"grid grid--3\">\n <Stat label=\"Status\" value={report.status} />\n <Stat label=\"Uptime\" value={formatUptime(report.uptimeSecs)} />\n <Stat label=\"Version\" value={report.version} />\n </div>\n <Meta>\n Auth {authEnabled ? \"enabled\" : \"disabled — all operations permitted\"} · Capture concurrency{\" \"}\n {formatOptional(config.captureConcurrency)} · Purge TTL{\" \"}\n {formatOptional(config.purgeTtlDays)} days · Branch TTL{\" \"}\n {config.branchTtlDays === null\n ? \"disabled\"\n : `${formatOptional(config.branchTtlDays)} days`}\n </Meta>\n {authMethods && authMethods.length > 0 ? (\n <HStack>\n {authMethods.map((method) => (\n <Badge key={method}>{method}</Badge>\n ))}\n </HStack>\n ) : null}\n <Meta>\n Scratch dir {formatOptional(config.scratchDir)} · Published domain{\" \"}\n {formatOptional(config.publishedBaseDomain)} · Max upload{\" \"}\n {formatOptional(config.maxUploadBytes)} bytes\n </Meta>\n </Card>\n );\n}\n\n/** Credential encryption state with the admin-only re-encrypt action. */\nfunction CredentialsCard(props: {\n probe: CredentialProbe;\n config: ShelfConfig;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { probe, config } = props;\n const rotating = Boolean(config.previousSecret);\n return (\n <Card>\n <SectionTitle>Stored credentials</SectionTitle>\n <Meta>\n Webhook secrets, git tokens and notification secrets are encrypted with the server secret.\n </Meta>\n <div class=\"grid grid--3\">\n <Stat label=\"Current secret\" value={String(probe.current)} />\n <Stat label=\"Previous secret\" value={String(probe.previous)} />\n <Stat label=\"Unreadable\" value={String(probe.unreadable.length)} />\n </div>\n {probe.unreadable.length > 0 ? (\n <Alert tone=\"danger\" title=\"Some credentials cannot be decrypted\">\n No configured secret matches {probe.unreadable.length} stored credential(s) (\n {[...new Set(probe.unreadable.map((row) => row.source))].join(\", \")}). Check that SECRET\n and SECRET_PREVIOUS are correct, or recreate them.\n </Alert>\n ) : null}\n {rotating && probe.previous > 0 ? (\n <form\n method=\"post\"\n action=\"/admin/credentials/reencrypt\"\n hx-post=\"/admin/credentials/reencrypt\"\n hx-target=\"body\"\n >\n {csrfField()}\n <Button type=\"submit\" variant=\"secondary\" size=\"sm\">\n Re-encrypt with current secret\n </Button>\n </form>\n ) : (\n <Meta>\n {rotating\n ? \"All credentials use the current secret. Remove SECRET_PREVIOUS.\"\n : \"To rotate, set SECRET to the new value and SECRET_PREVIOUS to the old one.\"}\n </Meta>\n )}\n </Card>\n );\n}\n\n/** Site-admin System page: adapter inventory with in-depth health. */\nexport function renderAdminSystemPage(data: AdminSystemData): RenderedContent {\n const { report, authEnabled, config, authMethods, credentials } = data;\n return (\n <DocumentLayout title=\"System\" nav={{ active: \"admin\" }}>\n <PageHeader\n title=\"System\"\n description=\"Adapter inventory with in-depth health. Visible to site admins only.\"\n meta={\n <>\n {report.adapters.length} adapters · {report.status} · up{\" \"}\n {formatUptime(report.uptimeSecs)}\n </>\n }\n actions={\n <Button variant=\"secondary\" size=\"sm\" href=\"/admin\">\n Recheck\n </Button>\n }\n />\n <ServerFacts\n report={report}\n authEnabled={authEnabled}\n config={config}\n authMethods={authMethods}\n />\n {credentials ? (\n <div class=\"mt-1\">\n <CredentialsCard probe={credentials} config={config} />\n </div>\n ) : null}\n <div class=\"mt-1\">\n <Card>\n <SectionTitle>Adapters</SectionTitle>\n <Meta>\n Live probe per adapter (2s timeout, parallel). Setup failures surface as failed rows;\n adapters without lifecycle hooks report their wired state.\n </Meta>\n <AdapterTable report={report} />\n </Card>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"admin-system.mjs","names":[],"sources":["../../src/pages/admin-system.tsx"],"sourcesContent":["import type { RunnerTarget } from \"@storyshelf/core/adapter/capture-runner\";\nimport type { ShelfConfig } from \"@storyshelf/core/config\";\nimport type { CredentialProbe } from \"@storyshelf/core/models\";\nimport type { CaptureHostSummary } from \"@storyshelf/core/utils\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { HealthReport } from \"../routers/health-report.ts\";\nimport {\n Alert,\n Badge,\n Button,\n Card,\n EmptyState,\n HStack,\n Meta,\n PageHeader,\n SectionTitle,\n Stat,\n Table,\n} from \"../ui/components.tsx\";\nimport { csrfField } from \"../ui/csrf-field.tsx\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport { PublicOriginCard, type PublicOriginView } from \"./admin-system-origin.tsx\";\nimport { CaptureTargetsCard } from \"./admin-system-targets.tsx\";\nimport { NotificationsPausedNotice } from \"./notifications-paused.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Data for the site-admin System page (adapters, health, safe config). */\nexport interface AdminSystemData {\n report: HealthReport;\n authEnabled: boolean;\n config: ShelfConfig;\n /** Active login method labels (read-only badges; empty when auth is off). */\n authMethods?: string[];\n /** Stored-credential state; null/undefined when no secret is configured. */\n credentials?: CredentialProbe | null;\n /** True when AI is configured (shows the AI usage link). */\n aiEnabled?: boolean;\n /** Whether `options.testing` is set; false shows the hosting-only notice. */\n testing?: boolean;\n /** Snapshots hidden because `testing` is off (only meaningful when it is). */\n hiddenSnapshots?: number;\n /** Detected host budget and effective capture concurrency; null when capture runs elsewhere. */\n capture?: CaptureHostSummary | null;\n /** The browsers the capture runner offers (ADR 0007); omitted without a runner list. */\n targets?: readonly RunnerTarget[] | undefined;\n /** The public origin and where it came from; omitted when the store has no resolver. */\n publicOrigin?: PublicOriginView | undefined;\n /** Notifiers are wired but no public origin resolves, so nothing is sent (ADR 0014, decision 11). */\n notificationsPaused?: boolean | undefined;\n}\n\nfunction healthTone(state: string): \"success\" | \"info\" | \"danger\" | \"neutral\" {\n if (state === \"ok\") {\n return \"success\";\n }\n if (state === \"starting\") {\n return \"info\";\n }\n if (state === \"failed\" || state === \"degraded\") {\n return \"danger\";\n }\n return \"neutral\";\n}\n\nfunction formatUptime(uptimeSecs: number): string {\n if (uptimeSecs < 60) {\n return `${uptimeSecs}s`;\n }\n const minutes = Math.floor(uptimeSecs / 60);\n if (minutes < 60) {\n return `${minutes}m ${uptimeSecs % 60}s`;\n }\n return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;\n}\n\nfunction formatLatency(latencyMs: number | undefined): string {\n return latencyMs === undefined ? \"—\" : `${latencyMs} ms`;\n}\n\nfunction formatOptional(value: string | number | null | undefined): string {\n if (value === undefined || value === null || value === \"\") {\n return \"—\";\n }\n return String(value);\n}\n\n/** One adapter row: identity, version, live probe state, latency, detail. */\nfunction AdapterRow(props: {\n adapter: HealthReport[\"adapters\"][number];\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { adapter } = props;\n return (\n <tr key={`${adapter.category}/${adapter.kind}`}>\n <td>\n <strong>{adapter.name}</strong>\n {adapter.description ? <Meta as=\"div\">{adapter.description}</Meta> : null}\n {adapter.hasLifecycle ? null : <Meta as=\"div\">No lifecycle hooks</Meta>}\n </td>\n <td>\n <Meta as=\"span\">{adapter.category}</Meta>\n </td>\n <td>\n <Meta as=\"span\" mono>\n {adapter.kind}\n </Meta>\n </td>\n <td>\n <Meta as=\"span\" mono>\n {adapter.version}\n </Meta>\n </td>\n <td>\n <Badge tone={healthTone(adapter.state)} icon>\n {adapter.state}\n </Badge>\n </td>\n <td class=\"nowrap\">\n <Meta as=\"span\">{formatLatency(adapter.latencyMs)}</Meta>\n </td>\n <td>\n <Meta as=\"span\">{adapter.detail ?? \"—\"}</Meta>\n </td>\n </tr>\n );\n}\n\ninterface AdapterTableProps {\n report: HealthReport;\n}\n\n/** Adapter inventory with live health states. */\nfunction AdapterTable(props: AdapterTableProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { report } = props;\n if (report.adapters.length === 0) {\n return <EmptyState description=\"No adapters wired.\" />;\n }\n return (\n <Table>\n <table>\n <thead>\n <tr>\n <th>Adapter</th>\n <th>Category</th>\n <th>Kind</th>\n <th>Version</th>\n <th>Health</th>\n <th>Latency</th>\n <th>Detail</th>\n </tr>\n </thead>\n <tbody>\n {report.adapters.map((adapter) => (\n <AdapterRow key={`${adapter.category}/${adapter.kind}`} adapter={adapter} />\n ))}\n </tbody>\n </table>\n </Table>\n );\n}\n\n/** Detected hardware and the capture concurrency it yields (auto unless configured). */\nfunction CaptureSizing(props: {\n capture: CaptureHostSummary;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { cpus, memoryMb, builds, pages } = props.capture;\n return (\n <Meta>\n Capture host {cpus} CPU · {memoryMb} MB · builds at once {builds.value}\n {builds.auto ? \" (auto)\" : \" (configured)\"} · pages per build {pages.start}–{pages.max} (auto,\n adapts to load unless the runner pins it)\n </Meta>\n );\n}\n\ninterface ServerFactsProps {\n data: AdminSystemData;\n}\n\n/** Non-secret server facts (never secret, admin token, or credentials). */\nfunction ServerFacts(props: ServerFactsProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { report, authEnabled, config, authMethods, capture } = props.data;\n return (\n <Card>\n <SectionTitle>Server</SectionTitle>\n <div class=\"grid grid--3\">\n <Stat label=\"Status\" value={report.status} />\n <Stat label=\"Uptime\" value={formatUptime(report.uptimeSecs)} />\n <Stat label=\"Version\" value={report.version} />\n </div>\n <Meta>\n Auth {authEnabled ? \"enabled\" : \"disabled — all operations permitted\"} · Purge TTL{\" \"}\n {formatOptional(config.purgeTtlDays)} days · Branch TTL{\" \"}\n {config.branchTtlDays === null\n ? \"disabled\"\n : `${formatOptional(config.branchTtlDays)} days`}\n </Meta>\n {authMethods && authMethods.length > 0 ? (\n <HStack>\n {authMethods.map((method) => (\n <Badge key={method}>{method}</Badge>\n ))}\n </HStack>\n ) : null}\n {capture ? <CaptureSizing capture={capture} /> : null}\n <Meta>\n Scratch dir {formatOptional(config.scratchDir)} · Published domain{\" \"}\n {formatOptional(config.publishedBaseDomain)} · Max upload{\" \"}\n {formatOptional(config.maxUploadBytes)} bytes\n </Meta>\n </Card>\n );\n}\n\n/** Credential encryption state with the admin-only re-encrypt action. */\nfunction CredentialsCard(props: {\n probe: CredentialProbe;\n config: ShelfConfig;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { probe, config } = props;\n const rotating = Boolean(config.previousSecret);\n return (\n <Card>\n <SectionTitle>Stored credentials</SectionTitle>\n <Meta>\n Webhook secrets, git tokens and notification secrets are encrypted with the server secret.\n </Meta>\n <div class=\"grid grid--3\">\n <Stat label=\"Current secret\" value={String(probe.current)} />\n <Stat label=\"Previous secret\" value={String(probe.previous)} />\n <Stat label=\"Unreadable\" value={String(probe.unreadable.length)} />\n </div>\n {probe.unreadable.length > 0 ? (\n <Alert tone=\"danger\" title=\"Some credentials cannot be decrypted\">\n No configured secret matches {probe.unreadable.length} stored credential(s) (\n {[...new Set(probe.unreadable.map((row) => row.source))].join(\", \")}). Check that SECRET\n and SECRET_PREVIOUS are correct, or recreate them.\n </Alert>\n ) : null}\n {rotating && probe.previous > 0 ? (\n <form\n method=\"post\"\n action=\"/admin/credentials/reencrypt\"\n hx-post=\"/admin/credentials/reencrypt\"\n hx-target=\"body\"\n >\n {csrfField()}\n <Button type=\"submit\" variant=\"secondary\" size=\"sm\">\n Re-encrypt with current secret\n </Button>\n </form>\n ) : (\n <Meta>\n {rotating\n ? \"All credentials use the current secret. Remove SECRET_PREVIOUS.\"\n : \"To rotate, set SECRET to the new value and SECRET_PREVIOUS to the old one.\"}\n </Meta>\n )}\n </Card>\n );\n}\n\n/** Notice that `testing` is off, so a misconfigured instance in a fleet is visible (ADR 0006). */\nfunction HostingOnlyNotice(props: {\n hiddenSnapshots: number;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { hiddenSnapshots } = props;\n return (\n <div class=\"mt-1\">\n <Alert tone=\"info\" title=\"Testing is off: Storybook hosting only\">\n This instance publishes and serves Storybooks, but does not capture, compare or keep\n baselines, and no testing status is posted. Set the <code>testing</code> option to turn\n testing on.\n {hiddenSnapshots > 0\n ? ` ${hiddenSnapshots} existing snapshot(s) are hidden, not deleted, and reappear when testing is turned on.`\n : \"\"}\n </Alert>\n </div>\n );\n}\n\n/** The public origin and the capture browsers (ADR 0007), when the instance has them. */\nfunction CaptureCards(props: {\n data: AdminSystemData;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { publicOrigin, targets } = props.data;\n return (\n <>\n {publicOrigin ? (\n <div class=\"mt-1\">\n <PublicOriginCard origin={publicOrigin} />\n </div>\n ) : null}\n {targets && targets.length > 0 ? (\n <div class=\"mt-1\">\n <CaptureTargetsCard targets={targets} />\n </div>\n ) : null}\n </>\n );\n}\n\n/** Site-admin System page: adapter inventory with in-depth health. */\nexport function renderAdminSystemPage(data: AdminSystemData): RenderedContent {\n const { report, config, credentials } = data;\n return (\n <DocumentLayout title=\"System\" nav={{ active: \"admin\" }}>\n <PageHeader\n title=\"System\"\n description=\"Adapter inventory with in-depth health. Visible to site admins only.\"\n meta={\n <>\n {report.adapters.length} adapters · {report.status} · up{\" \"}\n {formatUptime(report.uptimeSecs)}\n </>\n }\n actions={\n <HStack>\n {data.aiEnabled ? (\n <Button variant=\"secondary\" size=\"sm\" href=\"/admin/ai\">\n AI usage\n </Button>\n ) : null}\n <Button variant=\"secondary\" size=\"sm\" href=\"/admin\">\n Recheck\n </Button>\n </HStack>\n }\n />\n <NotificationsPausedNotice paused={data.notificationsPaused} />\n {data.testing === false ? (\n <HostingOnlyNotice hiddenSnapshots={data.hiddenSnapshots ?? 0} />\n ) : null}\n <ServerFacts data={data} />\n <CaptureCards data={data} />\n {credentials ? (\n <div class=\"mt-1\">\n <CredentialsCard probe={credentials} config={config} />\n </div>\n ) : null}\n <div class=\"mt-1\">\n <Card>\n <SectionTitle>Adapters</SectionTitle>\n <Meta>\n Live probe per adapter (2s timeout, parallel). Setup failures surface as failed rows;\n adapters without lifecycle hooks report their wired state.\n </Meta>\n <AdapterTable report={report} />\n </Card>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
@@ -1,18 +1,29 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { getStore } from "../store.mjs";
4
+ import { catalogOf, getStore, testingEnabled } from "../store.mjs";
5
5
  import { Button } from "../ui/buttons.mjs";
6
6
  import { Badge, EmptyState, Meta, Stat, statusTone } from "../ui/feedback.mjs";
7
- import { TextareaField } from "../ui/forms.mjs";
8
7
  import { Card, PageHeader, SectionTitle } from "../ui/layout.mjs";
9
8
  import { HStack, VStack } from "../ui/stacks.mjs";
10
9
  import { Thumbnail } from "../ui/thumbnail.mjs";
11
10
  import { DocumentLayout } from "../ui/document.mjs";
12
11
  import { loadBaselineView } from "../routers/baseline-guard.mjs";
13
- import { reviewComment, reviewCommentBody, reviewCommentHead, snapshotCard, snapshotCardBody, snapshotCardHead, snapshotCardMeta, snapshotGrid } from "../ui/styles/review.mjs";
12
+ import { designUrlsFor, loadDesignResolutions, providerNameFor } from "../routers/design.handlers.mjs";
13
+ import { loadReviewSection } from "../routers/review-section.handlers.mjs";
14
+ import { snapshotCard, snapshotCardBody, snapshotCardHead, snapshotCardMeta, snapshotGrid } from "../ui/styles/review.mjs";
15
+ import { a11ySummaryNote, a11ySummaryOf } from "./a11y-violations.mjs";
16
+ import { snapshotVariant } from "./build-diff-model.mjs";
14
17
  import { StaleBaselineNotice } from "./build-diff-stale.mjs";
15
- import { BuildModel, CaptureAttemptModel, CaptureLogModel, CommentModel, ProjectModel, SnapshotModel } from "@storyshelf/core/models";
18
+ import { LinkedUsage } from "./build-diff-usage.mjs";
19
+ import { TestsCard } from "./build-tests.mjs";
20
+ import { DesignButtons } from "./design-links.mjs";
21
+ import { ChangesetCard } from "./review-changeset.mjs";
22
+ import { CommentsCard } from "./review-comments.mjs";
23
+ import { VerdictCard } from "./review-verdict.mjs";
24
+ import { groupComponents, parseTestResults } from "@storyshelf/core/capture";
25
+ import { BuildModel, CaptureAttemptModel, CaptureLogModel, LabelModel, ProjectModel, SnapshotModel } from "@storyshelf/core/models";
26
+ import { CANCELLABLE_BUILD_STATUSES } from "@storyshelf/core/types";
16
27
  import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
17
28
  import { createUrlBuilder } from "@storyshelf/core/urls";
18
29
  //#region src/pages/build-detail.tsx
@@ -24,22 +35,29 @@ function logTone(level) {
24
35
  return "neutral";
25
36
  }
26
37
  /** Build overview page: snapshot grid, bulk actions, and build comments. */
27
- async function renderBuildDetailPage(buildId) {
38
+ async function renderBuildDetailPage(buildId, usageByStory = /* @__PURE__ */ new Map()) {
28
39
  const { db } = getStore();
29
40
  const build = await new BuildModel(db).get(buildId);
30
41
  if (!build) return null;
31
42
  const project = await new ProjectModel(db).get(build.projectId);
32
43
  if (!project) return null;
33
44
  const snapshots = await new SnapshotModel(db).listByBuild(build.id);
34
- const comments = await new CommentModel(db).listByBuild(build.id);
35
45
  const attempts = await new CaptureAttemptModel(db).listByBuild(build.id);
36
46
  const logs = new CaptureLogModel(db);
37
47
  const attemptLogs = /* @__PURE__ */ new Map();
38
48
  for (const attempt of attempts) attemptLogs.set(attempt.id, await logs.listByAttempt(attempt.id));
39
49
  const canReview = !getStore().authEnabled || Boolean(getStore().user);
40
50
  const { drifted } = await loadBaselineView(project, build, snapshots);
51
+ const designs = await loadDesignResolutions(project, snapshots).catch(() => /* @__PURE__ */ new Map());
41
52
  const staleCount = Object.keys(drifted).length;
42
53
  const urls = createUrlBuilder("/", getStore().config.publishedBaseDomain);
54
+ const testing = testingEnabled();
55
+ const cancelled = build.status === "cancelled";
56
+ const published = build.status === "approved" && attempts.length === 0;
57
+ const [catalog, labels] = await Promise.all([catalogOf().load(project.id, build.id).catch(() => null), new LabelModel(db).listForBuild(build.id)]);
58
+ const review = await loadReviewSection(project, build, catalog);
59
+ const components = catalog ? groupComponents(catalog.entries) : [];
60
+ const storyCount = catalog?.entries.filter((entry) => entry.type === "story").length ?? 0;
43
61
  const grouped = /* @__PURE__ */ new Map();
44
62
  for (const snap of snapshots) {
45
63
  const key = snap.status;
@@ -47,7 +65,7 @@ async function renderBuildDetailPage(buildId) {
47
65
  list.push(snap);
48
66
  grouped.set(key, list);
49
67
  }
50
- const inheritedCount = snapshots.filter((snap) => snap.inherited).length;
68
+ const a11y = a11ySummaryOf(snapshots);
51
69
  const selective = build.affectedOnly && build.affectedImportPaths !== null;
52
70
  return /* @__PURE__ */ jsxs(DocumentLayout, {
53
71
  title: `Build ${build.gitBranch}`,
@@ -69,16 +87,24 @@ async function renderBuildDetailPage(buildId) {
69
87
  " ",
70
88
  /* @__PURE__ */ jsx(Badge, {
71
89
  tone: statusTone(build.status),
72
- children: build.status
90
+ children: published ? "Published" : build.status
73
91
  }),
74
- selective ? /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Badge, {
92
+ testing && selective ? /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Badge, {
75
93
  tone: "info",
76
94
  children: "affected capture"
77
95
  })] }) : null,
78
- !build.affectedOnly ? /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Badge, {
96
+ testing && !build.affectedOnly ? /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Badge, {
79
97
  tone: "neutral",
80
98
  children: "full capture"
81
- })] }) : null
99
+ })] }) : null,
100
+ labels.map((label) => /* @__PURE__ */ jsxs("span", { children: [" ", /* @__PURE__ */ jsxs(Badge, {
101
+ tone: "neutral",
102
+ children: [
103
+ label.typeKey,
104
+ ": ",
105
+ label.value
106
+ ]
107
+ })] }, `${label.typeKey}:${label.value}`))
82
108
  ] }),
83
109
  description: /* @__PURE__ */ jsxs(Fragment, { children: [
84
110
  build.message ?? "No message",
@@ -87,27 +113,28 @@ async function renderBuildDetailPage(buildId) {
87
113
  " ",
88
114
  "· ",
89
115
  new Date(build.createdAt).toLocaleString(),
90
- selective ? /* @__PURE__ */ jsxs(Fragment, { children: [
116
+ testing && selective ? /* @__PURE__ */ jsxs(Fragment, { children: [
91
117
  " ",
92
118
  "· ",
93
- snapshots.length - inheritedCount,
119
+ build.snapshotCount - build.inheritedCount,
94
120
  " rendered, ",
95
- inheritedCount,
96
- " inherited"
121
+ build.inheritedCount,
122
+ " ",
123
+ "inherited"
97
124
  ] }) : null
98
125
  ] }),
99
126
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [
100
- /* @__PURE__ */ jsx(Button, {
127
+ testing ? /* @__PURE__ */ jsx(Button, {
101
128
  variant: "primary",
102
129
  href: urls.buildDiff(project.slug, build.id),
103
130
  children: "Review diffs"
104
- }),
131
+ }) : null,
105
132
  /* @__PURE__ */ jsx(Button, {
106
- variant: "ghost",
133
+ variant: testing ? "ghost" : "primary",
107
134
  href: urls.short(build.id),
108
135
  children: "View Storybook"
109
136
  }),
110
- /* @__PURE__ */ jsx("form", {
137
+ testing ? /* @__PURE__ */ jsx("form", {
111
138
  method: "post",
112
139
  action: `/api/v1/projects/${project.slug}/builds/${build.id}/retry`,
113
140
  "hx-post": `/api/v1/projects/${project.slug}/builds/${build.id}/retry`,
@@ -117,10 +144,22 @@ async function renderBuildDetailPage(buildId) {
117
144
  type: "submit",
118
145
  children: "Retry capture"
119
146
  })
120
- })
147
+ }) : null,
148
+ testing && CANCELLABLE_BUILD_STATUSES.includes(build.status) ? /* @__PURE__ */ jsx("form", {
149
+ method: "post",
150
+ action: `/api/v1/projects/${project.slug}/builds/${build.id}/cancel`,
151
+ "hx-post": `/api/v1/projects/${project.slug}/builds/${build.id}/cancel`,
152
+ "hx-target": "body",
153
+ children: /* @__PURE__ */ jsx(Button, {
154
+ variant: "danger",
155
+ size: "sm",
156
+ type: "submit",
157
+ children: "Cancel build"
158
+ })
159
+ }) : null
121
160
  ] })
122
161
  }),
123
- staleCount > 0 ? /* @__PURE__ */ jsx(StaleBaselineNotice, {
162
+ testing && staleCount > 0 ? /* @__PURE__ */ jsx(StaleBaselineNotice, {
124
163
  project,
125
164
  build,
126
165
  staleCount,
@@ -130,11 +169,21 @@ async function renderBuildDetailPage(buildId) {
130
169
  class: "mb-1",
131
170
  children: /* @__PURE__ */ jsxs("div", {
132
171
  class: "grid grid--3",
133
- children: [
172
+ children: [catalog ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(Stat, {
173
+ label: "Components",
174
+ value: components.length
175
+ }) }), /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(Stat, {
176
+ label: "Stories",
177
+ value: storyCount
178
+ }) })] }) : null, testing ? /* @__PURE__ */ jsxs(Fragment, { children: [
134
179
  /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(Stat, {
135
180
  label: "Snapshots",
136
181
  value: snapshots.length
137
182
  }) }),
183
+ /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(Stat, {
184
+ label: "Inherited",
185
+ value: build.inheritedCount
186
+ }), /* @__PURE__ */ jsx(Meta, { children: "Reused from the baseline without rendering." })] }),
138
187
  /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(Stat, {
139
188
  label: "Changed / new",
140
189
  value: build.changedCount,
@@ -144,11 +193,16 @@ async function renderBuildDetailPage(buildId) {
144
193
  label: "Approved / unchanged",
145
194
  value: build.approvedCount,
146
195
  tone: "success"
147
- }) })
148
- ]
196
+ }) }),
197
+ /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(Stat, {
198
+ label: "Accessibility findings",
199
+ value: a11y.findings,
200
+ tone: a11y.findings > 0 ? "warning" : "neutral"
201
+ }), /* @__PURE__ */ jsx(Meta, { children: a11ySummaryNote(a11y) })] })
202
+ ] }) : null]
149
203
  })
150
204
  }),
151
- attempts.length > 0 ? /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(SectionTitle, { children: "Capture attempts" }), attempts.map((attempt, index) => /* @__PURE__ */ jsxs("details", {
205
+ testing && attempts.length > 0 ? /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(SectionTitle, { children: "Capture attempts" }), attempts.map((attempt, index) => /* @__PURE__ */ jsxs("details", {
152
206
  open: index === attempts.length - 1,
153
207
  children: [
154
208
  /* @__PURE__ */ jsxs("summary", { children: [
@@ -193,7 +247,8 @@ async function renderBuildDetailPage(buildId) {
193
247
  })
194
248
  ]
195
249
  }, attempt.id))] }) : null,
196
- canReview && (build.status === "reviewing" || build.status === "comparing") ? /* @__PURE__ */ jsx("div", {
250
+ /* @__PURE__ */ jsx(TestsCard, { results: parseTestResults(build.testResults) }),
251
+ testing && canReview && (build.status === "reviewing" || build.status === "comparing") ? /* @__PURE__ */ jsx("div", {
197
252
  class: "mb-1",
198
253
  children: /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(HStack, { children: [
199
254
  /* @__PURE__ */ jsx(Meta, {
@@ -235,7 +290,11 @@ async function renderBuildDetailPage(buildId) {
235
290
  })
236
291
  ] }) })
237
292
  }) : null,
238
- snapshots.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
293
+ review ? /* @__PURE__ */ jsx("div", {
294
+ class: "mb-1",
295
+ children: /* @__PURE__ */ jsxs(VStack, { children: [testing || cancelled ? null : /* @__PURE__ */ jsx(VerdictCard, { data: review }), /* @__PURE__ */ jsx(ChangesetCard, { data: review })] })
296
+ }) : null,
297
+ !testing ? null : snapshots.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
239
298
  title: "No snapshots yet",
240
299
  description: "Capture is pending or failed. Try retrying capture."
241
300
  }) : /* @__PURE__ */ jsx("div", {
@@ -256,7 +315,7 @@ async function renderBuildDetailPage(buildId) {
256
315
  /* @__PURE__ */ jsxs("span", {
257
316
  class: snapshotCardMeta,
258
317
  children: [
259
- snap.viewportName,
318
+ snapshotVariant(snap),
260
319
  " · ",
261
320
  snap.viewportWidth,
262
321
  "×",
@@ -290,6 +349,10 @@ async function renderBuildDetailPage(buildId) {
290
349
  snap.diffPixels === null || snap.diffPixels === void 0 ? "" : ` · ${snap.diffPixels} px`
291
350
  ]
292
351
  }),
352
+ snap.storyImportPath && usageByStory.has(snap.storyImportPath) ? /* @__PURE__ */ jsx(LinkedUsage, {
353
+ usage: usageByStory.get(snap.storyImportPath) ?? [],
354
+ publishedBaseDomain: getStore().config.publishedBaseDomain
355
+ }) : null,
293
356
  /* @__PURE__ */ jsxs(HStack, { children: [
294
357
  /* @__PURE__ */ jsx(Button, {
295
358
  variant: "secondary",
@@ -303,6 +366,16 @@ async function renderBuildDetailPage(buildId) {
303
366
  href: urls.short(build.id),
304
367
  children: "View Storybook"
305
368
  }),
369
+ snap.storyImportPath ? /* @__PURE__ */ jsx(Button, {
370
+ variant: "ghost",
371
+ size: "sm",
372
+ href: `${urls.component(project.slug, snap.storyImportPath)}#${snap.storyId}`,
373
+ children: "Component"
374
+ }) : null,
375
+ /* @__PURE__ */ jsx(DesignButtons, {
376
+ urls: designUrlsFor(designs, snap),
377
+ providerName: providerNameFor
378
+ }),
306
379
  canReview && (snap.status === "new" || snap.status === "changed") ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("form", {
307
380
  method: "post",
308
381
  action: `/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/approve`,
@@ -334,55 +407,10 @@ async function renderBuildDetailPage(buildId) {
334
407
  })]
335
408
  }, snap.id))
336
409
  }),
337
- /* @__PURE__ */ jsx("div", {
410
+ review ? /* @__PURE__ */ jsx("div", {
338
411
  class: "mt-1",
339
- children: /* @__PURE__ */ jsxs(Card, { children: [
340
- /* @__PURE__ */ jsx(SectionTitle, { children: "Comments" }),
341
- comments.length === 0 ? /* @__PURE__ */ jsx(Meta, { children: "No comments. Add one in the diff review page for a specific snapshot." }) : null,
342
- /* @__PURE__ */ jsx(VStack, { children: comments.map((comment) => /* @__PURE__ */ jsxs("div", {
343
- class: reviewComment,
344
- children: [/* @__PURE__ */ jsxs("div", {
345
- class: reviewCommentHead,
346
- children: [
347
- /* @__PURE__ */ jsx("strong", { children: comment.userId ?? "anonymous" }),
348
- /* @__PURE__ */ jsxs("span", { children: ["· ", new Date(comment.createdAt).toLocaleString()] }),
349
- comment.snapshotId ? /* @__PURE__ */ jsx(Badge, {
350
- tone: "neutral",
351
- children: snapshots.find((snapshot) => snapshot.id === comment.snapshotId)?.storyName ?? comment.snapshotId.slice(0, 6)
352
- }) : /* @__PURE__ */ jsx(Badge, {
353
- tone: "neutral",
354
- children: "build"
355
- }),
356
- comment.resolved ? /* @__PURE__ */ jsx(Badge, {
357
- tone: "success",
358
- children: "resolved"
359
- }) : null
360
- ]
361
- }), /* @__PURE__ */ jsx("p", {
362
- class: reviewCommentBody,
363
- children: comment.body
364
- })]
365
- }, comment.id)) }),
366
- /* @__PURE__ */ jsx("form", {
367
- method: "post",
368
- action: `/api/v1/projects/${project.slug}/builds/${build.id}/comments`,
369
- "hx-post": `/api/v1/projects/${project.slug}/builds/${build.id}/comments`,
370
- "hx-target": "body",
371
- class: "mt-1 max-w-prose",
372
- children: /* @__PURE__ */ jsxs(VStack, { children: [/* @__PURE__ */ jsx(TextareaField, {
373
- label: "Add build comment",
374
- name: "body",
375
- rows: 3,
376
- required: true,
377
- placeholder: "Leave a comment on this build…"
378
- }), /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, {
379
- variant: "primary",
380
- type: "submit",
381
- children: "Comment"
382
- }) })] })
383
- })
384
- ] })
385
- })
412
+ children: /* @__PURE__ */ jsxs(VStack, { children: [testing && !cancelled ? /* @__PURE__ */ jsx(VerdictCard, { data: review }) : null, /* @__PURE__ */ jsx(CommentsCard, { data: review })] })
413
+ }) : null
386
414
  ]
387
415
  });
388
416
  }
@@ -1 +1 @@
1
- {"version":3,"file":"build-detail.mjs","names":[],"sources":["../../src/pages/build-detail.tsx"],"sourcesContent":["import { BuildModel } from \"@storyshelf/core/models\";\nimport { CaptureAttemptModel } from \"@storyshelf/core/models\";\nimport { CaptureLogModel } from \"@storyshelf/core/models\";\nimport { CommentModel } from \"@storyshelf/core/models\";\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport { SnapshotModel } from \"@storyshelf/core/models\";\nimport { createUrlBuilder } from \"@storyshelf/core/urls\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { loadBaselineView } from \"../routers/baseline-guard.ts\";\nimport { getStore } from \"../store.ts\";\nimport {\n Badge,\n Button,\n Card,\n EmptyState,\n HStack,\n Meta,\n PageHeader,\n SectionTitle,\n Stat,\n TextareaField,\n Thumbnail,\n VStack,\n statusTone,\n} from \"../ui/components.tsx\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport {\n reviewComment,\n reviewCommentBody,\n reviewCommentHead,\n snapshotCard,\n snapshotCardBody,\n snapshotCardHead,\n snapshotCardMeta,\n snapshotGrid,\n} from \"../ui/styles/review.ts\";\nimport { StaleBaselineNotice } from \"./build-diff-stale.tsx\";\n\n/** Map a capture log level to its badge tone. */\nfunction logTone(level: string): \"neutral\" | \"success\" | \"warning\" | \"danger\" | \"info\" {\n if (level === \"error\") {\n return \"danger\";\n }\n if (level === \"warn\") {\n return \"warning\";\n }\n if (level === \"info\") {\n return \"info\";\n }\n return \"neutral\";\n}\n/** Build overview page: snapshot grid, bulk actions, and build comments. */\nexport async function renderBuildDetailPage(buildId: string): Promise<RenderedContent | null> {\n const { db } = getStore();\n const build = await new BuildModel(db).get(buildId);\n if (!build) {\n return null;\n }\n const project = await new ProjectModel(db).get(build.projectId);\n if (!project) {\n return null;\n }\n const snapshots = await new SnapshotModel(db).listByBuild(build.id);\n const comments = await new CommentModel(db).listByBuild(build.id);\n const attempts = await new CaptureAttemptModel(db).listByBuild(build.id);\n const logs = new CaptureLogModel(db);\n const attemptLogs = new Map<string, Awaited<ReturnType<typeof logs.listByAttempt>>>();\n /* oxlint-disable-next-line eslint/no-await-in-loop -- attempts are few; sequential reads keep code simple */\n for (const attempt of attempts) {\n attemptLogs.set(attempt.id, await logs.listByAttempt(attempt.id));\n }\n const canReview = !getStore().authEnabled || Boolean(getStore().user);\n const { drifted } = await loadBaselineView(project, build, snapshots);\n const staleCount = Object.keys(drifted).length;\n const urls = createUrlBuilder(\"/\", getStore().config.publishedBaseDomain);\n\n const grouped = new Map<string, typeof snapshots>();\n for (const snap of snapshots) {\n const key = snap.status;\n const list = grouped.get(key) ?? [];\n list.push(snap);\n grouped.set(key, list);\n }\n const inheritedCount = snapshots.filter((snap) => snap.inherited).length;\n const selective = build.affectedOnly && build.affectedImportPaths !== null;\n\n return (\n <DocumentLayout\n title={`Build ${build.gitBranch}`}\n nav={{\n active: \"builds\",\n projectSlug: project.slug,\n projectName: project.name,\n trail: [{ label: `${build.gitBranch} · ${build.gitSha.slice(0, 7)}` }],\n }}\n >\n <PageHeader\n title={\n <>\n {build.gitBranch} <Meta as=\"span\">· {build.gitSha.slice(0, 7)}</Meta>{\" \"}\n <Badge tone={statusTone(build.status)}>{build.status}</Badge>\n {selective ? (\n <>\n {\" \"}\n <Badge tone=\"info\">affected capture</Badge>\n </>\n ) : null}\n {!build.affectedOnly ? (\n <>\n {\" \"}\n <Badge tone=\"neutral\">full capture</Badge>\n </>\n ) : null}\n </>\n }\n description={\n <>\n {build.message ?? \"No message\"}{\" \"}\n {build.authorName\n ? `· ${build.authorName}${build.authorEmail ? ` <${build.authorEmail}>` : \"\"}`\n : \"\"}{\" \"}\n · {new Date(build.createdAt).toLocaleString()}\n {selective ? (\n <>\n {\" \"}\n · {snapshots.length - inheritedCount} rendered, {inheritedCount} inherited\n </>\n ) : null}\n </>\n }\n actions={\n <>\n <Button variant=\"primary\" href={urls.buildDiff(project.slug, build.id)}>\n Review diffs\n </Button>\n <Button variant=\"ghost\" href={urls.short(build.id)}>\n View Storybook\n </Button>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/retry`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/retry`}\n hx-target=\"body\"\n >\n <Button variant=\"secondary\" type=\"submit\">\n Retry capture\n </Button>\n </form>\n </>\n }\n />\n\n {staleCount > 0 ? (\n <StaleBaselineNotice\n project={project}\n build={build}\n staleCount={staleCount}\n canReview={canReview}\n />\n ) : null}\n\n <div class=\"mb-1\">\n <div class=\"grid grid--3\">\n <Card>\n <Stat label=\"Snapshots\" value={snapshots.length} />\n </Card>\n <Card>\n <Stat label=\"Changed / new\" value={build.changedCount} tone=\"warning\" />\n </Card>\n <Card>\n <Stat label=\"Approved / unchanged\" value={build.approvedCount} tone=\"success\" />\n </Card>\n </div>\n </div>\n\n {attempts.length > 0 ? (\n <Card>\n <SectionTitle>Capture attempts</SectionTitle>\n {attempts.map((attempt, index): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <details key={attempt.id} open={index === attempts.length - 1}>\n <summary>\n Attempt {attempt.attemptNo}{\" \"}\n <Badge tone={statusTone(attempt.status)}>{attempt.status}</Badge>{\" \"}\n <Meta as=\"span\">\n {attempt.storyCount} stories\n {attempt.reqId ? ` · ${attempt.reqId}` : \"\"}\n </Meta>\n </summary>\n {attempt.error ? <Meta as=\"pre\">{attempt.error}</Meta> : null}\n <div class=\"mt-1\">\n <VStack>\n {(attemptLogs.get(attempt.id) ?? []).map(\n (line): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <HStack key={line.id} align=\"baseline\" wrap={false}>\n <Badge tone={logTone(line.level)}>{line.level}</Badge>\n <span>{line.message}</span>\n {line.fields ? <Meta as=\"code\">{line.fields}</Meta> : null}\n </HStack>\n ),\n )}\n </VStack>\n </div>\n </details>\n ))}\n </Card>\n ) : null}\n\n {canReview && (build.status === \"reviewing\" || build.status === \"comparing\") ? (\n <div class=\"mb-1\">\n <Card>\n <HStack>\n <Meta as=\"span\">Bulk actions:</Meta>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/approve-all`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/approve-all`}\n hx-target=\"body\"\n hx-confirm=\"Approve all remaining changes?\"\n data-toast=\"Approved all remaining changes\"\n >\n <Button variant=\"primary\" size=\"sm\" type=\"submit\">\n Approve all\n </Button>\n </form>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/reject-all`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/reject-all`}\n hx-target=\"body\"\n hx-confirm=\"Reject all remaining changes?\"\n data-toast=\"Rejected all remaining changes\"\n data-toast-tone=\"warning\"\n >\n <Button variant=\"danger\" size=\"sm\" type=\"submit\">\n Reject all\n </Button>\n </form>\n <Meta as=\"span\">Or review individually in the diff view.</Meta>\n </HStack>\n </Card>\n </div>\n ) : null}\n\n {snapshots.length === 0 ? (\n <EmptyState\n title=\"No snapshots yet\"\n description=\"Capture is pending or failed. Try retrying capture.\"\n />\n ) : (\n <div class={snapshotGrid}>\n {snapshots.map((snap): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <div key={snap.id} class={snapshotCard}>\n <div class={snapshotCardHead}>\n <Badge tone={statusTone(snap.status)}>{snap.status}</Badge>\n {snap.inherited ? (\n <>\n {\" \"}\n <Badge tone=\"neutral\">inherited</Badge>\n </>\n ) : null}\n <span class={snapshotCardMeta}>\n {snap.viewportName} · {snap.viewportWidth}×{snap.viewportHeight}\n </span>\n </div>\n <div class={snapshotCardBody}>\n <a href={`${urls.buildDiff(project.slug, build.id)}?snapshot=${snap.id}`}>\n <Thumbnail\n src={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/image`}\n alt={`${snap.storyTitle} / ${snap.storyName}`}\n />\n </a>\n <SectionTitle>\n {snap.storyTitle} <Meta as=\"span\">/ {snap.storyName}</Meta>\n </SectionTitle>\n <Meta as=\"div\">\n {snap.storyId}\n {snap.diffRatio !== null && snap.diffRatio !== undefined\n ? ` · diff ${(snap.diffRatio * 100).toFixed(2)}%`\n : \"\"}\n {snap.diffPixels === null || snap.diffPixels === undefined\n ? \"\"\n : ` · ${snap.diffPixels} px`}\n </Meta>\n <HStack>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n href={`${urls.buildDiff(project.slug, build.id)}?snapshot=${snap.id}`}\n >\n Review\n </Button>\n <Button variant=\"ghost\" size=\"sm\" href={urls.short(build.id)}>\n View Storybook\n </Button>\n {canReview && (snap.status === \"new\" || snap.status === \"changed\") ? (\n <>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/approve`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/approve`}\n hx-target=\"body\"\n data-toast={`Approved ${snap.storyName}`}\n >\n <Button variant=\"ghost\" size=\"sm\" type=\"submit\">\n Approve\n </Button>\n </form>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/reject`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/reject`}\n hx-target=\"body\"\n data-toast={`Rejected ${snap.storyName}`}\n data-toast-tone=\"warning\"\n >\n <Button variant=\"ghost\" size=\"sm\" type=\"submit\">\n Reject\n </Button>\n </form>\n </>\n ) : null}\n </HStack>\n </div>\n </div>\n ))}\n </div>\n )}\n\n <div class=\"mt-1\">\n <Card>\n <SectionTitle>Comments</SectionTitle>\n {comments.length === 0 ? (\n <Meta>No comments. Add one in the diff review page for a specific snapshot.</Meta>\n ) : null}\n <VStack>\n {comments.map((comment): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <div key={comment.id} class={reviewComment}>\n <div class={reviewCommentHead}>\n <strong>{comment.userId ?? \"anonymous\"}</strong>\n <span>· {new Date(comment.createdAt).toLocaleString()}</span>\n {comment.snapshotId ? (\n <Badge tone=\"neutral\">\n {snapshots.find((snapshot) => snapshot.id === comment.snapshotId)\n ?.storyName ?? comment.snapshotId.slice(0, 6)}\n </Badge>\n ) : (\n <Badge tone=\"neutral\">build</Badge>\n )}\n {comment.resolved ? <Badge tone=\"success\">resolved</Badge> : null}\n </div>\n <p class={reviewCommentBody}>{comment.body}</p>\n </div>\n ))}\n </VStack>\n\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/comments`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/comments`}\n hx-target=\"body\"\n class=\"mt-1 max-w-prose\"\n >\n <VStack>\n <TextareaField\n label=\"Add build comment\"\n name=\"body\"\n rows={3}\n required\n placeholder=\"Leave a comment on this build…\"\n />\n <div>\n <Button variant=\"primary\" type=\"submit\">\n Comment\n </Button>\n </div>\n </VStack>\n </form>\n </Card>\n </div>\n </DocumentLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"build-detail.mjs","names":[],"sources":["../../src/pages/build-detail.tsx"],"sourcesContent":["import { groupComponents, parseTestResults } from \"@storyshelf/core/capture\";\nimport { BuildModel } from \"@storyshelf/core/models\";\nimport { CaptureAttemptModel } from \"@storyshelf/core/models\";\nimport { CaptureLogModel } from \"@storyshelf/core/models\";\nimport { LabelModel } from \"@storyshelf/core/models\";\nimport { ProjectModel } from \"@storyshelf/core/models\";\nimport { SnapshotModel } from \"@storyshelf/core/models\";\nimport { CANCELLABLE_BUILD_STATUSES } from \"@storyshelf/core/types\";\nimport { createUrlBuilder } from \"@storyshelf/core/urls\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { loadBaselineView } from \"../routers/baseline-guard.ts\";\nimport {\n designUrlsFor,\n loadDesignResolutions,\n providerNameFor,\n} from \"../routers/design.handlers.ts\";\nimport { loadReviewSection } from \"../routers/review-section.handlers.ts\";\nimport { catalogOf, getStore, testingEnabled } from \"../store.ts\";\nimport {\n Badge,\n Button,\n Card,\n EmptyState,\n HStack,\n Meta,\n PageHeader,\n SectionTitle,\n Stat,\n Thumbnail,\n VStack,\n statusTone,\n} from \"../ui/components.tsx\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport {\n snapshotCard,\n snapshotCardBody,\n snapshotCardHead,\n snapshotCardMeta,\n snapshotGrid,\n} from \"../ui/styles/review.ts\";\nimport { a11ySummaryNote, a11ySummaryOf } from \"./a11y-violations.tsx\";\nimport { snapshotVariant } from \"./build-diff-model.ts\";\nimport { StaleBaselineNotice } from \"./build-diff-stale.tsx\";\nimport { LinkedUsage, type LinkedUsageItem } from \"./build-diff-usage.tsx\";\nimport { TestsCard } from \"./build-tests.tsx\";\nimport { DesignButtons } from \"./design-links.tsx\";\nimport { ChangesetCard } from \"./review-changeset.tsx\";\nimport { CommentsCard } from \"./review-comments.tsx\";\nimport { VerdictCard } from \"./review-verdict.tsx\";\n\n/** Map a capture log level to its badge tone. */\nfunction logTone(level: string): \"neutral\" | \"success\" | \"warning\" | \"danger\" | \"info\" {\n if (level === \"error\") {\n return \"danger\";\n }\n if (level === \"warn\") {\n return \"warning\";\n }\n if (level === \"info\") {\n return \"info\";\n }\n return \"neutral\";\n}\n/** Build overview page: snapshot grid, bulk actions, and build comments. */\nexport async function renderBuildDetailPage(\n buildId: string,\n usageByStory: Map<string, LinkedUsageItem[]> = new Map(),\n): Promise<RenderedContent | null> {\n const { db } = getStore();\n const build = await new BuildModel(db).get(buildId);\n if (!build) {\n return null;\n }\n const project = await new ProjectModel(db).get(build.projectId);\n if (!project) {\n return null;\n }\n const snapshots = await new SnapshotModel(db).listByBuild(build.id);\n const attempts = await new CaptureAttemptModel(db).listByBuild(build.id);\n const logs = new CaptureLogModel(db);\n const attemptLogs = new Map<string, Awaited<ReturnType<typeof logs.listByAttempt>>>();\n /* oxlint-disable-next-line eslint/no-await-in-loop -- attempts are few; sequential reads keep code simple */\n for (const attempt of attempts) {\n attemptLogs.set(attempt.id, await logs.listByAttempt(attempt.id));\n }\n const canReview = !getStore().authEnabled || Boolean(getStore().user);\n const { drifted } = await loadBaselineView(project, build, snapshots);\n const designs = await loadDesignResolutions(project, snapshots).catch(() => new Map());\n const staleCount = Object.keys(drifted).length;\n const urls = createUrlBuilder(\"/\", getStore().config.publishedBaseDomain);\n const testing = testingEnabled();\n const cancelled = build.status === \"cancelled\";\n const published = build.status === \"approved\" && attempts.length === 0;\n const [catalog, labels] = await Promise.all([\n catalogOf()\n .load(project.id, build.id)\n .catch(() => null),\n new LabelModel(db).listForBuild(build.id),\n ]);\n const review = await loadReviewSection(project, build, catalog);\n const components = catalog ? groupComponents(catalog.entries) : [];\n const storyCount = catalog?.entries.filter((entry) => entry.type === \"story\").length ?? 0;\n\n const grouped = new Map<string, typeof snapshots>();\n for (const snap of snapshots) {\n const key = snap.status;\n const list = grouped.get(key) ?? [];\n list.push(snap);\n grouped.set(key, list);\n }\n const a11y = a11ySummaryOf(snapshots);\n const selective = build.affectedOnly && build.affectedImportPaths !== null;\n\n return (\n <DocumentLayout\n title={`Build ${build.gitBranch}`}\n nav={{\n active: \"builds\",\n projectSlug: project.slug,\n projectName: project.name,\n trail: [{ label: `${build.gitBranch} · ${build.gitSha.slice(0, 7)}` }],\n }}\n >\n <PageHeader\n title={\n <>\n {build.gitBranch} <Meta as=\"span\">· {build.gitSha.slice(0, 7)}</Meta>{\" \"}\n <Badge tone={statusTone(build.status)}>{published ? \"Published\" : build.status}</Badge>\n {testing && selective ? (\n <>\n {\" \"}\n <Badge tone=\"info\">affected capture</Badge>\n </>\n ) : null}\n {testing && !build.affectedOnly ? (\n <>\n {\" \"}\n <Badge tone=\"neutral\">full capture</Badge>\n </>\n ) : null}\n {labels.map((label): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <span key={`${label.typeKey}:${label.value}`}>\n {\" \"}\n <Badge tone=\"neutral\">\n {label.typeKey}: {label.value}\n </Badge>\n </span>\n ))}\n </>\n }\n description={\n <>\n {build.message ?? \"No message\"}{\" \"}\n {build.authorName\n ? `· ${build.authorName}${build.authorEmail ? ` <${build.authorEmail}>` : \"\"}`\n : \"\"}{\" \"}\n · {new Date(build.createdAt).toLocaleString()}\n {testing && selective ? (\n <>\n {\" \"}\n · {build.snapshotCount - build.inheritedCount} rendered, {build.inheritedCount}{\" \"}\n inherited\n </>\n ) : null}\n </>\n }\n actions={\n <>\n {testing ? (\n <Button variant=\"primary\" href={urls.buildDiff(project.slug, build.id)}>\n Review diffs\n </Button>\n ) : null}\n <Button variant={testing ? \"ghost\" : \"primary\"} href={urls.short(build.id)}>\n View Storybook\n </Button>\n {testing ? (\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/retry`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/retry`}\n hx-target=\"body\"\n >\n <Button variant=\"secondary\" type=\"submit\">\n Retry capture\n </Button>\n </form>\n ) : null}\n {testing && CANCELLABLE_BUILD_STATUSES.includes(build.status) ? (\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/cancel`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/cancel`}\n hx-target=\"body\"\n >\n <Button variant=\"danger\" size=\"sm\" type=\"submit\">\n Cancel build\n </Button>\n </form>\n ) : null}\n </>\n }\n />\n\n {testing && staleCount > 0 ? (\n <StaleBaselineNotice\n project={project}\n build={build}\n staleCount={staleCount}\n canReview={canReview}\n />\n ) : null}\n\n <div class=\"mb-1\">\n <div class=\"grid grid--3\">\n {catalog ? (\n <>\n <Card>\n <Stat label=\"Components\" value={components.length} />\n </Card>\n <Card>\n <Stat label=\"Stories\" value={storyCount} />\n </Card>\n </>\n ) : null}\n {testing ? (\n <>\n <Card>\n <Stat label=\"Snapshots\" value={snapshots.length} />\n </Card>\n <Card>\n <Stat label=\"Inherited\" value={build.inheritedCount} />\n <Meta>Reused from the baseline without rendering.</Meta>\n </Card>\n <Card>\n <Stat label=\"Changed / new\" value={build.changedCount} tone=\"warning\" />\n </Card>\n <Card>\n <Stat label=\"Approved / unchanged\" value={build.approvedCount} tone=\"success\" />\n </Card>\n <Card>\n <Stat\n label=\"Accessibility findings\"\n value={a11y.findings}\n tone={a11y.findings > 0 ? \"warning\" : \"neutral\"}\n />\n <Meta>{a11ySummaryNote(a11y)}</Meta>\n </Card>\n </>\n ) : null}\n </div>\n </div>\n\n {testing && attempts.length > 0 ? (\n <Card>\n <SectionTitle>Capture attempts</SectionTitle>\n {attempts.map((attempt, index): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <details key={attempt.id} open={index === attempts.length - 1}>\n <summary>\n Attempt {attempt.attemptNo}{\" \"}\n <Badge tone={statusTone(attempt.status)}>{attempt.status}</Badge>{\" \"}\n <Meta as=\"span\">\n {attempt.storyCount} stories\n {attempt.reqId ? ` · ${attempt.reqId}` : \"\"}\n </Meta>\n </summary>\n {attempt.error ? <Meta as=\"pre\">{attempt.error}</Meta> : null}\n <div class=\"mt-1\">\n <VStack>\n {(attemptLogs.get(attempt.id) ?? []).map(\n (line): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <HStack key={line.id} align=\"baseline\" wrap={false}>\n <Badge tone={logTone(line.level)}>{line.level}</Badge>\n <span>{line.message}</span>\n {line.fields ? <Meta as=\"code\">{line.fields}</Meta> : null}\n </HStack>\n ),\n )}\n </VStack>\n </div>\n </details>\n ))}\n </Card>\n ) : null}\n\n <TestsCard results={parseTestResults(build.testResults)} />\n\n {testing && canReview && (build.status === \"reviewing\" || build.status === \"comparing\") ? (\n <div class=\"mb-1\">\n <Card>\n <HStack>\n <Meta as=\"span\">Bulk actions:</Meta>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/approve-all`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/approve-all`}\n hx-target=\"body\"\n hx-confirm=\"Approve all remaining changes?\"\n data-toast=\"Approved all remaining changes\"\n >\n <Button variant=\"primary\" size=\"sm\" type=\"submit\">\n Approve all\n </Button>\n </form>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/reject-all`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/reject-all`}\n hx-target=\"body\"\n hx-confirm=\"Reject all remaining changes?\"\n data-toast=\"Rejected all remaining changes\"\n data-toast-tone=\"warning\"\n >\n <Button variant=\"danger\" size=\"sm\" type=\"submit\">\n Reject all\n </Button>\n </form>\n <Meta as=\"span\">Or review individually in the diff view.</Meta>\n </HStack>\n </Card>\n </div>\n ) : null}\n\n {review ? (\n <div class=\"mb-1\">\n <VStack>\n {testing || cancelled ? null : <VerdictCard data={review} />}\n <ChangesetCard data={review} />\n </VStack>\n </div>\n ) : null}\n\n {!testing ? null : snapshots.length === 0 ? (\n <EmptyState\n title=\"No snapshots yet\"\n description=\"Capture is pending or failed. Try retrying capture.\"\n />\n ) : (\n <div class={snapshotGrid}>\n {snapshots.map((snap): HtmlEscapedString | Promise<HtmlEscapedString> => (\n <div key={snap.id} class={snapshotCard}>\n <div class={snapshotCardHead}>\n <Badge tone={statusTone(snap.status)}>{snap.status}</Badge>\n {snap.inherited ? (\n <>\n {\" \"}\n <Badge tone=\"neutral\">inherited</Badge>\n </>\n ) : null}\n <span class={snapshotCardMeta}>\n {snapshotVariant(snap)} · {snap.viewportWidth}×{snap.viewportHeight}\n </span>\n </div>\n <div class={snapshotCardBody}>\n <a href={`${urls.buildDiff(project.slug, build.id)}?snapshot=${snap.id}`}>\n <Thumbnail\n src={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/image`}\n alt={`${snap.storyTitle} / ${snap.storyName}`}\n />\n </a>\n <SectionTitle>\n {snap.storyTitle} <Meta as=\"span\">/ {snap.storyName}</Meta>\n </SectionTitle>\n <Meta as=\"div\">\n {snap.storyId}\n {snap.diffRatio !== null && snap.diffRatio !== undefined\n ? ` · diff ${(snap.diffRatio * 100).toFixed(2)}%`\n : \"\"}\n {snap.diffPixels === null || snap.diffPixels === undefined\n ? \"\"\n : ` · ${snap.diffPixels} px`}\n </Meta>\n {snap.storyImportPath && usageByStory.has(snap.storyImportPath) ? (\n <LinkedUsage\n usage={usageByStory.get(snap.storyImportPath) ?? []}\n publishedBaseDomain={getStore().config.publishedBaseDomain}\n />\n ) : null}\n <HStack>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n href={`${urls.buildDiff(project.slug, build.id)}?snapshot=${snap.id}`}\n >\n Review\n </Button>\n <Button variant=\"ghost\" size=\"sm\" href={urls.short(build.id)}>\n View Storybook\n </Button>\n {snap.storyImportPath ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n href={`${urls.component(project.slug, snap.storyImportPath)}#${snap.storyId}`}\n >\n Component\n </Button>\n ) : null}\n <DesignButtons\n urls={designUrlsFor(designs, snap)}\n providerName={providerNameFor}\n />\n {canReview && (snap.status === \"new\" || snap.status === \"changed\") ? (\n <>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/approve`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/approve`}\n hx-target=\"body\"\n data-toast={`Approved ${snap.storyName}`}\n >\n <Button variant=\"ghost\" size=\"sm\" type=\"submit\">\n Approve\n </Button>\n </form>\n <form\n method=\"post\"\n action={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/reject`}\n hx-post={`/api/v1/projects/${project.slug}/builds/${build.id}/snapshots/${snap.id}/reject`}\n hx-target=\"body\"\n data-toast={`Rejected ${snap.storyName}`}\n data-toast-tone=\"warning\"\n >\n <Button variant=\"ghost\" size=\"sm\" type=\"submit\">\n Reject\n </Button>\n </form>\n </>\n ) : null}\n </HStack>\n </div>\n </div>\n ))}\n </div>\n )}\n\n {review ? (\n <div class=\"mt-1\">\n <VStack>\n {testing && !cancelled ? <VerdictCard data={review} /> : null}\n <CommentsCard data={review} />\n </VStack>\n </div>\n ) : null}\n </DocumentLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,SAAS,QAAQ,OAAsE;CACrF,IAAI,UAAU,SACZ,OAAO;CAET,IAAI,UAAU,QACZ,OAAO;CAET,IAAI,UAAU,QACZ,OAAO;CAET,OAAO;AACT;;AAEA,eAAsB,sBACpB,SACA,+BAA+C,IAAI,IAAI,GACtB;CACjC,MAAM,EAAE,OAAO,SAAS;CACxB,MAAM,QAAQ,MAAM,IAAI,WAAW,EAAE,CAAC,CAAC,IAAI,OAAO;CAClD,IAAI,CAAC,OACH,OAAO;CAET,MAAM,UAAU,MAAM,IAAI,aAAa,EAAE,CAAC,CAAC,IAAI,MAAM,SAAS;CAC9D,IAAI,CAAC,SACH,OAAO;CAET,MAAM,YAAY,MAAM,IAAI,cAAc,EAAE,CAAC,CAAC,YAAY,MAAM,EAAE;CAClE,MAAM,WAAW,MAAM,IAAI,oBAAoB,EAAE,CAAC,CAAC,YAAY,MAAM,EAAE;CACvE,MAAM,OAAO,IAAI,gBAAgB,EAAE;CACnC,MAAM,8BAAc,IAAI,IAA4D;CAEpF,KAAK,MAAM,WAAW,UACpB,YAAY,IAAI,QAAQ,IAAI,MAAM,KAAK,cAAc,QAAQ,EAAE,CAAC;CAElE,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC,eAAe,QAAQ,SAAS,CAAC,CAAC,IAAI;CACpE,MAAM,EAAE,YAAY,MAAM,iBAAiB,SAAS,OAAO,SAAS;CACpE,MAAM,UAAU,MAAM,sBAAsB,SAAS,SAAS,CAAC,CAAC,4BAAY,IAAI,IAAI,CAAC;CACrF,MAAM,aAAa,OAAO,KAAK,OAAO,CAAC,CAAC;CACxC,MAAM,OAAO,iBAAiB,KAAK,SAAS,CAAC,CAAC,OAAO,mBAAmB;CACxE,MAAM,UAAU,eAAe;CAC/B,MAAM,YAAY,MAAM,WAAW;CACnC,MAAM,YAAY,MAAM,WAAW,cAAc,SAAS,WAAW;CACrE,MAAM,CAAC,SAAS,UAAU,MAAM,QAAQ,IAAI,CAC1C,UAAU,CAAC,CACR,KAAK,QAAQ,IAAI,MAAM,EAAE,CAAC,CAC1B,YAAY,IAAI,GACnB,IAAI,WAAW,EAAE,CAAC,CAAC,aAAa,MAAM,EAAE,CAC1C,CAAC;CACD,MAAM,SAAS,MAAM,kBAAkB,SAAS,OAAO,OAAO;CAC9D,MAAM,aAAa,UAAU,gBAAgB,QAAQ,OAAO,IAAI,CAAC;CACjE,MAAM,aAAa,SAAS,QAAQ,QAAQ,UAAU,MAAM,SAAS,OAAO,CAAC,CAAC,UAAU;CAExF,MAAM,0BAAU,IAAI,IAA8B;CAClD,KAAK,MAAM,QAAQ,WAAW;EAC5B,MAAM,MAAM,KAAK;EACjB,MAAM,OAAO,QAAQ,IAAI,GAAG,KAAK,CAAC;EAClC,KAAK,KAAK,IAAI;EACd,QAAQ,IAAI,KAAK,IAAI;CACvB;CACA,MAAM,OAAO,cAAc,SAAS;CACpC,MAAM,YAAY,MAAM,gBAAgB,MAAM,wBAAwB;CAEtE,OACE,qBAAC,gBAAD;EACE,OAAO,SAAS,MAAM;EACtB,KAAK;GACH,QAAQ;GACR,aAAa,QAAQ;GACrB,aAAa,QAAQ;GACrB,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,UAAU,KAAK,MAAM,OAAO,MAAM,GAAG,CAAC,IAAI,CAAC;EACvE;EAPF,UAAA;GASE,oBAAC,YAAD;IACE,OACE,qBAAA,UAAA,EAAA,UAAA;KACG,MAAM;KAAU;KAAC,qBAAC,MAAD;MAAM,IAAG;MAAT,UAAA,CAAgB,MAAG,MAAM,OAAO,MAAM,GAAG,CAAC,CAAQ;;KAAE;KACtE,oBAAC,OAAD;MAAO,MAAM,WAAW,MAAM,MAAM;MAAI,UAAA,YAAY,cAAc,MAAM;KAAc,CAAA;KACrF,WAAW,YACV,qBAAA,UAAA,EAAA,UAAA,CACG,KACD,oBAAC,OAAD;MAAO,MAAK;MAAO,UAAA;KAAuB,CAAA,CAC1C,EAAA,CAAA,IACA;KACH,WAAW,CAAC,MAAM,eACjB,qBAAA,UAAA,EAAA,UAAA,CACG,KACD,oBAAC,OAAD;MAAO,MAAK;MAAU,UAAA;KAAmB,CAAA,CACzC,EAAA,CAAA,IACA;KACH,OAAO,KAAK,UACX,qBAAC,QAAD,EAAA,UAAA,CACG,KACD,qBAAC,OAAD;MAAO,MAAK;MAAZ,UAAA;OACG,MAAM;OAAQ;OAAG,MAAM;MACnB;KACH,CAAA,CAAA,EAAA,GALK,GAAG,MAAM,QAAQ,GAAG,MAAM,OAK/B,CACP;IACD,EAAA,CAAA;IAEJ,aACE,qBAAA,UAAA,EAAA,UAAA;KACG,MAAM,WAAW;KAAc;KAC/B,MAAM,aACH,KAAK,MAAM,aAAa,MAAM,cAAc,KAAK,MAAM,YAAY,KAAK,OACxE;KAAI;KAAI;KACT,IAAI,KAAK,MAAM,SAAS,CAAC,CAAC,eAAe;KAC3C,WAAW,YACV,qBAAA,UAAA,EAAA,UAAA;MACG;MAAI;MACF,MAAM,gBAAgB,MAAM;MAAe;MAAY,MAAM;MAAgB;MAAI;KAEpF,EAAA,CAAA,IACA;IACJ,EAAA,CAAA;IAEJ,SACE,qBAAA,UAAA,EAAA,UAAA;KACG,UACC,oBAAC,QAAD;MAAQ,SAAQ;MAAU,MAAM,KAAK,UAAU,QAAQ,MAAM,MAAM,EAAE;MAAG,UAAA;KAEhE,CAAA,IACN;KACJ,oBAAC,QAAD;MAAQ,SAAS,UAAU,UAAU;MAAW,MAAM,KAAK,MAAM,MAAM,EAAE;MAAG,UAAA;KAEpE,CAAA;KACP,UACC,oBAAC,QAAD;MACE,QAAO;MACP,QAAQ,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG;MAC5D,WAAS,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG;MAC7D,aAAU;MAEV,UAAA,oBAAC,QAAD;OAAQ,SAAQ;OAAY,MAAK;OAAS,UAAA;MAElC,CAAA;KACJ,CAAA,IACJ;KACH,WAAW,2BAA2B,SAAS,MAAM,MAAM,IAC1D,oBAAC,QAAD;MACE,QAAO;MACP,QAAQ,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG;MAC5D,WAAS,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG;MAC7D,aAAU;MAEV,UAAA,oBAAC,QAAD;OAAQ,SAAQ;OAAS,MAAK;OAAK,MAAK;OAAS,UAAA;MAEzC,CAAA;KACJ,CAAA,IACJ;IACJ,EAAA,CAAA;GAEL,CAAA;GAEA,WAAW,aAAa,IACvB,oBAAC,qBAAD;IACW;IACF;IACK;IACD;GACZ,CAAA,IACC;GAEJ,oBAAC,OAAD;IAAK,OAAM;IACT,UAAA,qBAAC,OAAD;KAAK,OAAM;KAAX,UAAA,CACG,UACC,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,MAAD,EAAA,UACE,oBAAC,MAAD;MAAM,OAAM;MAAa,OAAO,WAAW;KAAS,CAAA,EAChD,CAAA,GACN,oBAAC,MAAD,EAAA,UACE,oBAAC,MAAD;MAAM,OAAM;MAAU,OAAO;KAAa,CAAA,EACtC,CAAA,CACN,EAAA,CAAA,IACA,MACH,UACC,qBAAA,UAAA,EAAA,UAAA;MACE,oBAAC,MAAD,EAAA,UACE,oBAAC,MAAD;OAAM,OAAM;OAAY,OAAO,UAAU;MAAS,CAAA,EAC9C,CAAA;MACN,qBAAC,MAAD,EAAA,UAAA,CACE,oBAAC,MAAD;OAAM,OAAM;OAAY,OAAO,MAAM;MAAiB,CAAA,GACtD,oBAAC,MAAD,EAAA,UAAM,8CAAiD,CAAA,CACnD,EAAA,CAAA;MACN,oBAAC,MAAD,EAAA,UACE,oBAAC,MAAD;OAAM,OAAM;OAAgB,OAAO,MAAM;OAAc,MAAK;MAAW,CAAA,EACnE,CAAA;MACN,oBAAC,MAAD,EAAA,UACE,oBAAC,MAAD;OAAM,OAAM;OAAuB,OAAO,MAAM;OAAe,MAAK;MAAW,CAAA,EAC3E,CAAA;MACN,qBAAC,MAAD,EAAA,UAAA,CACE,oBAAC,MAAD;OACE,OAAM;OACN,OAAO,KAAK;OACZ,MAAM,KAAK,WAAW,IAAI,YAAY;MACvC,CAAA,GACD,oBAAC,MAAD,EAAA,UAAO,gBAAgB,IAAI,EAAQ,CAAA,CAC/B,EAAA,CAAA;KACN,EAAA,CAAA,IACA,IACD;;GACF,CAAA;GAEJ,WAAW,SAAS,SAAS,IAC5B,qBAAC,MAAD,EAAA,UAAA,CACE,oBAAC,cAAD,EAAA,UAAc,mBAA8B,CAAA,GAC3C,SAAS,KAAK,SAAS,UACtB,qBAAC,WAAD;IAA0B,MAAM,UAAU,SAAS,SAAS;IAA5D,UAAA;KACE,qBAAC,WAAD,EAAA,UAAA;MAAS;MACE,QAAQ;MAAW;MAC5B,oBAAC,OAAD;OAAO,MAAM,WAAW,QAAQ,MAAM;OAAI,UAAA,QAAQ;MAAc,CAAA;MAAE;MAClE,qBAAC,MAAD;OAAM,IAAG;OAAT,UAAA;QACG,QAAQ;QAAW;QACnB,QAAQ,QAAQ,MAAM,QAAQ,UAAU;OACrC;;KACC,EAAA,CAAA;KACR,QAAQ,QAAQ,oBAAC,MAAD;MAAM,IAAG;MAAO,UAAA,QAAQ;KAAY,CAAA,IAAI;KACzD,oBAAC,OAAD;MAAK,OAAM;MACT,UAAA,oBAAC,QAAD,EAAA,WACI,YAAY,IAAI,QAAQ,EAAE,KAAK,CAAC,EAAA,CAAG,KAClC,SACC,qBAAC,QAAD;OAAsB,OAAM;OAAW,MAAM;OAA7C,UAAA;QACE,oBAAC,OAAD;SAAO,MAAM,QAAQ,KAAK,KAAK;SAAI,UAAA,KAAK;QAAa,CAAA;QACrD,oBAAC,QAAD,EAAA,UAAO,KAAK,QAAc,CAAA;QACzB,KAAK,SAAS,oBAAC,MAAD;SAAM,IAAG;SAAQ,UAAA,KAAK;QAAa,CAAA,IAAI;OAChD;MAJK,GAAA,KAAK,EAIV,CAEZ,EACM,CAAA;KACL,CAAA;IACE;GAvBK,GAAA,QAAQ,EAuBb,CACV,CACG,EAAA,CAAA,IACJ;GAEJ,oBAAC,WAAD,EAAW,SAAS,iBAAiB,MAAM,WAAW,EAAI,CAAA;GAEzD,WAAW,cAAc,MAAM,WAAW,eAAe,MAAM,WAAW,eACzE,oBAAC,OAAD;IAAK,OAAM;IACT,UAAA,oBAAC,MAAD,EAAA,UACE,qBAAC,QAAD,EAAA,UAAA;KACE,oBAAC,MAAD;MAAM,IAAG;MAAO,UAAA;KAAmB,CAAA;KACnC,oBAAC,QAAD;MACE,QAAO;MACP,QAAQ,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG;MAC5D,WAAS,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG;MAC7D,aAAU;MACV,cAAW;MACX,cAAW;MAEX,UAAA,oBAAC,QAAD;OAAQ,SAAQ;OAAU,MAAK;OAAK,MAAK;OAAS,UAAA;MAE1C,CAAA;KACJ,CAAA;KACN,oBAAC,QAAD;MACE,QAAO;MACP,QAAQ,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG;MAC5D,WAAS,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG;MAC7D,aAAU;MACV,cAAW;MACX,cAAW;MACX,mBAAgB;MAEhB,UAAA,oBAAC,QAAD;OAAQ,SAAQ;OAAS,MAAK;OAAK,MAAK;OAAS,UAAA;MAEzC,CAAA;KACJ,CAAA;KACN,oBAAC,MAAD;MAAM,IAAG;MAAO,UAAA;KAA8C,CAAA;IACxD,EAAA,CAAA,EACJ,CAAA;GACH,CAAA,IACH;GAEH,SACC,oBAAC,OAAD;IAAK,OAAM;IACT,UAAA,qBAAC,QAAD,EAAA,UAAA,CACG,WAAW,YAAY,OAAO,oBAAC,aAAD,EAAa,MAAM,OAAS,CAAA,GAC3D,oBAAC,eAAD,EAAe,MAAM,OAAS,CAAA,CACxB,EAAA,CAAA;GACL,CAAA,IACH;GAEH,CAAC,UAAU,OAAO,UAAU,WAAW,IACtC,oBAAC,YAAD;IACE,OAAM;IACN,aAAY;GACb,CAAA,IAED,oBAAC,OAAD;IAAK,OAAO;IACT,UAAA,UAAU,KAAK,SACd,qBAAC,OAAD;KAAmB,OAAO;KAA1B,UAAA,CACE,qBAAC,OAAD;MAAK,OAAO;MAAZ,UAAA;OACE,oBAAC,OAAD;QAAO,MAAM,WAAW,KAAK,MAAM;QAAI,UAAA,KAAK;OAAc,CAAA;OACzD,KAAK,YACJ,qBAAA,UAAA,EAAA,UAAA,CACG,KACD,oBAAC,OAAD;QAAO,MAAK;QAAU,UAAA;OAAgB,CAAA,CACtC,EAAA,CAAA,IACA;OACJ,qBAAC,QAAD;QAAM,OAAO;QAAb,UAAA;SACG,gBAAgB,IAAI;SAAE;SAAI,KAAK;SAAc;SAAE,KAAK;QACjD;;MACH;KACL,CAAA,GAAA,qBAAC,OAAD;MAAK,OAAO;MAAZ,UAAA;OACE,oBAAC,KAAD;QAAG,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,MAAM,EAAE,EAAE,YAAY,KAAK;QAClE,UAAA,oBAAC,WAAD;SACE,KAAK,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG,aAAa,KAAK,GAAG;SAC9E,KAAK,GAAG,KAAK,WAAW,KAAK,KAAK;QACnC,CAAA;OACA,CAAA;OACH,qBAAC,cAAD,EAAA,UAAA;QACG,KAAK;QAAW;QAAC,qBAAC,MAAD;SAAM,IAAG;SAAT,UAAA,CAAgB,MAAG,KAAK,SAAgB;;OAC9C,EAAA,CAAA;OACd,qBAAC,MAAD;QAAM,IAAG;QAAT,UAAA;SACG,KAAK;SACL,KAAK,cAAc,QAAQ,KAAK,cAAc,KAAA,IAC3C,YAAY,KAAK,YAAY,IAAA,CAAK,QAAQ,CAAC,EAAE,KAC7C;SACH,KAAK,eAAe,QAAQ,KAAK,eAAe,KAAA,IAC7C,KACA,MAAM,KAAK,WAAW;QACtB;;OACL,KAAK,mBAAmB,aAAa,IAAI,KAAK,eAAe,IAC5D,oBAAC,aAAD;QACE,OAAO,aAAa,IAAI,KAAK,eAAe,KAAK,CAAC;QAClD,qBAAqB,SAAS,CAAC,CAAC,OAAO;OACxC,CAAA,IACC;OACJ,qBAAC,QAAD,EAAA,UAAA;QACE,oBAAC,QAAD;SACE,SAAQ;SACR,MAAK;SACL,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,MAAM,EAAE,EAAE,YAAY,KAAK;SAClE,UAAA;QAEO,CAAA;QACR,oBAAC,QAAD;SAAQ,SAAQ;SAAQ,MAAK;SAAK,MAAM,KAAK,MAAM,MAAM,EAAE;SAAG,UAAA;QAEtD,CAAA;QACP,KAAK,kBACJ,oBAAC,QAAD;SACE,SAAQ;SACR,MAAK;SACL,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,KAAK,eAAe,EAAE,GAAG,KAAK;SACrE,UAAA;QAEO,CAAA,IACN;QACJ,oBAAC,eAAD;SACE,MAAM,cAAc,SAAS,IAAI;SACjC,cAAc;QACf,CAAA;QACA,cAAc,KAAK,WAAW,SAAS,KAAK,WAAW,aACtD,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;SACE,QAAO;SACP,QAAQ,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG,aAAa,KAAK,GAAG;SACjF,WAAS,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG,aAAa,KAAK,GAAG;SAClF,aAAU;SACV,cAAY,YAAY,KAAK;SAE7B,UAAA,oBAAC,QAAD;UAAQ,SAAQ;UAAQ,MAAK;UAAK,MAAK;UAAS,UAAA;SAExC,CAAA;QACJ,CAAA,GACN,oBAAC,QAAD;SACE,QAAO;SACP,QAAQ,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG,aAAa,KAAK,GAAG;SACjF,WAAS,oBAAoB,QAAQ,KAAK,UAAU,MAAM,GAAG,aAAa,KAAK,GAAG;SAClF,aAAU;SACV,cAAY,YAAY,KAAK;SAC7B,mBAAgB;SAEhB,UAAA,oBAAC,QAAD;UAAQ,SAAQ;UAAQ,MAAK;UAAK,MAAK;UAAS,UAAA;SAExC,CAAA;QACJ,CAAA,CACN,EAAA,CAAA,IACA;OACE,EAAA,CAAA;MACL;KACF,CAAA,CAAA;IA3FK,GAAA,KAAK,EA2FV,CACN;GACE,CAAA;GAGN,SACC,oBAAC,OAAD;IAAK,OAAM;IACT,UAAA,qBAAC,QAAD,EAAA,UAAA,CACG,WAAW,CAAC,YAAY,oBAAC,aAAD,EAAa,MAAM,OAAS,CAAA,IAAI,MACzD,oBAAC,cAAD,EAAc,MAAM,OAAS,CAAA,CACvB,EAAA,CAAA;GACL,CAAA,IACH;EACU;;AAEpB"}