@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,101 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { Button } from "../ui/buttons.mjs";
5
+ import { Badge, Meta, statusTone } from "../ui/feedback.mjs";
6
+ import { TextareaField } from "../ui/forms.mjs";
7
+ import { Card, SectionTitle } from "../ui/layout.mjs";
8
+ import { HStack, VStack } from "../ui/stacks.mjs";
9
+ import { RelativeTime } from "../ui/time.mjs";
10
+ import { csrfField } from "../ui/csrf-field.mjs";
11
+ import { BUILD_STATUSES } from "@storyshelf/core/types";
12
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
13
+ // src/pages/review-verdict.tsx
14
+ /** The verdict card of the build page: where the build stands, the verdicts, and the reviewer's controls. */
15
+ /** Build-level verdicts: the approvals so far, each reviewer's latest verdict, and the controls. */
16
+ function VerdictCard(props) {
17
+ const { data } = props;
18
+ return /* @__PURE__ */ jsxs(Card, { children: [
19
+ /* @__PURE__ */ jsxs(HStack, {
20
+ justify: "between",
21
+ children: [/* @__PURE__ */ jsx(SectionTitle, { children: "Review" }), /* @__PURE__ */ jsx(Badge, {
22
+ tone: statusTone(data.state.status),
23
+ children: standing(data)
24
+ })]
25
+ }),
26
+ /* @__PURE__ */ jsx(Meta, { children: summary(data) }),
27
+ /* @__PURE__ */ jsx(VStack, { children: data.state.latest.map((review) => /* @__PURE__ */ jsxs(HStack, {
28
+ align: "baseline",
29
+ wrap: false,
30
+ children: [
31
+ /* @__PURE__ */ jsx(Badge, {
32
+ tone: review.verdict === "approved" ? "success" : "danger",
33
+ children: review.verdict
34
+ }),
35
+ /* @__PURE__ */ jsx("strong", { children: review.userId ? data.names.get(review.userId) ?? review.userId : "anonymous" }),
36
+ /* @__PURE__ */ jsx(Meta, {
37
+ as: "span",
38
+ children: /* @__PURE__ */ jsx(RelativeTime, { value: review.createdAt })
39
+ }),
40
+ review.body ? /* @__PURE__ */ jsx("span", { children: review.body }) : null
41
+ ]
42
+ }, review.id)) }),
43
+ /* @__PURE__ */ jsx(VerdictControls, { data })
44
+ ] });
45
+ }
46
+ /**
47
+ * The form for a reviewer once the build can take a verdict; while its Storybook is still
48
+ * publishing a notice stands in (the server refuses a verdict until then, ADR 0013, decision 3).
49
+ */
50
+ function VerdictControls(props) {
51
+ const { data } = props;
52
+ if (!data.canVerdict || data.build.status === "failed") return null;
53
+ if (data.publishing === "pending") return /* @__PURE__ */ jsx(Meta, { children: "Waiting for the Storybook to publish. You can give a verdict once it has." });
54
+ return data.publishing === "success" ? /* @__PURE__ */ jsx(VerdictForm, { data }) : null;
55
+ }
56
+ /** The reviewer's controls: an optional note and the two verdicts. */
57
+ function VerdictForm(props) {
58
+ const { project, build } = props.data;
59
+ const url = `/projects/${project.slug}/builds/${build.id}/reviews`;
60
+ return /* @__PURE__ */ jsxs("form", {
61
+ method: "post",
62
+ action: url,
63
+ "hx-post": url,
64
+ "hx-target": "body",
65
+ class: "mt-1 max-w-prose",
66
+ children: [csrfField(), /* @__PURE__ */ jsxs(VStack, { children: [/* @__PURE__ */ jsx(TextareaField, {
67
+ label: "Note with your verdict (optional)",
68
+ name: "body",
69
+ id: "verdict-body",
70
+ rows: 2
71
+ }), /* @__PURE__ */ jsxs(HStack, { children: [/* @__PURE__ */ jsx(Button, {
72
+ variant: "primary",
73
+ type: "submit",
74
+ name: "verdict",
75
+ value: "approved",
76
+ children: "Approve build"
77
+ }), /* @__PURE__ */ jsx(Button, {
78
+ variant: "danger",
79
+ type: "submit",
80
+ name: "verdict",
81
+ value: "rejected",
82
+ children: "Reject build"
83
+ })] })] })]
84
+ });
85
+ }
86
+ /** The status badge text: the build's status, worded for review. */
87
+ function standing(data) {
88
+ const { status } = data.state;
89
+ if (status === "pending") return "waiting for review";
90
+ return BUILD_STATUSES.includes(status) ? status : "unknown";
91
+ }
92
+ /** One line on what decides the build. */
93
+ function summary(data) {
94
+ const { state, testing } = data;
95
+ if (testing) return "Screenshots decide this build; a verdict is a sign-off and does not change its status.";
96
+ const needed = `${state.approvalsRequired} ${state.approvalsRequired === 1 ? "approval" : "approvals"}`;
97
+ return `${state.approvals} of ${needed} given. Any rejection rejects the build. Open comments do not block a verdict.`;
98
+ }
99
+ export { VerdictCard };
100
+
101
+ //# sourceMappingURL=review-verdict.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-verdict.mjs","names":[],"sources":["../../src/pages/review-verdict.tsx"],"sourcesContent":["/** The verdict card of the build page: where the build stands, the verdicts, and the reviewer's controls. */\nimport { BUILD_STATUSES } from \"@storyshelf/core/types\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport type { ReviewSectionData } from \"../routers/review-section.handlers.ts\";\nimport {\n Badge,\n Button,\n Card,\n HStack,\n Meta,\n RelativeTime,\n SectionTitle,\n TextareaField,\n VStack,\n statusTone,\n} from \"../ui/components.tsx\";\nimport { csrfField } from \"../ui/csrf-field.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype Rendered = HtmlEscapedString | Promise<HtmlEscapedString>;\n\n/** Build-level verdicts: the approvals so far, each reviewer's latest verdict, and the controls. */\nexport function VerdictCard(props: { data: ReviewSectionData }): Rendered {\n const { data } = props;\n return (\n <Card>\n <HStack justify=\"between\">\n <SectionTitle>Review</SectionTitle>\n <Badge tone={statusTone(data.state.status)}>{standing(data)}</Badge>\n </HStack>\n <Meta>{summary(data)}</Meta>\n <VStack>\n {data.state.latest.map((review): Rendered => (\n <HStack key={review.id} align=\"baseline\" wrap={false}>\n <Badge tone={review.verdict === \"approved\" ? \"success\" : \"danger\"}>\n {review.verdict}\n </Badge>\n <strong>\n {review.userId ? (data.names.get(review.userId) ?? review.userId) : \"anonymous\"}\n </strong>\n <Meta as=\"span\">\n <RelativeTime value={review.createdAt} />\n </Meta>\n {review.body ? <span>{review.body}</span> : null}\n </HStack>\n ))}\n </VStack>\n <VerdictControls data={data} />\n </Card>\n );\n}\n\n/**\n * The form for a reviewer once the build can take a verdict; while its Storybook is still\n * publishing a notice stands in (the server refuses a verdict until then, ADR 0013, decision 3).\n */\nfunction VerdictControls(props: { data: ReviewSectionData }): Rendered | null {\n const { data } = props;\n if (!data.canVerdict || data.build.status === \"failed\") {\n return null;\n }\n if (data.publishing === \"pending\") {\n return <Meta>Waiting for the Storybook to publish. You can give a verdict once it has.</Meta>;\n }\n return data.publishing === \"success\" ? <VerdictForm data={data} /> : null;\n}\n\n/** The reviewer's controls: an optional note and the two verdicts. */\nfunction VerdictForm(props: { data: ReviewSectionData }): Rendered {\n const { project, build } = props.data;\n const url = `/projects/${project.slug}/builds/${build.id}/reviews`;\n return (\n <form method=\"post\" action={url} hx-post={url} hx-target=\"body\" class=\"mt-1 max-w-prose\">\n {csrfField()}\n <VStack>\n <TextareaField\n label=\"Note with your verdict (optional)\"\n name=\"body\"\n id=\"verdict-body\"\n rows={2}\n />\n <HStack>\n <Button variant=\"primary\" type=\"submit\" name=\"verdict\" value=\"approved\">\n Approve build\n </Button>\n <Button variant=\"danger\" type=\"submit\" name=\"verdict\" value=\"rejected\">\n Reject build\n </Button>\n </HStack>\n </VStack>\n </form>\n );\n}\n\n/** The status badge text: the build's status, worded for review. */\nfunction standing(data: ReviewSectionData): string {\n const { status } = data.state;\n if (status === \"pending\") {\n return \"waiting for review\";\n }\n return BUILD_STATUSES.includes(status) ? status : \"unknown\";\n}\n\n/** One line on what decides the build. */\nfunction summary(data: ReviewSectionData): string {\n const { state, testing } = data;\n if (testing) {\n return \"Screenshots decide this build; a verdict is a sign-off and does not change its status.\";\n }\n const needed = `${state.approvalsRequired} ${state.approvalsRequired === 1 ? \"approval\" : \"approvals\"}`;\n return `${state.approvals} of ${needed} given. Any rejection rejects the build. Open comments do not block a verdict.`;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAuBA,SAAgB,YAAY,OAA8C;CACxE,MAAM,EAAE,SAAS;CACjB,OACE,qBAAC,MAAD,EAAA,UAAA;EACE,qBAAC,QAAD;GAAQ,SAAQ;GAAhB,UAAA,CACE,oBAAC,cAAD,EAAA,UAAc,SAAoB,CAAA,GAClC,oBAAC,OAAD;IAAO,MAAM,WAAW,KAAK,MAAM,MAAM;IAAI,UAAA,SAAS,IAAI;GAAS,CAAA,CAC7D;;EACR,oBAAC,MAAD,EAAA,UAAO,QAAQ,IAAI,EAAQ,CAAA;EAC3B,oBAAC,QAAD,EAAA,UACG,KAAK,MAAM,OAAO,KAAK,WACtB,qBAAC,QAAD;GAAwB,OAAM;GAAW,MAAM;GAA/C,UAAA;IACE,oBAAC,OAAD;KAAO,MAAM,OAAO,YAAY,aAAa,YAAY;KACtD,UAAA,OAAO;IACH,CAAA;IACP,oBAAC,UAAD,EAAA,UACG,OAAO,SAAU,KAAK,MAAM,IAAI,OAAO,MAAM,KAAK,OAAO,SAAU,YAC9D,CAAA;IACR,oBAAC,MAAD;KAAM,IAAG;KACP,UAAA,oBAAC,cAAD,EAAc,OAAO,OAAO,UAAY,CAAA;IACpC,CAAA;IACL,OAAO,OAAO,oBAAC,QAAD,EAAA,UAAO,OAAO,KAAW,CAAA,IAAI;GACtC;EAXK,GAAA,OAAO,EAWZ,CACT,EACK,CAAA;EACR,oBAAC,iBAAD,EAAuB,KAAO,CAAA;CAC1B,EAAA,CAAA;AAEV;;;;;AAMA,SAAS,gBAAgB,OAAqD;CAC5E,MAAM,EAAE,SAAS;CACjB,IAAI,CAAC,KAAK,cAAc,KAAK,MAAM,WAAW,UAC5C,OAAO;CAET,IAAI,KAAK,eAAe,WACtB,OAAO,oBAAC,MAAD,EAAA,UAAM,4EAA+E,CAAA;CAE9F,OAAO,KAAK,eAAe,YAAY,oBAAC,aAAD,EAAmB,KAAO,CAAA,IAAI;AACvE;;AAGA,SAAS,YAAY,OAA8C;CACjE,MAAM,EAAE,SAAS,UAAU,MAAM;CACjC,MAAM,MAAM,aAAa,QAAQ,KAAK,UAAU,MAAM,GAAG;CACzD,OACE,qBAAC,QAAD;EAAM,QAAO;EAAO,QAAQ;EAAK,WAAS;EAAK,aAAU;EAAO,OAAM;EAAtE,UAAA,CACG,UAAU,GACX,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,eAAD;GACE,OAAM;GACN,MAAK;GACL,IAAG;GACH,MAAM;EACP,CAAA,GACD,qBAAC,QAAD,EAAA,UAAA,CACE,oBAAC,QAAD;GAAQ,SAAQ;GAAU,MAAK;GAAS,MAAK;GAAU,OAAM;GAAW,UAAA;EAEhE,CAAA,GACR,oBAAC,QAAD;GAAQ,SAAQ;GAAS,MAAK;GAAS,MAAK;GAAU,OAAM;GAAW,UAAA;EAE/D,CAAA,CACF,EAAA,CAAA,CACF,EAAA,CAAA,CACJ;;AAEV;;AAGA,SAAS,SAAS,MAAiC;CACjD,MAAM,EAAE,WAAW,KAAK;CACxB,IAAI,WAAW,WACb,OAAO;CAET,OAAO,eAAe,SAAS,MAAM,IAAI,SAAS;AACpD;;AAGA,SAAS,QAAQ,MAAiC;CAChD,MAAM,EAAE,OAAO,YAAY;CAC3B,IAAI,SACF,OAAO;CAET,MAAM,SAAS,GAAG,MAAM,kBAAkB,GAAG,MAAM,sBAAsB,IAAI,aAAa;CAC1F,OAAO,GAAG,MAAM,UAAU,MAAM,OAAO;AACzC"}
@@ -8,7 +8,7 @@ import { Card, SectionTitle } from "../ui/layout.mjs";
8
8
  import { HStack, VStack } from "../ui/stacks.mjs";
9
9
  import { DocumentLayout } from "../ui/document.mjs";
10
10
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
11
- //#region src/pages/root.tsx
11
+ // src/pages/root.tsx
12
12
  /** Landing hero card: card surface with centered marketing padding. */
13
13
  const heroCard = css`
14
14
  /* hero-card */
@@ -92,7 +92,6 @@ function renderRootPage() {
92
92
  })]
93
93
  });
94
94
  }
95
- //#endregion
96
95
  export { renderRootPage };
97
96
 
98
97
  //# sourceMappingURL=root.mjs.map
@@ -0,0 +1,68 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { Button } from "../ui/buttons.mjs";
5
+ import { Alert, Meta } from "../ui/feedback.mjs";
6
+ import { FormActions, SelectField } from "../ui/forms.mjs";
7
+ import { Card, SectionTitle } from "../ui/layout.mjs";
8
+ import { csrfField } from "../ui/csrf-field.mjs";
9
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
10
+ // src/pages/settings-ai.tsx
11
+ function ProfileForm(props) {
12
+ const { project, ai } = props;
13
+ return /* @__PURE__ */ jsxs("form", {
14
+ method: "post",
15
+ action: `/projects/${project.slug}/settings/ai`,
16
+ "hx-post": `/projects/${project.slug}/settings/ai`,
17
+ "hx-target": "body",
18
+ "hx-swap": "outerHTML",
19
+ children: [
20
+ csrfField(),
21
+ /* @__PURE__ */ jsx(SelectField, {
22
+ label: "AI profile",
23
+ name: "aiProfile",
24
+ value: project.aiProfile ?? "",
25
+ disabled: !ai.isSiteAdmin,
26
+ options: [{
27
+ value: "",
28
+ label: "Off (no data leaves the server)"
29
+ }, ...ai.profiles.map((name) => ({
30
+ value: name,
31
+ label: name === ai.defaultProfile ? `${name} (default)` : name
32
+ }))],
33
+ hint: "Only site admins can change this because it decides where project data is sent."
34
+ }),
35
+ ai.isSiteAdmin ? /* @__PURE__ */ jsx(FormActions, { children: /* @__PURE__ */ jsx(Button, {
36
+ variant: "primary",
37
+ type: "submit",
38
+ icon: "check",
39
+ children: "Save changes"
40
+ }) }) : /* @__PURE__ */ jsx(Meta, { children: "You need site admin access to change the AI profile." })
41
+ ]
42
+ });
43
+ }
44
+ /** AI settings tab: per-project profile gate (site admins choose; others read). */
45
+ function renderSettingsAi(project, ai, formState) {
46
+ if (!ai?.available) return /* @__PURE__ */ jsx(Alert, {
47
+ tone: "info",
48
+ title: "AI is not configured",
49
+ children: "This server has no AI configured, so insights are unavailable."
50
+ });
51
+ return /* @__PURE__ */ jsxs("div", {
52
+ class: "grid max-w-form",
53
+ children: [formState?.globalError ? /* @__PURE__ */ jsx(Alert, {
54
+ tone: "danger",
55
+ children: formState.globalError
56
+ }) : null, /* @__PURE__ */ jsxs(Card, { children: [
57
+ /* @__PURE__ */ jsx(SectionTitle, { children: "AI insights" }),
58
+ /* @__PURE__ */ jsx(Meta, { children: "Choosing a profile turns on AI triage for builds and a project health digest. The profile decides which models see this project's data (including screenshots when its model supports vision). Insights are advisory only: they never approve or reject anything." }),
59
+ /* @__PURE__ */ jsx(ProfileForm, {
60
+ project,
61
+ ai
62
+ })
63
+ ] })]
64
+ });
65
+ }
66
+ export { renderSettingsAi };
67
+
68
+ //# sourceMappingURL=settings-ai.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-ai.mjs","names":[],"sources":["../../src/pages/settings-ai.tsx"],"sourcesContent":["import type { Project } from \"@storyshelf/core/schema\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\nimport {\n Alert,\n Button,\n Card,\n FormActions,\n Meta,\n SectionTitle,\n SelectField,\n} from \"../ui/components.tsx\";\nimport { csrfField } from \"../ui/csrf-field.tsx\";\n\n/** AI availability and profile choices passed to the settings tab. */\nexport interface SettingsAiData {\n available: boolean;\n profiles: string[];\n defaultProfile: string;\n isSiteAdmin: boolean;\n}\n\nfunction ProfileForm(props: {\n project: Project;\n ai: SettingsAiData;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, ai } = props;\n return (\n <form\n method=\"post\"\n action={`/projects/${project.slug}/settings/ai`}\n hx-post={`/projects/${project.slug}/settings/ai`}\n hx-target=\"body\"\n hx-swap=\"outerHTML\"\n >\n {csrfField()}\n <SelectField\n label=\"AI profile\"\n name=\"aiProfile\"\n value={project.aiProfile ?? \"\"}\n disabled={!ai.isSiteAdmin}\n options={[\n { value: \"\", label: \"Off (no data leaves the server)\" },\n ...ai.profiles.map((name) => ({\n value: name,\n label: name === ai.defaultProfile ? `${name} (default)` : name,\n })),\n ]}\n hint=\"Only site admins can change this because it decides where project data is sent.\"\n />\n {ai.isSiteAdmin ? (\n <FormActions>\n <Button variant=\"primary\" type=\"submit\" icon=\"check\">\n Save changes\n </Button>\n </FormActions>\n ) : (\n <Meta>You need site admin access to change the AI profile.</Meta>\n )}\n </form>\n );\n}\n\n/** AI settings tab: per-project profile gate (site admins choose; others read). */\nexport function renderSettingsAi(\n project: Project,\n ai: SettingsAiData | undefined,\n formState?: { globalError?: string },\n): unknown {\n if (!ai?.available) {\n return (\n <Alert tone=\"info\" title=\"AI is not configured\">\n This server has no AI configured, so insights are unavailable.\n </Alert>\n );\n }\n return (\n <div class=\"grid max-w-form\">\n {formState?.globalError ? <Alert tone=\"danger\">{formState.globalError}</Alert> : null}\n <Card>\n <SectionTitle>AI insights</SectionTitle>\n <Meta>\n Choosing a profile turns on AI triage for builds and a project health digest. The profile\n decides which models see this project&apos;s data (including screenshots when its model\n supports vision). Insights are advisory only: they never approve or reject anything.\n </Meta>\n <ProfileForm project={project} ai={ai} />\n </Card>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAsBA,SAAS,YAAY,OAG8B;CACjD,MAAM,EAAE,SAAS,OAAO;CACxB,OACE,qBAAC,QAAD;EACE,QAAO;EACP,QAAQ,aAAa,QAAQ,KAAK;EAClC,WAAS,aAAa,QAAQ,KAAK;EACnC,aAAU;EACV,WAAQ;EALV,UAAA;GAOG,UAAU;GACX,oBAAC,aAAD;IACE,OAAM;IACN,MAAK;IACL,OAAO,QAAQ,aAAa;IAC5B,UAAU,CAAC,GAAG;IACd,SAAS,CACP;KAAE,OAAO;KAAI,OAAO;IAAkC,GACtD,GAAG,GAAG,SAAS,KAAK,UAAU;KAC5B,OAAO;KACP,OAAO,SAAS,GAAG,iBAAiB,GAAG,KAAK,cAAc;IAC5D,EAAE,CACJ;IACA,MAAK;GACN,CAAA;GACA,GAAG,cACF,oBAAC,aAAD,EAAA,UACE,oBAAC,QAAD;IAAQ,SAAQ;IAAU,MAAK;IAAS,MAAK;IAAQ,UAAA;GAE7C,CAAA,EACG,CAAA,IAEb,oBAAC,MAAD,EAAA,UAAM,uDAA0D,CAAA;EAE9D;;AAEV;;AAGA,SAAgB,iBACd,SACA,IACA,WACS;CACT,IAAI,CAAC,IAAI,WACP,OACE,oBAAC,OAAD;EAAO,MAAK;EAAO,OAAM;EAAuB,UAAA;CAEzC,CAAA;CAGX,OACE,qBAAC,OAAD;EAAK,OAAM;EAAX,UAAA,CACG,WAAW,cAAc,oBAAC,OAAD;GAAO,MAAK;GAAU,UAAA,UAAU;EAAmB,CAAA,IAAI,MACjF,qBAAC,MAAD,EAAA,UAAA;GACE,oBAAC,cAAD,EAAA,UAAc,cAAyB,CAAA;GACvC,oBAAC,MAAD,EAAA,UAAM,oQAIA,CAAA;GACN,oBAAC,aAAD;IAAsB;IAAa;GAAK,CAAA;EACpC,EAAA,CAAA,CACH;;AAET"}
@@ -0,0 +1,123 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { Button } from "../ui/buttons.mjs";
5
+ import { Alert, Meta } from "../ui/feedback.mjs";
6
+ import { CheckField, Field, FormActions, SelectField, TextareaField } from "../ui/forms.mjs";
7
+ import { Card, SectionTitle } from "../ui/layout.mjs";
8
+ import { csrfField } from "../ui/csrf-field.mjs";
9
+ import { targetChoices } from "./target-choices.mjs";
10
+ import { MAX_STORYBOOK_URL_TTL, MIN_STORYBOOK_URL_TTL_OVERRIDE } from "@storyshelf/core/utils";
11
+ import { MAX_BROWSERS, parseBrowsers, parseModesAllowlist } from "@storyshelf/core/capture";
12
+ import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
13
+ import { isCaptureTarget } from "@storyshelf/core/adapter/capture-runner";
14
+ // src/pages/settings-capture.tsx
15
+ /** Primary engine select plus one checkbox per additional engine. */
16
+ function BrowserFields(props) {
17
+ const { primary, extras, disabled, targets } = props;
18
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
19
+ /* @__PURE__ */ jsx(SelectField, {
20
+ label: "Primary browser",
21
+ name: "browser",
22
+ value: primary,
23
+ disabled,
24
+ options: targets.choices,
25
+ hint: "Used for the published preview and to bootstrap other engines."
26
+ }),
27
+ targets.choices.map((engine) => /* @__PURE__ */ jsx(CheckField, {
28
+ label: `Also capture on ${engine.label}`,
29
+ name: `extra_${engine.value}`,
30
+ value: engine.value,
31
+ checked: extras.includes(engine.value) ? true : void 0,
32
+ disabled
33
+ }, engine.value)),
34
+ targets.unavailable.length > 0 ? /* @__PURE__ */ jsxs(Meta, { children: [
35
+ "Not selectable on this server: ",
36
+ targets.unavailable.join("; "),
37
+ "."
38
+ ] }) : null
39
+ ] });
40
+ }
41
+ /** One mode name per line; empty runs every declared mode. */
42
+ function AllowlistField(props) {
43
+ return /* @__PURE__ */ jsx(TextareaField, {
44
+ label: "Mode allowlist",
45
+ name: "modesAllowlist",
46
+ rows: 4,
47
+ value: props.allowlist.join("\n"),
48
+ placeholder: "dark\nmobile",
49
+ hint: "One mode name per line. Empty runs every mode declared in your stories' parameters."
50
+ });
51
+ }
52
+ /** The project's override of the signed Storybook URL lifetime (ADR 0007, decision 5). */
53
+ function UrlLifetimeField(props) {
54
+ const { project, disabled } = props;
55
+ return /* @__PURE__ */ jsx(Field, {
56
+ label: "Signed Storybook URL lifetime (seconds)",
57
+ name: "storybookUrlTtlSeconds",
58
+ type: "number",
59
+ step: "1",
60
+ min: String(MIN_STORYBOOK_URL_TTL_OVERRIDE),
61
+ max: String(MAX_STORYBOOK_URL_TTL),
62
+ value: project.storybookUrlTtlSeconds ? String(project.storybookUrlTtlSeconds) : "",
63
+ placeholder: "Derived from the stories",
64
+ disabled,
65
+ hint: `How long the URL given to a remote browser that cannot reach this server stays valid (${MIN_STORYBOOK_URL_TTL_OVERRIDE} to ${MAX_STORYBOOK_URL_TTL}; the floor is one slice of captures plus a grace, so a render never outlives its URL). Empty derives it from the build's size: at least 2 hours, at most 24.`
66
+ });
67
+ }
68
+ /** Capture settings tab: engines to capture on and the mode allowlist. */
69
+ function renderSettingsCapture(project, isAdmin, formState, offered) {
70
+ const browsers = parseBrowsers(project.browsers) ?? [project.browser ?? "chromium"];
71
+ const [primary = "chromium", ...extras] = browsers;
72
+ const targets = targetChoices(offered, browsers.filter((entry) => isCaptureTarget(entry)));
73
+ const allowlist = parseModesAllowlist(project.modesAllowlist) ?? [];
74
+ const action = `/projects/${project.slug}/settings/capture`;
75
+ return /* @__PURE__ */ jsxs("div", {
76
+ class: "grid max-w-form",
77
+ children: [formState?.globalError ? /* @__PURE__ */ jsx(Alert, {
78
+ tone: "danger",
79
+ children: formState.globalError
80
+ }) : null, /* @__PURE__ */ jsxs(Card, { children: [
81
+ /* @__PURE__ */ jsx(SectionTitle, { children: "Browsers" }),
82
+ /* @__PURE__ */ jsxs(Meta, { children: [
83
+ "Every build is captured on each selected target, one after another, with a baseline per target. One approval covers a story and mode on all engines; use",
84
+ " ",
85
+ /* @__PURE__ */ jsx("em", { children: "Only this browser" }),
86
+ " in the review for an engine-specific decision. Up to",
87
+ " ",
88
+ MAX_BROWSERS,
89
+ " targets. A newly added target starts from its first build."
90
+ ] }),
91
+ /* @__PURE__ */ jsxs("form", {
92
+ method: "post",
93
+ action,
94
+ "hx-post": action,
95
+ "hx-target": "body",
96
+ "hx-swap": "outerHTML",
97
+ children: [
98
+ csrfField(),
99
+ /* @__PURE__ */ jsx(BrowserFields, {
100
+ primary,
101
+ extras,
102
+ disabled: !isAdmin,
103
+ targets
104
+ }),
105
+ /* @__PURE__ */ jsx(AllowlistField, { allowlist }),
106
+ /* @__PURE__ */ jsx(UrlLifetimeField, {
107
+ project,
108
+ disabled: !isAdmin
109
+ }),
110
+ isAdmin ? /* @__PURE__ */ jsx(FormActions, { children: /* @__PURE__ */ jsx(Button, {
111
+ variant: "primary",
112
+ type: "submit",
113
+ icon: "check",
114
+ children: "Save changes"
115
+ }) }) : /* @__PURE__ */ jsx(Meta, { children: "You need admin access to edit settings." })
116
+ ]
117
+ })
118
+ ] })]
119
+ });
120
+ }
121
+ export { renderSettingsCapture };
122
+
123
+ //# sourceMappingURL=settings-capture.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-capture.mjs","names":[],"sources":["../../src/pages/settings-capture.tsx"],"sourcesContent":["import { isCaptureTarget, type RunnerTarget } from \"@storyshelf/core/adapter/capture-runner\";\nimport { MAX_BROWSERS, parseBrowsers, parseModesAllowlist } from \"@storyshelf/core/capture\";\nimport type { Project } from \"@storyshelf/core/schema\";\nimport { MAX_STORYBOOK_URL_TTL, MIN_STORYBOOK_URL_TTL_OVERRIDE } from \"@storyshelf/core/utils\";\nimport type { HtmlEscapedString } from \"hono/utils/html\";\nimport {\n Alert,\n Button,\n Card,\n CheckField,\n Field,\n FormActions,\n Meta,\n SectionTitle,\n SelectField,\n TextareaField,\n} from \"../ui/components.tsx\";\nimport { csrfField } from \"../ui/csrf-field.tsx\";\nimport { targetChoices, type TargetChoices } from \"./target-choices.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Primary engine select plus one checkbox per additional engine. */\nfunction BrowserFields(props: {\n primary: string;\n extras: string[];\n disabled: boolean;\n targets: TargetChoices;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { primary, extras, disabled, targets } = props;\n return (\n <>\n <SelectField\n label=\"Primary browser\"\n name=\"browser\"\n value={primary}\n disabled={disabled}\n options={targets.choices}\n hint=\"Used for the published preview and to bootstrap other engines.\"\n />\n {targets.choices.map((engine) => (\n <CheckField\n key={engine.value}\n label={`Also capture on ${engine.label}`}\n name={`extra_${engine.value}`}\n value={engine.value}\n checked={extras.includes(engine.value) ? true : undefined}\n disabled={disabled}\n />\n ))}\n {targets.unavailable.length > 0 ? (\n <Meta>Not selectable on this server: {targets.unavailable.join(\"; \")}.</Meta>\n ) : null}\n </>\n );\n}\n\n/** One mode name per line; empty runs every declared mode. */\nfunction AllowlistField(props: {\n allowlist: string[];\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n return (\n <TextareaField\n label=\"Mode allowlist\"\n name=\"modesAllowlist\"\n rows={4}\n value={props.allowlist.join(\"\\n\")}\n placeholder=\"dark&#10;mobile\"\n hint=\"One mode name per line. Empty runs every mode declared in your stories' parameters.\"\n />\n );\n}\n\n/** The project's override of the signed Storybook URL lifetime (ADR 0007, decision 5). */\nfunction UrlLifetimeField(props: {\n project: Project;\n disabled: boolean;\n}): HtmlEscapedString | Promise<HtmlEscapedString> {\n const { project, disabled } = props;\n return (\n <Field\n label=\"Signed Storybook URL lifetime (seconds)\"\n name=\"storybookUrlTtlSeconds\"\n type=\"number\"\n step=\"1\"\n min={String(MIN_STORYBOOK_URL_TTL_OVERRIDE)}\n max={String(MAX_STORYBOOK_URL_TTL)}\n value={project.storybookUrlTtlSeconds ? String(project.storybookUrlTtlSeconds) : \"\"}\n placeholder=\"Derived from the stories\"\n disabled={disabled}\n hint={`How long the URL given to a remote browser that cannot reach this server stays valid (${MIN_STORYBOOK_URL_TTL_OVERRIDE} to ${MAX_STORYBOOK_URL_TTL}; the floor is one slice of captures plus a grace, so a render never outlives its URL). Empty derives it from the build's size: at least 2 hours, at most 24.`}\n />\n );\n}\n\n/** Capture settings tab: engines to capture on and the mode allowlist. */\nexport function renderSettingsCapture(\n project: Project,\n isAdmin: boolean,\n formState?: { globalError?: string },\n /** What the runner offers; omitted when it publishes no list. */\n offered?: readonly RunnerTarget[],\n): unknown {\n const browsers = parseBrowsers(project.browsers) ?? [project.browser ?? \"chromium\"];\n const [primary = \"chromium\", ...extras] = browsers;\n const targets = targetChoices(\n offered,\n browsers.filter((entry) => isCaptureTarget(entry)),\n );\n const allowlist = parseModesAllowlist(project.modesAllowlist) ?? [];\n const action = `/projects/${project.slug}/settings/capture`;\n return (\n <div class=\"grid max-w-form\">\n {formState?.globalError ? <Alert tone=\"danger\">{formState.globalError}</Alert> : null}\n <Card>\n <SectionTitle>Browsers</SectionTitle>\n <Meta>\n Every build is captured on each selected target, one after another, with a baseline per\n target. One approval covers a story and mode on all engines; use{\" \"}\n <em>Only this browser</em> in the review for an engine-specific decision. Up to{\" \"}\n {MAX_BROWSERS} targets. A newly added target starts from its first build.\n </Meta>\n <form method=\"post\" action={action} hx-post={action} hx-target=\"body\" hx-swap=\"outerHTML\">\n {csrfField()}\n <BrowserFields primary={primary} extras={extras} disabled={!isAdmin} targets={targets} />\n <AllowlistField allowlist={allowlist} />\n <UrlLifetimeField project={project} disabled={!isAdmin} />\n {isAdmin ? (\n <FormActions>\n <Button variant=\"primary\" type=\"submit\" icon=\"check\">\n Save changes\n </Button>\n </FormActions>\n ) : (\n <Meta>You need admin access to edit settings.</Meta>\n )}\n </form>\n </Card>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAuBA,SAAS,cAAc,OAK4B;CACjD,MAAM,EAAE,SAAS,QAAQ,UAAU,YAAY;CAC/C,OACE,qBAAA,UAAA,EAAA,UAAA;EACE,oBAAC,aAAD;GACE,OAAM;GACN,MAAK;GACL,OAAO;GACG;GACV,SAAS,QAAQ;GACjB,MAAK;EACN,CAAA;EACA,QAAQ,QAAQ,KAAK,WACpB,oBAAC,YAAD;GAEE,OAAO,mBAAmB,OAAO;GACjC,MAAM,SAAS,OAAO;GACtB,OAAO,OAAO;GACd,SAAS,OAAO,SAAS,OAAO,KAAK,IAAI,OAAO,KAAA;GACtC;EACX,GANM,OAAO,KAMb,CACF;EACA,QAAQ,YAAY,SAAS,IAC5B,qBAAC,MAAD,EAAA,UAAA;GAAM;GAAgC,QAAQ,YAAY,KAAK,IAAI;GAAE;EAAO,EAAA,CAAA,IAC1E;CACJ,EAAA,CAAA;AAEN;;AAGA,SAAS,eAAe,OAE2B;CACjD,OACE,oBAAC,eAAD;EACE,OAAM;EACN,MAAK;EACL,MAAM;EACN,OAAO,MAAM,UAAU,KAAK,IAAI;EAChC,aAAY;EACZ,MAAK;CACN,CAAA;AAEL;;AAGA,SAAS,iBAAiB,OAGyB;CACjD,MAAM,EAAE,SAAS,aAAa;CAC9B,OACE,oBAAC,OAAD;EACE,OAAM;EACN,MAAK;EACL,MAAK;EACL,MAAK;EACL,KAAK,OAAO,8BAA8B;EAC1C,KAAK,OAAO,qBAAqB;EACjC,OAAO,QAAQ,yBAAyB,OAAO,QAAQ,sBAAsB,IAAI;EACjF,aAAY;EACF;EACV,MAAM,yFAAyF,+BAA+B,MAAM,sBAAsB;CAC3J,CAAA;AAEL;;AAGA,SAAgB,sBACd,SACA,SACA,WAEA,SACS;CACT,MAAM,WAAW,cAAc,QAAQ,QAAQ,KAAK,CAAC,QAAQ,WAAW,UAAU;CAClF,MAAM,CAAC,UAAU,YAAY,GAAG,UAAU;CAC1C,MAAM,UAAU,cACd,SACA,SAAS,QAAQ,UAAU,gBAAgB,KAAK,CAAC,CACnD;CACA,MAAM,YAAY,oBAAoB,QAAQ,cAAc,KAAK,CAAC;CAClE,MAAM,SAAS,aAAa,QAAQ,KAAK;CACzC,OACE,qBAAC,OAAD;EAAK,OAAM;EAAX,UAAA,CACG,WAAW,cAAc,oBAAC,OAAD;GAAO,MAAK;GAAU,UAAA,UAAU;EAAmB,CAAA,IAAI,MACjF,qBAAC,MAAD,EAAA,UAAA;GACE,oBAAC,cAAD,EAAA,UAAc,WAAsB,CAAA;GACpC,qBAAC,MAAD,EAAA,UAAA;IAAM;IAE6D;IACjE,oBAAC,MAAD,EAAA,UAAI,oBAAqB,CAAA;IAAC;IAAsD;IAC/E;IAAa;GACV,EAAA,CAAA;GACN,qBAAC,QAAD;IAAM,QAAO;IAAe;IAAQ,WAAS;IAAQ,aAAU;IAAO,WAAQ;IAA9E,UAAA;KACG,UAAU;KACX,oBAAC,eAAD;MAAwB;MAAiB;MAAQ,UAAU,CAAC;MAAkB;KAAU,CAAA;KACxF,oBAAC,gBAAD,EAA2B,UAAY,CAAA;KACvC,oBAAC,kBAAD;MAA2B;MAAS,UAAU,CAAC;KAAU,CAAA;KACxD,UACC,oBAAC,aAAD,EAAA,UACE,oBAAC,QAAD;MAAQ,SAAQ;MAAU,MAAK;MAAS,MAAK;MAAQ,UAAA;KAE7C,CAAA,EACG,CAAA,IAEb,oBAAC,MAAD,EAAA,UAAM,0CAA6C,CAAA;IAEjD;;EACF,EAAA,CAAA,CACH;;AAET"}
@@ -0,0 +1,98 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { Button } from "../ui/buttons.mjs";
5
+ import { Badge, Meta } from "../ui/feedback.mjs";
6
+ import { Field, FormActions } from "../ui/forms.mjs";
7
+ import { Card, SectionTitle } from "../ui/layout.mjs";
8
+ import { csrfField } from "../ui/csrf-field.mjs";
9
+ import { RemoveForm } from "./settings-design-links.mjs";
10
+ import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
11
+ // src/pages/settings-design-credential.tsx
12
+ const STATUS_BADGES = {
13
+ unknown: {
14
+ tone: "neutral",
15
+ text: "Not used yet"
16
+ },
17
+ ok: {
18
+ tone: "success",
19
+ text: "Working"
20
+ },
21
+ unauthorized: {
22
+ tone: "danger",
23
+ text: "Expired or revoked"
24
+ },
25
+ forbidden: {
26
+ tone: "warning",
27
+ text: "No access to a linked file"
28
+ }
29
+ };
30
+ /** Settings card for one provider's project-wide credential (admin only). */
31
+ function CredentialCard(props) {
32
+ const { base, provider, formState } = props;
33
+ const action = `${base}/${provider.kind}/token`;
34
+ return /* @__PURE__ */ jsxs(Card, { children: [
35
+ /* @__PURE__ */ jsx(SectionTitle, { children: provider.credentialLabel }),
36
+ /* @__PURE__ */ jsx(Meta, { children: provider.credentialHelp }),
37
+ provider.credential ? /* @__PURE__ */ jsx(CredentialStatus, {
38
+ action,
39
+ provider,
40
+ credential: provider.credential
41
+ }) : /* @__PURE__ */ jsx(Meta, { children: "No credential set." }),
42
+ /* @__PURE__ */ jsx(CredentialForm, {
43
+ action,
44
+ provider,
45
+ error: formState?.errors?.[`token:${provider.kind}`]
46
+ })
47
+ ] });
48
+ }
49
+ function CredentialStatus(props) {
50
+ const { action, provider, credential } = props;
51
+ const badge = STATUS_BADGES[credential.status];
52
+ const days = credential.daysLeft;
53
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("p", { children: [
54
+ /* @__PURE__ */ jsx(Badge, {
55
+ tone: badge.tone,
56
+ children: badge.text
57
+ }),
58
+ " ",
59
+ days === null ? null : /* @__PURE__ */ jsx(Meta, {
60
+ as: "span",
61
+ children: days >= 0 ? `Rotate within ${days} day${days === 1 ? "" : "s"}` : `Past the ${provider.maxAgeDays}-day cap — rotate now`
62
+ })
63
+ ] }), /* @__PURE__ */ jsx(RemoveForm, {
64
+ action: `${action}/delete`,
65
+ confirm: `Remove the ${provider.name} credential?`,
66
+ label: `Remove ${provider.name} credential`
67
+ })] });
68
+ }
69
+ function CredentialForm(props) {
70
+ const { action, provider } = props;
71
+ const replace = provider.credential !== null;
72
+ return /* @__PURE__ */ jsxs("form", {
73
+ method: "post",
74
+ action,
75
+ "hx-post": action,
76
+ "hx-target": "body",
77
+ children: [
78
+ csrfField(),
79
+ /* @__PURE__ */ jsx(Field, {
80
+ label: replace ? "Replace token" : "Token",
81
+ name: "token",
82
+ type: "password",
83
+ required: true,
84
+ autocomplete: "off",
85
+ placeholder: provider.credentialPlaceholder,
86
+ error: props.error
87
+ }),
88
+ /* @__PURE__ */ jsx(FormActions, { children: /* @__PURE__ */ jsx(Button, {
89
+ variant: "primary",
90
+ type: "submit",
91
+ children: replace ? "Rotate token" : "Save token"
92
+ }) })
93
+ ]
94
+ });
95
+ }
96
+ export { CredentialCard };
97
+
98
+ //# sourceMappingURL=settings-design-credential.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings-design-credential.mjs","names":[],"sources":["../../src/pages/settings-design-credential.tsx"],"sourcesContent":["import type { HtmlEscapedString } from \"hono/utils/html\";\nimport { Badge, Button, Card, Field, FormActions, Meta, SectionTitle } from \"../ui/components.tsx\";\nimport { csrfField } from \"../ui/csrf-field.tsx\";\nimport { RemoveForm, type DesignFormState } from \"./settings-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/** Last observed outcome of a design credential. */\nexport type DesignCredentialState = \"unknown\" | \"ok\" | \"unauthorized\" | \"forbidden\";\n\n/** A stored credential as shown to admins: never the value. */\nexport interface SettingsDesignCredential {\n status: DesignCredentialState;\n /** Days until the provider's maximum credential age, counted from when it was set or replaced (negative once past); null without a limit. */\n daysLeft: number | null;\n checkedAt: string | null;\n}\n\n/** A configured design provider with its (optional) project credential. */\nexport interface SettingsDesignProvider {\n kind: string;\n name: string;\n credentialLabel: string;\n credentialHelp: string;\n credentialPlaceholder?: string;\n urlHint: string;\n /** Provider's maximum credential lifetime in days, when it has one. */\n maxAgeDays?: number;\n credential: SettingsDesignCredential | null;\n}\n\nconst STATUS_BADGES: Record<\n DesignCredentialState,\n { tone: \"neutral\" | \"success\" | \"warning\" | \"danger\"; text: string }\n> = {\n unknown: { tone: \"neutral\", text: \"Not used yet\" },\n ok: { tone: \"success\", text: \"Working\" },\n unauthorized: { tone: \"danger\", text: \"Expired or revoked\" },\n forbidden: { tone: \"warning\", text: \"No access to a linked file\" },\n};\n\n/** Settings card for one provider's project-wide credential (admin only). */\nexport function CredentialCard(props: {\n base: string;\n provider: SettingsDesignProvider;\n formState?: DesignFormState;\n}): Rendered {\n const { base, provider, formState } = props;\n const action = `${base}/${provider.kind}/token`;\n return (\n <Card>\n <SectionTitle>{provider.credentialLabel}</SectionTitle>\n <Meta>{provider.credentialHelp}</Meta>\n {provider.credential ? (\n <CredentialStatus action={action} provider={provider} credential={provider.credential} />\n ) : (\n <Meta>No credential set.</Meta>\n )}\n <CredentialForm\n action={action}\n provider={provider}\n error={formState?.errors?.[`token:${provider.kind}`]}\n />\n </Card>\n );\n}\n\nfunction CredentialStatus(props: {\n action: string;\n provider: SettingsDesignProvider;\n credential: SettingsDesignCredential;\n}): Rendered {\n const { action, provider, credential } = props;\n const badge = STATUS_BADGES[credential.status];\n const days = credential.daysLeft;\n return (\n <>\n <p>\n <Badge tone={badge.tone}>{badge.text}</Badge>{\" \"}\n {days === null ? null : (\n <Meta as=\"span\">\n {days >= 0\n ? `Rotate within ${days} day${days === 1 ? \"\" : \"s\"}`\n : `Past the ${provider.maxAgeDays}-day cap — rotate now`}\n </Meta>\n )}\n </p>\n <RemoveForm\n action={`${action}/delete`}\n confirm={`Remove the ${provider.name} credential?`}\n label={`Remove ${provider.name} credential`}\n />\n </>\n );\n}\n\nfunction CredentialForm(props: {\n action: string;\n provider: SettingsDesignProvider;\n error?: string;\n}): Rendered {\n const { action, provider } = props;\n const replace = provider.credential !== null;\n return (\n <form method=\"post\" action={action} hx-post={action} hx-target=\"body\">\n {csrfField()}\n <Field\n label={replace ? \"Replace token\" : \"Token\"}\n name=\"token\"\n type=\"password\"\n required\n autocomplete=\"off\"\n placeholder={provider.credentialPlaceholder}\n error={props.error}\n />\n <FormActions>\n <Button variant=\"primary\" type=\"submit\">\n {replace ? \"Rotate token\" : \"Save token\"}\n </Button>\n </FormActions>\n </form>\n );\n}\n"],"mappings":";;;;;;;;;;;AAiCA,MAAM,gBAGF;CACF,SAAS;EAAE,MAAM;EAAW,MAAM;CAAe;CACjD,IAAI;EAAE,MAAM;EAAW,MAAM;CAAU;CACvC,cAAc;EAAE,MAAM;EAAU,MAAM;CAAqB;CAC3D,WAAW;EAAE,MAAM;EAAW,MAAM;CAA6B;AACnE;;AAGA,SAAgB,eAAe,OAIlB;CACX,MAAM,EAAE,MAAM,UAAU,cAAc;CACtC,MAAM,SAAS,GAAG,KAAK,GAAG,SAAS,KAAK;CACxC,OACE,qBAAC,MAAD,EAAA,UAAA;EACE,oBAAC,cAAD,EAAA,UAAe,SAAS,gBAA8B,CAAA;EACtD,oBAAC,MAAD,EAAA,UAAO,SAAS,eAAqB,CAAA;EACpC,SAAS,aACR,oBAAC,kBAAD;GAA0B;GAAkB;GAAU,YAAY,SAAS;EAAa,CAAA,IAExF,oBAAC,MAAD,EAAA,UAAM,qBAAwB,CAAA;EAEhC,oBAAC,gBAAD;GACU;GACE;GACV,OAAO,WAAW,SAAS,SAAS,SAAS;EAC9C,CAAA;CACG,EAAA,CAAA;AAEV;AAEA,SAAS,iBAAiB,OAIb;CACX,MAAM,EAAE,QAAQ,UAAU,eAAe;CACzC,MAAM,QAAQ,cAAc,WAAW;CACvC,MAAM,OAAO,WAAW;CACxB,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,KAAD,EAAA,UAAA;EACE,oBAAC,OAAD;GAAO,MAAM,MAAM;GAAO,UAAA,MAAM;EAAY,CAAA;EAAE;EAC7C,SAAS,OAAO,OACf,oBAAC,MAAD;GAAM,IAAG;GACN,UAAA,QAAQ,IACL,iBAAiB,KAAK,MAAM,SAAS,IAAI,KAAK,QAC9C,YAAY,SAAS,WAAW;EAChC,CAAA;CAEP,EAAA,CAAA,GACH,oBAAC,YAAD;EACE,QAAQ,GAAG,OAAO;EAClB,SAAS,cAAc,SAAS,KAAK;EACrC,OAAO,UAAU,SAAS,KAAK;CAChC,CAAA,CACD,EAAA,CAAA;AAEN;AAEA,SAAS,eAAe,OAIX;CACX,MAAM,EAAE,QAAQ,aAAa;CAC7B,MAAM,UAAU,SAAS,eAAe;CACxC,OACE,qBAAC,QAAD;EAAM,QAAO;EAAe;EAAQ,WAAS;EAAQ,aAAU;EAA/D,UAAA;GACG,UAAU;GACX,oBAAC,OAAD;IACE,OAAO,UAAU,kBAAkB;IACnC,MAAK;IACL,MAAK;IACL,UAAA;IACA,cAAa;IACb,aAAa,SAAS;IACtB,OAAO,MAAM;GACd,CAAA;GACD,oBAAC,aAAD,EAAA,UACE,oBAAC,QAAD;IAAQ,SAAQ;IAAU,MAAK;IAC5B,UAAA,UAAU,iBAAiB;GACtB,CAAA,EACG,CAAA;EACT;;AAEV"}
@@ -0,0 +1,178 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { Button } from "../ui/buttons.mjs";
5
+ import { Meta } from "../ui/feedback.mjs";
6
+ import { Field, FormActions } from "../ui/forms.mjs";
7
+ import { Card, SectionTitle } from "../ui/layout.mjs";
8
+ import { Table } from "../ui/table.mjs";
9
+ import { csrfField } from "../ui/csrf-field.mjs";
10
+ import { jsx, jsxs } from "hono/jsx/jsx-runtime";
11
+ // src/pages/settings-design-links.tsx
12
+ function FilesCard(props) {
13
+ const { base, files, isAdmin, formState, urlHint } = props;
14
+ return /* @__PURE__ */ jsxs(Card, { children: [
15
+ /* @__PURE__ */ jsx(SectionTitle, { children: "Design files" }),
16
+ /* @__PURE__ */ jsx(Meta, { children: "Design files for this project. They appear in the library header; individual stories link to a design through their component (see below) or their story parameters." }),
17
+ /* @__PURE__ */ jsx(LinkTable, {
18
+ rows: files.map((file) => ({
19
+ id: file.id,
20
+ key: file.label,
21
+ url: file.url,
22
+ removeAction: isAdmin ? `${base}/files/${file.id}/delete` : void 0
23
+ })),
24
+ keyHeading: "Label",
25
+ confirm: "Remove this design file?"
26
+ }),
27
+ files.length === 0 ? /* @__PURE__ */ jsx(Meta, { children: "No design files linked." }) : null,
28
+ isAdmin ? /* @__PURE__ */ jsx(AddFileForm, {
29
+ base,
30
+ urlHint,
31
+ error: formState?.errors?.["file"]
32
+ }) : null
33
+ ] });
34
+ }
35
+ /** Rows of `key → design link` with an optional remove action. */
36
+ function LinkTable(props) {
37
+ return /* @__PURE__ */ jsx(Table, { children: /* @__PURE__ */ jsxs("table", { children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
38
+ /* @__PURE__ */ jsx("th", { children: props.keyHeading }),
39
+ /* @__PURE__ */ jsx("th", { children: "Design" }),
40
+ /* @__PURE__ */ jsx("th", { children: /* @__PURE__ */ jsx("span", {
41
+ class: "visually-hidden",
42
+ children: "Actions"
43
+ }) })
44
+ ] }) }), /* @__PURE__ */ jsx("tbody", { children: props.rows.map((row) => /* @__PURE__ */ jsxs("tr", { children: [
45
+ /* @__PURE__ */ jsx("td", { children: row.key }),
46
+ /* @__PURE__ */ jsx("td", {
47
+ class: "truncate max-w-cell",
48
+ title: row.url,
49
+ children: /* @__PURE__ */ jsx("a", {
50
+ href: row.url,
51
+ target: "_blank",
52
+ rel: "noopener noreferrer",
53
+ children: row.url
54
+ })
55
+ }),
56
+ /* @__PURE__ */ jsx("td", { children: row.removeAction ? /* @__PURE__ */ jsx(RemoveForm, {
57
+ action: row.removeAction,
58
+ confirm: props.confirm,
59
+ label: `Remove ${row.key}`
60
+ }) : null })
61
+ ] }, row.id)) })] }) });
62
+ }
63
+ function AddFileForm(props) {
64
+ const action = `${props.base}/files`;
65
+ return /* @__PURE__ */ jsxs("form", {
66
+ method: "post",
67
+ action,
68
+ "hx-post": action,
69
+ "hx-target": "body",
70
+ children: [
71
+ csrfField(),
72
+ /* @__PURE__ */ jsxs("div", {
73
+ class: "grid grid--2",
74
+ children: [/* @__PURE__ */ jsx(Field, {
75
+ label: "Label",
76
+ name: "label",
77
+ required: true,
78
+ placeholder: "Design system"
79
+ }), /* @__PURE__ */ jsx(Field, {
80
+ label: "Design URL",
81
+ name: "url",
82
+ type: "url",
83
+ required: true,
84
+ placeholder: props.urlHint,
85
+ error: props.error
86
+ })]
87
+ }),
88
+ /* @__PURE__ */ jsx(FormActions, { children: /* @__PURE__ */ jsx(Button, {
89
+ variant: "primary",
90
+ type: "submit",
91
+ icon: "plus",
92
+ children: "Add design file"
93
+ }) })
94
+ ]
95
+ });
96
+ }
97
+ function ComponentsCard(props) {
98
+ const { base, components, formState, urlHint } = props;
99
+ return /* @__PURE__ */ jsxs(Card, { children: [
100
+ /* @__PURE__ */ jsx(SectionTitle, { children: "Component mapping" }),
101
+ /* @__PURE__ */ jsxs(Meta, { children: [
102
+ "Map a story file (its import path) to a design. A manual mapping overrides the story's own ",
103
+ /* @__PURE__ */ jsx("code", { children: "storyshelf.figma" }),
104
+ " and ",
105
+ /* @__PURE__ */ jsx("code", { children: "design" }),
106
+ " parameters, and survives new builds. Mappings are keyed by the story file path, so update one after a file is renamed or moved."
107
+ ] }),
108
+ /* @__PURE__ */ jsx(LinkTable, {
109
+ rows: components.map((component) => ({
110
+ id: component.id,
111
+ key: component.importPath,
112
+ url: component.url,
113
+ removeAction: `${base}/components/${component.id}/delete`
114
+ })),
115
+ keyHeading: "Story file",
116
+ confirm: "Remove this mapping?"
117
+ }),
118
+ components.length === 0 ? /* @__PURE__ */ jsx(Meta, { children: "No component mappings." }) : null,
119
+ /* @__PURE__ */ jsx(MapComponentForm, {
120
+ base,
121
+ urlHint,
122
+ error: formState?.errors?.["component"]
123
+ })
124
+ ] });
125
+ }
126
+ function MapComponentForm(props) {
127
+ const action = `${props.base}/components`;
128
+ return /* @__PURE__ */ jsxs("form", {
129
+ method: "post",
130
+ action,
131
+ "hx-post": action,
132
+ "hx-target": "body",
133
+ children: [
134
+ csrfField(),
135
+ /* @__PURE__ */ jsxs("div", {
136
+ class: "grid grid--2",
137
+ children: [/* @__PURE__ */ jsx(Field, {
138
+ label: "Story file",
139
+ name: "importPath",
140
+ required: true,
141
+ placeholder: "./src/Button.stories.tsx"
142
+ }), /* @__PURE__ */ jsx(Field, {
143
+ label: "Design URL",
144
+ name: "url",
145
+ type: "url",
146
+ required: true,
147
+ placeholder: props.urlHint,
148
+ error: props.error
149
+ })]
150
+ }),
151
+ /* @__PURE__ */ jsx(FormActions, { children: /* @__PURE__ */ jsx(Button, {
152
+ variant: "primary",
153
+ type: "submit",
154
+ icon: "plus",
155
+ children: "Map component"
156
+ }) })
157
+ ]
158
+ });
159
+ }
160
+ function RemoveForm(props) {
161
+ return /* @__PURE__ */ jsxs("form", {
162
+ method: "post",
163
+ action: props.action,
164
+ "hx-post": props.action,
165
+ "hx-target": "body",
166
+ "hx-confirm": props.confirm,
167
+ children: [csrfField(), /* @__PURE__ */ jsx(Button, {
168
+ variant: "ghost",
169
+ size: "sm",
170
+ type: "submit",
171
+ "aria-label": props.label,
172
+ children: "Remove"
173
+ })]
174
+ });
175
+ }
176
+ export { ComponentsCard, FilesCard, RemoveForm };
177
+
178
+ //# sourceMappingURL=settings-design-links.mjs.map