@storyshelf/app 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (429) hide show
  1. package/README.md +42 -4
  2. package/dist/app-types.d.mts +2 -6
  3. package/dist/asset-manifest.mjs +14 -7
  4. package/dist/asset-manifest.mjs.map +1 -1
  5. package/dist/assets/{htmx.min.js → htmx.mjs} +9 -1
  6. package/dist/assets/htmx.mjs.map +1 -0
  7. package/dist/assets/mark.mjs +9 -0
  8. package/dist/assets/mark.mjs.map +1 -0
  9. package/dist/boot-tasks.mjs +79 -0
  10. package/dist/boot-tasks.mjs.map +1 -0
  11. package/dist/build-state-flags.mjs +17 -0
  12. package/dist/build-state-flags.mjs.map +1 -0
  13. package/dist/build-state-setup.mjs +74 -0
  14. package/dist/build-state-setup.mjs.map +1 -0
  15. package/dist/capture-alerts.mjs +45 -0
  16. package/dist/capture-alerts.mjs.map +1 -0
  17. package/dist/capture-process.mjs +58 -0
  18. package/dist/capture-process.mjs.map +1 -0
  19. package/dist/capture-setup.mjs +96 -119
  20. package/dist/capture-setup.mjs.map +1 -1
  21. package/dist/connections.mjs +33 -0
  22. package/dist/connections.mjs.map +1 -0
  23. package/dist/consumer-setup.mjs +57 -0
  24. package/dist/consumer-setup.mjs.map +1 -0
  25. package/dist/credential-keys.mjs +1 -2
  26. package/dist/credentials.mjs +1 -2
  27. package/dist/git-status.mjs +45 -0
  28. package/dist/git-status.mjs.map +1 -0
  29. package/dist/hidden-data.mjs +23 -0
  30. package/dist/hidden-data.mjs.map +1 -0
  31. package/dist/index.d.mts +3 -5
  32. package/dist/index.mjs +204 -127
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/insights/budget.mjs +48 -0
  35. package/dist/insights/budget.mjs.map +1 -0
  36. package/dist/insights/deps.mjs +20 -0
  37. package/dist/insights/deps.mjs.map +1 -0
  38. package/dist/insights/errors.mjs +21 -0
  39. package/dist/insights/errors.mjs.map +1 -0
  40. package/dist/insights/health-panel.mjs +39 -0
  41. package/dist/insights/health-panel.mjs.map +1 -0
  42. package/dist/insights/job.mjs +102 -0
  43. package/dist/insights/job.mjs.map +1 -0
  44. package/dist/insights/load-health.mjs +58 -0
  45. package/dist/insights/load-health.mjs.map +1 -0
  46. package/dist/insights/load-triage.mjs +97 -0
  47. package/dist/insights/load-triage.mjs.map +1 -0
  48. package/dist/insights/panel.mjs +27 -0
  49. package/dist/insights/panel.mjs.map +1 -0
  50. package/dist/insights/request.mjs +112 -0
  51. package/dist/insights/request.mjs.map +1 -0
  52. package/dist/insights/stale.mjs +20 -0
  53. package/dist/insights/stale.mjs.map +1 -0
  54. package/dist/insights/usage-report.mjs +59 -0
  55. package/dist/insights/usage-report.mjs.map +1 -0
  56. package/dist/insights/view.mjs +47 -0
  57. package/dist/insights/view.mjs.map +1 -0
  58. package/dist/job-canceller.mjs +13 -0
  59. package/dist/job-canceller.mjs.map +1 -0
  60. package/dist/job-queue.mjs +50 -0
  61. package/dist/job-queue.mjs.map +1 -0
  62. package/dist/jobs-setup.mjs +131 -0
  63. package/dist/jobs-setup.mjs.map +1 -0
  64. package/dist/lifecycle.mjs +93 -22
  65. package/dist/lifecycle.mjs.map +1 -1
  66. package/dist/middleware/auth-gate.mjs +5 -4
  67. package/dist/middleware/auth-gate.mjs.map +1 -1
  68. package/dist/middleware/csrf.mjs +1 -2
  69. package/dist/middleware/rate-limit.mjs +42 -35
  70. package/dist/middleware/rate-limit.mjs.map +1 -1
  71. package/dist/middleware/request-log.mjs +4 -4
  72. package/dist/middleware/request-log.mjs.map +1 -1
  73. package/dist/middleware/require-review.mjs +50 -0
  74. package/dist/middleware/require-review.mjs.map +1 -0
  75. package/dist/middleware/require-testing.mjs +40 -0
  76. package/dist/middleware/require-testing.mjs.map +1 -0
  77. package/dist/middleware/server-timing.mjs +1 -2
  78. package/dist/middleware/setup-gate.mjs +1 -2
  79. package/dist/middleware/setup-gate.mjs.map +1 -1
  80. package/dist/middleware/store-scope.mjs +13 -4
  81. package/dist/middleware/store-scope.mjs.map +1 -1
  82. package/dist/notifications-pause.mjs +32 -0
  83. package/dist/notifications-pause.mjs.map +1 -0
  84. package/dist/notify.d.mts +3 -4
  85. package/dist/notify.mjs +79 -41
  86. package/dist/notify.mjs.map +1 -1
  87. package/dist/pages/a11y-violations.mjs +115 -0
  88. package/dist/pages/a11y-violations.mjs.map +1 -0
  89. package/dist/pages/admin-ai.mjs +77 -0
  90. package/dist/pages/admin-ai.mjs.map +1 -0
  91. package/dist/pages/admin-system-origin.mjs +132 -0
  92. package/dist/pages/admin-system-origin.mjs.map +1 -0
  93. package/dist/pages/admin-system-targets.mjs +52 -0
  94. package/dist/pages/admin-system-targets.mjs.map +1 -0
  95. package/dist/pages/admin-system.mjs +64 -15
  96. package/dist/pages/admin-system.mjs.map +1 -1
  97. package/dist/pages/build-detail.mjs +105 -78
  98. package/dist/pages/build-detail.mjs.map +1 -1
  99. package/dist/pages/build-diff-actions.mjs +88 -35
  100. package/dist/pages/build-diff-actions.mjs.map +1 -1
  101. package/dist/pages/build-diff-comments.mjs +17 -16
  102. package/dist/pages/build-diff-comments.mjs.map +1 -1
  103. package/dist/pages/build-diff-default.mjs +53 -0
  104. package/dist/pages/build-diff-default.mjs.map +1 -0
  105. package/dist/pages/build-diff-environment.mjs +81 -0
  106. package/dist/pages/build-diff-environment.mjs.map +1 -0
  107. package/dist/pages/build-diff-header.mjs +23 -6
  108. package/dist/pages/build-diff-header.mjs.map +1 -1
  109. package/dist/pages/build-diff-insight.mjs +112 -0
  110. package/dist/pages/build-diff-insight.mjs.map +1 -0
  111. package/dist/pages/build-diff-model.mjs +74 -12
  112. package/dist/pages/build-diff-model.mjs.map +1 -1
  113. package/dist/pages/build-diff-nav.mjs +29 -14
  114. package/dist/pages/build-diff-nav.mjs.map +1 -1
  115. package/dist/pages/build-diff-reference.mjs +84 -0
  116. package/dist/pages/build-diff-reference.mjs.map +1 -0
  117. package/dist/pages/build-diff-shortcuts.mjs +6 -3
  118. package/dist/pages/build-diff-shortcuts.mjs.map +1 -1
  119. package/dist/pages/build-diff-stale.mjs +1 -2
  120. package/dist/pages/build-diff-usage.mjs +62 -0
  121. package/dist/pages/build-diff-usage.mjs.map +1 -0
  122. package/dist/pages/build-diff-viewer.mjs +97 -14
  123. package/dist/pages/build-diff-viewer.mjs.map +1 -1
  124. package/dist/pages/build-diff.mjs +37 -22
  125. package/dist/pages/build-diff.mjs.map +1 -1
  126. package/dist/pages/build-tests.mjs +73 -0
  127. package/dist/pages/build-tests.mjs.map +1 -0
  128. package/dist/pages/component-detail.mjs +248 -0
  129. package/dist/pages/component-detail.mjs.map +1 -0
  130. package/dist/pages/component-stories.mjs +167 -0
  131. package/dist/pages/component-stories.mjs.map +1 -0
  132. package/dist/pages/compute-jobs.mjs +14 -28
  133. package/dist/pages/compute-jobs.mjs.map +1 -1
  134. package/dist/pages/design-links.mjs +181 -0
  135. package/dist/pages/design-links.mjs.map +1 -0
  136. package/dist/pages/environment-change.mjs +52 -0
  137. package/dist/pages/environment-change.mjs.map +1 -0
  138. package/dist/pages/health-panel.mjs +125 -0
  139. package/dist/pages/health-panel.mjs.map +1 -0
  140. package/dist/pages/invite.mjs +1 -2
  141. package/dist/pages/label-detail.mjs +1 -2
  142. package/dist/pages/library-grid.mjs +104 -0
  143. package/dist/pages/library-grid.mjs.map +1 -0
  144. package/dist/pages/library-model.mjs +46 -0
  145. package/dist/pages/library-model.mjs.map +1 -0
  146. package/dist/pages/library-snapshots.mjs +129 -0
  147. package/dist/pages/library-snapshots.mjs.map +1 -0
  148. package/dist/pages/library.mjs +113 -220
  149. package/dist/pages/library.mjs.map +1 -1
  150. package/dist/pages/login.mjs +1 -2
  151. package/dist/pages/notifications-paused.mjs +21 -0
  152. package/dist/pages/notifications-paused.mjs.map +1 -0
  153. package/dist/pages/passkey-ceremony.mjs +1 -2
  154. package/dist/pages/profile.mjs +54 -50
  155. package/dist/pages/profile.mjs.map +1 -1
  156. package/dist/pages/project-builds-table.mjs +3 -4
  157. package/dist/pages/project-builds-table.mjs.map +1 -1
  158. package/dist/pages/project-builds.mjs +52 -23
  159. package/dist/pages/project-builds.mjs.map +1 -1
  160. package/dist/pages/project-create.mjs +1 -2
  161. package/dist/pages/project-settings.mjs +52 -14
  162. package/dist/pages/project-settings.mjs.map +1 -1
  163. package/dist/pages/projects-card.mjs +15 -3
  164. package/dist/pages/projects-card.mjs.map +1 -1
  165. package/dist/pages/projects.mjs +7 -4
  166. package/dist/pages/projects.mjs.map +1 -1
  167. package/dist/pages/review-changeset.mjs +115 -0
  168. package/dist/pages/review-changeset.mjs.map +1 -0
  169. package/dist/pages/review-comments.mjs +102 -0
  170. package/dist/pages/review-comments.mjs.map +1 -0
  171. package/dist/pages/review-verdict.mjs +101 -0
  172. package/dist/pages/review-verdict.mjs.map +1 -0
  173. package/dist/pages/root.mjs +1 -2
  174. package/dist/pages/settings-ai.mjs +68 -0
  175. package/dist/pages/settings-ai.mjs.map +1 -0
  176. package/dist/pages/settings-capture.mjs +123 -0
  177. package/dist/pages/settings-capture.mjs.map +1 -0
  178. package/dist/pages/settings-design-credential.mjs +98 -0
  179. package/dist/pages/settings-design-credential.mjs.map +1 -0
  180. package/dist/pages/settings-design-links.mjs +178 -0
  181. package/dist/pages/settings-design-links.mjs.map +1 -0
  182. package/dist/pages/settings-design.mjs +47 -0
  183. package/dist/pages/settings-design.mjs.map +1 -0
  184. package/dist/pages/settings-general.mjs +47 -23
  185. package/dist/pages/settings-general.mjs.map +1 -1
  186. package/dist/pages/settings-labels.mjs +44 -4
  187. package/dist/pages/settings-labels.mjs.map +1 -1
  188. package/dist/pages/settings-links.mjs +111 -0
  189. package/dist/pages/settings-links.mjs.map +1 -0
  190. package/dist/pages/settings-members.mjs +1 -2
  191. package/dist/pages/settings-notifications-git.mjs +127 -0
  192. package/dist/pages/settings-notifications-git.mjs.map +1 -0
  193. package/dist/pages/settings-notifications.mjs +80 -44
  194. package/dist/pages/settings-notifications.mjs.map +1 -1
  195. package/dist/pages/settings-tests.mjs +34 -4
  196. package/dist/pages/settings-tests.mjs.map +1 -1
  197. package/dist/pages/settings-tokens.mjs +1 -2
  198. package/dist/pages/settings-webhooks.mjs +1 -2
  199. package/dist/pages/storybook.mjs +1 -2
  200. package/dist/pages/target-choices.mjs +38 -0
  201. package/dist/pages/target-choices.mjs.map +1 -0
  202. package/dist/publish-setup.mjs +88 -0
  203. package/dist/publish-setup.mjs.map +1 -0
  204. package/dist/retention-timer.mjs +12 -12
  205. package/dist/retention-timer.mjs.map +1 -1
  206. package/dist/review-events.mjs +47 -0
  207. package/dist/review-events.mjs.map +1 -0
  208. package/dist/routers/admin-pages.mjs +46 -5
  209. package/dist/routers/admin-pages.mjs.map +1 -1
  210. package/dist/routers/admin-public-origin.mjs +92 -0
  211. package/dist/routers/admin-public-origin.mjs.map +1 -0
  212. package/dist/routers/admin.mjs +8 -4
  213. package/dist/routers/admin.mjs.map +1 -1
  214. package/dist/routers/assets.mjs +10 -4
  215. package/dist/routers/assets.mjs.map +1 -1
  216. package/dist/routers/attempts.mjs +1 -2
  217. package/dist/routers/auth-sync.mjs +1 -2
  218. package/dist/routers/auth.mjs +2 -3
  219. package/dist/routers/baseline-guard.mjs +4 -5
  220. package/dist/routers/baseline-guard.mjs.map +1 -1
  221. package/dist/routers/baseline-reference.mjs +72 -0
  222. package/dist/routers/baseline-reference.mjs.map +1 -0
  223. package/dist/routers/baselines.handlers.mjs +63 -0
  224. package/dist/routers/baselines.handlers.mjs.map +1 -0
  225. package/dist/routers/baselines.mjs +124 -0
  226. package/dist/routers/baselines.mjs.map +1 -0
  227. package/dist/routers/build-cancel.mjs +67 -0
  228. package/dist/routers/build-cancel.mjs.map +1 -0
  229. package/dist/routers/build-changeset.mjs +69 -0
  230. package/dist/routers/build-changeset.mjs.map +1 -0
  231. package/dist/routers/build-reviews.handlers.mjs +85 -0
  232. package/dist/routers/build-reviews.handlers.mjs.map +1 -0
  233. package/dist/routers/build-reviews.mjs +73 -0
  234. package/dist/routers/build-reviews.mjs.map +1 -0
  235. package/dist/routers/builds.handlers.mjs +65 -60
  236. package/dist/routers/builds.handlers.mjs.map +1 -1
  237. package/dist/routers/builds.mjs +77 -19
  238. package/dist/routers/builds.mjs.map +1 -1
  239. package/dist/routers/capture-cache.mjs +29 -0
  240. package/dist/routers/capture-cache.mjs.map +1 -0
  241. package/dist/routers/capture-config.mjs +34 -0
  242. package/dist/routers/capture-config.mjs.map +1 -0
  243. package/dist/routers/comments.mjs +42 -17
  244. package/dist/routers/comments.mjs.map +1 -1
  245. package/dist/routers/comparison-image.mjs +29 -0
  246. package/dist/routers/comparison-image.mjs.map +1 -0
  247. package/dist/routers/component-catalog.handlers.mjs +70 -0
  248. package/dist/routers/component-catalog.handlers.mjs.map +1 -0
  249. package/dist/routers/component.handlers.mjs +120 -0
  250. package/dist/routers/component.handlers.mjs.map +1 -0
  251. package/dist/routers/connections.mjs +117 -0
  252. package/dist/routers/connections.mjs.map +1 -0
  253. package/dist/routers/design-connect.handlers.mjs +43 -0
  254. package/dist/routers/design-connect.handlers.mjs.map +1 -0
  255. package/dist/routers/design.handlers.mjs +113 -0
  256. package/dist/routers/design.handlers.mjs.map +1 -0
  257. package/dist/routers/flash.mjs +1 -2
  258. package/dist/routers/health-report.mjs +3 -5
  259. package/dist/routers/health-report.mjs.map +1 -1
  260. package/dist/routers/health.mjs +5 -5
  261. package/dist/routers/health.mjs.map +1 -1
  262. package/dist/routers/helpers.mjs +20 -5
  263. package/dist/routers/helpers.mjs.map +1 -1
  264. package/dist/routers/htmx.mjs +1 -2
  265. package/dist/routers/insights-ui.mjs +63 -0
  266. package/dist/routers/insights-ui.mjs.map +1 -0
  267. package/dist/routers/insights.handlers.mjs +44 -0
  268. package/dist/routers/insights.handlers.mjs.map +1 -0
  269. package/dist/routers/insights.mjs +188 -0
  270. package/dist/routers/insights.mjs.map +1 -0
  271. package/dist/routers/labels.mjs +1 -2
  272. package/dist/routers/links.handlers.mjs +79 -0
  273. package/dist/routers/links.handlers.mjs.map +1 -0
  274. package/dist/routers/links.mjs +138 -0
  275. package/dist/routers/links.mjs.map +1 -0
  276. package/dist/routers/media.mjs +30 -7
  277. package/dist/routers/media.mjs.map +1 -1
  278. package/dist/routers/members.mjs +3 -3
  279. package/dist/routers/members.mjs.map +1 -1
  280. package/dist/routers/notification-channels.mjs +18 -25
  281. package/dist/routers/notification-channels.mjs.map +1 -1
  282. package/dist/routers/notification-subscriptions.mjs +1 -2
  283. package/dist/routers/oauth-state.mjs +44 -0
  284. package/dist/routers/oauth-state.mjs.map +1 -0
  285. package/dist/routers/preview-base.mjs +37 -0
  286. package/dist/routers/preview-base.mjs.map +1 -0
  287. package/dist/routers/preview-token.mjs +50 -0
  288. package/dist/routers/preview-token.mjs.map +1 -0
  289. package/dist/routers/profile.mjs +4 -3
  290. package/dist/routers/profile.mjs.map +1 -1
  291. package/dist/routers/projects.mjs +18 -3
  292. package/dist/routers/projects.mjs.map +1 -1
  293. package/dist/routers/publish-upload.mjs +85 -0
  294. package/dist/routers/publish-upload.mjs.map +1 -0
  295. package/dist/routers/published-access.mjs +51 -0
  296. package/dist/routers/published-access.mjs.map +1 -0
  297. package/dist/routers/published-compression.mjs +109 -0
  298. package/dist/routers/published-compression.mjs.map +1 -0
  299. package/dist/routers/published-files.mjs +69 -0
  300. package/dist/routers/published-files.mjs.map +1 -0
  301. package/dist/routers/review-forms.mjs +71 -0
  302. package/dist/routers/review-forms.mjs.map +1 -0
  303. package/dist/routers/review-next.mjs +1 -2
  304. package/dist/routers/review-section.handlers.mjs +84 -0
  305. package/dist/routers/review-section.handlers.mjs.map +1 -0
  306. package/dist/routers/review-switch-off.mjs +28 -0
  307. package/dist/routers/review-switch-off.mjs.map +1 -0
  308. package/dist/routers/schemas.mjs +141 -24
  309. package/dist/routers/schemas.mjs.map +1 -1
  310. package/dist/routers/scoped-models.mjs +21 -0
  311. package/dist/routers/scoped-models.mjs.map +1 -0
  312. package/dist/routers/settings-ai.mjs +34 -0
  313. package/dist/routers/settings-ai.mjs.map +1 -0
  314. package/dist/routers/settings-capture.mjs +76 -0
  315. package/dist/routers/settings-capture.mjs.map +1 -0
  316. package/dist/routers/settings-design.mjs +102 -0
  317. package/dist/routers/settings-design.mjs.map +1 -0
  318. package/dist/routers/settings-general.mjs +56 -12
  319. package/dist/routers/settings-general.mjs.map +1 -1
  320. package/dist/routers/settings-git-channel.mjs +76 -0
  321. package/dist/routers/settings-git-channel.mjs.map +1 -0
  322. package/dist/routers/settings-labels.mjs +28 -3
  323. package/dist/routers/settings-labels.mjs.map +1 -1
  324. package/dist/routers/settings-links.mjs +62 -0
  325. package/dist/routers/settings-links.mjs.map +1 -0
  326. package/dist/routers/settings-members.mjs +5 -9
  327. package/dist/routers/settings-members.mjs.map +1 -1
  328. package/dist/routers/settings-notifications.mjs +16 -12
  329. package/dist/routers/settings-notifications.mjs.map +1 -1
  330. package/dist/routers/settings-tokens.mjs +3 -7
  331. package/dist/routers/settings-tokens.mjs.map +1 -1
  332. package/dist/routers/settings-webhooks.mjs +8 -26
  333. package/dist/routers/settings-webhooks.mjs.map +1 -1
  334. package/dist/routers/settings.handlers.mjs +108 -28
  335. package/dist/routers/settings.handlers.mjs.map +1 -1
  336. package/dist/routers/settings.mjs +17 -6
  337. package/dist/routers/settings.mjs.map +1 -1
  338. package/dist/routers/signed-storybook.mjs +55 -0
  339. package/dist/routers/signed-storybook.mjs.map +1 -0
  340. package/dist/routers/snapshot-groups.mjs +62 -0
  341. package/dist/routers/snapshot-groups.mjs.map +1 -0
  342. package/dist/routers/snapshots.mjs +104 -7
  343. package/dist/routers/snapshots.mjs.map +1 -1
  344. package/dist/routers/storybook.mjs +57 -104
  345. package/dist/routers/storybook.mjs.map +1 -1
  346. package/dist/routers/tokens.mjs +3 -7
  347. package/dist/routers/tokens.mjs.map +1 -1
  348. package/dist/routers/ui.mjs +38 -7
  349. package/dist/routers/ui.mjs.map +1 -1
  350. package/dist/routers/webhooks.mjs +6 -13
  351. package/dist/routers/webhooks.mjs.map +1 -1
  352. package/dist/routers/well-known.mjs +12 -6
  353. package/dist/routers/well-known.mjs.map +1 -1
  354. package/dist/routes/api-routes.mjs +92 -0
  355. package/dist/routes/api-routes.mjs.map +1 -0
  356. package/dist/routes/classification.mjs +37 -0
  357. package/dist/routes/classification.mjs.map +1 -0
  358. package/dist/routes/openapi-classes.mjs +21 -0
  359. package/dist/routes/openapi-classes.mjs.map +1 -0
  360. package/dist/routes/page-routes.mjs +103 -0
  361. package/dist/routes/page-routes.mjs.map +1 -0
  362. package/dist/runtime.mjs +19 -15
  363. package/dist/runtime.mjs.map +1 -1
  364. package/dist/serverless.mjs +4 -0
  365. package/dist/store.mjs +20 -3
  366. package/dist/store.mjs.map +1 -1
  367. package/dist/targets.mjs +45 -0
  368. package/dist/targets.mjs.map +1 -0
  369. package/dist/ui/avatar.mjs +1 -2
  370. package/dist/ui/buttons.mjs +17 -5
  371. package/dist/ui/buttons.mjs.map +1 -1
  372. package/dist/ui/code-block.mjs +1 -2
  373. package/dist/ui/compare-stage.mjs +86 -54
  374. package/dist/ui/compare-stage.mjs.map +1 -1
  375. package/dist/ui/csrf-field.mjs +1 -2
  376. package/dist/ui/css.mjs +1 -2
  377. package/dist/ui/design-reference.mjs +96 -0
  378. package/dist/ui/design-reference.mjs.map +1 -0
  379. package/dist/ui/document.mjs +2 -4
  380. package/dist/ui/document.mjs.map +1 -1
  381. package/dist/ui/dropdown.mjs +1 -2
  382. package/dist/ui/feedback.mjs +1 -2
  383. package/dist/ui/filter-input.mjs +1 -2
  384. package/dist/ui/forms.mjs +7 -7
  385. package/dist/ui/forms.mjs.map +1 -1
  386. package/dist/ui/icons/icon.mjs +1 -2
  387. package/dist/ui/icons/paths.mjs +2 -2
  388. package/dist/ui/icons/paths.mjs.map +1 -1
  389. package/dist/ui/icons/sprite.mjs +1 -2
  390. package/dist/ui/kbd.mjs +1 -2
  391. package/dist/ui/layout.mjs +1 -2
  392. package/dist/ui/preview-embed.mjs +81 -0
  393. package/dist/ui/preview-embed.mjs.map +1 -0
  394. package/dist/ui/progress.mjs +1 -2
  395. package/dist/ui/segmented.mjs +1 -2
  396. package/dist/ui/shell/account.mjs +1 -2
  397. package/dist/ui/shell/bare.mjs +1 -2
  398. package/dist/ui/shell/boot-script.mjs +1 -2
  399. package/dist/ui/shell/client-script.mjs +86 -2
  400. package/dist/ui/shell/client-script.mjs.map +1 -1
  401. package/dist/ui/shell/nav.mjs +13 -10
  402. package/dist/ui/shell/nav.mjs.map +1 -1
  403. package/dist/ui/shell/review-script.mjs +25 -3
  404. package/dist/ui/shell/review-script.mjs.map +1 -1
  405. package/dist/ui/shell/sidebar.mjs +3 -4
  406. package/dist/ui/shell/sidebar.mjs.map +1 -1
  407. package/dist/ui/shell/topbar.mjs +1 -2
  408. package/dist/ui/stacks.mjs +1 -2
  409. package/dist/ui/styles/base.mjs +1 -2
  410. package/dist/ui/styles/behavior.mjs +1 -2
  411. package/dist/ui/styles/review-layout.mjs +1 -2
  412. package/dist/ui/styles/review.mjs +1 -2
  413. package/dist/ui/styles/tokens.mjs +1 -2
  414. package/dist/ui/styles.mjs +1 -2
  415. package/dist/ui/subnav.mjs +1 -2
  416. package/dist/ui/table.mjs +1 -2
  417. package/dist/ui/theme.mjs +1 -2
  418. package/dist/ui/thumbnail.mjs +1 -2
  419. package/dist/ui/time.mjs +1 -2
  420. package/package.json +14 -12
  421. package/dist/asset-paths.mjs +0 -23
  422. package/dist/asset-paths.mjs.map +0 -1
  423. package/dist/assets/mark.svg +0 -1
  424. package/dist/pages/settings-status.mjs +0 -137
  425. package/dist/pages/settings-status.mjs.map +0 -1
  426. package/dist/routers/settings-status.mjs +0 -77
  427. package/dist/routers/settings-status.mjs.map +0 -1
  428. package/dist/routers/status-configs.mjs +0 -108
  429. package/dist/routers/status-configs.mjs.map +0 -1
@@ -0,0 +1,52 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { Badge, Meta } from "../ui/feedback.mjs";
5
+ import { Card, SectionTitle } from "../ui/layout.mjs";
6
+ import { Table } from "../ui/table.mjs";
7
+ import { targetLabel } from "../targets.mjs";
8
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
9
+ import { unavailableReason } from "@storyshelf/core/adapter/capture-runner";
10
+ // src/pages/admin-system-targets.tsx
11
+ /** The browsers the capture runner offers and whether each can render now (ADR 0007, decision 7). */
12
+ function CaptureTargetsCard(props) {
13
+ const { targets } = props;
14
+ return /* @__PURE__ */ jsxs(Card, { children: [
15
+ /* @__PURE__ */ jsx(SectionTitle, { children: "Capture browsers" }),
16
+ /* @__PURE__ */ jsx(Meta, { children: "What the capture runner offers, default first. A project can only pick a target that is ready; a declared engine that is not installed is listed with the reason." }),
17
+ /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsxs("table", { children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
18
+ /* @__PURE__ */ jsx("th", { children: "Target" }),
19
+ /* @__PURE__ */ jsx("th", { children: "Reaches Storybook" }),
20
+ /* @__PURE__ */ jsx("th", { children: "Status" })
21
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: targets.map((entry, index) => {
22
+ const reason = unavailableReason(entry.status);
23
+ return /* @__PURE__ */ jsxs("tr", { children: [
24
+ /* @__PURE__ */ jsxs("td", { children: [
25
+ /* @__PURE__ */ jsx("strong", { children: targetLabel(entry.target) }),
26
+ " ",
27
+ /* @__PURE__ */ jsx(Meta, {
28
+ as: "span",
29
+ mono: true,
30
+ children: entry.target
31
+ }),
32
+ index === 0 ? /* @__PURE__ */ jsx(Badge, { children: "default" }) : null
33
+ ] }),
34
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(Meta, {
35
+ as: "span",
36
+ children: entry.reach === "url" ? "signed URL" : "local server"
37
+ }) }),
38
+ /* @__PURE__ */ jsxs("td", { children: [/* @__PURE__ */ jsx(Badge, {
39
+ tone: reason === null ? "success" : "danger",
40
+ icon: true,
41
+ children: reason === null ? "ready" : "unavailable"
42
+ }), reason === null ? null : /* @__PURE__ */ jsx(Meta, {
43
+ as: "div",
44
+ children: reason
45
+ })] })
46
+ ] }, entry.target);
47
+ }) })] }) })
48
+ ] });
49
+ }
50
+ export { CaptureTargetsCard };
51
+
52
+ //# sourceMappingURL=admin-system-targets.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-system-targets.mjs","names":[],"sources":["../../src/pages/admin-system-targets.tsx"],"sourcesContent":["import { unavailableReason, type RunnerTarget } from \"@storyshelf/core/adapter/capture-runner\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { targetLabel } from \"../targets.ts\";\nimport { Badge, Card, Meta, SectionTitle, Table } from \"../ui/components.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** The browsers the capture runner offers and whether each can render now (ADR 0007, decision 7). */\nexport function CaptureTargetsCard(props: {\n targets: readonly RunnerTarget[];\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { targets } = props;\n return (\n <Card>\n <SectionTitle>Capture browsers</SectionTitle>\n <Meta>\n What the capture runner offers, default first. A project can only pick a target that is\n ready; a declared engine that is not installed is listed with the reason.\n </Meta>\n <Table>\n <table>\n <thead>\n <tr>\n <th>Target</th>\n <th>Reaches Storybook</th>\n <th>Status</th>\n </tr>\n </thead>\n <tbody>\n {targets.map((entry, index) => {\n const reason = unavailableReason(entry.status);\n return (\n <tr key={entry.target}>\n <td>\n <strong>{targetLabel(entry.target)}</strong>{\" \"}\n <Meta as=\"span\" mono>\n {entry.target}\n </Meta>\n {index === 0 ? <Badge>default</Badge> : null}\n </td>\n <td>\n <Meta as=\"span\">{entry.reach === \"url\" ? \"signed URL\" : \"local server\"}</Meta>\n </td>\n <td>\n <Badge tone={reason === null ? \"success\" : \"danger\"} icon>\n {reason === null ? \"ready\" : \"unavailable\"}\n </Badge>\n {reason === null ? null : <Meta as=\"div\">{reason}</Meta>}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </Table>\n </Card>\n );\n}\n"],"mappings":";;;;;;;;;;;AAQA,SAAgB,mBAAmB,OAEgB;CACjD,MAAM,EAAE,YAAY;CACpB,OACE,qBAAC,MAAD,EAAA,UAAA;EACE,oBAAC,cAAD,EAAA,UAAc,mBAA8B,CAAA;EAC5C,oBAAC,MAAD,EAAA,UAAM,oKAGA,CAAA;EACN,oBAAC,OAAD,EAAA,UACE,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,SAAD,EAAA,UACE,qBAAC,MAAD,EAAA,UAAA;GACE,oBAAC,MAAD,EAAA,UAAI,SAAU,CAAA;GACd,oBAAC,MAAD,EAAA,UAAI,oBAAqB,CAAA;GACzB,oBAAC,MAAD,EAAA,UAAI,SAAU,CAAA;EACZ,EAAA,CAAA,EACC,CAAA,GACP,oBAAC,SAAD,EAAA,UACG,QAAQ,KAAK,OAAO,UAAU;GAC7B,MAAM,SAAS,kBAAkB,MAAM,MAAM;GAC7C,OACE,qBAAC,MAAD,EAAA,UAAA;IACE,qBAAC,MAAD,EAAA,UAAA;KACE,oBAAC,UAAD,EAAA,UAAS,YAAY,MAAM,MAAM,EAAU,CAAA;KAAE;KAC7C,oBAAC,MAAD;MAAM,IAAG;MAAO,MAAA;MACb,UAAA,MAAM;KACH,CAAA;KACL,UAAU,IAAI,oBAAC,OAAD,EAAA,UAAO,UAAc,CAAA,IAAI;IACtC,EAAA,CAAA;IACJ,oBAAC,MAAD,EAAA,UACE,oBAAC,MAAD;KAAM,IAAG;KAAQ,UAAA,MAAM,UAAU,QAAQ,eAAe;IAAqB,CAAA,EAC3E,CAAA;IACJ,qBAAC,MAAD,EAAA,UAAA,CACE,oBAAC,OAAD;KAAO,MAAM,WAAW,OAAO,YAAY;KAAU,MAAA;KAClD,UAAA,WAAW,OAAO,UAAU;IACxB,CAAA,GACN,WAAW,OAAO,OAAO,oBAAC,MAAD;KAAM,IAAG;KAAO,UAAA;IAAa,CAAA,CACrD,EAAA,CAAA;GACF,EAAA,GAjBK,MAAM,MAiBX;EAER,CAAC,EACI,CAAA,CACF,EAAA,CAAA,EACF,CAAA;CACH,EAAA,CAAA;AAEV"}
@@ -8,8 +8,11 @@ import { HStack } from "../ui/stacks.mjs";
8
8
  import { Table } from "../ui/table.mjs";
9
9
  import { csrfField } from "../ui/csrf-field.mjs";
10
10
  import { DocumentLayout } from "../ui/document.mjs";
11
+ import { PublicOriginCard } from "./admin-system-origin.mjs";
12
+ import { CaptureTargetsCard } from "./admin-system-targets.mjs";
13
+ import { NotificationsPausedNotice } from "./notifications-paused.mjs";
11
14
  import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
12
- //#region src/pages/admin-system.tsx
15
+ // src/pages/admin-system.tsx
13
16
  function healthTone(state) {
14
17
  if (state === "ok") return "success";
15
18
  if (state === "starting") return "info";
@@ -90,9 +93,27 @@ function AdapterTable(props) {
90
93
  /* @__PURE__ */ jsx("th", { children: "Detail" })
91
94
  ] }) }), /* @__PURE__ */ jsx("tbody", { children: report.adapters.map((adapter) => /* @__PURE__ */ jsx(AdapterRow, { adapter }, `${adapter.category}/${adapter.kind}`)) })] }) });
92
95
  }
96
+ /** Detected hardware and the capture concurrency it yields (auto unless configured). */
97
+ function CaptureSizing(props) {
98
+ const { cpus, memoryMb, builds, pages } = props.capture;
99
+ return /* @__PURE__ */ jsxs(Meta, { children: [
100
+ "Capture host ",
101
+ cpus,
102
+ " CPU · ",
103
+ memoryMb,
104
+ " MB · builds at once ",
105
+ builds.value,
106
+ builds.auto ? " (auto)" : " (configured)",
107
+ " · pages per build ",
108
+ pages.start,
109
+ "–",
110
+ pages.max,
111
+ " (auto, adapts to load unless the runner pins it)"
112
+ ] });
113
+ }
93
114
  /** Non-secret server facts (never secret, admin token, or credentials). */
94
115
  function ServerFacts(props) {
95
- const { report, authEnabled, config, authMethods } = props;
116
+ const { report, authEnabled, config, authMethods, capture } = props.data;
96
117
  return /* @__PURE__ */ jsxs(Card, { children: [
97
118
  /* @__PURE__ */ jsx(SectionTitle, { children: "Server" }),
98
119
  /* @__PURE__ */ jsxs("div", {
@@ -115,9 +136,6 @@ function ServerFacts(props) {
115
136
  /* @__PURE__ */ jsxs(Meta, { children: [
116
137
  "Auth ",
117
138
  authEnabled ? "enabled" : "disabled — all operations permitted",
118
- " · Capture concurrency",
119
- " ",
120
- formatOptional(config.captureConcurrency),
121
139
  " · Purge TTL",
122
140
  " ",
123
141
  formatOptional(config.purgeTtlDays),
@@ -126,6 +144,7 @@ function ServerFacts(props) {
126
144
  config.branchTtlDays === null ? "disabled" : `${formatOptional(config.branchTtlDays)} days`
127
145
  ] }),
128
146
  authMethods && authMethods.length > 0 ? /* @__PURE__ */ jsx(HStack, { children: authMethods.map((method) => /* @__PURE__ */ jsx(Badge, { children: method }, method)) }) : null,
147
+ capture ? /* @__PURE__ */ jsx(CaptureSizing, { capture }) : null,
129
148
  /* @__PURE__ */ jsxs(Meta, { children: [
130
149
  "Scratch dir ",
131
150
  formatOptional(config.scratchDir),
@@ -188,9 +207,37 @@ function CredentialsCard(props) {
188
207
  }) : /* @__PURE__ */ jsx(Meta, { children: rotating ? "All credentials use the current secret. Remove SECRET_PREVIOUS." : "To rotate, set SECRET to the new value and SECRET_PREVIOUS to the old one." })
189
208
  ] });
190
209
  }
210
+ /** Notice that `testing` is off, so a misconfigured instance in a fleet is visible (ADR 0006). */
211
+ function HostingOnlyNotice(props) {
212
+ const { hiddenSnapshots } = props;
213
+ return /* @__PURE__ */ jsx("div", {
214
+ class: "mt-1",
215
+ children: /* @__PURE__ */ jsxs(Alert, {
216
+ tone: "info",
217
+ title: "Testing is off: Storybook hosting only",
218
+ children: [
219
+ "This instance publishes and serves Storybooks, but does not capture, compare or keep baselines, and no testing status is posted. Set the ",
220
+ /* @__PURE__ */ jsx("code", { children: "testing" }),
221
+ " option to turn testing on.",
222
+ hiddenSnapshots > 0 ? ` ${hiddenSnapshots} existing snapshot(s) are hidden, not deleted, and reappear when testing is turned on.` : ""
223
+ ]
224
+ })
225
+ });
226
+ }
227
+ /** The public origin and the capture browsers (ADR 0007), when the instance has them. */
228
+ function CaptureCards(props) {
229
+ const { publicOrigin, targets } = props.data;
230
+ return /* @__PURE__ */ jsxs(Fragment, { children: [publicOrigin ? /* @__PURE__ */ jsx("div", {
231
+ class: "mt-1",
232
+ children: /* @__PURE__ */ jsx(PublicOriginCard, { origin: publicOrigin })
233
+ }) : null, targets && targets.length > 0 ? /* @__PURE__ */ jsx("div", {
234
+ class: "mt-1",
235
+ children: /* @__PURE__ */ jsx(CaptureTargetsCard, { targets })
236
+ }) : null] });
237
+ }
191
238
  /** Site-admin System page: adapter inventory with in-depth health. */
192
239
  function renderAdminSystemPage(data) {
193
- const { report, authEnabled, config, authMethods, credentials } = data;
240
+ const { report, config, credentials } = data;
194
241
  return /* @__PURE__ */ jsxs(DocumentLayout, {
195
242
  title: "System",
196
243
  nav: { active: "admin" },
@@ -206,19 +253,22 @@ function renderAdminSystemPage(data) {
206
253
  " ",
207
254
  formatUptime(report.uptimeSecs)
208
255
  ] }),
209
- actions: /* @__PURE__ */ jsx(Button, {
256
+ actions: /* @__PURE__ */ jsxs(HStack, { children: [data.aiEnabled ? /* @__PURE__ */ jsx(Button, {
257
+ variant: "secondary",
258
+ size: "sm",
259
+ href: "/admin/ai",
260
+ children: "AI usage"
261
+ }) : null, /* @__PURE__ */ jsx(Button, {
210
262
  variant: "secondary",
211
263
  size: "sm",
212
264
  href: "/admin",
213
265
  children: "Recheck"
214
- })
215
- }),
216
- /* @__PURE__ */ jsx(ServerFacts, {
217
- report,
218
- authEnabled,
219
- config,
220
- authMethods
266
+ })] })
221
267
  }),
268
+ /* @__PURE__ */ jsx(NotificationsPausedNotice, { paused: data.notificationsPaused }),
269
+ data.testing === false ? /* @__PURE__ */ jsx(HostingOnlyNotice, { hiddenSnapshots: data.hiddenSnapshots ?? 0 }) : null,
270
+ /* @__PURE__ */ jsx(ServerFacts, { data }),
271
+ /* @__PURE__ */ jsx(CaptureCards, { data }),
222
272
  credentials ? /* @__PURE__ */ jsx("div", {
223
273
  class: "mt-1",
224
274
  children: /* @__PURE__ */ jsx(CredentialsCard, {
@@ -237,7 +287,6 @@ function renderAdminSystemPage(data) {
237
287
  ]
238
288
  });
239
289
  }
240
- //#endregion
241
290
  export { renderAdminSystemPage };
242
291
 
243
292
  //# sourceMappingURL=admin-system.mjs.map
@@ -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,21 +1,32 @@
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
- //#region src/pages/build-detail.tsx
29
+ // src/pages/build-detail.tsx
19
30
  /** Map a capture log level to its badge tone. */
20
31
  function logTone(level) {
21
32
  if (level === "error") return "danger";
@@ -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,59 +407,13 @@ 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
  }
389
- //#endregion
390
417
  export { renderBuildDetailPage };
391
418
 
392
419
  //# sourceMappingURL=build-detail.mjs.map