@storyshelf/app 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (364) hide show
  1. package/README.md +42 -4
  2. package/dist/app-types.d.mts +1 -3
  3. package/dist/asset-manifest.mjs +13 -5
  4. package/dist/asset-manifest.mjs.map +1 -1
  5. package/dist/assets/{htmx.min.js → htmx.mjs} +10 -1
  6. package/dist/assets/htmx.mjs.map +1 -0
  7. package/dist/assets/mark.mjs +10 -0
  8. package/dist/assets/mark.mjs.map +1 -0
  9. package/dist/boot-tasks.mjs +80 -0
  10. package/dist/boot-tasks.mjs.map +1 -0
  11. package/dist/build-state-flags.mjs +18 -0
  12. package/dist/build-state-flags.mjs.map +1 -0
  13. package/dist/build-state-setup.mjs +75 -0
  14. package/dist/build-state-setup.mjs.map +1 -0
  15. package/dist/capture-alerts.mjs +46 -0
  16. package/dist/capture-alerts.mjs.map +1 -0
  17. package/dist/capture-process.mjs +59 -0
  18. package/dist/capture-process.mjs.map +1 -0
  19. package/dist/capture-setup.mjs +95 -117
  20. package/dist/capture-setup.mjs.map +1 -1
  21. package/dist/connections.mjs +34 -0
  22. package/dist/connections.mjs.map +1 -0
  23. package/dist/consumer-setup.mjs +58 -0
  24. package/dist/consumer-setup.mjs.map +1 -0
  25. package/dist/credentials.mjs +3 -2
  26. package/dist/credentials.mjs.map +1 -1
  27. package/dist/git-status.mjs +46 -0
  28. package/dist/git-status.mjs.map +1 -0
  29. package/dist/hidden-data.mjs +24 -0
  30. package/dist/hidden-data.mjs.map +1 -0
  31. package/dist/index.d.mts +3 -3
  32. package/dist/index.mjs +202 -124
  33. package/dist/index.mjs.map +1 -1
  34. package/dist/insights/budget.mjs +49 -0
  35. package/dist/insights/budget.mjs.map +1 -0
  36. package/dist/insights/deps.mjs +21 -0
  37. package/dist/insights/deps.mjs.map +1 -0
  38. package/dist/insights/errors.mjs +22 -0
  39. package/dist/insights/errors.mjs.map +1 -0
  40. package/dist/insights/health-panel.mjs +40 -0
  41. package/dist/insights/health-panel.mjs.map +1 -0
  42. package/dist/insights/job.mjs +103 -0
  43. package/dist/insights/job.mjs.map +1 -0
  44. package/dist/insights/load-health.mjs +59 -0
  45. package/dist/insights/load-health.mjs.map +1 -0
  46. package/dist/insights/load-triage.mjs +98 -0
  47. package/dist/insights/load-triage.mjs.map +1 -0
  48. package/dist/insights/panel.mjs +28 -0
  49. package/dist/insights/panel.mjs.map +1 -0
  50. package/dist/insights/request.mjs +113 -0
  51. package/dist/insights/request.mjs.map +1 -0
  52. package/dist/insights/stale.mjs +21 -0
  53. package/dist/insights/stale.mjs.map +1 -0
  54. package/dist/insights/usage-report.mjs +60 -0
  55. package/dist/insights/usage-report.mjs.map +1 -0
  56. package/dist/insights/view.mjs +48 -0
  57. package/dist/insights/view.mjs.map +1 -0
  58. package/dist/job-canceller.mjs +14 -0
  59. package/dist/job-canceller.mjs.map +1 -0
  60. package/dist/job-queue.mjs +51 -0
  61. package/dist/job-queue.mjs.map +1 -0
  62. package/dist/jobs-setup.mjs +132 -0
  63. package/dist/jobs-setup.mjs.map +1 -0
  64. package/dist/lifecycle.mjs +109 -19
  65. package/dist/lifecycle.mjs.map +1 -1
  66. package/dist/middleware/auth-gate.mjs +4 -2
  67. package/dist/middleware/auth-gate.mjs.map +1 -1
  68. package/dist/middleware/rate-limit.mjs +41 -33
  69. package/dist/middleware/rate-limit.mjs.map +1 -1
  70. package/dist/middleware/request-log.mjs +3 -2
  71. package/dist/middleware/request-log.mjs.map +1 -1
  72. package/dist/middleware/require-review.mjs +51 -0
  73. package/dist/middleware/require-review.mjs.map +1 -0
  74. package/dist/middleware/require-testing.mjs +41 -0
  75. package/dist/middleware/require-testing.mjs.map +1 -0
  76. package/dist/middleware/setup-gate.mjs.map +1 -1
  77. package/dist/middleware/store-scope.mjs +12 -2
  78. package/dist/middleware/store-scope.mjs.map +1 -1
  79. package/dist/notifications-pause.mjs +33 -0
  80. package/dist/notifications-pause.mjs.map +1 -0
  81. package/dist/notify.d.mts +2 -1
  82. package/dist/notify.mjs +78 -39
  83. package/dist/notify.mjs.map +1 -1
  84. package/dist/pages/a11y-violations.mjs +116 -0
  85. package/dist/pages/a11y-violations.mjs.map +1 -0
  86. package/dist/pages/admin-ai.mjs +78 -0
  87. package/dist/pages/admin-ai.mjs.map +1 -0
  88. package/dist/pages/admin-system-origin.mjs +123 -0
  89. package/dist/pages/admin-system-origin.mjs.map +1 -0
  90. package/dist/pages/admin-system-targets.mjs +53 -0
  91. package/dist/pages/admin-system-targets.mjs.map +1 -0
  92. package/dist/pages/admin-system.mjs +63 -13
  93. package/dist/pages/admin-system.mjs.map +1 -1
  94. package/dist/pages/build-detail.mjs +104 -76
  95. package/dist/pages/build-detail.mjs.map +1 -1
  96. package/dist/pages/build-diff-actions.mjs +87 -33
  97. package/dist/pages/build-diff-actions.mjs.map +1 -1
  98. package/dist/pages/build-diff-comments.mjs +16 -14
  99. package/dist/pages/build-diff-comments.mjs.map +1 -1
  100. package/dist/pages/build-diff-environment.mjs +82 -0
  101. package/dist/pages/build-diff-environment.mjs.map +1 -0
  102. package/dist/pages/build-diff-header.mjs +22 -4
  103. package/dist/pages/build-diff-header.mjs.map +1 -1
  104. package/dist/pages/build-diff-insight.mjs +113 -0
  105. package/dist/pages/build-diff-insight.mjs.map +1 -0
  106. package/dist/pages/build-diff-model.mjs +55 -10
  107. package/dist/pages/build-diff-model.mjs.map +1 -1
  108. package/dist/pages/build-diff-nav.mjs +28 -12
  109. package/dist/pages/build-diff-nav.mjs.map +1 -1
  110. package/dist/pages/build-diff-shortcuts.mjs +5 -1
  111. package/dist/pages/build-diff-shortcuts.mjs.map +1 -1
  112. package/dist/pages/build-diff-usage.mjs +63 -0
  113. package/dist/pages/build-diff-usage.mjs.map +1 -0
  114. package/dist/pages/build-diff-viewer.mjs +62 -5
  115. package/dist/pages/build-diff-viewer.mjs.map +1 -1
  116. package/dist/pages/build-diff.mjs +34 -13
  117. package/dist/pages/build-diff.mjs.map +1 -1
  118. package/dist/pages/build-tests.mjs +74 -0
  119. package/dist/pages/build-tests.mjs.map +1 -0
  120. package/dist/pages/component-detail.mjs +249 -0
  121. package/dist/pages/component-detail.mjs.map +1 -0
  122. package/dist/pages/component-stories.mjs +168 -0
  123. package/dist/pages/component-stories.mjs.map +1 -0
  124. package/dist/pages/compute-jobs.mjs +13 -26
  125. package/dist/pages/compute-jobs.mjs.map +1 -1
  126. package/dist/pages/design-links.mjs +182 -0
  127. package/dist/pages/design-links.mjs.map +1 -0
  128. package/dist/pages/environment-change.mjs +53 -0
  129. package/dist/pages/environment-change.mjs.map +1 -0
  130. package/dist/pages/health-panel.mjs +126 -0
  131. package/dist/pages/health-panel.mjs.map +1 -0
  132. package/dist/pages/library-grid.mjs +105 -0
  133. package/dist/pages/library-grid.mjs.map +1 -0
  134. package/dist/pages/library-model.mjs +47 -0
  135. package/dist/pages/library-model.mjs.map +1 -0
  136. package/dist/pages/library-snapshots.mjs +130 -0
  137. package/dist/pages/library-snapshots.mjs.map +1 -0
  138. package/dist/pages/library.mjs +112 -218
  139. package/dist/pages/library.mjs.map +1 -1
  140. package/dist/pages/notifications-paused.mjs +22 -0
  141. package/dist/pages/notifications-paused.mjs.map +1 -0
  142. package/dist/pages/profile.mjs +53 -48
  143. package/dist/pages/profile.mjs.map +1 -1
  144. package/dist/pages/project-builds-table.mjs +2 -2
  145. package/dist/pages/project-builds-table.mjs.map +1 -1
  146. package/dist/pages/project-builds.mjs +51 -21
  147. package/dist/pages/project-builds.mjs.map +1 -1
  148. package/dist/pages/project-settings.mjs +51 -12
  149. package/dist/pages/project-settings.mjs.map +1 -1
  150. package/dist/pages/projects-card.mjs +14 -1
  151. package/dist/pages/projects-card.mjs.map +1 -1
  152. package/dist/pages/projects.mjs +6 -2
  153. package/dist/pages/projects.mjs.map +1 -1
  154. package/dist/pages/review-changeset.mjs +116 -0
  155. package/dist/pages/review-changeset.mjs.map +1 -0
  156. package/dist/pages/review-comments.mjs +103 -0
  157. package/dist/pages/review-comments.mjs.map +1 -0
  158. package/dist/pages/review-verdict.mjs +102 -0
  159. package/dist/pages/review-verdict.mjs.map +1 -0
  160. package/dist/pages/settings-ai.mjs +69 -0
  161. package/dist/pages/settings-ai.mjs.map +1 -0
  162. package/dist/pages/settings-capture.mjs +124 -0
  163. package/dist/pages/settings-capture.mjs.map +1 -0
  164. package/dist/pages/settings-design-credential.mjs +99 -0
  165. package/dist/pages/settings-design-credential.mjs.map +1 -0
  166. package/dist/pages/settings-design-links.mjs +179 -0
  167. package/dist/pages/settings-design-links.mjs.map +1 -0
  168. package/dist/pages/settings-design.mjs +48 -0
  169. package/dist/pages/settings-design.mjs.map +1 -0
  170. package/dist/pages/settings-general.mjs +46 -21
  171. package/dist/pages/settings-general.mjs.map +1 -1
  172. package/dist/pages/settings-labels.mjs +43 -2
  173. package/dist/pages/settings-labels.mjs.map +1 -1
  174. package/dist/pages/settings-links.mjs +112 -0
  175. package/dist/pages/settings-links.mjs.map +1 -0
  176. package/dist/pages/settings-notifications-git.mjs +128 -0
  177. package/dist/pages/settings-notifications-git.mjs.map +1 -0
  178. package/dist/pages/settings-notifications.mjs +79 -42
  179. package/dist/pages/settings-notifications.mjs.map +1 -1
  180. package/dist/pages/settings-tests.mjs +33 -2
  181. package/dist/pages/settings-tests.mjs.map +1 -1
  182. package/dist/pages/target-choices.mjs +39 -0
  183. package/dist/pages/target-choices.mjs.map +1 -0
  184. package/dist/publish-setup.mjs +89 -0
  185. package/dist/publish-setup.mjs.map +1 -0
  186. package/dist/retention-timer.mjs +2 -1
  187. package/dist/retention-timer.mjs.map +1 -1
  188. package/dist/review-events.mjs +48 -0
  189. package/dist/review-events.mjs.map +1 -0
  190. package/dist/routers/admin-pages.mjs +45 -3
  191. package/dist/routers/admin-pages.mjs.map +1 -1
  192. package/dist/routers/admin-public-origin.mjs +85 -0
  193. package/dist/routers/admin-public-origin.mjs.map +1 -0
  194. package/dist/routers/assets.mjs +9 -2
  195. package/dist/routers/assets.mjs.map +1 -1
  196. package/dist/routers/auth.mjs +2 -2
  197. package/dist/routers/auth.mjs.map +1 -1
  198. package/dist/routers/baseline-guard.mjs +2 -2
  199. package/dist/routers/baseline-guard.mjs.map +1 -1
  200. package/dist/routers/baselines.handlers.mjs +64 -0
  201. package/dist/routers/baselines.handlers.mjs.map +1 -0
  202. package/dist/routers/baselines.mjs +125 -0
  203. package/dist/routers/baselines.mjs.map +1 -0
  204. package/dist/routers/build-cancel.mjs +68 -0
  205. package/dist/routers/build-cancel.mjs.map +1 -0
  206. package/dist/routers/build-changeset.mjs +70 -0
  207. package/dist/routers/build-changeset.mjs.map +1 -0
  208. package/dist/routers/build-reviews.handlers.mjs +86 -0
  209. package/dist/routers/build-reviews.handlers.mjs.map +1 -0
  210. package/dist/routers/build-reviews.mjs +74 -0
  211. package/dist/routers/build-reviews.mjs.map +1 -0
  212. package/dist/routers/builds.handlers.mjs +46 -55
  213. package/dist/routers/builds.handlers.mjs.map +1 -1
  214. package/dist/routers/builds.mjs +71 -17
  215. package/dist/routers/builds.mjs.map +1 -1
  216. package/dist/routers/capture-config.mjs +35 -0
  217. package/dist/routers/capture-config.mjs.map +1 -0
  218. package/dist/routers/comments.mjs +41 -15
  219. package/dist/routers/comments.mjs.map +1 -1
  220. package/dist/routers/component-catalog.handlers.mjs +71 -0
  221. package/dist/routers/component-catalog.handlers.mjs.map +1 -0
  222. package/dist/routers/component.handlers.mjs +121 -0
  223. package/dist/routers/component.handlers.mjs.map +1 -0
  224. package/dist/routers/connections.mjs +118 -0
  225. package/dist/routers/connections.mjs.map +1 -0
  226. package/dist/routers/design-connect.handlers.mjs +44 -0
  227. package/dist/routers/design-connect.handlers.mjs.map +1 -0
  228. package/dist/routers/design.handlers.mjs +114 -0
  229. package/dist/routers/design.handlers.mjs.map +1 -0
  230. package/dist/routers/health-report.mjs +2 -3
  231. package/dist/routers/health-report.mjs.map +1 -1
  232. package/dist/routers/health.mjs +4 -3
  233. package/dist/routers/health.mjs.map +1 -1
  234. package/dist/routers/helpers.mjs +19 -3
  235. package/dist/routers/helpers.mjs.map +1 -1
  236. package/dist/routers/insights-ui.mjs +64 -0
  237. package/dist/routers/insights-ui.mjs.map +1 -0
  238. package/dist/routers/insights.handlers.mjs +45 -0
  239. package/dist/routers/insights.handlers.mjs.map +1 -0
  240. package/dist/routers/insights.mjs +189 -0
  241. package/dist/routers/insights.mjs.map +1 -0
  242. package/dist/routers/links.handlers.mjs +80 -0
  243. package/dist/routers/links.handlers.mjs.map +1 -0
  244. package/dist/routers/links.mjs +139 -0
  245. package/dist/routers/links.mjs.map +1 -0
  246. package/dist/routers/media.mjs +18 -4
  247. package/dist/routers/media.mjs.map +1 -1
  248. package/dist/routers/members.mjs +2 -1
  249. package/dist/routers/members.mjs.map +1 -1
  250. package/dist/routers/notification-channels.mjs +17 -23
  251. package/dist/routers/notification-channels.mjs.map +1 -1
  252. package/dist/routers/oauth-state.mjs +45 -0
  253. package/dist/routers/oauth-state.mjs.map +1 -0
  254. package/dist/routers/preview-base.mjs +38 -0
  255. package/dist/routers/preview-base.mjs.map +1 -0
  256. package/dist/routers/preview-token.mjs +51 -0
  257. package/dist/routers/preview-token.mjs.map +1 -0
  258. package/dist/routers/profile.mjs +4 -2
  259. package/dist/routers/profile.mjs.map +1 -1
  260. package/dist/routers/projects.mjs +17 -1
  261. package/dist/routers/projects.mjs.map +1 -1
  262. package/dist/routers/publish-upload.mjs +78 -0
  263. package/dist/routers/publish-upload.mjs.map +1 -0
  264. package/dist/routers/published-access.mjs +52 -0
  265. package/dist/routers/published-access.mjs.map +1 -0
  266. package/dist/routers/published-files.mjs +52 -0
  267. package/dist/routers/published-files.mjs.map +1 -0
  268. package/dist/routers/review-forms.mjs +72 -0
  269. package/dist/routers/review-forms.mjs.map +1 -0
  270. package/dist/routers/review-section.handlers.mjs +85 -0
  271. package/dist/routers/review-section.handlers.mjs.map +1 -0
  272. package/dist/routers/review-switch-off.mjs +29 -0
  273. package/dist/routers/review-switch-off.mjs.map +1 -0
  274. package/dist/routers/schemas.mjs +140 -22
  275. package/dist/routers/schemas.mjs.map +1 -1
  276. package/dist/routers/scoped-models.mjs +22 -0
  277. package/dist/routers/scoped-models.mjs.map +1 -0
  278. package/dist/routers/settings-ai.mjs +35 -0
  279. package/dist/routers/settings-ai.mjs.map +1 -0
  280. package/dist/routers/settings-capture.mjs +77 -0
  281. package/dist/routers/settings-capture.mjs.map +1 -0
  282. package/dist/routers/settings-design.mjs +103 -0
  283. package/dist/routers/settings-design.mjs.map +1 -0
  284. package/dist/routers/settings-general.mjs +55 -10
  285. package/dist/routers/settings-general.mjs.map +1 -1
  286. package/dist/routers/settings-git-channel.mjs +77 -0
  287. package/dist/routers/settings-git-channel.mjs.map +1 -0
  288. package/dist/routers/settings-labels.mjs +27 -1
  289. package/dist/routers/settings-labels.mjs.map +1 -1
  290. package/dist/routers/settings-links.mjs +63 -0
  291. package/dist/routers/settings-links.mjs.map +1 -0
  292. package/dist/routers/settings-members.mjs +4 -7
  293. package/dist/routers/settings-members.mjs.map +1 -1
  294. package/dist/routers/settings-notifications.mjs +15 -10
  295. package/dist/routers/settings-notifications.mjs.map +1 -1
  296. package/dist/routers/settings-tokens.mjs +2 -5
  297. package/dist/routers/settings-tokens.mjs.map +1 -1
  298. package/dist/routers/settings-webhooks.mjs +7 -24
  299. package/dist/routers/settings-webhooks.mjs.map +1 -1
  300. package/dist/routers/settings.handlers.mjs +107 -26
  301. package/dist/routers/settings.handlers.mjs.map +1 -1
  302. package/dist/routers/settings.mjs +16 -4
  303. package/dist/routers/settings.mjs.map +1 -1
  304. package/dist/routers/signed-storybook.mjs +53 -0
  305. package/dist/routers/signed-storybook.mjs.map +1 -0
  306. package/dist/routers/snapshot-groups.mjs +63 -0
  307. package/dist/routers/snapshot-groups.mjs.map +1 -0
  308. package/dist/routers/snapshots.mjs +103 -5
  309. package/dist/routers/snapshots.mjs.map +1 -1
  310. package/dist/routers/storybook.mjs +37 -88
  311. package/dist/routers/storybook.mjs.map +1 -1
  312. package/dist/routers/tokens.mjs +2 -5
  313. package/dist/routers/tokens.mjs.map +1 -1
  314. package/dist/routers/ui.mjs +34 -5
  315. package/dist/routers/ui.mjs.map +1 -1
  316. package/dist/routers/webhooks.mjs +5 -11
  317. package/dist/routers/webhooks.mjs.map +1 -1
  318. package/dist/routers/well-known.mjs +11 -4
  319. package/dist/routers/well-known.mjs.map +1 -1
  320. package/dist/routes/api-routes.mjs +93 -0
  321. package/dist/routes/api-routes.mjs.map +1 -0
  322. package/dist/routes/classification.mjs +38 -0
  323. package/dist/routes/classification.mjs.map +1 -0
  324. package/dist/routes/openapi-classes.mjs +22 -0
  325. package/dist/routes/openapi-classes.mjs.map +1 -0
  326. package/dist/routes/page-routes.mjs +104 -0
  327. package/dist/routes/page-routes.mjs.map +1 -0
  328. package/dist/runtime.mjs +18 -13
  329. package/dist/runtime.mjs.map +1 -1
  330. package/dist/serverless.mjs +5 -0
  331. package/dist/store.mjs +19 -1
  332. package/dist/store.mjs.map +1 -1
  333. package/dist/targets.mjs +46 -0
  334. package/dist/targets.mjs.map +1 -0
  335. package/dist/ui/buttons.mjs +16 -3
  336. package/dist/ui/buttons.mjs.map +1 -1
  337. package/dist/ui/design-reference.mjs +97 -0
  338. package/dist/ui/design-reference.mjs.map +1 -0
  339. package/dist/ui/document.mjs +1 -2
  340. package/dist/ui/document.mjs.map +1 -1
  341. package/dist/ui/forms.mjs +6 -5
  342. package/dist/ui/forms.mjs.map +1 -1
  343. package/dist/ui/icons/paths.mjs +1 -0
  344. package/dist/ui/icons/paths.mjs.map +1 -1
  345. package/dist/ui/preview-embed.mjs +82 -0
  346. package/dist/ui/preview-embed.mjs.map +1 -0
  347. package/dist/ui/shell/client-script.mjs +85 -0
  348. package/dist/ui/shell/client-script.mjs.map +1 -1
  349. package/dist/ui/shell/nav.mjs +12 -8
  350. package/dist/ui/shell/nav.mjs.map +1 -1
  351. package/dist/ui/shell/review-script.mjs +8 -0
  352. package/dist/ui/shell/review-script.mjs.map +1 -1
  353. package/dist/ui/shell/sidebar.mjs +2 -2
  354. package/dist/ui/shell/sidebar.mjs.map +1 -1
  355. package/package.json +11 -6
  356. package/dist/asset-paths.mjs +0 -23
  357. package/dist/asset-paths.mjs.map +0 -1
  358. package/dist/assets/mark.svg +0 -1
  359. package/dist/pages/settings-status.mjs +0 -137
  360. package/dist/pages/settings-status.mjs.map +0 -1
  361. package/dist/routers/settings-status.mjs +0 -77
  362. package/dist/routers/settings-status.mjs.map +0 -1
  363. package/dist/routers/status-configs.mjs +0 -108
  364. package/dist/routers/status-configs.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"build-diff-viewer.mjs","names":[],"sources":["../../src/pages/build-diff-viewer.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { Badge, CompareStage, VStack, statusTone } from \"../ui/components.tsx\";\nimport { viewerHead, viewerMeta, viewerTitle } from \"../ui/styles/review-layout.ts\";\nimport { diffPercent, snapshotImageUrl } from \"./build-diff-model.ts\";\nimport { StaleSnapshotNotice } from \"./build-diff-stale.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Viewer props for the selected snapshot. */\nexport interface DiffViewerProps {\n project: Project;\n build: Build;\n selected: Snapshot;\n hasBaseline: Record<string, boolean>;\n /** Set when the baseline changed after this snapshot's diff was computed. */\n drifted?: \"stale\" | \"removed\";\n}\n\n/** Why a snapshot has no diff image, phrased for the empty pane. */\nfunction diffEmptyText(status: string): string {\n return status === \"unchanged\" || status === \"approved\"\n ? \"No diff — within threshold\"\n : \"No diff yet\";\n}\n\n/** Story heading plus the comparison stage for the selected snapshot. */\nexport function DiffViewer(props: DiffViewerProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, selected, hasBaseline, drifted } = props;\n const percent = diffPercent(selected);\n return (\n <VStack>\n <div class={viewerHead}>\n <h2 class={viewerTitle}>\n {selected.storyTitle} / {selected.storyName}\n </h2>\n <p class={viewerMeta}>\n <Badge tone={statusTone(selected.status)} icon>\n {selected.status}\n </Badge>\n <span>\n {selected.viewportName} · {selected.viewportWidth}×{selected.viewportHeight}\n </span>\n {selected.diffPixels === null || selected.diffPixels === undefined ? null : (\n <span class=\"mono\">{selected.diffPixels} px</span>\n )}\n {percent ? <span class=\"mono\">{percent}</span> : null}\n </p>\n </div>\n {drifted ? <StaleSnapshotNotice status={drifted} /> : null}\n <CompareStage\n baselineSrc={\n hasBaseline[selected.id] ? snapshotImageUrl(project, build, selected, \"baseline\") : null\n }\n currentSrc={snapshotImageUrl(project, build, selected, \"image\")}\n diffSrc={selected.diffPath ? snapshotImageUrl(project, build, selected, \"diff\") : null}\n subject={`${selected.storyTitle} / ${selected.storyName}`}\n diffEmpty={diffEmptyText(selected.status)}\n diffMeta={percent ?? undefined}\n />\n </VStack>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,SAAS,cAAc,QAAwB;CAC7C,OAAO,WAAW,eAAe,WAAW,aACxC,+BACA;AACN;;AAGA,SAAgB,WAAW,OAAwE;CACjG,MAAM,EAAE,SAAS,OAAO,UAAU,aAAa,YAAY;CAC3D,MAAM,UAAU,YAAY,QAAQ;CACpC,OACE,qBAAC,QAAD,EAAA,UAAA;EACE,qBAAC,OAAD;GAAK,OAAO;GAAZ,UAAA,CACE,qBAAC,MAAD;IAAI,OAAO;IAAX,UAAA;KACG,SAAS;KAAW;KAAI,SAAS;IAChC;GACJ,CAAA,GAAA,qBAAC,KAAD;IAAG,OAAO;IAAV,UAAA;KACE,oBAAC,OAAD;MAAO,MAAM,WAAW,SAAS,MAAM;MAAG,MAAA;MACvC,UAAA,SAAS;KACL,CAAA;KACP,qBAAC,QAAD,EAAA,UAAA;MACG,SAAS;MAAa;MAAI,SAAS;MAAc;MAAE,SAAS;KACzD,EAAA,CAAA;KACL,SAAS,eAAe,QAAQ,SAAS,eAAe,KAAA,IAAY,OACnE,qBAAC,QAAD;MAAM,OAAM;MAAZ,UAAA,CAAoB,SAAS,YAAW,KAAS;;KAElD,UAAU,oBAAC,QAAD;MAAM,OAAM;MAAQ,UAAA;KAAc,CAAA,IAAI;IAChD;GACA,CAAA,CAAA;;EACJ,UAAU,oBAAC,qBAAD,EAAqB,QAAQ,QAAU,CAAA,IAAI;EACtD,oBAAC,cAAD;GACE,aACE,YAAY,SAAS,MAAM,iBAAiB,SAAS,OAAO,UAAU,UAAU,IAAI;GAEtF,YAAY,iBAAiB,SAAS,OAAO,UAAU,OAAO;GAC9D,SAAS,SAAS,WAAW,iBAAiB,SAAS,OAAO,UAAU,MAAM,IAAI;GAClF,SAAS,GAAG,SAAS,WAAW,KAAK,SAAS;GAC9C,WAAW,cAAc,SAAS,MAAM;GACxC,UAAU,WAAW,KAAA;EACtB,CAAA;CACK,EAAA,CAAA;AAEZ"}
1
+ {"version":3,"file":"build-diff-viewer.mjs","names":[],"sources":["../../src/pages/build-diff-viewer.tsx"],"sourcesContent":["import type { Build, Project, Snapshot } from \"@storyshelf/core/schema\";\nimport { createUrlBuilder } from \"@storyshelf/core/urls\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { Badge, Button, CompareStage, Segmented, VStack, statusTone } from \"../ui/components.tsx\";\nimport { viewerHead, viewerMeta, viewerTitle } from \"../ui/styles/review-layout.ts\";\nimport { A11yViolationsNotice, a11yCarrierOf } from \"./a11y-violations.tsx\";\nimport { EnvironmentChangeSnapshotNotice } from \"./build-diff-environment.tsx\";\nimport {\n diffPercent,\n snapshotImageUrl,\n snapshotPageUrl,\n snapshotVariant,\n} from \"./build-diff-model.ts\";\nimport { StaleSnapshotNotice } from \"./build-diff-stale.tsx\";\nimport { LinkedUsage, type LinkedUsageItem } from \"./build-diff-usage.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\n/** Viewer props for the selected snapshot. */\nexport interface DiffViewerProps {\n project: Project;\n build: Build;\n selected: Snapshot;\n /** The selected story/mode's snapshots on every engine (browser tabs when more than one). */\n siblings?: Snapshot[];\n hasBaseline: Record<string, boolean>;\n /** Set when the baseline changed after this snapshot's diff was computed. */\n drifted?: \"stale\" | \"removed\";\n /** Linked packages the story uses (provenance badges + upstream deep links). */\n usage?: LinkedUsageItem[];\n /** Resolved design links of the story's component (open-in-Figma + stale/deleted notices). */\n design?: DesignItem[];\n publishedBaseDomain?: string;\n}\n\n/** Why a snapshot has no diff image, phrased for the empty pane. */\nfunction diffEmptyText(status: string): string {\n return status === \"unchanged\" || status === \"approved\"\n ? \"No diff — within threshold\"\n : \"No diff yet\";\n}\n\n/** Browser tabs (one link per engine) when the story/mode was captured on several. */\nfunction BrowserTabs(props: {\n project: Project;\n build: Build;\n selected: Snapshot;\n siblings: Snapshot[];\n}): HtmlEscapedString | Promise<HtmlEscapedString> | null {\n const { project, build, selected, siblings } = props;\n if (siblings.length < 2) return null;\n return (\n <Segmented\n label=\"Browser\"\n data-browser-tabs\n items={siblings.map((snap) => ({\n label: `${snap.browser ?? \"chromium\"} · ${snap.status}`,\n value: snap.id,\n href: snapshotPageUrl(project, build, snap.id),\n active: snap.id === selected.id,\n }))}\n />\n );\n}\n\n/** Link to the story file's component page, anchored at the story. */\nfunction ViewComponentButton(props: {\n project: Project;\n selected: Snapshot;\n base?: string;\n}): HtmlEscapedString | Promise<HtmlEscapedString> | null {\n const { project, selected, base } = props;\n if (!selected.storyImportPath) return null;\n const urls = createUrlBuilder(\"/\", base);\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n href={`${urls.component(project.slug, selected.storyImportPath)}#${selected.storyId}`}\n >\n View component\n </Button>\n );\n}\n\n/** Story heading plus the comparison stage for the selected snapshot. */\nexport function DiffViewer(props: DiffViewerProps): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, selected, hasBaseline, drifted, usage, design, publishedBaseDomain } =\n props;\n const siblings = props.siblings ?? [selected];\n const percent = diffPercent(selected);\n return (\n <VStack>\n <div class={viewerHead}>\n <h2 class={viewerTitle}>\n {selected.storyTitle} / {selected.storyName}\n </h2>\n <p class={viewerMeta}>\n <Badge tone={statusTone(selected.status)} icon>\n {selected.status}\n </Badge>\n <span>\n {snapshotVariant(selected)} · {selected.viewportWidth}×{selected.viewportHeight}\n </span>\n {selected.diffPixels === null || selected.diffPixels === undefined ? null : (\n <span class=\"mono\">{selected.diffPixels} px</span>\n )}\n {percent ? <span class=\"mono\">{percent}</span> : null}\n <ViewComponentButton project={project} selected={selected} base={publishedBaseDomain} />\n </p>\n </div>\n <BrowserTabs project={project} build={build} selected={selected} siblings={siblings} />\n {drifted ? <StaleSnapshotNotice status={drifted} /> : null}\n <EnvironmentChangeSnapshotNotice snapshot={selected} migrated={build.isDefault} />\n <A11yViolationsNotice snapshot={a11yCarrierOf(selected, siblings)} />\n {usage && usage.length > 0 ? (\n <LinkedUsage usage={usage} publishedBaseDomain={publishedBaseDomain} />\n ) : null}\n {design && design.length > 0 ? <DesignStrip items={design} /> : null}\n <CompareStage\n baselineSrc={\n hasBaseline[selected.id] ? snapshotImageUrl(project, build, selected, \"baseline\") : null\n }\n currentSrc={snapshotImageUrl(project, build, selected, \"image\")}\n diffSrc={selected.diffPath ? snapshotImageUrl(project, build, selected, \"diff\") : null}\n subject={`${selected.storyTitle} / ${selected.storyName}`}\n diffEmpty={diffEmptyText(selected.status)}\n diffMeta={percent ?? undefined}\n />\n {design && design.length > 0 ? <DesignReferences items={design} /> : null}\n </VStack>\n );\n}\n"],"mappings":"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"}
@@ -3,19 +3,26 @@ import "node:url";
3
3
  import.meta.url;
4
4
  import { EmptyState } from "../ui/feedback.mjs";
5
5
  import { DocumentLayout } from "../ui/document.mjs";
6
- import { StaleBaselineNotice } from "./build-diff-stale.mjs";
6
+ import { InsightSlot } from "./build-diff-insight.mjs";
7
+ import { groupOf, reviewProgress } from "./build-diff-model.mjs";
8
+ import { DiffComments } from "./build-diff-comments.mjs";
7
9
  import { canvasColumn, workspace } from "../ui/styles/review-layout.mjs";
8
10
  import { DiffActionBar } from "./build-diff-actions.mjs";
9
- import { DiffComments } from "./build-diff-comments.mjs";
11
+ import { ReviewNotices } from "./build-diff-environment.mjs";
10
12
  import { DiffHeader } from "./build-diff-header.mjs";
11
13
  import { DiffNav } from "./build-diff-nav.mjs";
12
14
  import { ShortcutsDialog } from "./build-diff-shortcuts.mjs";
13
15
  import { DiffViewer } from "./build-diff-viewer.mjs";
14
16
  import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
15
17
  //#region src/pages/build-diff.tsx
18
+ /** The snapshot the review page shows: the requested one, else the first needing review. */
19
+ function selectedSnapshot(snapshots, selectedId) {
20
+ return snapshots.find((s) => s.id === selectedId) ?? snapshots.find((s) => s.status === "changed" || s.status === "new") ?? snapshots[0];
21
+ }
16
22
  /** Snapshot nav plus the viewer/comments column for the selected snapshot. */
17
23
  function DiffReviewGrid(props) {
18
24
  const { project, build, snapshots, comments, selected, canReview, hasBaseline, drifted } = props;
25
+ const { usage, design, publishedBaseDomain } = props;
19
26
  return /* @__PURE__ */ jsxs("div", {
20
27
  class: workspace,
21
28
  children: [/* @__PURE__ */ jsx(DiffNav, {
@@ -30,13 +37,18 @@ function DiffReviewGrid(props) {
30
37
  project,
31
38
  build,
32
39
  selected,
40
+ siblings: groupOf(snapshots, selected.id)?.members,
33
41
  hasBaseline,
34
- drifted: drifted[selected.id]
42
+ drifted: drifted[selected.id],
43
+ usage,
44
+ design,
45
+ publishedBaseDomain
35
46
  }),
36
- /* @__PURE__ */ jsx(DiffComments, {
47
+ project.reviewEnabled === false ? null : /* @__PURE__ */ jsx(DiffComments, {
37
48
  project,
38
49
  build,
39
50
  selectedId: selected.id,
51
+ selectedStoryId: selected.storyId,
40
52
  comments,
41
53
  canReview
42
54
  }),
@@ -46,7 +58,7 @@ function DiffReviewGrid(props) {
46
58
  snapshots,
47
59
  selected,
48
60
  canReview,
49
- drifted: drifted[selected.id] !== void 0
61
+ drifted
50
62
  })
51
63
  ] }) : /* @__PURE__ */ jsx(EmptyState, { description: "Select a snapshot to review." })
52
64
  })]
@@ -67,8 +79,8 @@ function reviewNav(project, build) {
67
79
  /** Three-up diff review page: baseline, current, and diff with keyboard review. */
68
80
  function renderBuildDiffPage(data) {
69
81
  const { project, build, snapshots, comments, selectedId, canReview, hasBaseline, drifted } = data;
70
- const selected = snapshots.find((s) => s.id === selectedId) ?? snapshots.find((s) => s.status === "changed" || s.status === "new") ?? snapshots[0];
71
- const pending = snapshots.filter((s) => s.status === "new" || s.status === "changed");
82
+ const selected = selectedSnapshot(snapshots, selectedId);
83
+ const pendingGroups = reviewProgress(snapshots).pending;
72
84
  return /* @__PURE__ */ jsxs(DocumentLayout, {
73
85
  title: `Review · ${build.gitBranch}`,
74
86
  nav: reviewNav(project, build),
@@ -78,15 +90,21 @@ function renderBuildDiffPage(data) {
78
90
  project,
79
91
  build,
80
92
  snapshots,
81
- pendingCount: pending.length,
93
+ pendingCount: pendingGroups,
82
94
  canReview
83
95
  }),
84
- Object.keys(drifted).length > 0 ? /* @__PURE__ */ jsx(StaleBaselineNotice, {
96
+ /* @__PURE__ */ jsx(ReviewNotices, {
85
97
  project,
86
98
  build,
87
- staleCount: Object.keys(drifted).length,
99
+ snapshots,
100
+ drifted,
88
101
  canReview
89
- }) : null,
102
+ }),
103
+ /* @__PURE__ */ jsx(InsightSlot, {
104
+ project,
105
+ build,
106
+ data: data.insight
107
+ }),
90
108
  snapshots.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
91
109
  title: "No snapshots",
92
110
  description: "This build has no snapshots yet. Capture may still be running."
@@ -98,13 +116,16 @@ function renderBuildDiffPage(data) {
98
116
  selected,
99
117
  canReview,
100
118
  hasBaseline,
101
- drifted
119
+ drifted,
120
+ usage: data.usage ?? [],
121
+ design: data.design ?? [],
122
+ publishedBaseDomain: data.publishedBaseDomain
102
123
  }),
103
124
  /* @__PURE__ */ jsx(ShortcutsDialog, {})
104
125
  ]
105
126
  });
106
127
  }
107
128
  //#endregion
108
- export { renderBuildDiffPage };
129
+ export { renderBuildDiffPage, selectedSnapshot };
109
130
 
110
131
  //# sourceMappingURL=build-diff.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-diff.mjs","names":[],"sources":["../../src/pages/build-diff.tsx"],"sourcesContent":["import type { Build } from \"@storyshelf/core/schema\";\nimport type { Comment } from \"@storyshelf/core/schema\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport type { Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport { EmptyState } from \"../ui/components.tsx\";\nimport { DocumentLayout, type NavConfig, type RenderedContent } from \"../ui/document.tsx\";\nimport { canvasColumn, workspace } from \"../ui/styles/review-layout.ts\";\nimport { DiffActionBar } from \"./build-diff-actions.tsx\";\nimport { DiffComments } from \"./build-diff-comments.tsx\";\nimport { DiffHeader } from \"./build-diff-header.tsx\";\nimport { DiffNav } from \"./build-diff-nav.tsx\";\nimport { ShortcutsDialog } from \"./build-diff-shortcuts.tsx\";\nimport { StaleBaselineNotice } from \"./build-diff-stale.tsx\";\nimport { DiffViewer } from \"./build-diff-viewer.tsx\";\n\n/** Data required to render the three-up build diff review page. */\nexport interface BuildDiffData {\n project: Project;\n build: Build;\n snapshots: Snapshot[];\n comments: Comment[];\n selectedId?: string;\n canReview: boolean;\n hasBaseline: Record<string, boolean>;\n /** Open snapshots whose baseline changed after their diff was computed. */\n drifted: Record<string, \"stale\" | \"removed\">;\n}\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ninterface DiffReviewGridProps {\n project: Project;\n build: Build;\n snapshots: Snapshot[];\n comments: Comment[];\n selected?: Snapshot;\n canReview: boolean;\n hasBaseline: Record<string, boolean>;\n drifted: Record<string, \"stale\" | \"removed\">;\n}\n\n/** Snapshot nav plus the viewer/comments column for the selected snapshot. */\nfunction DiffReviewGrid(\n props: DiffReviewGridProps,\n): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, snapshots, comments, selected, canReview, hasBaseline, drifted } = props;\n return (\n <div class={workspace}>\n <DiffNav project={project} build={build} snapshots={snapshots} selectedId={selected?.id} />\n <div class={canvasColumn}>\n {selected ? (\n <>\n <DiffViewer\n project={project}\n build={build}\n selected={selected}\n hasBaseline={hasBaseline}\n drifted={drifted[selected.id]}\n />\n <DiffComments\n project={project}\n build={build}\n selectedId={selected.id}\n comments={comments}\n canReview={canReview}\n />\n <DiffActionBar\n project={project}\n build={build}\n snapshots={snapshots}\n selected={selected}\n canReview={canReview}\n drifted={drifted[selected.id] !== undefined}\n />\n </>\n ) : (\n <EmptyState description=\"Select a snapshot to review.\" />\n )}\n </div>\n </div>\n );\n}\n\n/** Sidebar/breadcrumb context for the review page: Builds / <build> / Review. */\nfunction reviewNav(project: Project, build: Build): NavConfig {\n return {\n active: \"builds\",\n projectSlug: project.slug,\n projectName: project.name,\n trail: [\n {\n label: `${build.gitBranch} · ${build.gitSha.slice(0, 7)}`,\n href: `/projects/${project.slug}/builds/${build.id}`,\n },\n { label: \"Review\" },\n ],\n };\n}\n\n/** Three-up diff review page: baseline, current, and diff with keyboard review. */\nexport function renderBuildDiffPage(data: BuildDiffData): RenderedContent {\n const { project, build, snapshots, comments, selectedId, canReview, hasBaseline, drifted } = data;\n const selected =\n snapshots.find((s) => s.id === selectedId) ??\n snapshots.find((s) => s.status === \"changed\" || s.status === \"new\") ??\n snapshots[0];\n const pending = snapshots.filter((s) => s.status === \"new\" || s.status === \"changed\");\n\n return (\n <DocumentLayout\n title={`Review · ${build.gitBranch}`}\n nav={reviewNav(project, build)}\n layout=\"wide\"\n >\n <DiffHeader\n project={project}\n build={build}\n snapshots={snapshots}\n pendingCount={pending.length}\n canReview={canReview}\n />\n\n {Object.keys(drifted).length > 0 ? (\n <StaleBaselineNotice\n project={project}\n build={build}\n staleCount={Object.keys(drifted).length}\n canReview={canReview}\n />\n ) : null}\n\n {snapshots.length === 0 ? (\n <EmptyState\n title=\"No snapshots\"\n description=\"This build has no snapshots yet. Capture may still be running.\"\n />\n ) : (\n <DiffReviewGrid\n project={project}\n build={build}\n snapshots={snapshots}\n comments={comments}\n selected={selected}\n canReview={canReview}\n hasBaseline={hasBaseline}\n drifted={drifted}\n />\n )}\n <ShortcutsDialog />\n </DocumentLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA2CA,SAAS,eACP,OACgD;CAChD,MAAM,EAAE,SAAS,OAAO,WAAW,UAAU,UAAU,WAAW,aAAa,YAAY;CAC3F,OACE,qBAAC,OAAD;EAAK,OAAO;EAAZ,UAAA,CACE,oBAAC,SAAD;GAAkB;GAAgB;GAAkB;GAAW,YAAY,UAAU;EAAK,CAAA,GAC1F,oBAAC,OAAD;GAAK,OAAO;GACT,UAAA,WACC,qBAAA,UAAA,EAAA,UAAA;IACE,oBAAC,YAAD;KACW;KACF;KACG;KACG;KACb,SAAS,QAAQ,SAAS;IAC3B,CAAA;IACD,oBAAC,cAAD;KACW;KACF;KACP,YAAY,SAAS;KACX;KACC;IACZ,CAAA;IACD,oBAAC,eAAD;KACW;KACF;KACI;KACD;KACC;KACX,SAAS,QAAQ,SAAS,QAAQ,KAAA;IACnC,CAAA;GACD,EAAA,CAAA,IAEF,oBAAC,YAAD,EAAY,aAAY,+BAAgC,CAAA;EAEvD,CAAA,CACF;;AAET;;AAGA,SAAS,UAAU,SAAkB,OAAyB;CAC5D,OAAO;EACL,QAAQ;EACR,aAAa,QAAQ;EACrB,aAAa,QAAQ;EACrB,OAAO,CACL;GACE,OAAO,GAAG,MAAM,UAAU,KAAK,MAAM,OAAO,MAAM,GAAG,CAAC;GACtD,MAAM,aAAa,QAAQ,KAAK,UAAU,MAAM;EAClD,GACA,EAAE,OAAO,SAAS,CACpB;CACF;AACF;;AAGA,SAAgB,oBAAoB,MAAsC;CACxE,MAAM,EAAE,SAAS,OAAO,WAAW,UAAU,YAAY,WAAW,aAAa,YAAY;CAC7F,MAAM,WACJ,UAAU,MAAM,MAAM,EAAE,OAAO,UAAU,KACzC,UAAU,MAAM,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,KAAK,KAClE,UAAU;CACZ,MAAM,UAAU,UAAU,QAAQ,MAAM,EAAE,WAAW,SAAS,EAAE,WAAW,SAAS;CAEpF,OACE,qBAAC,gBAAD;EACE,OAAO,YAAY,MAAM;EACzB,KAAK,UAAU,SAAS,KAAK;EAC7B,QAAO;EAHT,UAAA;GAKE,oBAAC,YAAD;IACW;IACF;IACI;IACX,cAAc,QAAQ;IACX;GACZ,CAAA;GAEA,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,IAC7B,oBAAC,qBAAD;IACW;IACF;IACP,YAAY,OAAO,KAAK,OAAO,CAAC,CAAC;IACtB;GACZ,CAAA,IACC;GAEH,UAAU,WAAW,IACpB,oBAAC,YAAD;IACE,OAAM;IACN,aAAY;GACb,CAAA,IAED,oBAAC,gBAAD;IACW;IACF;IACI;IACD;IACA;IACC;IACE;IACJ;GACV,CAAA;GAEH,oBAAC,iBAAD,CAAkB,CAAA;EACJ;;AAEpB"}
1
+ {"version":3,"file":"build-diff.mjs","names":[],"sources":["../../src/pages/build-diff.tsx"],"sourcesContent":["import type { Build } from \"@storyshelf/core/schema\";\nimport type { Comment } from \"@storyshelf/core/schema\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport type { Snapshot } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { InsightPanelData } from \"../insights/panel.ts\";\nimport { EmptyState } from \"../ui/components.tsx\";\nimport { DocumentLayout, type NavConfig, type RenderedContent } from \"../ui/document.tsx\";\nimport { canvasColumn, workspace } from \"../ui/styles/review-layout.ts\";\nimport { DiffActionBar } from \"./build-diff-actions.tsx\";\nimport { DiffComments } from \"./build-diff-comments.tsx\";\nimport { ReviewNotices } from \"./build-diff-environment.tsx\";\nimport { DiffHeader } from \"./build-diff-header.tsx\";\nimport { InsightSlot } from \"./build-diff-insight.tsx\";\nimport { groupOf, reviewProgress } from \"./build-diff-model.ts\";\nimport { DiffNav } from \"./build-diff-nav.tsx\";\nimport { ShortcutsDialog } from \"./build-diff-shortcuts.tsx\";\nimport type { LinkedUsageItem } from \"./build-diff-usage.tsx\";\nimport { DiffViewer } from \"./build-diff-viewer.tsx\";\nimport type { DesignItem } from \"./design-links.tsx\";\n\n/** Data required to render the three-up build diff review page. */\nexport interface BuildDiffData {\n project: Project;\n build: Build;\n snapshots: Snapshot[];\n comments: Comment[];\n selectedId?: string;\n canReview: boolean;\n hasBaseline: Record<string, boolean>;\n /** Open snapshots whose baseline changed after their diff was computed. */\n drifted: Record<string, \"stale\" | \"removed\">;\n /** AI triage panel data; omitted/null hides the panel. */\n insight?: InsightPanelData | null;\n /** Linked packages the selected snapshot's story uses; omitted/empty hides the badges. */\n usage?: LinkedUsageItem[];\n /** Design links the selected snapshot's component resolves to; omitted/empty hides the strip. */\n design?: DesignItem[];\n /** Base domain for published Storybook deep links. */\n publishedBaseDomain?: string;\n}\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ninterface DiffReviewGridProps {\n project: Project;\n build: Build;\n snapshots: Snapshot[];\n comments: Comment[];\n selected?: Snapshot;\n canReview: boolean;\n hasBaseline: Record<string, boolean>;\n drifted: Record<string, \"stale\" | \"removed\">;\n usage: LinkedUsageItem[];\n design: DesignItem[];\n publishedBaseDomain?: string;\n}\n\n/** The snapshot the review page shows: the requested one, else the first needing review. */\nexport function selectedSnapshot(snapshots: Snapshot[], selectedId?: string): Snapshot | undefined {\n return (\n snapshots.find((s) => s.id === selectedId) ??\n snapshots.find((s) => s.status === \"changed\" || s.status === \"new\") ??\n snapshots[0]\n );\n}\n\n/** Snapshot nav plus the viewer/comments column for the selected snapshot. */\nfunction DiffReviewGrid(\n props: DiffReviewGridProps,\n): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, build, snapshots, comments, selected, canReview, hasBaseline, drifted } = props;\n const { usage, design, publishedBaseDomain } = props;\n return (\n <div class={workspace}>\n <DiffNav project={project} build={build} snapshots={snapshots} selectedId={selected?.id} />\n <div class={canvasColumn}>\n {selected ? (\n <>\n <DiffViewer\n project={project}\n build={build}\n selected={selected}\n siblings={groupOf(snapshots, selected.id)?.members}\n hasBaseline={hasBaseline}\n drifted={drifted[selected.id]}\n usage={usage}\n design={design}\n publishedBaseDomain={publishedBaseDomain}\n />\n {project.reviewEnabled === false ? null : (\n <DiffComments\n project={project}\n build={build}\n selectedId={selected.id}\n selectedStoryId={selected.storyId}\n comments={comments}\n canReview={canReview}\n />\n )}\n <DiffActionBar\n project={project}\n build={build}\n snapshots={snapshots}\n selected={selected}\n canReview={canReview}\n drifted={drifted}\n />\n </>\n ) : (\n <EmptyState description=\"Select a snapshot to review.\" />\n )}\n </div>\n </div>\n );\n}\n\n/** Sidebar/breadcrumb context for the review page: Builds / <build> / Review. */\nfunction reviewNav(project: Project, build: Build): NavConfig {\n return {\n active: \"builds\",\n projectSlug: project.slug,\n projectName: project.name,\n trail: [\n {\n label: `${build.gitBranch} · ${build.gitSha.slice(0, 7)}`,\n href: `/projects/${project.slug}/builds/${build.id}`,\n },\n { label: \"Review\" },\n ],\n };\n}\n\n/** Three-up diff review page: baseline, current, and diff with keyboard review. */\nexport function renderBuildDiffPage(data: BuildDiffData): RenderedContent {\n const { project, build, snapshots, comments, selectedId, canReview, hasBaseline, drifted } = data;\n const selected = selectedSnapshot(snapshots, selectedId);\n const pendingGroups = reviewProgress(snapshots).pending;\n\n return (\n <DocumentLayout\n title={`Review · ${build.gitBranch}`}\n nav={reviewNav(project, build)}\n layout=\"wide\"\n >\n <DiffHeader\n project={project}\n build={build}\n snapshots={snapshots}\n pendingCount={pendingGroups}\n canReview={canReview}\n />\n <ReviewNotices\n project={project}\n build={build}\n snapshots={snapshots}\n drifted={drifted}\n canReview={canReview}\n />\n <InsightSlot project={project} build={build} data={data.insight} />\n\n {snapshots.length === 0 ? (\n <EmptyState\n title=\"No snapshots\"\n description=\"This build has no snapshots yet. Capture may still be running.\"\n />\n ) : (\n <DiffReviewGrid\n project={project}\n build={build}\n snapshots={snapshots}\n comments={comments}\n selected={selected}\n canReview={canReview}\n hasBaseline={hasBaseline}\n drifted={drifted}\n usage={data.usage ?? []}\n design={data.design ?? []}\n publishedBaseDomain={data.publishedBaseDomain}\n />\n )}\n <ShortcutsDialog />\n </DocumentLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA2DA,SAAgB,iBAAiB,WAAuB,YAA2C;CACjG,OACE,UAAU,MAAM,MAAM,EAAE,OAAO,UAAU,KACzC,UAAU,MAAM,MAAM,EAAE,WAAW,aAAa,EAAE,WAAW,KAAK,KAClE,UAAU;AAEd;;AAGA,SAAS,eACP,OACgD;CAChD,MAAM,EAAE,SAAS,OAAO,WAAW,UAAU,UAAU,WAAW,aAAa,YAAY;CAC3F,MAAM,EAAE,OAAO,QAAQ,wBAAwB;CAC/C,OACE,qBAAC,OAAD;EAAK,OAAO;EAAZ,UAAA,CACE,oBAAC,SAAD;GAAkB;GAAgB;GAAkB;GAAW,YAAY,UAAU;EAAK,CAAA,GAC1F,oBAAC,OAAD;GAAK,OAAO;GACT,UAAA,WACC,qBAAA,UAAA,EAAA,UAAA;IACE,oBAAC,YAAD;KACW;KACF;KACG;KACV,UAAU,QAAQ,WAAW,SAAS,EAAE,CAAC,EAAE;KAC9B;KACb,SAAS,QAAQ,SAAS;KACnB;KACC;KACa;IACtB,CAAA;IACA,QAAQ,kBAAkB,QAAQ,OACjC,oBAAC,cAAD;KACW;KACF;KACP,YAAY,SAAS;KACrB,iBAAiB,SAAS;KAChB;KACC;IACZ,CAAA;IAEH,oBAAC,eAAD;KACW;KACF;KACI;KACD;KACC;KACF;IACV,CAAA;GACD,EAAA,CAAA,IAEF,oBAAC,YAAD,EAAY,aAAY,+BAAgC,CAAA;EAEvD,CAAA,CACF;;AAET;;AAGA,SAAS,UAAU,SAAkB,OAAyB;CAC5D,OAAO;EACL,QAAQ;EACR,aAAa,QAAQ;EACrB,aAAa,QAAQ;EACrB,OAAO,CACL;GACE,OAAO,GAAG,MAAM,UAAU,KAAK,MAAM,OAAO,MAAM,GAAG,CAAC;GACtD,MAAM,aAAa,QAAQ,KAAK,UAAU,MAAM;EAClD,GACA,EAAE,OAAO,SAAS,CACpB;CACF;AACF;;AAGA,SAAgB,oBAAoB,MAAsC;CACxE,MAAM,EAAE,SAAS,OAAO,WAAW,UAAU,YAAY,WAAW,aAAa,YAAY;CAC7F,MAAM,WAAW,iBAAiB,WAAW,UAAU;CACvD,MAAM,gBAAgB,eAAe,SAAS,CAAC,CAAC;CAEhD,OACE,qBAAC,gBAAD;EACE,OAAO,YAAY,MAAM;EACzB,KAAK,UAAU,SAAS,KAAK;EAC7B,QAAO;EAHT,UAAA;GAKE,oBAAC,YAAD;IACW;IACF;IACI;IACX,cAAc;IACH;GACZ,CAAA;GACD,oBAAC,eAAD;IACW;IACF;IACI;IACF;IACE;GACZ,CAAA;GACD,oBAAC,aAAD;IAAsB;IAAgB;IAAO,MAAM,KAAK;GAAU,CAAA;GAEjE,UAAU,WAAW,IACpB,oBAAC,YAAD;IACE,OAAM;IACN,aAAY;GACb,CAAA,IAED,oBAAC,gBAAD;IACW;IACF;IACI;IACD;IACA;IACC;IACE;IACJ;IACT,OAAO,KAAK,SAAS,CAAC;IACtB,QAAQ,KAAK,UAAU,CAAC;IACxB,qBAAqB,KAAK;GAC3B,CAAA;GAEH,oBAAC,iBAAD,CAAkB,CAAA;EACJ;;AAEpB"}
@@ -0,0 +1,74 @@
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
+ //#region 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
+ //#endregion
72
+ export { TestsCard, testsSummaryNote };
73
+
74
+ //# 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,249 @@
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
+ //#region 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
+ //#endregion
247
+ export { componentTabs, renderComponentPage };
248
+
249
+ //# 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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"}