@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,73 @@
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 { HStack, VStack } from "../ui/stacks.mjs";
7
+ import { summarizeTests } from "@storyshelf/core/capture";
8
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
9
+ // src/pages/build-tests.tsx
10
+ /** The line under the card title: counts, and what a failure means for the build. */
11
+ function testsSummaryNote(results) {
12
+ const { total, passed, failed } = summarizeTests(results);
13
+ if (total === 0) return "No Storybook tests ran.";
14
+ return failed > 0 ? `${failed} of ${total} failed. A failing test fails the build, like a failing play function.` : `All ${passed} passed.`;
15
+ }
16
+ /**
17
+ * The build's Storybook `.test()` results: failures first with their message, passes behind a
18
+ * disclosure. Null when the build ran no tests (project setting off, or none in the Storybook).
19
+ */
20
+ function TestsCard(props) {
21
+ if (props.results.length === 0) return null;
22
+ const failed = props.results.filter((result) => result.status === "failed");
23
+ const passed = props.results.filter((result) => result.status === "passed");
24
+ return /* @__PURE__ */ jsxs(Card, { children: [
25
+ /* @__PURE__ */ jsx(SectionTitle, { children: "Storybook tests" }),
26
+ /* @__PURE__ */ jsxs(HStack, { children: [/* @__PURE__ */ jsx(Badge, {
27
+ tone: failed.length > 0 ? "danger" : "success",
28
+ children: failed.length > 0 ? `${failed.length} failed` : "all passed"
29
+ }), /* @__PURE__ */ jsxs(Meta, {
30
+ as: "span",
31
+ children: [
32
+ passed.length,
33
+ " passed · ",
34
+ failed.length,
35
+ " failed"
36
+ ]
37
+ })] }),
38
+ /* @__PURE__ */ jsx(Meta, { children: testsSummaryNote(props.results) }),
39
+ /* @__PURE__ */ jsx(VStack, { children: failed.map((result) => /* @__PURE__ */ jsx(TestRow, { result }, result.id)) }),
40
+ passed.length > 0 ? /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsxs("summary", { children: [
41
+ "Passed tests (",
42
+ passed.length,
43
+ ")"
44
+ ] }), /* @__PURE__ */ jsx(VStack, { children: passed.map((result) => /* @__PURE__ */ jsx(TestRow, { result }, result.id)) })] }) : null
45
+ ] });
46
+ }
47
+ function TestRow(props) {
48
+ const { result } = props;
49
+ return /* @__PURE__ */ jsxs(VStack, {
50
+ gap: "sm",
51
+ children: [/* @__PURE__ */ jsxs(HStack, {
52
+ align: "baseline",
53
+ wrap: false,
54
+ children: [/* @__PURE__ */ jsx(Badge, {
55
+ tone: result.status === "failed" ? "danger" : "success",
56
+ children: result.status
57
+ }), /* @__PURE__ */ jsxs("span", { children: [
58
+ result.title,
59
+ " ",
60
+ /* @__PURE__ */ jsxs(Meta, {
61
+ as: "span",
62
+ children: ["/ ", result.name]
63
+ })
64
+ ] })]
65
+ }), result.error ? /* @__PURE__ */ jsx(Meta, {
66
+ as: "pre",
67
+ children: result.error
68
+ }) : null]
69
+ });
70
+ }
71
+ export { TestsCard, testsSummaryNote };
72
+
73
+ //# sourceMappingURL=build-tests.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-tests.mjs","names":[],"sources":["../../src/pages/build-tests.tsx"],"sourcesContent":["import { summarizeTests, type TestResult } from \"@storyshelf/core/capture\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { Badge, Card, HStack, Meta, SectionTitle, VStack } from \"../ui/components.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype Rendered = HtmlEscapedString | Promise<HtmlEscapedString>;\n\n/** The line under the card title: counts, and what a failure means for the build. */\nexport function testsSummaryNote(results: readonly TestResult[]): string {\n const { total, passed, failed } = summarizeTests(results);\n if (total === 0) {\n return \"No Storybook tests ran.\";\n }\n return failed > 0\n ? `${failed} of ${total} failed. A failing test fails the build, like a failing play function.`\n : `All ${passed} passed.`;\n}\n\n/**\n * The build's Storybook `.test()` results: failures first with their message, passes behind a\n * disclosure. Null when the build ran no tests (project setting off, or none in the Storybook).\n */\nexport function TestsCard(props: { results: readonly TestResult[] }): Rendered | null {\n if (props.results.length === 0) {\n return null;\n }\n const failed = props.results.filter((result) => result.status === \"failed\");\n const passed = props.results.filter((result) => result.status === \"passed\");\n return (\n <Card>\n <SectionTitle>Storybook tests</SectionTitle>\n <HStack>\n <Badge tone={failed.length > 0 ? \"danger\" : \"success\"}>\n {failed.length > 0 ? `${failed.length} failed` : \"all passed\"}\n </Badge>\n <Meta as=\"span\">\n {passed.length} passed · {failed.length} failed\n </Meta>\n </HStack>\n <Meta>{testsSummaryNote(props.results)}</Meta>\n <VStack>\n {failed.map((result) => (\n <TestRow key={result.id} result={result} />\n ))}\n </VStack>\n {passed.length > 0 ? (\n <details>\n <summary>Passed tests ({passed.length})</summary>\n <VStack>\n {passed.map((result) => (\n <TestRow key={result.id} result={result} />\n ))}\n </VStack>\n </details>\n ) : null}\n </Card>\n );\n}\n\nfunction TestRow(props: { result: TestResult }): Rendered {\n const { result } = props;\n return (\n <VStack gap=\"sm\">\n <HStack align=\"baseline\" wrap={false}>\n <Badge tone={result.status === \"failed\" ? \"danger\" : \"success\"}>{result.status}</Badge>\n <span>\n {result.title} <Meta as=\"span\">/ {result.name}</Meta>\n </span>\n </HStack>\n {result.error ? <Meta as=\"pre\">{result.error}</Meta> : null}\n </VStack>\n );\n}\n"],"mappings":";;;;;;;;;;AASA,SAAgB,iBAAiB,SAAwC;CACvE,MAAM,EAAE,OAAO,QAAQ,WAAW,eAAe,OAAO;CACxD,IAAI,UAAU,GACZ,OAAO;CAET,OAAO,SAAS,IACZ,GAAG,OAAO,MAAM,MAAM,0EACtB,OAAO,OAAO;AACpB;;;;;AAMA,SAAgB,UAAU,OAA4D;CACpF,IAAI,MAAM,QAAQ,WAAW,GAC3B,OAAO;CAET,MAAM,SAAS,MAAM,QAAQ,QAAQ,WAAW,OAAO,WAAW,QAAQ;CAC1E,MAAM,SAAS,MAAM,QAAQ,QAAQ,WAAW,OAAO,WAAW,QAAQ;CAC1E,OACE,qBAAC,MAAD,EAAA,UAAA;EACE,oBAAC,cAAD,EAAA,UAAc,kBAA6B,CAAA;EAC3C,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,OAAD;GAAO,MAAM,OAAO,SAAS,IAAI,WAAW;GACzC,UAAA,OAAO,SAAS,IAAI,GAAG,OAAO,OAAO,WAAW;EAC5C,CAAA,GACP,qBAAC,MAAD;GAAM,IAAG;GAAT,UAAA;IACG,OAAO;IAAO;IAAW,OAAO;IAAO;GACpC;EACA,CAAA,CAAA,EAAA,CAAA;EACR,oBAAC,MAAD,EAAA,UAAO,iBAAiB,MAAM,OAAO,EAAQ,CAAA;EAC7C,oBAAC,QAAD,EAAA,UACG,OAAO,KAAK,WACX,oBAAC,SAAD,EAAiC,OAAS,GAA5B,OAAO,EAAqB,CAC3C,EACK,CAAA;EACP,OAAO,SAAS,IACf,qBAAC,WAAD,EAAA,UAAA,CACE,qBAAC,WAAD,EAAA,UAAA;GAAS;GAAe,OAAO;GAAO;EAAU,EAAA,CAAA,GAChD,oBAAC,QAAD,EAAA,UACG,OAAO,KAAK,WACX,oBAAC,SAAD,EAAiC,OAAS,GAA5B,OAAO,EAAqB,CAC3C,EACK,CAAA,CACD,EAAA,CAAA,IACP;CACA,EAAA,CAAA;AAEV;AAEA,SAAS,QAAQ,OAAyC;CACxD,MAAM,EAAE,WAAW;CACnB,OACE,qBAAC,QAAD;EAAQ,KAAI;EAAZ,UAAA,CACE,qBAAC,QAAD;GAAQ,OAAM;GAAW,MAAM;GAA/B,UAAA,CACE,oBAAC,OAAD;IAAO,MAAM,OAAO,WAAW,WAAW,WAAW;IAAY,UAAA,OAAO;GAAc,CAAA,GACtF,qBAAC,QAAD,EAAA,UAAA;IACG,OAAO;IAAM;IAAC,qBAAC,MAAD;KAAM,IAAG;KAAT,UAAA,CAAgB,MAAG,OAAO,IAAW;;GAChD,EAAA,CAAA,CACA;EACP,CAAA,GAAA,OAAO,QAAQ,oBAAC,MAAD;GAAM,IAAG;GAAO,UAAA,OAAO;EAAY,CAAA,IAAI,IACjD;;AAEZ"}
@@ -0,0 +1,248 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { Button, Tabs } from "../ui/buttons.mjs";
5
+ import { Alert, Badge, EmptyState, Meta, statusTone } from "../ui/feedback.mjs";
6
+ import { Card, PageHeader, SectionTitle } from "../ui/layout.mjs";
7
+ import { HStack, VStack } from "../ui/stacks.mjs";
8
+ import { Table } from "../ui/table.mjs";
9
+ import { Thumbnail } from "../ui/thumbnail.mjs";
10
+ import { DocumentLayout } from "../ui/document.mjs";
11
+ import { snapshotCardBody, snapshotCardHead, snapshotCardMeta, snapshotGrid } from "../ui/styles/review.mjs";
12
+ import { snapshotImageUrl, snapshotVariant } from "./build-diff-model.mjs";
13
+ import { LinkedUsage } from "./build-diff-usage.mjs";
14
+ import { DesignReferences, DesignStrip } from "./design-links.mjs";
15
+ import { ComponentBuilds, StoriesTab } from "./component-stories.mjs";
16
+ import { worstStatus } from "@storyshelf/core/models";
17
+ import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
18
+ import { createUrlBuilder } from "@storyshelf/core/urls";
19
+ // src/pages/component-detail.tsx
20
+ const TAB_LABELS = {
21
+ stories: "Stories",
22
+ states: "States",
23
+ history: "History",
24
+ design: "Design",
25
+ linked: "Linked"
26
+ };
27
+ /** The tabs a page offers: the catalog's two in both modes, the snapshot ones only with `testing`. */
28
+ function componentTabs(testing) {
29
+ return testing ? [
30
+ "states",
31
+ "stories",
32
+ "history",
33
+ "design",
34
+ "linked"
35
+ ] : ["stories", "history"];
36
+ }
37
+ /** Component detail page: stories, states, history, design and linked packages of one story file. */
38
+ function renderComponentPage(data) {
39
+ const { project, importPath, title } = data;
40
+ return /* @__PURE__ */ jsxs(DocumentLayout, {
41
+ title: `${title} · Component`,
42
+ nav: {
43
+ active: "library",
44
+ projectSlug: project.slug,
45
+ projectName: project.name,
46
+ trail: [{ label: title }]
47
+ },
48
+ children: [
49
+ /* @__PURE__ */ jsx(PageHeader, {
50
+ title,
51
+ description: /* @__PURE__ */ jsx("span", {
52
+ class: "mono",
53
+ children: importPath
54
+ }),
55
+ meta: /* @__PURE__ */ jsx(HeaderMeta, { data })
56
+ }),
57
+ /* @__PURE__ */ jsx(PresenceNotice, { data }),
58
+ /* @__PURE__ */ jsx(Tabs, {
59
+ label: "Component sections",
60
+ tabs: componentTabs(data.testing).map((tab) => ({
61
+ label: TAB_LABELS[tab],
62
+ href: `?tab=${tab}`,
63
+ active: data.tab === tab
64
+ }))
65
+ }),
66
+ /* @__PURE__ */ jsx(ActiveTab, { data })
67
+ ]
68
+ });
69
+ }
70
+ function HeaderMeta(props) {
71
+ const { captured, catalog } = props.data;
72
+ const stories = catalog.entries.filter((entry) => entry.type === "story").length;
73
+ const docs = catalog.entries.length - stories;
74
+ const counts = `${stories} ${stories === 1 ? "story" : "stories"}${docs > 0 ? ` · ${docs} docs` : ""}`;
75
+ if (!captured) return /* @__PURE__ */ jsx(Fragment, { children: counts });
76
+ const status = worstStatus(captured.groups.map((group) => group.primary.status));
77
+ const modes = new Set(captured.groups.map((group) => group.modeKey)).size;
78
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
79
+ /* @__PURE__ */ jsx(Badge, {
80
+ tone: statusTone(status),
81
+ icon: true,
82
+ children: status
83
+ }),
84
+ " ",
85
+ counts,
86
+ " · ",
87
+ modes,
88
+ " ",
89
+ modes === 1 ? "mode" : "modes"
90
+ ] });
91
+ }
92
+ function PresenceNotice(props) {
93
+ const { data } = props;
94
+ const urls = createUrlBuilder("/", data.publishedBaseDomain);
95
+ const presence = data.captured?.presence ?? data.catalog.presence;
96
+ const { project } = data;
97
+ if (presence.kind === "moved") return /* @__PURE__ */ jsx(Alert, {
98
+ tone: "warning",
99
+ title: "This component moved",
100
+ children: /* @__PURE__ */ jsxs(HStack, { children: [/* @__PURE__ */ jsx("span", { children: "The file now lives at" }), /* @__PURE__ */ jsx(Button, {
101
+ variant: "secondary",
102
+ size: "sm",
103
+ href: urls.component(project.slug, presence.to),
104
+ children: presence.to
105
+ })] })
106
+ });
107
+ if (presence.kind === "deleted") return /* @__PURE__ */ jsxs(Alert, {
108
+ tone: "warning",
109
+ title: "Not in the latest build",
110
+ children: [
111
+ "Last seen in",
112
+ " ",
113
+ /* @__PURE__ */ jsxs("a", {
114
+ href: urls.build(project.slug, presence.lastSeen.id),
115
+ children: [
116
+ "build ",
117
+ presence.lastSeen.gitSha.slice(0, 7),
118
+ " (",
119
+ presence.lastSeen.gitBranch,
120
+ ")"
121
+ ]
122
+ }),
123
+ ". The states below are from that build."
124
+ ]
125
+ });
126
+ return null;
127
+ }
128
+ function ActiveTab(props) {
129
+ const { data } = props;
130
+ if (data.tab === "stories") return /* @__PURE__ */ jsx(StoriesTab, { data });
131
+ if (data.tab === "history") return /* @__PURE__ */ jsx(HistoryTab, { data });
132
+ if (data.tab === "design") return /* @__PURE__ */ jsx(DesignTab, { design: data.design });
133
+ if (data.tab === "linked") return /* @__PURE__ */ jsx(LinkedTab, { data });
134
+ return /* @__PURE__ */ jsx(StatesTab, { data });
135
+ }
136
+ function StatesTab(props) {
137
+ const { data } = props;
138
+ const { project, captured } = data;
139
+ if (!captured) return /* @__PURE__ */ jsx(EmptyState, {
140
+ title: "Not captured yet",
141
+ description: "No capture has rendered this file's stories. Its stories are listed under Stories."
142
+ });
143
+ const { shownBuild } = captured;
144
+ const urls = createUrlBuilder("/", data.publishedBaseDomain);
145
+ return /* @__PURE__ */ jsx("div", {
146
+ class: snapshotGrid,
147
+ children: captured.groups.map((group) => /* @__PURE__ */ jsx("div", {
148
+ id: group.storyId,
149
+ children: /* @__PURE__ */ jsxs(Card, {
150
+ padded: false,
151
+ children: [/* @__PURE__ */ jsxs("div", {
152
+ class: snapshotCardHead,
153
+ children: [/* @__PURE__ */ jsx("strong", { children: group.primary.storyName }), /* @__PURE__ */ jsxs("span", {
154
+ class: snapshotCardMeta,
155
+ children: [
156
+ /* @__PURE__ */ jsx(Badge, {
157
+ tone: statusTone(group.primary.status),
158
+ children: group.primary.status
159
+ }),
160
+ " ",
161
+ snapshotVariant(group.primary)
162
+ ]
163
+ })]
164
+ }), /* @__PURE__ */ jsx("div", {
165
+ class: snapshotCardBody,
166
+ children: /* @__PURE__ */ jsx("a", {
167
+ href: `${urls.buildDiff(project.slug, shownBuild.id)}?snapshot=${group.representative.id}`,
168
+ children: /* @__PURE__ */ jsx(Thumbnail, {
169
+ src: snapshotImageUrl(project, shownBuild, group.primary, "image"),
170
+ alt: `${group.primary.storyTitle} / ${group.primary.storyName}`
171
+ })
172
+ })
173
+ })]
174
+ })
175
+ }, group.key))
176
+ });
177
+ }
178
+ function HistoryTab(props) {
179
+ const { data } = props;
180
+ return /* @__PURE__ */ jsxs(VStack, { children: [data.captured ? /* @__PURE__ */ jsx(CapturedHistory, {
181
+ data,
182
+ captured: data.captured
183
+ }) : null, /* @__PURE__ */ jsx(ComponentBuilds, { data })] });
184
+ }
185
+ function CapturedHistory(props) {
186
+ const { data, captured } = props;
187
+ return /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(SectionTitle, { children: "Captured builds" }), /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsxs("table", { children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
188
+ /* @__PURE__ */ jsx("th", { children: "Build" }),
189
+ /* @__PURE__ */ jsx("th", { children: "Branch" }),
190
+ /* @__PURE__ */ jsx("th", { children: "States changed" }),
191
+ /* @__PURE__ */ jsx("th", { children: "Status" }),
192
+ /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx("span", {
193
+ class: "visually-hidden",
194
+ children: "Actions"
195
+ }) })
196
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: captured.history.map((row) => /* @__PURE__ */ jsx(CapturedRow, {
197
+ row,
198
+ data
199
+ }, row.build.id)) })] }) })] });
200
+ }
201
+ function CapturedRow(props) {
202
+ const { row, data } = props;
203
+ const urls = createUrlBuilder("/", data.publishedBaseDomain);
204
+ const slug = data.project.slug;
205
+ return /* @__PURE__ */ jsxs("tr", { children: [
206
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("a", {
207
+ href: urls.build(slug, row.build.id),
208
+ children: row.build.gitSha.slice(0, 7)
209
+ }) }),
210
+ /* @__PURE__ */ jsx("td", { children: row.build.gitBranch }),
211
+ /* @__PURE__ */ jsxs("td", { children: [
212
+ row.changedStates,
213
+ " / ",
214
+ row.states
215
+ ] }),
216
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(Badge, {
217
+ tone: statusTone(row.status),
218
+ children: row.status
219
+ }) }),
220
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(Button, {
221
+ variant: "ghost",
222
+ size: "sm",
223
+ href: `${urls.buildDiff(slug, row.build.id)}?snapshot=${row.snapshotId}`,
224
+ children: "Review"
225
+ }) })
226
+ ] });
227
+ }
228
+ function DesignTab(props) {
229
+ if (props.design.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
230
+ title: "No design linked",
231
+ description: "Declare a design link in the stories’ parameters, or map this file under Settings → Design."
232
+ });
233
+ return /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(DesignStrip, { items: props.design }), /* @__PURE__ */ jsx(DesignReferences, { items: props.design })] });
234
+ }
235
+ function LinkedTab(props) {
236
+ const { data } = props;
237
+ if (data.usage.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
238
+ title: "No linked packages",
239
+ description: "This component's stories use no packages linked to another project in the latest build."
240
+ });
241
+ return /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(Meta, { children: "Packages these stories use, linked to the projects that publish them." }), /* @__PURE__ */ jsx(LinkedUsage, {
242
+ usage: data.usage,
243
+ publishedBaseDomain: data.publishedBaseDomain
244
+ })] });
245
+ }
246
+ export { componentTabs, renderComponentPage };
247
+
248
+ //# sourceMappingURL=component-detail.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-detail.mjs","names":[],"sources":["../../src/pages/component-detail.tsx"],"sourcesContent":["import {\n worstStatus,\n type ComponentHistoryRow,\n type ComponentPresence,\n} from \"@storyshelf/core/models\";\nimport type { Build, Project } from \"@storyshelf/core/schema\";\nimport { createUrlBuilder } from \"@storyshelf/core/urls\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { ComponentCatalogView } from \"../routers/component-catalog.handlers.ts\";\nimport type { ComponentReview } from \"../routers/review-section.handlers.ts\";\nimport {\n Alert,\n Badge,\n Button,\n Card,\n EmptyState,\n HStack,\n Meta,\n PageHeader,\n SectionTitle,\n Table,\n Tabs,\n Thumbnail,\n VStack,\n statusTone,\n} from \"../ui/components.tsx\";\nimport { DocumentLayout, type RenderedContent } from \"../ui/document.tsx\";\nimport {\n snapshotCardBody,\n snapshotCardHead,\n snapshotCardMeta,\n snapshotGrid,\n} from \"../ui/styles/review.ts\";\nimport type { SnapshotGroup } from \"./build-diff-model.ts\";\nimport { snapshotImageUrl, snapshotVariant } from \"./build-diff-model.ts\";\nimport { LinkedUsage, type LinkedUsageItem } from \"./build-diff-usage.tsx\";\nimport { ComponentBuilds, StoriesTab } from \"./component-stories.tsx\";\nimport { DesignReferences, DesignStrip, type DesignItem } from \"./design-links.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype Rendered = HtmlEscapedString | Promise<HtmlEscapedString>;\n\n/** Tabs of the component page. */\nexport type ComponentTab = \"stories\" | \"states\" | \"history\" | \"design\" | \"linked\";\n\nconst TAB_LABELS: Record<ComponentTab, string> = {\n stories: \"Stories\",\n states: \"States\",\n history: \"History\",\n design: \"Design\",\n linked: \"Linked\",\n};\n\n/** The tabs a page offers: the catalog's two in both modes, the snapshot ones only with `testing`. */\nexport function componentTabs(testing: boolean): readonly ComponentTab[] {\n return testing ? [\"states\", \"stories\", \"history\", \"design\", \"linked\"] : [\"stories\", \"history\"];\n}\n\n/** What captured snapshots add to a component (`testing` only; absent until it was captured). */\nexport interface CapturedComponent {\n /** Newest build that has the file; its states fill the States/Design/Linked tabs. */\n shownBuild: Build;\n groups: SnapshotGroup[];\n history: ComponentHistoryRow[];\n presence: ComponentPresence;\n}\n\n/** Data behind the component page. */\nexport interface ComponentPageData {\n project: Project;\n /** Normalized story-file path (the page's identity). */\n importPath: string;\n /** Title of the newest snapshot, else the catalog's (retitles update in place). */\n title: string;\n tab: ComponentTab;\n /** Whether the instance has `testing`. */\n testing: boolean;\n /** The published Storybook's view of the file: stories, docs entries, build history. */\n catalog: ComponentCatalogView;\n captured: CapturedComponent | null;\n /** Story id the Stories tab previews (the first story when unset or unknown). */\n story: string | undefined;\n /**\n * Root the preview frame loads the Storybook from: the build's own URL when it is open to anyone\n * who reaches the app, a signed `/_s/<token>` URL for a private build (needs `testing` and a\n * `secret`), or null when no frame can load.\n */\n previewBase: string | null;\n design: DesignItem[];\n usage: LinkedUsageItem[];\n /** Story comments of the shown build; null when the project has review off. */\n review: ComponentReview | null;\n publishedBaseDomain?: string;\n}\n\n/** Component detail page: stories, states, history, design and linked packages of one story file. */\nexport function renderComponentPage(data: ComponentPageData): RenderedContent {\n const { project, importPath, title } = data;\n return (\n <DocumentLayout\n title={`${title} · Component`}\n nav={{\n active: \"library\",\n projectSlug: project.slug,\n projectName: project.name,\n trail: [{ label: title }],\n }}\n >\n <PageHeader\n title={title}\n description={<span class=\"mono\">{importPath}</span>}\n meta={<HeaderMeta data={data} />}\n />\n <PresenceNotice data={data} />\n <Tabs\n label=\"Component sections\"\n tabs={componentTabs(data.testing).map((tab) => ({\n label: TAB_LABELS[tab],\n href: `?tab=${tab}`,\n active: data.tab === tab,\n }))}\n />\n <ActiveTab data={data} />\n </DocumentLayout>\n );\n}\n\nfunction HeaderMeta(props: { data: ComponentPageData }): Rendered {\n const { captured, catalog } = props.data;\n const stories = catalog.entries.filter((entry) => entry.type === \"story\").length;\n const docs = catalog.entries.length - stories;\n const counts = `${stories} ${stories === 1 ? \"story\" : \"stories\"}${docs > 0 ? ` · ${docs} docs` : \"\"}`;\n if (!captured) {\n return <>{counts}</>;\n }\n const status = worstStatus(captured.groups.map((group) => group.primary.status));\n const modes = new Set(captured.groups.map((group) => group.modeKey)).size;\n return (\n <>\n <Badge tone={statusTone(status)} icon>\n {status}\n </Badge>{\" \"}\n {counts} · {modes} {modes === 1 ? \"mode\" : \"modes\"}\n </>\n );\n}\n\nfunction PresenceNotice(props: { data: ComponentPageData }): Rendered | null {\n const { data } = props;\n const urls = createUrlBuilder(\"/\", data.publishedBaseDomain);\n const presence = data.captured?.presence ?? data.catalog.presence;\n const { project } = data;\n if (presence.kind === \"moved\") {\n return (\n <Alert tone=\"warning\" title=\"This component moved\">\n <HStack>\n <span>The file now lives at</span>\n <Button variant=\"secondary\" size=\"sm\" href={urls.component(project.slug, presence.to)}>\n {presence.to}\n </Button>\n </HStack>\n </Alert>\n );\n }\n if (presence.kind === \"deleted\") {\n return (\n <Alert tone=\"warning\" title=\"Not in the latest build\">\n Last seen in{\" \"}\n <a href={urls.build(project.slug, presence.lastSeen.id)}>\n build {presence.lastSeen.gitSha.slice(0, 7)} ({presence.lastSeen.gitBranch})\n </a>\n . The states below are from that build.\n </Alert>\n );\n }\n return null;\n}\n\nfunction ActiveTab(props: { data: ComponentPageData }): Rendered {\n const { data } = props;\n if (data.tab === \"stories\") return <StoriesTab data={data} />;\n if (data.tab === \"history\") return <HistoryTab data={data} />;\n if (data.tab === \"design\") return <DesignTab design={data.design} />;\n if (data.tab === \"linked\") return <LinkedTab data={data} />;\n return <StatesTab data={data} />;\n}\n\nfunction StatesTab(props: { data: ComponentPageData }): Rendered {\n const { data } = props;\n const { project, captured } = data;\n if (!captured) {\n return (\n <EmptyState\n title=\"Not captured yet\"\n description=\"No capture has rendered this file's stories. Its stories are listed under Stories.\"\n />\n );\n }\n const { shownBuild } = captured;\n const urls = createUrlBuilder(\"/\", data.publishedBaseDomain);\n return (\n <div class={snapshotGrid}>\n {captured.groups.map((group): Rendered => (\n <div key={group.key} id={group.storyId}>\n <Card padded={false}>\n <div class={snapshotCardHead}>\n <strong>{group.primary.storyName}</strong>\n <span class={snapshotCardMeta}>\n <Badge tone={statusTone(group.primary.status)}>{group.primary.status}</Badge>{\" \"}\n {snapshotVariant(group.primary)}\n </span>\n </div>\n <div class={snapshotCardBody}>\n <a\n href={`${urls.buildDiff(project.slug, shownBuild.id)}?snapshot=${group.representative.id}`}\n >\n <Thumbnail\n src={snapshotImageUrl(project, shownBuild, group.primary, \"image\")}\n alt={`${group.primary.storyTitle} / ${group.primary.storyName}`}\n />\n </a>\n </div>\n </Card>\n </div>\n ))}\n </div>\n );\n}\n\nfunction HistoryTab(props: { data: ComponentPageData }): Rendered {\n const { data } = props;\n return (\n <VStack>\n {data.captured ? <CapturedHistory data={data} captured={data.captured} /> : null}\n <ComponentBuilds data={data} />\n </VStack>\n );\n}\n\nfunction CapturedHistory(props: {\n data: ComponentPageData;\n captured: CapturedComponent;\n}): Rendered {\n const { data, captured } = props;\n return (\n <Card>\n <SectionTitle>Captured builds</SectionTitle>\n <Table>\n <table>\n <thead>\n <tr>\n <th>Build</th>\n <th>Branch</th>\n <th>States changed</th>\n <th>Status</th>\n <th>\n <span class=\"visually-hidden\">Actions</span>\n </th>\n </tr>\n </thead>\n <tbody>\n {captured.history.map((row): Rendered => (\n <CapturedRow key={row.build.id} row={row} data={data} />\n ))}\n </tbody>\n </table>\n </Table>\n </Card>\n );\n}\n\nfunction CapturedRow(props: { row: ComponentHistoryRow; data: ComponentPageData }): Rendered {\n const { row, data } = props;\n const urls = createUrlBuilder(\"/\", data.publishedBaseDomain);\n const slug = data.project.slug;\n return (\n <tr>\n <td>\n <a href={urls.build(slug, row.build.id)}>{row.build.gitSha.slice(0, 7)}</a>\n </td>\n <td>{row.build.gitBranch}</td>\n <td>\n {row.changedStates} / {row.states}\n </td>\n <td>\n <Badge tone={statusTone(row.status)}>{row.status}</Badge>\n </td>\n <td>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n href={`${urls.buildDiff(slug, row.build.id)}?snapshot=${row.snapshotId}`}\n >\n Review\n </Button>\n </td>\n </tr>\n );\n}\n\nfunction DesignTab(props: { design: DesignItem[] }): Rendered {\n if (props.design.length === 0) {\n return (\n <EmptyState\n title=\"No design linked\"\n description=\"Declare a design link in the stories’ parameters, or map this file under Settings → Design.\"\n />\n );\n }\n return (\n <Card>\n <DesignStrip items={props.design} />\n <DesignReferences items={props.design} />\n </Card>\n );\n}\n\nfunction LinkedTab(props: { data: ComponentPageData }): Rendered {\n const { data } = props;\n if (data.usage.length === 0) {\n return (\n <EmptyState\n title=\"No linked packages\"\n description=\"This component's stories use no packages linked to another project in the latest build.\"\n />\n );\n }\n return (\n <Card>\n <Meta>Packages these stories use, linked to the projects that publish them.</Meta>\n <LinkedUsage usage={data.usage} publishedBaseDomain={data.publishedBaseDomain} />\n </Card>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,167 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { Button } from "../ui/buttons.mjs";
5
+ import { Badge, EmptyState, Meta } from "../ui/feedback.mjs";
6
+ import { Card, SectionTitle } from "../ui/layout.mjs";
7
+ import { HStack, VStack } from "../ui/stacks.mjs";
8
+ import { Table } from "../ui/table.mjs";
9
+ import { PreviewEmbed } from "../ui/preview-embed.mjs";
10
+ import { PREVIEW_TOKEN_REFRESH, isSignedPreviewBase } from "../routers/preview-base.mjs";
11
+ import { StoryComments } from "./review-comments.mjs";
12
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
13
+ import { createUrlBuilder } from "@storyshelf/core/urls";
14
+ // src/pages/component-stories.tsx
15
+ /** Stories tab: the selected story in a sandboxed preview, and every story and docs entry. */
16
+ function StoriesTab(props) {
17
+ const { data } = props;
18
+ const { entries } = data.catalog;
19
+ const selected = selectedStory(data);
20
+ if (entries.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
21
+ title: "No stories in this build",
22
+ description: "The build's Storybook index lists no stories for this file."
23
+ });
24
+ return /* @__PURE__ */ jsxs(VStack, { children: [
25
+ selected ? /* @__PURE__ */ jsx(PreviewCard, {
26
+ data,
27
+ story: selected
28
+ }) : null,
29
+ selected && data.review ? /* @__PURE__ */ jsx(StoryComments, {
30
+ project: data.project,
31
+ build: data.catalog.build,
32
+ storyId: selected.id,
33
+ comments: data.review.comments,
34
+ canComment: data.review.canComment
35
+ }) : null,
36
+ /* @__PURE__ */ jsx(Card, {
37
+ padded: false,
38
+ children: /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsxs("table", { children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
39
+ /* @__PURE__ */ jsx("th", { children: "Name" }),
40
+ /* @__PURE__ */ jsx("th", { children: "Kind" }),
41
+ /* @__PURE__ */ jsx("th", { children: "Tags" }),
42
+ /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx("span", {
43
+ class: "visually-hidden",
44
+ children: "Actions"
45
+ }) })
46
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: entries.map((entry) => /* @__PURE__ */ jsx(EntryRow, {
47
+ entry,
48
+ data,
49
+ selected: selected?.id
50
+ }, entry.id)) })] }) })
51
+ })
52
+ ] });
53
+ }
54
+ /** The story the preview shows: `?story=` when it names one, else the first. */
55
+ function selectedStory(data) {
56
+ const stories = data.catalog.entries.filter((entry) => entry.type === "story");
57
+ return stories.find((entry) => entry.id === data.story) ?? stories[0];
58
+ }
59
+ const previewPath = (story) => `/iframe.html?id=${encodeURIComponent(story.id)}&viewMode=story`;
60
+ /** A frame under a signed URL asks for a new token before the old one expires. */
61
+ function refreshOf(data, story) {
62
+ if (data.previewBase === null || !isSignedPreviewBase(data.previewBase)) return {};
63
+ return { refresh: {
64
+ url: `/api/v1/projects/${data.project.slug}/builds/${data.catalog.build.id}/preview-token`,
65
+ path: previewPath(story),
66
+ seconds: PREVIEW_TOKEN_REFRESH
67
+ } };
68
+ }
69
+ function PreviewCard(props) {
70
+ const { data, story } = props;
71
+ return /* @__PURE__ */ jsxs(Card, {
72
+ id: "preview",
73
+ children: [/* @__PURE__ */ jsx(SectionTitle, { children: story.name }), data.previewBase === null ? /* @__PURE__ */ jsx(Meta, { children: "This build needs a sign-in to view, so it cannot run in a frame here. Open it in Storybook instead." }) : /* @__PURE__ */ jsx(PreviewEmbed, {
74
+ src: `${data.previewBase}${previewPath(story)}`,
75
+ title: `${story.title} / ${story.name}`,
76
+ ...refreshOf(data, story)
77
+ })]
78
+ });
79
+ }
80
+ function EntryRow(props) {
81
+ const { entry, data } = props;
82
+ return /* @__PURE__ */ jsxs("tr", {
83
+ id: entry.id,
84
+ children: [
85
+ /* @__PURE__ */ jsx("td", { children: entry.name }),
86
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(Badge, {
87
+ tone: entry.type === "docs" ? "info" : "neutral",
88
+ children: entry.type
89
+ }) }),
90
+ /* @__PURE__ */ jsx("td", { children: entry.tags.join(", ") }),
91
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsxs(HStack, { children: [entry.type === "story" && entry.id !== props.selected ? /* @__PURE__ */ jsx(Button, {
92
+ variant: "ghost",
93
+ size: "sm",
94
+ icon: "image",
95
+ href: `?tab=stories&story=${encodeURIComponent(entry.id)}#preview`,
96
+ children: "Preview"
97
+ }) : null, /* @__PURE__ */ jsx(Button, {
98
+ variant: "ghost",
99
+ size: "sm",
100
+ icon: "external-link",
101
+ href: `${storybookBase(data)}/?storyId=${encodeURIComponent(entry.id)}${entry.type === "docs" ? "&viewMode=docs" : ""}`,
102
+ target: "_blank",
103
+ rel: "noopener",
104
+ children: "Open"
105
+ })] }) })
106
+ ]
107
+ });
108
+ }
109
+ /** Published builds that hold the component, and whether its stories changed since the build before. */
110
+ function ComponentBuilds(props) {
111
+ const { data } = props;
112
+ const urls = createUrlBuilder("/", data.publishedBaseDomain);
113
+ const { history } = data.catalog;
114
+ if (history.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
115
+ title: "No published builds",
116
+ description: "No catalogued build holds this file yet."
117
+ });
118
+ return /* @__PURE__ */ jsxs(Card, { children: [/* @__PURE__ */ jsx(SectionTitle, { children: "Published builds" }), /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsxs("table", { children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
119
+ /* @__PURE__ */ jsx("th", { children: "Build" }),
120
+ /* @__PURE__ */ jsx("th", { children: "Branch" }),
121
+ /* @__PURE__ */ jsx("th", { children: "Stories" }),
122
+ /* @__PURE__ */ jsx("th", { children: "Changes" })
123
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: history.map((row) => /* @__PURE__ */ jsxs("tr", { children: [
124
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("a", {
125
+ href: urls.build(data.project.slug, row.build.id),
126
+ children: row.build.gitSha.slice(0, 7)
127
+ }) }),
128
+ /* @__PURE__ */ jsx("td", { children: row.build.gitBranch }),
129
+ /* @__PURE__ */ jsx("td", { children: row.component.storyCount }),
130
+ /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(ChangeCell, { row }) })
131
+ ] }, row.build.id)) })] }) })] });
132
+ }
133
+ function ChangeCell(props) {
134
+ const { row } = props;
135
+ if (row.change === "new") return /* @__PURE__ */ jsx(Badge, {
136
+ tone: "info",
137
+ children: "new here"
138
+ });
139
+ if (row.change === "unchanged") return /* @__PURE__ */ jsx(Meta, {
140
+ as: "span",
141
+ children: "unchanged"
142
+ });
143
+ const parts = changeParts(row);
144
+ return /* @__PURE__ */ jsxs(HStack, { children: [/* @__PURE__ */ jsx(Badge, {
145
+ tone: "warning",
146
+ children: "changed"
147
+ }), parts ? /* @__PURE__ */ jsx(Meta, {
148
+ as: "span",
149
+ children: parts
150
+ }) : null] });
151
+ }
152
+ /** "2 added · 1 removed · 1 renamed", or empty when the counts are not known. */
153
+ function changeParts(row) {
154
+ const { changes } = row;
155
+ if (!changes) return "";
156
+ return [
157
+ changes.added > 0 ? `${changes.added} added` : "",
158
+ changes.removed > 0 ? `${changes.removed} removed` : "",
159
+ changes.renamed > 0 ? `${changes.renamed} renamed` : ""
160
+ ].filter((part) => part !== "").join(" · ");
161
+ }
162
+ function storybookBase(data) {
163
+ return createUrlBuilder("/", data.publishedBaseDomain).storybookBuild(data.project.slug, data.catalog.build.id);
164
+ }
165
+ export { ComponentBuilds, StoriesTab };
166
+
167
+ //# sourceMappingURL=component-stories.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-stories.mjs","names":[],"sources":["../../src/pages/component-stories.tsx"],"sourcesContent":["/** The catalog's tabs of the component page: its stories with a click-to-load preview, and its builds. */\nimport type { CatalogEntry } from \"@storyshelf/core/capture\";\nimport { createUrlBuilder } from \"@storyshelf/core/urls\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { CatalogHistoryRow } from \"../routers/component-catalog.handlers.ts\";\nimport { isSignedPreviewBase, PREVIEW_TOKEN_REFRESH } from \"../routers/preview-base.ts\";\nimport {\n Badge,\n Button,\n Card,\n EmptyState,\n HStack,\n Meta,\n PreviewEmbed,\n SectionTitle,\n Table,\n VStack,\n} from \"../ui/components.tsx\";\nimport type { ComponentPageData } from \"./component-detail.tsx\";\nimport { StoryComments } from \"./review-comments.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype Rendered = HtmlEscapedString | Promise<HtmlEscapedString>;\n\n/** Stories tab: the selected story in a sandboxed preview, and every story and docs entry. */\nexport function StoriesTab(props: { data: ComponentPageData }): Rendered {\n const { data } = props;\n const { entries } = data.catalog;\n const selected = selectedStory(data);\n if (entries.length === 0) {\n return (\n <EmptyState\n title=\"No stories in this build\"\n description=\"The build's Storybook index lists no stories for this file.\"\n />\n );\n }\n return (\n <VStack>\n {selected ? <PreviewCard data={data} story={selected} /> : null}\n {selected && data.review ? (\n <StoryComments\n project={data.project}\n build={data.catalog.build}\n storyId={selected.id}\n comments={data.review.comments}\n canComment={data.review.canComment}\n />\n ) : null}\n <Card padded={false}>\n <Table>\n <table>\n <thead>\n <tr>\n <th>Name</th>\n <th>Kind</th>\n <th>Tags</th>\n <th>\n <span class=\"visually-hidden\">Actions</span>\n </th>\n </tr>\n </thead>\n <tbody>\n {entries.map((entry): Rendered => (\n <EntryRow key={entry.id} entry={entry} data={data} selected={selected?.id} />\n ))}\n </tbody>\n </table>\n </Table>\n </Card>\n </VStack>\n );\n}\n\n/** The story the preview shows: `?story=` when it names one, else the first. */\nfunction selectedStory(data: ComponentPageData): CatalogEntry | undefined {\n const stories = data.catalog.entries.filter((entry) => entry.type === \"story\");\n return stories.find((entry) => entry.id === data.story) ?? stories[0];\n}\n\nconst previewPath = (story: CatalogEntry): string =>\n `/iframe.html?id=${encodeURIComponent(story.id)}&viewMode=story`;\n\n/** A frame under a signed URL asks for a new token before the old one expires. */\nfunction refreshOf(\n data: ComponentPageData,\n story: CatalogEntry,\n): { refresh?: { url: string; path: string; seconds: number } } {\n if (data.previewBase === null || !isSignedPreviewBase(data.previewBase)) {\n return {};\n }\n return {\n refresh: {\n url: `/api/v1/projects/${data.project.slug}/builds/${data.catalog.build.id}/preview-token`,\n path: previewPath(story),\n seconds: PREVIEW_TOKEN_REFRESH,\n },\n };\n}\n\nfunction PreviewCard(props: { data: ComponentPageData; story: CatalogEntry }): Rendered {\n const { data, story } = props;\n return (\n <Card id=\"preview\">\n <SectionTitle>{story.name}</SectionTitle>\n {data.previewBase === null ? (\n <Meta>\n This build needs a sign-in to view, so it cannot run in a frame here. Open it in Storybook\n instead.\n </Meta>\n ) : (\n <PreviewEmbed\n src={`${data.previewBase}${previewPath(story)}`}\n title={`${story.title} / ${story.name}`}\n {...refreshOf(data, story)}\n />\n )}\n </Card>\n );\n}\n\nfunction EntryRow(props: {\n entry: CatalogEntry;\n data: ComponentPageData;\n selected: string | undefined;\n}): Rendered {\n const { entry, data } = props;\n return (\n <tr id={entry.id}>\n <td>{entry.name}</td>\n <td>\n <Badge tone={entry.type === \"docs\" ? \"info\" : \"neutral\"}>{entry.type}</Badge>\n </td>\n <td>{entry.tags.join(\", \")}</td>\n <td>\n <HStack>\n {entry.type === \"story\" && entry.id !== props.selected ? (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n icon=\"image\"\n href={`?tab=stories&story=${encodeURIComponent(entry.id)}#preview`}\n >\n Preview\n </Button>\n ) : null}\n <Button\n variant=\"ghost\"\n size=\"sm\"\n icon=\"external-link\"\n href={`${storybookBase(data)}/?storyId=${encodeURIComponent(entry.id)}${entry.type === \"docs\" ? \"&viewMode=docs\" : \"\"}`}\n target=\"_blank\"\n rel=\"noopener\"\n >\n Open\n </Button>\n </HStack>\n </td>\n </tr>\n );\n}\n\n/** Published builds that hold the component, and whether its stories changed since the build before. */\nexport function ComponentBuilds(props: { data: ComponentPageData }): Rendered {\n const { data } = props;\n const urls = createUrlBuilder(\"/\", data.publishedBaseDomain);\n const { history } = data.catalog;\n if (history.length === 0) {\n return (\n <EmptyState\n title=\"No published builds\"\n description=\"No catalogued build holds this file yet.\"\n />\n );\n }\n return (\n <Card>\n <SectionTitle>Published builds</SectionTitle>\n <Table>\n <table>\n <thead>\n <tr>\n <th>Build</th>\n <th>Branch</th>\n <th>Stories</th>\n <th>Changes</th>\n </tr>\n </thead>\n <tbody>\n {history.map((row): Rendered => (\n <tr key={row.build.id}>\n <td>\n <a href={urls.build(data.project.slug, row.build.id)}>\n {row.build.gitSha.slice(0, 7)}\n </a>\n </td>\n <td>{row.build.gitBranch}</td>\n <td>{row.component.storyCount}</td>\n <td>\n <ChangeCell row={row} />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </Table>\n </Card>\n );\n}\n\nfunction ChangeCell(props: { row: CatalogHistoryRow }): Rendered {\n const { row } = props;\n if (row.change === \"new\") {\n return <Badge tone=\"info\">new here</Badge>;\n }\n if (row.change === \"unchanged\") {\n return <Meta as=\"span\">unchanged</Meta>;\n }\n const parts = changeParts(row);\n return (\n <HStack>\n <Badge tone=\"warning\">changed</Badge>\n {parts ? <Meta as=\"span\">{parts}</Meta> : null}\n </HStack>\n );\n}\n\n/** \"2 added · 1 removed · 1 renamed\", or empty when the counts are not known. */\nfunction changeParts(row: CatalogHistoryRow): string {\n const { changes } = row;\n if (!changes) {\n return \"\";\n }\n return [\n changes.added > 0 ? `${changes.added} added` : \"\",\n changes.removed > 0 ? `${changes.removed} removed` : \"\",\n changes.renamed > 0 ? `${changes.renamed} renamed` : \"\",\n ]\n .filter((part) => part !== \"\")\n .join(\" · \");\n}\n\nfunction storybookBase(data: ComponentPageData): string {\n return createUrlBuilder(\"/\", data.publishedBaseDomain).storybookBuild(\n data.project.slug,\n data.catalog.build.id,\n );\n}\n"],"mappings":"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"}