@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
@@ -0,0 +1,104 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/routes/page-routes.ts
5
+ /** HTML and auth routes, keyed by `METHOD /hono-pattern`. */
6
+ const PAGE_ROUTE_CLASSES = {
7
+ "GET /": "hosting",
8
+ "GET /projects": "hosting",
9
+ "GET /projects/new": "hosting",
10
+ "POST /projects/new": "hosting",
11
+ "GET /projects/:slug/labels": "hosting",
12
+ "GET /projects/:slug/labels/:key/*": "hosting",
13
+ "GET /projects/:slug/settings": "hosting",
14
+ "POST /projects/:slug/settings": "hosting",
15
+ "POST /projects/:slug/settings/review": "hosting",
16
+ "POST /projects/:slug/delete": "hosting",
17
+ "GET /projects/:slug/settings/labels": "hosting",
18
+ "POST /projects/:slug/settings/labels": "hosting",
19
+ "POST /projects/:slug/settings/labels/:key": "hosting",
20
+ "POST /projects/:slug/settings/labels/:key/delete": "hosting",
21
+ "GET /projects/:slug/settings/links": "hosting",
22
+ "POST /projects/:slug/settings/links": "hosting",
23
+ "POST /projects/:slug/settings/links/delete": "hosting",
24
+ "GET /projects/:slug/settings/design": "hosting",
25
+ "POST /projects/:slug/settings/design/files": "hosting",
26
+ "POST /projects/:slug/settings/design/files/:id/delete": "hosting",
27
+ "POST /projects/:slug/settings/design/components": "hosting",
28
+ "POST /projects/:slug/settings/design/components/:id/delete": "hosting",
29
+ "POST /projects/:slug/settings/design/:provider/token": "hosting",
30
+ "POST /projects/:slug/settings/design/:provider/token/delete": "hosting",
31
+ "GET /projects/:slug/settings/tokens": "hosting",
32
+ "POST /projects/:slug/settings/tokens": "hosting",
33
+ "POST /projects/:slug/settings/tokens/:tokenId/delete": "hosting",
34
+ "GET /projects/:slug/settings/webhooks": "hosting",
35
+ "POST /projects/:slug/settings/webhooks": "hosting",
36
+ "POST /projects/:slug/settings/webhooks/:webhookId/delete": "hosting",
37
+ "GET /projects/:slug/settings/notifications": "hosting",
38
+ "POST /projects/:slug/settings/notifications": "hosting",
39
+ "POST /projects/:slug/settings/notifications/:channelId/delete": "hosting",
40
+ "POST /projects/:slug/settings/notifications/me": "hosting",
41
+ "GET /projects/:slug/settings/members": "hosting",
42
+ "POST /projects/:slug/settings/members": "hosting",
43
+ "POST /projects/:slug/settings/members/:userId/remove": "hosting",
44
+ "POST /projects/:slug/settings/members/groups": "hosting",
45
+ "POST /projects/:slug/settings/members/groups/:mappingId/remove": "hosting",
46
+ "GET /admin": "hosting",
47
+ "POST /admin/credentials/reencrypt": "hosting",
48
+ "POST /admin/public-origin": "hosting",
49
+ "POST /admin/public-origin/report": "hosting",
50
+ "POST /admin/public-origin/clear": "hosting",
51
+ "GET /_": "hosting",
52
+ "GET /_/": "hosting",
53
+ "GET /_/:id": "hosting",
54
+ "GET /_/:id/*": "hosting",
55
+ "GET /projects/:slug/storybook": "hosting",
56
+ "GET /projects/:slug/storybook/:key/:value": "hosting",
57
+ "GET /projects/:slug/storybook/build/:buildId/*": "hosting",
58
+ "GET /_s/:token/*": "testing",
59
+ "GET /auth/login": "hosting",
60
+ "GET /auth/engine/login": "hosting",
61
+ "POST /auth/engine/login": "hosting",
62
+ "GET /auth/engine/:providerId": "hosting",
63
+ "GET /auth/invites/:inviteId": "hosting",
64
+ "POST /auth/invites/:inviteId": "hosting",
65
+ "POST /auth/logout": "hosting",
66
+ "GET /.well-known/openid-configuration": "hosting",
67
+ "GET /.well-known/change-password": "hosting",
68
+ "GET /profile": "hosting",
69
+ "POST /profile": "hosting",
70
+ "POST /profile/password": "hosting",
71
+ "POST /profile/sessions/revoke": "hosting",
72
+ "POST /profile/sessions/revoke-others": "hosting",
73
+ "POST /profile/passkeys/:passkeyId/delete": "hosting",
74
+ "POST /profile/notifications": "hosting",
75
+ "GET /connections/:service/start": "hosting",
76
+ "GET /connections/:service/callback": "hosting",
77
+ "POST /connections/:service/disconnect": "hosting",
78
+ "ALL /api/auth/*": "hosting",
79
+ "GET /projects/:slug/jobs": "testing",
80
+ "GET /projects/:slug/builds/:buildId/diff": "testing",
81
+ "GET /projects/:slug/settings/tests": "testing",
82
+ "POST /projects/:slug/settings/tests": "testing",
83
+ "GET /projects/:slug/settings/capture": "testing",
84
+ "POST /projects/:slug/settings/capture": "testing",
85
+ "GET /projects/:slug/settings/ai": "testing",
86
+ "POST /projects/:slug/settings/ai": "testing",
87
+ "GET /admin/ai": "testing",
88
+ "GET /projects/:slug/insights/health/panel": "testing",
89
+ "POST /projects/:slug/insights/health/generate": "testing",
90
+ "GET /projects/:slug/builds/:buildId/insights/panel": "testing",
91
+ "POST /projects/:slug/builds/:buildId/insights/generate": "testing",
92
+ "POST /projects/:slug/builds/:buildId/comments": "review",
93
+ "POST /projects/:slug/builds/:buildId/comments/:commentId/resolve": "review",
94
+ "POST /projects/:slug/builds/:buildId/reviews": "review",
95
+ "GET /projects/:slug": "both",
96
+ "GET /projects/:slug/builds": "both",
97
+ "GET /projects/:slug/builds/:buildId": "both",
98
+ "GET /projects/:slug/library": "both",
99
+ "GET /projects/:slug/components/*": "both"
100
+ };
101
+ //#endregion
102
+ export { PAGE_ROUTE_CLASSES };
103
+
104
+ //# sourceMappingURL=page-routes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-routes.mjs","names":[],"sources":["../../src/routes/page-routes.ts"],"sourcesContent":["/** Classes of the HTML pages, Storybook serving, auth flow and engine routes. */\nimport type { RouteClass } from \"./classification.ts\";\n\n/** HTML and auth routes, keyed by `METHOD /hono-pattern`. */\nexport const PAGE_ROUTE_CLASSES: Readonly<Record<string, RouteClass>> = {\n // hosting\n \"GET /\": \"hosting\",\n \"GET /projects\": \"hosting\",\n \"GET /projects/new\": \"hosting\",\n \"POST /projects/new\": \"hosting\",\n \"GET /projects/:slug/labels\": \"hosting\",\n \"GET /projects/:slug/labels/:key/*\": \"hosting\",\n \"GET /projects/:slug/settings\": \"hosting\",\n \"POST /projects/:slug/settings\": \"hosting\",\n \"POST /projects/:slug/settings/review\": \"hosting\",\n \"POST /projects/:slug/delete\": \"hosting\",\n \"GET /projects/:slug/settings/labels\": \"hosting\",\n \"POST /projects/:slug/settings/labels\": \"hosting\",\n \"POST /projects/:slug/settings/labels/:key\": \"hosting\",\n \"POST /projects/:slug/settings/labels/:key/delete\": \"hosting\",\n \"GET /projects/:slug/settings/links\": \"hosting\",\n \"POST /projects/:slug/settings/links\": \"hosting\",\n \"POST /projects/:slug/settings/links/delete\": \"hosting\",\n \"GET /projects/:slug/settings/design\": \"hosting\",\n \"POST /projects/:slug/settings/design/files\": \"hosting\",\n \"POST /projects/:slug/settings/design/files/:id/delete\": \"hosting\",\n \"POST /projects/:slug/settings/design/components\": \"hosting\",\n \"POST /projects/:slug/settings/design/components/:id/delete\": \"hosting\",\n \"POST /projects/:slug/settings/design/:provider/token\": \"hosting\",\n \"POST /projects/:slug/settings/design/:provider/token/delete\": \"hosting\",\n \"GET /projects/:slug/settings/tokens\": \"hosting\",\n \"POST /projects/:slug/settings/tokens\": \"hosting\",\n \"POST /projects/:slug/settings/tokens/:tokenId/delete\": \"hosting\",\n \"GET /projects/:slug/settings/webhooks\": \"hosting\",\n \"POST /projects/:slug/settings/webhooks\": \"hosting\",\n \"POST /projects/:slug/settings/webhooks/:webhookId/delete\": \"hosting\",\n \"GET /projects/:slug/settings/notifications\": \"hosting\",\n \"POST /projects/:slug/settings/notifications\": \"hosting\",\n \"POST /projects/:slug/settings/notifications/:channelId/delete\": \"hosting\",\n \"POST /projects/:slug/settings/notifications/me\": \"hosting\",\n \"GET /projects/:slug/settings/members\": \"hosting\",\n \"POST /projects/:slug/settings/members\": \"hosting\",\n \"POST /projects/:slug/settings/members/:userId/remove\": \"hosting\",\n \"POST /projects/:slug/settings/members/groups\": \"hosting\",\n \"POST /projects/:slug/settings/members/groups/:mappingId/remove\": \"hosting\",\n \"GET /admin\": \"hosting\",\n \"POST /admin/credentials/reencrypt\": \"hosting\",\n \"POST /admin/public-origin\": \"hosting\",\n \"POST /admin/public-origin/report\": \"hosting\",\n \"POST /admin/public-origin/clear\": \"hosting\",\n \"GET /_\": \"hosting\",\n \"GET /_/\": \"hosting\",\n \"GET /_/:id\": \"hosting\",\n \"GET /_/:id/*\": \"hosting\",\n \"GET /projects/:slug/storybook\": \"hosting\",\n \"GET /projects/:slug/storybook/:key/:value\": \"hosting\",\n \"GET /projects/:slug/storybook/build/:buildId/*\": \"hosting\",\n \"GET /_s/:token/*\": \"testing\",\n \"GET /auth/login\": \"hosting\",\n \"GET /auth/engine/login\": \"hosting\",\n \"POST /auth/engine/login\": \"hosting\",\n \"GET /auth/engine/:providerId\": \"hosting\",\n \"GET /auth/invites/:inviteId\": \"hosting\",\n \"POST /auth/invites/:inviteId\": \"hosting\",\n \"POST /auth/logout\": \"hosting\",\n \"GET /.well-known/openid-configuration\": \"hosting\",\n \"GET /.well-known/change-password\": \"hosting\",\n \"GET /profile\": \"hosting\",\n \"POST /profile\": \"hosting\",\n \"POST /profile/password\": \"hosting\",\n \"POST /profile/sessions/revoke\": \"hosting\",\n \"POST /profile/sessions/revoke-others\": \"hosting\",\n \"POST /profile/passkeys/:passkeyId/delete\": \"hosting\",\n \"POST /profile/notifications\": \"hosting\",\n \"GET /connections/:service/start\": \"hosting\",\n \"GET /connections/:service/callback\": \"hosting\",\n \"POST /connections/:service/disconnect\": \"hosting\",\n \"ALL /api/auth/*\": \"hosting\",\n // testing\n \"GET /projects/:slug/jobs\": \"testing\",\n \"GET /projects/:slug/builds/:buildId/diff\": \"testing\",\n \"GET /projects/:slug/settings/tests\": \"testing\",\n \"POST /projects/:slug/settings/tests\": \"testing\",\n \"GET /projects/:slug/settings/capture\": \"testing\",\n \"POST /projects/:slug/settings/capture\": \"testing\",\n \"GET /projects/:slug/settings/ai\": \"testing\",\n \"POST /projects/:slug/settings/ai\": \"testing\",\n \"GET /admin/ai\": \"testing\",\n \"GET /projects/:slug/insights/health/panel\": \"testing\",\n \"POST /projects/:slug/insights/health/generate\": \"testing\",\n \"GET /projects/:slug/builds/:buildId/insights/panel\": \"testing\",\n \"POST /projects/:slug/builds/:buildId/insights/generate\": \"testing\",\n // review\n \"POST /projects/:slug/builds/:buildId/comments\": \"review\",\n \"POST /projects/:slug/builds/:buildId/comments/:commentId/resolve\": \"review\",\n \"POST /projects/:slug/builds/:buildId/reviews\": \"review\",\n // both\n \"GET /projects/:slug\": \"both\",\n \"GET /projects/:slug/builds\": \"both\",\n \"GET /projects/:slug/builds/:buildId\": \"both\",\n \"GET /projects/:slug/library\": \"both\",\n \"GET /projects/:slug/components/*\": \"both\",\n};\n"],"mappings":";;;;;AAIA,MAAa,qBAA2D;CAEtE,SAAS;CACT,iBAAiB;CACjB,qBAAqB;CACrB,sBAAsB;CACtB,8BAA8B;CAC9B,qCAAqC;CACrC,gCAAgC;CAChC,iCAAiC;CACjC,wCAAwC;CACxC,+BAA+B;CAC/B,uCAAuC;CACvC,wCAAwC;CACxC,6CAA6C;CAC7C,oDAAoD;CACpD,sCAAsC;CACtC,uCAAuC;CACvC,8CAA8C;CAC9C,uCAAuC;CACvC,8CAA8C;CAC9C,yDAAyD;CACzD,mDAAmD;CACnD,8DAA8D;CAC9D,wDAAwD;CACxD,+DAA+D;CAC/D,uCAAuC;CACvC,wCAAwC;CACxC,wDAAwD;CACxD,yCAAyC;CACzC,0CAA0C;CAC1C,4DAA4D;CAC5D,8CAA8C;CAC9C,+CAA+C;CAC/C,iEAAiE;CACjE,kDAAkD;CAClD,wCAAwC;CACxC,yCAAyC;CACzC,wDAAwD;CACxD,gDAAgD;CAChD,kEAAkE;CAClE,cAAc;CACd,qCAAqC;CACrC,6BAA6B;CAC7B,oCAAoC;CACpC,mCAAmC;CACnC,UAAU;CACV,WAAW;CACX,cAAc;CACd,gBAAgB;CAChB,iCAAiC;CACjC,6CAA6C;CAC7C,kDAAkD;CAClD,oBAAoB;CACpB,mBAAmB;CACnB,0BAA0B;CAC1B,2BAA2B;CAC3B,gCAAgC;CAChC,+BAA+B;CAC/B,gCAAgC;CAChC,qBAAqB;CACrB,yCAAyC;CACzC,oCAAoC;CACpC,gBAAgB;CAChB,iBAAiB;CACjB,0BAA0B;CAC1B,iCAAiC;CACjC,wCAAwC;CACxC,4CAA4C;CAC5C,+BAA+B;CAC/B,mCAAmC;CACnC,sCAAsC;CACtC,yCAAyC;CACzC,mBAAmB;CAEnB,4BAA4B;CAC5B,4CAA4C;CAC5C,sCAAsC;CACtC,uCAAuC;CACvC,wCAAwC;CACxC,yCAAyC;CACzC,mCAAmC;CACnC,oCAAoC;CACpC,iBAAiB;CACjB,6CAA6C;CAC7C,iDAAiD;CACjD,sDAAsD;CACtD,0DAA0D;CAE1D,iDAAiD;CACjD,oEAAoE;CACpE,gDAAgD;CAEhD,uBAAuB;CACvB,8BAA8B;CAC9B,uCAAuC;CACvC,+BAA+B;CAC/B,oCAAoC;AACtC"}
package/dist/runtime.mjs CHANGED
@@ -2,11 +2,11 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { validateConfig, validateUiConfig } from "@storyshelf/core/config";
5
+ import { createPublicOriginResolver } from "@storyshelf/core/models";
5
6
  import { createShelfLogger } from "@storyshelf/core/logger";
6
7
  //#region src/runtime.ts
7
8
  function addOptionalSnapshots(snap, options) {
8
- if (options.captureRunner) snap["captureRunner"] = options.captureRunner.metadata;
9
- if (options.captureQueue) snap["captureQueue"] = options.captureQueue.metadata;
9
+ if (options.testing?.runner) snap["captureRunner"] = options.testing.runner.metadata;
10
10
  }
11
11
  /**
12
12
  * Build a snapshot of adapter metadata for the `/api/v1/health` and docs.
@@ -14,7 +14,7 @@ function addOptionalSnapshots(snap, options) {
14
14
  * the running configuration at a glance.
15
15
  *
16
16
  * @param options - Router options containing the wired adapters
17
- * @returns Metadata map keyed by adapter category (e.g. `database`, `git:github`)
17
+ * @returns Metadata map keyed by adapter category (e.g. `database`, `notify:github`)
18
18
  */
19
19
  function buildAdapterSnapshot(options) {
20
20
  const snap = {
@@ -22,8 +22,8 @@ function buildAdapterSnapshot(options) {
22
22
  storage: options.storage.metadata
23
23
  };
24
24
  addOptionalSnapshots(snap, options);
25
- for (const p of options.gitHosts ?? []) snap[`git:${p.metadata.kind}`] = p.metadata;
26
- for (const n of options.notifiers ?? []) snap[`notify:${n.metadata.kind}`] = n.metadata;
25
+ for (const p of options.design ?? []) snap[`design:${p.metadata.kind}`] = p.metadata;
26
+ for (const n of options.notifications?.notifiers ?? []) snap[`notify:${n.metadata.kind}`] = n.metadata;
27
27
  return snap;
28
28
  }
29
29
  /** Validate config/ui and derive runtime singletons from options. */
@@ -32,18 +32,23 @@ function resolveRuntime(options) {
32
32
  const ui = options.ui ? validateUiConfig(options.ui) : {};
33
33
  const logger = options.logger ?? createShelfLogger();
34
34
  const authEnabled = options.auth !== void 0;
35
- const gitHosts = options.gitHosts ?? [];
36
- const notifiers = options.notifiers ?? [];
35
+ const design = options.design ?? [];
36
+ const notifiers = options.notifications?.notifiers ?? [];
37
+ const config = rawConfig.adapters ? rawConfig : {
38
+ ...rawConfig,
39
+ adapters: buildAdapterSnapshot(options)
40
+ };
37
41
  return {
38
- config: rawConfig.adapters ? rawConfig : {
39
- ...rawConfig,
40
- adapters: buildAdapterSnapshot(options)
41
- },
42
+ config,
42
43
  ui,
43
44
  logger,
44
45
  authEnabled,
45
- gitHosts,
46
- notifiers
46
+ design,
47
+ notifiers,
48
+ publicOrigin: createPublicOriginResolver({
49
+ db: options.database,
50
+ config
51
+ })
47
52
  };
48
53
  }
49
54
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.mjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import type { GitHostProvider } from \"@storyshelf/core/adapter/git-host\";\nimport type { AdapterMetadata, GitAdapterMetadata } from \"@storyshelf/core/adapter/metadata\";\nimport type { NotifierProvider } from \"@storyshelf/core/adapter/notifier\";\nimport type { ShelfConfig, ShelfOptions, UIConfig } from \"@storyshelf/core/config\";\nimport { validateConfig, validateUiConfig } from \"@storyshelf/core/config\";\nimport type { Logger } from \"@storyshelf/core/logger\";\nimport { createShelfLogger } from \"@storyshelf/core/logger\";\n\n/**\n * Resolved server runtime derived from {@link ShelfOptions}.\n * Holds the validated config, UI branding, structured logger, and\n * computed helpers like whether auth is enabled and which git hosts are wired.\n */\nexport interface ServerRuntime {\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n authEnabled: boolean;\n gitHosts: GitHostProvider[];\n notifiers: NotifierProvider[];\n}\n\nfunction addOptionalSnapshots(\n snap: Record<string, AdapterMetadata | GitAdapterMetadata>,\n options: ShelfOptions,\n): void {\n if (options.captureRunner) {\n snap[\"captureRunner\"] = options.captureRunner.metadata;\n }\n if (options.captureQueue) {\n snap[\"captureQueue\"] = options.captureQueue.metadata;\n }\n}\n\n/**\n * Build a snapshot of adapter metadata for the `/api/v1/health` and docs.\n * Collects the name/version of every wired adapter so operators can verify\n * the running configuration at a glance.\n *\n * @param options - Router options containing the wired adapters\n * @returns Metadata map keyed by adapter category (e.g. `database`, `git:github`)\n */\nexport function buildAdapterSnapshot(\n options: ShelfOptions,\n): Record<string, AdapterMetadata | GitAdapterMetadata> {\n const snap: Record<string, AdapterMetadata | GitAdapterMetadata> = {\n database: options.database.metadata,\n storage: options.storage.metadata,\n };\n addOptionalSnapshots(snap, options);\n for (const p of options.gitHosts ?? []) {\n snap[`git:${p.metadata.kind}`] = p.metadata;\n }\n for (const n of options.notifiers ?? []) {\n snap[`notify:${n.metadata.kind}`] = n.metadata;\n }\n return snap;\n}\n\n/** Validate config/ui and derive runtime singletons from options. */\nexport function resolveRuntime(options: ShelfOptions): ServerRuntime {\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- ShelfConfig lacks index signature\n const rawConfig = options.config\n ? validateConfig(options.config as unknown as Record<string, unknown>)\n : {};\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- UIConfig lacks index signature\n const ui = options.ui ? validateUiConfig(options.ui as unknown as Record<string, unknown>) : {};\n const logger = options.logger ?? createShelfLogger();\n const authEnabled = options.auth !== undefined;\n const gitHosts = options.gitHosts ?? [];\n const notifiers = options.notifiers ?? [];\n // Adapter introspection — auto-populate config.adapters if not supplied\n const config: ShelfConfig = rawConfig.adapters\n ? rawConfig\n : {\n ...rawConfig,\n adapters: buildAdapterSnapshot(options),\n };\n return { config, ui, logger, authEnabled, gitHosts, notifiers };\n}\n"],"mappings":";;;;;;AAsBA,SAAS,qBACP,MACA,SACM;CACN,IAAI,QAAQ,eACV,KAAK,mBAAmB,QAAQ,cAAc;CAEhD,IAAI,QAAQ,cACV,KAAK,kBAAkB,QAAQ,aAAa;AAEhD;;;;;;;;;AAUA,SAAgB,qBACd,SACsD;CACtD,MAAM,OAA6D;EACjE,UAAU,QAAQ,SAAS;EAC3B,SAAS,QAAQ,QAAQ;CAC3B;CACA,qBAAqB,MAAM,OAAO;CAClC,KAAK,MAAM,KAAK,QAAQ,YAAY,CAAC,GACnC,KAAK,OAAO,EAAE,SAAS,UAAU,EAAE;CAErC,KAAK,MAAM,KAAK,QAAQ,aAAa,CAAC,GACpC,KAAK,UAAU,EAAE,SAAS,UAAU,EAAE;CAExC,OAAO;AACT;;AAGA,SAAgB,eAAe,SAAsC;CAEnE,MAAM,YAAY,QAAQ,SACtB,eAAe,QAAQ,MAA4C,IACnE,CAAC;CAEL,MAAM,KAAK,QAAQ,KAAK,iBAAiB,QAAQ,EAAwC,IAAI,CAAC;CAC9F,MAAM,SAAS,QAAQ,UAAU,kBAAkB;CACnD,MAAM,cAAc,QAAQ,SAAS,KAAA;CACrC,MAAM,WAAW,QAAQ,YAAY,CAAC;CACtC,MAAM,YAAY,QAAQ,aAAa,CAAC;CAQxC,OAAO;EAAE,QANmB,UAAU,WAClC,YACA;GACE,GAAG;GACH,UAAU,qBAAqB,OAAO;EACxC;EACa;EAAI;EAAQ;EAAa;EAAU;CAAU;AAChE"}
1
+ {"version":3,"file":"runtime.mjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import type { DesignProvider } from \"@storyshelf/core/adapter/design\";\nimport type { AdapterMetadata } from \"@storyshelf/core/adapter/metadata\";\nimport type { NotifierProvider } from \"@storyshelf/core/adapter/notifier\";\nimport type { ShelfConfig, ShelfOptions, UIConfig } from \"@storyshelf/core/config\";\nimport { validateConfig, validateUiConfig } from \"@storyshelf/core/config\";\nimport type { Logger } from \"@storyshelf/core/logger\";\nimport { createShelfLogger } from \"@storyshelf/core/logger\";\nimport { createPublicOriginResolver, type PublicOriginResolver } from \"@storyshelf/core/models\";\n\n/**\n * Resolved server runtime derived from {@link ShelfOptions}.\n * Holds the validated config, UI branding, structured logger, and\n * computed helpers like whether auth is enabled and which notifiers are wired.\n */\nexport interface ServerRuntime {\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n authEnabled: boolean;\n design: DesignProvider[];\n notifiers: NotifierProvider[];\n /** The public origin for links built in the background (ADR 0007, decision 6). */\n publicOrigin: PublicOriginResolver;\n}\n\nfunction addOptionalSnapshots(snap: Record<string, AdapterMetadata>, options: ShelfOptions): void {\n if (options.testing?.runner) {\n snap[\"captureRunner\"] = options.testing.runner.metadata;\n }\n}\n\n/**\n * Build a snapshot of adapter metadata for the `/api/v1/health` and docs.\n * Collects the name/version of every wired adapter so operators can verify\n * the running configuration at a glance.\n *\n * @param options - Router options containing the wired adapters\n * @returns Metadata map keyed by adapter category (e.g. `database`, `notify:github`)\n */\nexport function buildAdapterSnapshot(options: ShelfOptions): Record<string, AdapterMetadata> {\n const snap: Record<string, AdapterMetadata> = {\n database: options.database.metadata,\n storage: options.storage.metadata,\n };\n addOptionalSnapshots(snap, options);\n for (const p of options.design ?? []) {\n snap[`design:${p.metadata.kind}`] = p.metadata;\n }\n for (const n of options.notifications?.notifiers ?? []) {\n snap[`notify:${n.metadata.kind}`] = n.metadata;\n }\n return snap;\n}\n\n/** Validate config/ui and derive runtime singletons from options. */\nexport function resolveRuntime(options: ShelfOptions): ServerRuntime {\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- ShelfConfig lacks index signature\n const rawConfig = options.config\n ? validateConfig(options.config as unknown as Record<string, unknown>)\n : {};\n // eslint-disable-next-line typescript/no-unnecessary-type-assertion -- UIConfig lacks index signature\n const ui = options.ui ? validateUiConfig(options.ui as unknown as Record<string, unknown>) : {};\n const logger = options.logger ?? createShelfLogger();\n const authEnabled = options.auth !== undefined;\n const design = options.design ?? [];\n const notifiers = options.notifications?.notifiers ?? [];\n // Adapter introspection — auto-populate config.adapters if not supplied\n const config: ShelfConfig = rawConfig.adapters\n ? rawConfig\n : {\n ...rawConfig,\n adapters: buildAdapterSnapshot(options),\n };\n const publicOrigin = createPublicOriginResolver({ db: options.database, config });\n return { config, ui, logger, authEnabled, design, notifiers, publicOrigin };\n}\n"],"mappings":";;;;;;;AAyBA,SAAS,qBAAqB,MAAuC,SAA6B;CAChG,IAAI,QAAQ,SAAS,QACnB,KAAK,mBAAmB,QAAQ,QAAQ,OAAO;AAEnD;;;;;;;;;AAUA,SAAgB,qBAAqB,SAAwD;CAC3F,MAAM,OAAwC;EAC5C,UAAU,QAAQ,SAAS;EAC3B,SAAS,QAAQ,QAAQ;CAC3B;CACA,qBAAqB,MAAM,OAAO;CAClC,KAAK,MAAM,KAAK,QAAQ,UAAU,CAAC,GACjC,KAAK,UAAU,EAAE,SAAS,UAAU,EAAE;CAExC,KAAK,MAAM,KAAK,QAAQ,eAAe,aAAa,CAAC,GACnD,KAAK,UAAU,EAAE,SAAS,UAAU,EAAE;CAExC,OAAO;AACT;;AAGA,SAAgB,eAAe,SAAsC;CAEnE,MAAM,YAAY,QAAQ,SACtB,eAAe,QAAQ,MAA4C,IACnE,CAAC;CAEL,MAAM,KAAK,QAAQ,KAAK,iBAAiB,QAAQ,EAAwC,IAAI,CAAC;CAC9F,MAAM,SAAS,QAAQ,UAAU,kBAAkB;CACnD,MAAM,cAAc,QAAQ,SAAS,KAAA;CACrC,MAAM,SAAS,QAAQ,UAAU,CAAC;CAClC,MAAM,YAAY,QAAQ,eAAe,aAAa,CAAC;CAEvD,MAAM,SAAsB,UAAU,WAClC,YACA;EACE,GAAG;EACH,UAAU,qBAAqB,OAAO;CACxC;CAEJ,OAAO;EAAE;EAAQ;EAAI;EAAQ;EAAa;EAAQ;EAAW,cADxC,2BAA2B;GAAE,IAAI,QAAQ;GAAU;EAAO,CACP;CAAE;AAC5E"}
@@ -0,0 +1,5 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { isServerlessHost as isServerlessHost$1 } from "@storyshelf/core/config";
5
+ export { isServerlessHost$1 as isServerlessHost };
package/dist/store.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
+ import { createCatalogLoader } from "@storyshelf/core/capture";
4
5
  import { AsyncLocalStorage } from "node:async_hooks";
5
6
  //#region src/store.ts
6
7
  const storage = new AsyncLocalStorage();
@@ -14,7 +15,24 @@ function getStore() {
14
15
  if (!store) throw new Error("Store not available. Use runWithStore().");
15
16
  return store;
16
17
  }
18
+ /**
19
+ * Whether this instance has `testing` (`options.testing` is set): it captures and compares builds,
20
+ * so pages layer snapshot data (screenshots, statuses, design links) onto what the catalog gives
21
+ * them. Without it the same pages render from the published Storybook alone, and the routes
22
+ * classified `testing` answer 404 (`requireTesting`).
23
+ */
24
+ function testingEnabled(store = getStore()) {
25
+ return store.testing;
26
+ }
27
+ /** The catalog loader pages read; hand-built stores without one get an uncached loader. */
28
+ function catalogOf(store = getStore()) {
29
+ return store.publish?.catalog ?? createCatalogLoader({
30
+ db: store.db,
31
+ storage: store.storage,
32
+ logger: store.logger
33
+ });
34
+ }
17
35
  //#endregion
18
- export { getStore, runWithStore };
36
+ export { catalogOf, getStore, runWithStore, testingEnabled };
19
37
 
20
38
  //# sourceMappingURL=store.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import type { CaptureQueue } from \"@storyshelf/core/adapter/capture-queue\";\nimport type { DatabaseAdapter } from \"@storyshelf/core/adapter/database\";\nimport type { GitHostProvider } from \"@storyshelf/core/adapter/git-host\";\nimport type { NotifierProvider } from \"@storyshelf/core/adapter/notifier\";\nimport type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\nimport type { ShelfConfig, UIConfig } from \"@storyshelf/core/config\";\nimport type { Logger } from \"@storyshelf/core/logger\";\nimport type { AuthUser } from \"@storyshelf/core/types\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\n\n/** Request-scoped dependencies and session state. */\nexport interface Store {\n db: DatabaseAdapter;\n storage: StorageAdapter;\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n user: AuthUser | null;\n authEnabled: boolean;\n /** Raw session cookie value (\"default\" when anonymous); binds CSRF tokens. */\n sessionId: string;\n enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;\n captureQueue?: CaptureQueue | null;\n gitHosts: GitHostProvider[];\n notifiers: NotifierProvider[];\n}\n\nconst storage = new AsyncLocalStorage<Store>();\n\n/** Run a function with the given request store in scope. */\nexport function runWithStore<T>(store: Store, fn: () => T): T {\n return storage.run(store, fn);\n}\n\n/** Return the current request store, throwing outside a request scope. */\nexport function getStore(): Store {\n const store = storage.getStore();\n if (!store) {\n throw new Error(\"Store not available. Use runWithStore().\");\n }\n return store;\n}\n"],"mappings":";;;;;AA2BA,MAAM,UAAU,IAAI,kBAAyB;;AAG7C,SAAgB,aAAgB,OAAc,IAAgB;CAC5D,OAAO,QAAQ,IAAI,OAAO,EAAE;AAC9B;;AAGA,SAAgB,WAAkB;CAChC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,0CAA0C;CAE5D,OAAO;AACT"}
1
+ {"version":3,"file":"store.mjs","names":[],"sources":["../src/store.ts"],"sourcesContent":["import type { CaptureQueue } from \"@storyshelf/core/adapter/capture-queue\";\nimport type { CaptureRunner } from \"@storyshelf/core/adapter/capture-runner\";\nimport type { DatabaseAdapter } from \"@storyshelf/core/adapter/database\";\nimport type { DesignProvider } from \"@storyshelf/core/adapter/design\";\nimport type { NotifierProvider } from \"@storyshelf/core/adapter/notifier\";\nimport type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\nimport type { Ai } from \"@storyshelf/core/ai\";\nimport {\n createCatalogLoader,\n type BuildStatePublisher,\n type CatalogLoader,\n} from \"@storyshelf/core/capture\";\nimport type { NotificationsConfig, ShelfConfig, UIConfig } from \"@storyshelf/core/config\";\nimport type { Logger } from \"@storyshelf/core/logger\";\nimport type { PublicOriginResolver } from \"@storyshelf/core/models\";\nimport type { AuthUser } from \"@storyshelf/core/types\";\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { PublishService } from \"./publish-setup.ts\";\n\n/** Request-scoped dependencies and session state. */\nexport interface Store {\n db: DatabaseAdapter;\n storage: StorageAdapter;\n config: ShelfConfig;\n ui: UIConfig;\n logger: Logger;\n user: AuthUser | null;\n authEnabled: boolean;\n /** Raw session cookie value (\"default\" when anonymous); binds CSRF tokens. */\n sessionId: string;\n /** `options.testing` is set: capture, compare and baselines run here (ADR 0006). */\n testing: boolean;\n enqueueCapture?: (buildId: string, reqId?: string) => Promise<void>;\n captureQueue?: CaptureQueue | null;\n /** This instance runs capture jobs itself, so retry and the compute-jobs controls apply. */\n capturesHere?: boolean;\n /** Publishing: completion and the `publish` job queue (absent in hand-built stores). */\n publish?: PublishService;\n /** Cancels the unfinished jobs of a build, of every kind (absent in hand-built stores). */\n cancelBuildJobs?: (buildId: string) => Promise<boolean>;\n /** Shows a build's state on its git channels (absent in hand-built stores: delivered inline). */\n buildState?: BuildStatePublisher;\n /** Explicit `testing.concurrency`; unset means sized from the hardware. */\n captureConcurrency?: number;\n /** The capture runner (`options.testing.runner`); its targets decide which browsers a project may pick. */\n runner?: Pick<CaptureRunner, \"metadata\"> | undefined;\n /** The public origin: configured, detected from the platform, or reported by an admin (ADR 0007). */\n publicOrigin?: PublicOriginResolver | undefined;\n /** Design providers (Figma, …); empty when none is wired. */\n design: DesignProvider[];\n notifiers: NotifierProvider[];\n /** Brand/sender settings for outgoing notifications (`notifications.config`). */\n notificationsConfig?: NotificationsConfig;\n /** AI insights surface; absent ⇒ AI off site-wide. */\n ai?: Ai;\n}\n\nconst storage = new AsyncLocalStorage<Store>();\n\n/** Run a function with the given request store in scope. */\nexport function runWithStore<T>(store: Store, fn: () => T): T {\n return storage.run(store, fn);\n}\n\n/** Return the current request store, throwing outside a request scope. */\nexport function getStore(): Store {\n const store = storage.getStore();\n if (!store) {\n throw new Error(\"Store not available. Use runWithStore().\");\n }\n return store;\n}\n\n/**\n * Whether this instance has `testing` (`options.testing` is set): it captures and compares builds,\n * so pages layer snapshot data (screenshots, statuses, design links) onto what the catalog gives\n * them. Without it the same pages render from the published Storybook alone, and the routes\n * classified `testing` answer 404 (`requireTesting`).\n */\nexport function testingEnabled(store: Store = getStore()): boolean {\n return store.testing;\n}\n\n/** The catalog loader pages read; hand-built stores without one get an uncached loader. */\nexport function catalogOf(store: Store = getStore()): CatalogLoader {\n return (\n store.publish?.catalog ??\n createCatalogLoader({ db: store.db, storage: store.storage, logger: store.logger })\n );\n}\n"],"mappings":";;;;;;AAyDA,MAAM,UAAU,IAAI,kBAAyB;;AAG7C,SAAgB,aAAgB,OAAc,IAAgB;CAC5D,OAAO,QAAQ,IAAI,OAAO,EAAE;AAC9B;;AAGA,SAAgB,WAAkB;CAChC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,0CAA0C;CAE5D,OAAO;AACT;;;;;;;AAQA,SAAgB,eAAe,QAAe,SAAS,GAAY;CACjE,OAAO,MAAM;AACf;;AAGA,SAAgB,UAAU,QAAe,SAAS,GAAkB;CAClE,OACE,MAAM,SAAS,WACf,oBAAoB;EAAE,IAAI,MAAM;EAAI,SAAS,MAAM;EAAS,QAAQ,MAAM;CAAO,CAAC;AAEtF"}
@@ -0,0 +1,46 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { getStore } from "./store.mjs";
5
+ import { MAX_BROWSERS, assertSupportedBrowsers, offeredTargets } from "@storyshelf/core/capture";
6
+ import { HTTPException } from "hono/http-exception";
7
+ import { isCaptureTarget, parseTarget } from "@storyshelf/core/adapter/capture-runner";
8
+ //#region src/targets.ts
9
+ /** Which capture targets this instance offers right now, and the checks a settings save runs (ADR 0007). */
10
+ /**
11
+ * The targets the runner offers, with the ones that need a URL marked `unavailable` while there is
12
+ * no `secret` or public origin. `undefined` when there is no runner or it publishes no list: any
13
+ * target is accepted, as before.
14
+ */
15
+ async function runnerTargets(store = getStore()) {
16
+ if (store.runner === void 0) return;
17
+ return offeredTargets(store.runner, {
18
+ publicOrigin: await store.publicOrigin?.url(),
19
+ hasSecret: store.config.secret !== void 0
20
+ });
21
+ }
22
+ /** Engine label for a target (`Chromium`), plus the remote's name (`Chromium on grid`). */
23
+ function targetLabel(target) {
24
+ const { engine, name } = parseTarget(target);
25
+ const label = engine === "webkit" ? "WebKit" : `${engine[0]?.toUpperCase()}${engine.slice(1)}`;
26
+ return name === void 0 ? label : `${label} on ${name}`;
27
+ }
28
+ /**
29
+ * Refuse, with 400 and the reason, a target list the runner cannot render (not offered, or
30
+ * unavailable), or one that is malformed or too long. Runs when a setting is saved, so a build
31
+ * never fails one by one with an unexplained error.
32
+ */
33
+ async function assertTargetsSelectable(targets, current = []) {
34
+ const bad = targets.find((target) => !isCaptureTarget(target));
35
+ if (bad !== void 0) throw new HTTPException(400, { message: `Invalid capture target "${bad}"` });
36
+ if (targets.length > MAX_BROWSERS) throw new HTTPException(400, { message: `Choose at most ${MAX_BROWSERS} browsers` });
37
+ try {
38
+ assertSupportedBrowsers(targets.filter((target) => !current.includes(target)), await runnerTargets());
39
+ } catch (error) {
40
+ throw new HTTPException(400, { message: error instanceof Error ? error.message : "Refused" });
41
+ }
42
+ }
43
+ //#endregion
44
+ export { assertTargetsSelectable, runnerTargets, targetLabel };
45
+
46
+ //# sourceMappingURL=targets.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"targets.mjs","names":[],"sources":["../src/targets.ts"],"sourcesContent":["/** Which capture targets this instance offers right now, and the checks a settings save runs (ADR 0007). */\nimport {\n isCaptureTarget,\n parseTarget,\n type CaptureTarget,\n type RunnerTarget,\n} from \"@storyshelf/core/adapter/capture-runner\";\nimport { MAX_BROWSERS, assertSupportedBrowsers, offeredTargets } from \"@storyshelf/core/capture\";\nimport { HTTPException } from \"hono/http-exception\";\nimport { getStore, type Store } from \"./store.ts\";\n\n/**\n * The targets the runner offers, with the ones that need a URL marked `unavailable` while there is\n * no `secret` or public origin. `undefined` when there is no runner or it publishes no list: any\n * target is accepted, as before.\n */\nexport async function runnerTargets(\n store: Store = getStore(),\n): Promise<readonly RunnerTarget[] | undefined> {\n if (store.runner === undefined) {\n return undefined;\n }\n return offeredTargets(store.runner, {\n publicOrigin: await store.publicOrigin?.url(),\n hasSecret: store.config.secret !== undefined,\n });\n}\n\n/** Engine label for a target (`Chromium`), plus the remote's name (`Chromium on grid`). */\nexport function targetLabel(target: CaptureTarget): string {\n const { engine, name } = parseTarget(target);\n const label = engine === \"webkit\" ? \"WebKit\" : `${engine[0]?.toUpperCase()}${engine.slice(1)}`;\n return name === undefined ? label : `${label} on ${name}`;\n}\n\n/**\n * Refuse, with 400 and the reason, a target list the runner cannot render (not offered, or\n * unavailable), or one that is malformed or too long. Runs when a setting is saved, so a build\n * never fails one by one with an unexplained error.\n */\nexport async function assertTargetsSelectable(\n targets: readonly string[],\n /** Targets the project already has: saving them again is not a new choice, so they are not refused. */\n current: readonly string[] = [],\n): Promise<void> {\n const bad = targets.find((target) => !isCaptureTarget(target));\n if (bad !== undefined) {\n throw new HTTPException(400, { message: `Invalid capture target \"${bad}\"` });\n }\n if (targets.length > MAX_BROWSERS) {\n throw new HTTPException(400, { message: `Choose at most ${MAX_BROWSERS} browsers` });\n }\n try {\n assertSupportedBrowsers(\n (targets as CaptureTarget[]).filter((target) => !current.includes(target)),\n await runnerTargets(),\n );\n } catch (error) {\n throw new HTTPException(400, { message: error instanceof Error ? error.message : \"Refused\" });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAgBA,eAAsB,cACpB,QAAe,SAAS,GACsB;CAC9C,IAAI,MAAM,WAAW,KAAA,GACnB;CAEF,OAAO,eAAe,MAAM,QAAQ;EAClC,cAAc,MAAM,MAAM,cAAc,IAAI;EAC5C,WAAW,MAAM,OAAO,WAAW,KAAA;CACrC,CAAC;AACH;;AAGA,SAAgB,YAAY,QAA+B;CACzD,MAAM,EAAE,QAAQ,SAAS,YAAY,MAAM;CAC3C,MAAM,QAAQ,WAAW,WAAW,WAAW,GAAG,OAAO,EAAE,EAAE,YAAY,IAAI,OAAO,MAAM,CAAC;CAC3F,OAAO,SAAS,KAAA,IAAY,QAAQ,GAAG,MAAM,MAAM;AACrD;;;;;;AAOA,eAAsB,wBACpB,SAEA,UAA6B,CAAC,GACf;CACf,MAAM,MAAM,QAAQ,MAAM,WAAW,CAAC,gBAAgB,MAAM,CAAC;CAC7D,IAAI,QAAQ,KAAA,GACV,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,2BAA2B,IAAI,GAAG,CAAC;CAE7E,IAAI,QAAQ,SAAS,cACnB,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,kBAAkB,aAAa,WAAW,CAAC;CAErF,IAAI;EACF,wBACG,QAA4B,QAAQ,WAAW,CAAC,QAAQ,SAAS,MAAM,CAAC,GACzE,MAAM,cAAc,CACtB;CACF,SAAS,OAAO;EACd,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,iBAAiB,QAAQ,MAAM,UAAU,UAAU,CAAC;CAC9F;AACF"}
@@ -149,7 +149,7 @@ const btnClasses = {
149
149
  sm: btnDangerSm
150
150
  }
151
151
  };
152
- css`
152
+ const tabsNav = css`
153
153
  /* tabs */
154
154
  display: flex;
155
155
  gap: 0.25rem;
@@ -171,7 +171,7 @@ const tabsLink = css`
171
171
  text-decoration: none;
172
172
  }
173
173
  `;
174
- css`
174
+ const tabsLinkActive = css`
175
175
  /* tabs-link-active */
176
176
  ${tabsLink}
177
177
  color: var(--text-primary);
@@ -199,7 +199,20 @@ const Button = ({ variant = "primary", size = "md", type = "button", href, disab
199
199
  children: content
200
200
  });
201
201
  };
202
+ /** Tab navigation bar for section switching. */
203
+ const Tabs = ({ tabs, label = "Sections" }) => {
204
+ return /* @__PURE__ */ jsx("nav", {
205
+ class: tabsNav,
206
+ "aria-label": label,
207
+ children: tabs.map((tab) => /* @__PURE__ */ jsx("a", {
208
+ class: tab.active ? tabsLinkActive : tabsLink,
209
+ href: tab.href,
210
+ "aria-current": tab.active ? "page" : void 0,
211
+ children: tab.label
212
+ }))
213
+ });
214
+ };
202
215
  //#endregion
203
- export { Button };
216
+ export { Button, Tabs };
204
217
 
205
218
  //# sourceMappingURL=buttons.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"buttons.mjs","names":[],"sources":["../../src/ui/buttons.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Icon } from \"./icons/icon.tsx\";\nimport type { IconName } from \"./icons/paths.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype ButtonVariant = \"primary\" | \"secondary\" | \"ghost\" | \"danger\" | \"outline\";\ntype ButtonSize = \"md\" | \"sm\";\n\nconst btnBase = css`\n /* btn */\n appearance: none;\n border: 1px solid transparent;\n border-radius: var(--radius);\n padding: 0.55rem 0.9rem;\n font-weight: 600;\n font-size: var(--text-base);\n cursor: pointer;\n text-decoration: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.4rem;\n line-height: 1.2;\n /* Top-aligned so a button in a <form> (a block with a line box) is exactly\n as tall as the button instead of picking up a baseline strut. */\n vertical-align: top;\n min-height: 40px;\n transition:\n background var(--dur-fast) var(--ease),\n border-color var(--dur-fast) var(--ease),\n filter var(--dur-fast) var(--ease),\n transform var(--dur-fast) var(--ease);\n &:active:not(:disabled) {\n transform: translateY(1px);\n }\n &:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n }\n`;\n\nconst btnPrimary = css`\n /* btn-primary */\n ${btnBase}\n background: var(--accent);\n color: var(--accent-contrast);\n border-color: var(--accent);\n box-shadow: var(--shadow);\n &:hover {\n filter: brightness(1.06);\n text-decoration: none;\n }\n`;\n\nconst btnPrimarySm = css`\n /* btn-primary-sm */\n ${btnPrimary}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnSecondary = css`\n /* btn-secondary */\n ${btnBase}\n background: var(--surface-card);\n color: var(--text-primary);\n border-color: var(--border);\n box-shadow: var(--shadow);\n &:hover {\n background: var(--surface-subtle);\n text-decoration: none;\n }\n`;\n\nconst btnSecondarySm = css`\n /* btn-secondary-sm */\n ${btnSecondary}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnOutline = css`\n /* btn-outline */\n ${btnSecondary}\n &:hover {\n background: var(--surface-subtle);\n text-decoration: none;\n }\n`;\n\nconst btnOutlineSm = css`\n /* btn-outline-sm */\n ${btnOutline}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnGhost = css`\n /* btn-ghost */\n ${btnBase}\n background: transparent;\n color: var(--text-secondary);\n border-color: transparent;\n &:hover {\n background: var(--surface-muted);\n color: var(--text-primary);\n text-decoration: none;\n }\n`;\n\nconst btnGhostSm = css`\n /* btn-ghost-sm */\n ${btnGhost}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnDanger = css`\n /* btn-danger */\n ${btnBase}\n background: color-mix(in srgb, var(--status-rejected) 82%, #000);\n color: #fff;\n border-color: color-mix(in srgb, var(--status-rejected) 82%, #000);\n &:hover {\n filter: brightness(1.06);\n text-decoration: none;\n }\n`;\n\nconst btnDangerSm = css`\n /* btn-danger-sm */\n ${btnDanger}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnClasses: Record<ButtonVariant, Record<ButtonSize, Promise<string>>> = {\n primary: { md: btnPrimary, sm: btnPrimarySm },\n secondary: { md: btnSecondary, sm: btnSecondarySm },\n outline: { md: btnOutline, sm: btnOutlineSm },\n ghost: { md: btnGhost, sm: btnGhostSm },\n danger: { md: btnDanger, sm: btnDangerSm },\n};\n\nconst tabsNav = css`\n /* tabs */\n display: flex;\n gap: 0.25rem;\n border-bottom: 1px solid var(--border);\n margin-bottom: 1rem;\n overflow-x: auto;\n`;\n\nconst tabsLink = css`\n /* tabs-link */\n padding: 0.55rem 0.75rem;\n border-bottom: 2px solid transparent;\n color: var(--text-secondary);\n font-size: 0.875rem;\n text-decoration: none;\n white-space: nowrap;\n font-weight: 600;\n &:hover {\n color: var(--text-primary);\n text-decoration: none;\n }\n`;\n\nconst tabsLinkActive = css`\n /* tabs-link-active */\n ${tabsLink}\n color: var(--text-primary);\n border-bottom-color: var(--accent);\n`;\n\n/** Button or link styled as a button (renders an anchor when `href` is set). */\nexport const Button: FC<{\n variant?: ButtonVariant;\n size?: ButtonSize;\n type?: \"button\" | \"submit\" | \"reset\";\n href?: string;\n disabled?: boolean;\n /** Leading icon from the sprite. */\n icon?: IconName;\n children?: unknown;\n [key: string]: unknown;\n}> = ({\n variant = \"primary\",\n size = \"md\",\n type = \"button\",\n href,\n disabled,\n icon,\n children,\n ...rest\n}) => {\n const cls = btnClasses[variant][size];\n const content = (\n <>\n {icon ? <Icon name={icon} size={size === \"sm\" ? \"sm\" : \"md\"} /> : null}\n {children}\n </>\n );\n if (href) {\n return (\n <a class={cls} href={href} aria-disabled={disabled ? \"true\" : undefined} {...rest}>\n {content}\n </a>\n );\n }\n return (\n <button class={cls} type={type} disabled={disabled} {...rest}>\n {content}\n </button>\n );\n};\n\n/** Tab navigation bar for section switching. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Tabs: FC<{\n tabs: { label: string; href: string; active?: boolean }[];\n label?: string;\n}> = ({ tabs, label = \"Sections\" }) => {\n return (\n <nav class={tabsNav} aria-label={label}>\n {tabs.map((tab) => (\n <a\n class={tab.active ? tabsLinkActive : tabsLink}\n href={tab.href}\n aria-current={tab.active ? \"page\" : undefined}\n >\n {tab.label}\n </a>\n ))}\n </nav>\n );\n};\n"],"mappings":";;;;;;;AAUA,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCnB,MAAM,aAAa,GAAG;;IAElB,QAAQ;;;;;;;;;;AAWZ,MAAM,eAAe,GAAG;;IAEpB,WAAW;;;;;AAMf,MAAM,eAAe,GAAG;;IAEpB,QAAQ;;;;;;;;;;AAWZ,MAAM,iBAAiB,GAAG;;IAEtB,aAAa;;;;;AAMjB,MAAM,aAAa,GAAG;;IAElB,aAAa;;;;;;AAOjB,MAAM,eAAe,GAAG;;IAEpB,WAAW;;;;;AAMf,MAAM,WAAW,GAAG;;IAEhB,QAAQ;;;;;;;;;;AAWZ,MAAM,aAAa,GAAG;;IAElB,SAAS;;;;;AAMb,MAAM,YAAY,GAAG;;IAEjB,QAAQ;;;;;;;;;AAUZ,MAAM,cAAc,GAAG;;IAEnB,UAAU;;;;;AAMd,MAAM,aAAyE;CAC7E,SAAS;EAAE,IAAI;EAAY,IAAI;CAAa;CAC5C,WAAW;EAAE,IAAI;EAAc,IAAI;CAAe;CAClD,SAAS;EAAE,IAAI;EAAY,IAAI;CAAa;CAC5C,OAAO;EAAE,IAAI;EAAU,IAAI;CAAW;CACtC,QAAQ;EAAE,IAAI;EAAW,IAAI;CAAY;AAC3C;AAEgB,GAAG;;;;;;;;AASnB,MAAM,WAAW,GAAG;;;;;;;;;;;;;;AAeG,GAAG;;IAEtB,SAAS;;;;;AAMb,MAAa,UAUP,EACJ,UAAU,WACV,OAAO,MACP,OAAO,UACP,MACA,UACA,MACA,UACA,GAAG,WACC;CACJ,MAAM,MAAM,WAAW,QAAQ,CAAC;CAChC,MAAM,UACJ,qBAAA,UAAA,EAAA,UAAA,CACG,OAAO,oBAAC,MAAD;EAAM,MAAM;EAAM,MAAM,SAAS,OAAO,OAAO;CAAO,CAAA,IAAI,MACjE,QACD,EAAA,CAAA;CAEJ,IAAI,MACF,OACE,oBAAC,KAAD;EAAG,OAAO;EAAW;EAAM,iBAAe,WAAW,SAAS,KAAA;EAAW,GAAI;EAC1E,UAAA;CACA,CAAA;CAGP,OACE,oBAAC,UAAD;EAAQ,OAAO;EAAW;EAAgB;EAAU,GAAI;EACrD,UAAA;CACK,CAAA;AAEZ"}
1
+ {"version":3,"file":"buttons.mjs","names":[],"sources":["../../src/ui/buttons.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Icon } from \"./icons/icon.tsx\";\nimport type { IconName } from \"./icons/paths.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\ntype ButtonVariant = \"primary\" | \"secondary\" | \"ghost\" | \"danger\" | \"outline\";\ntype ButtonSize = \"md\" | \"sm\";\n\nconst btnBase = css`\n /* btn */\n appearance: none;\n border: 1px solid transparent;\n border-radius: var(--radius);\n padding: 0.55rem 0.9rem;\n font-weight: 600;\n font-size: var(--text-base);\n cursor: pointer;\n text-decoration: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.4rem;\n line-height: 1.2;\n /* Top-aligned so a button in a <form> (a block with a line box) is exactly\n as tall as the button instead of picking up a baseline strut. */\n vertical-align: top;\n min-height: 40px;\n transition:\n background var(--dur-fast) var(--ease),\n border-color var(--dur-fast) var(--ease),\n filter var(--dur-fast) var(--ease),\n transform var(--dur-fast) var(--ease);\n &:active:not(:disabled) {\n transform: translateY(1px);\n }\n &:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n }\n`;\n\nconst btnPrimary = css`\n /* btn-primary */\n ${btnBase}\n background: var(--accent);\n color: var(--accent-contrast);\n border-color: var(--accent);\n box-shadow: var(--shadow);\n &:hover {\n filter: brightness(1.06);\n text-decoration: none;\n }\n`;\n\nconst btnPrimarySm = css`\n /* btn-primary-sm */\n ${btnPrimary}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnSecondary = css`\n /* btn-secondary */\n ${btnBase}\n background: var(--surface-card);\n color: var(--text-primary);\n border-color: var(--border);\n box-shadow: var(--shadow);\n &:hover {\n background: var(--surface-subtle);\n text-decoration: none;\n }\n`;\n\nconst btnSecondarySm = css`\n /* btn-secondary-sm */\n ${btnSecondary}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnOutline = css`\n /* btn-outline */\n ${btnSecondary}\n &:hover {\n background: var(--surface-subtle);\n text-decoration: none;\n }\n`;\n\nconst btnOutlineSm = css`\n /* btn-outline-sm */\n ${btnOutline}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnGhost = css`\n /* btn-ghost */\n ${btnBase}\n background: transparent;\n color: var(--text-secondary);\n border-color: transparent;\n &:hover {\n background: var(--surface-muted);\n color: var(--text-primary);\n text-decoration: none;\n }\n`;\n\nconst btnGhostSm = css`\n /* btn-ghost-sm */\n ${btnGhost}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnDanger = css`\n /* btn-danger */\n ${btnBase}\n background: color-mix(in srgb, var(--status-rejected) 82%, #000);\n color: #fff;\n border-color: color-mix(in srgb, var(--status-rejected) 82%, #000);\n &:hover {\n filter: brightness(1.06);\n text-decoration: none;\n }\n`;\n\nconst btnDangerSm = css`\n /* btn-danger-sm */\n ${btnDanger}\n padding: 0.35rem 0.6rem;\n font-size: 0.8rem;\n min-height: 32px;\n`;\n\nconst btnClasses: Record<ButtonVariant, Record<ButtonSize, Promise<string>>> = {\n primary: { md: btnPrimary, sm: btnPrimarySm },\n secondary: { md: btnSecondary, sm: btnSecondarySm },\n outline: { md: btnOutline, sm: btnOutlineSm },\n ghost: { md: btnGhost, sm: btnGhostSm },\n danger: { md: btnDanger, sm: btnDangerSm },\n};\n\nconst tabsNav = css`\n /* tabs */\n display: flex;\n gap: 0.25rem;\n border-bottom: 1px solid var(--border);\n margin-bottom: 1rem;\n overflow-x: auto;\n`;\n\nconst tabsLink = css`\n /* tabs-link */\n padding: 0.55rem 0.75rem;\n border-bottom: 2px solid transparent;\n color: var(--text-secondary);\n font-size: 0.875rem;\n text-decoration: none;\n white-space: nowrap;\n font-weight: 600;\n &:hover {\n color: var(--text-primary);\n text-decoration: none;\n }\n`;\n\nconst tabsLinkActive = css`\n /* tabs-link-active */\n ${tabsLink}\n color: var(--text-primary);\n border-bottom-color: var(--accent);\n`;\n\n/** Button or link styled as a button (renders an anchor when `href` is set). */\nexport const Button: FC<{\n variant?: ButtonVariant;\n size?: ButtonSize;\n type?: \"button\" | \"submit\" | \"reset\";\n href?: string;\n disabled?: boolean;\n /** Leading icon from the sprite. */\n icon?: IconName;\n children?: unknown;\n [key: string]: unknown;\n}> = ({\n variant = \"primary\",\n size = \"md\",\n type = \"button\",\n href,\n disabled,\n icon,\n children,\n ...rest\n}) => {\n const cls = btnClasses[variant][size];\n const content = (\n <>\n {icon ? <Icon name={icon} size={size === \"sm\" ? \"sm\" : \"md\"} /> : null}\n {children}\n </>\n );\n if (href) {\n return (\n <a class={cls} href={href} aria-disabled={disabled ? \"true\" : undefined} {...rest}>\n {content}\n </a>\n );\n }\n return (\n <button class={cls} type={type} disabled={disabled} {...rest}>\n {content}\n </button>\n );\n};\n\n/** Tab navigation bar for section switching. */\n// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const Tabs: FC<{\n tabs: { label: string; href: string; active?: boolean }[];\n label?: string;\n}> = ({ tabs, label = \"Sections\" }) => {\n return (\n <nav class={tabsNav} aria-label={label}>\n {tabs.map((tab) => (\n <a\n class={tab.active ? tabsLinkActive : tabsLink}\n href={tab.href}\n aria-current={tab.active ? \"page\" : undefined}\n >\n {tab.label}\n </a>\n ))}\n </nav>\n );\n};\n"],"mappings":";;;;;;;AAUA,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCnB,MAAM,aAAa,GAAG;;IAElB,QAAQ;;;;;;;;;;AAWZ,MAAM,eAAe,GAAG;;IAEpB,WAAW;;;;;AAMf,MAAM,eAAe,GAAG;;IAEpB,QAAQ;;;;;;;;;;AAWZ,MAAM,iBAAiB,GAAG;;IAEtB,aAAa;;;;;AAMjB,MAAM,aAAa,GAAG;;IAElB,aAAa;;;;;;AAOjB,MAAM,eAAe,GAAG;;IAEpB,WAAW;;;;;AAMf,MAAM,WAAW,GAAG;;IAEhB,QAAQ;;;;;;;;;;AAWZ,MAAM,aAAa,GAAG;;IAElB,SAAS;;;;;AAMb,MAAM,YAAY,GAAG;;IAEjB,QAAQ;;;;;;;;;AAUZ,MAAM,cAAc,GAAG;;IAEnB,UAAU;;;;;AAMd,MAAM,aAAyE;CAC7E,SAAS;EAAE,IAAI;EAAY,IAAI;CAAa;CAC5C,WAAW;EAAE,IAAI;EAAc,IAAI;CAAe;CAClD,SAAS;EAAE,IAAI;EAAY,IAAI;CAAa;CAC5C,OAAO;EAAE,IAAI;EAAU,IAAI;CAAW;CACtC,QAAQ;EAAE,IAAI;EAAW,IAAI;CAAY;AAC3C;AAEA,MAAM,UAAU,GAAG;;;;;;;;AASnB,MAAM,WAAW,GAAG;;;;;;;;;;;;;;AAepB,MAAM,iBAAiB,GAAG;;IAEtB,SAAS;;;;;AAMb,MAAa,UAUP,EACJ,UAAU,WACV,OAAO,MACP,OAAO,UACP,MACA,UACA,MACA,UACA,GAAG,WACC;CACJ,MAAM,MAAM,WAAW,QAAQ,CAAC;CAChC,MAAM,UACJ,qBAAA,UAAA,EAAA,UAAA,CACG,OAAO,oBAAC,MAAD;EAAM,MAAM;EAAM,MAAM,SAAS,OAAO,OAAO;CAAO,CAAA,IAAI,MACjE,QACD,EAAA,CAAA;CAEJ,IAAI,MACF,OACE,oBAAC,KAAD;EAAG,OAAO;EAAW;EAAM,iBAAe,WAAW,SAAS,KAAA;EAAW,GAAI;EAC1E,UAAA;CACA,CAAA;CAGP,OACE,oBAAC,UAAD;EAAQ,OAAO;EAAW;EAAgB;EAAU,GAAI;EACrD,UAAA;CACK,CAAA;AAEZ;;AAIA,MAAa,QAGP,EAAE,MAAM,QAAQ,iBAAiB;CACrC,OACE,oBAAC,OAAD;EAAK,OAAO;EAAS,cAAY;EAC9B,UAAA,KAAK,KAAK,QACT,oBAAC,KAAD;GACE,OAAO,IAAI,SAAS,iBAAiB;GACrC,MAAM,IAAI;GACV,gBAAc,IAAI,SAAS,SAAS,KAAA;GAEnC,UAAA,IAAI;EACJ,CAAA,CACJ;CACE,CAAA;AAET"}
@@ -0,0 +1,97 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { css } from "./css.mjs";
5
+ import { Icon } from "./icons/icon.mjs";
6
+ import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
7
+ //#region src/ui/design-reference.tsx
8
+ const referenceRoot = css`
9
+ /* design-reference */
10
+ display: grid;
11
+ background: var(--surface-card);
12
+ border: 1px solid var(--border);
13
+ border-radius: var(--radius);
14
+ overflow: hidden;
15
+ .reference__label {
16
+ display: flex;
17
+ align-items: center;
18
+ justify-content: space-between;
19
+ gap: var(--space-3);
20
+ padding: 0.35rem 0.6rem;
21
+ background: var(--surface-subtle);
22
+ border-bottom: 1px solid var(--border);
23
+ color: var(--text-secondary);
24
+ font-size: var(--text-xs);
25
+ font-weight: 600;
26
+ letter-spacing: 0.06em;
27
+ text-transform: uppercase;
28
+ }
29
+ .reference__img {
30
+ display: block;
31
+ max-width: 100%;
32
+ max-height: 60vh;
33
+ margin: 0 auto;
34
+ object-fit: contain;
35
+ }
36
+ `;
37
+ const previewImg = css`
38
+ /* design-preview */
39
+ display: block;
40
+ width: 16rem;
41
+ max-width: 100%;
42
+ max-height: 14rem;
43
+ margin: 0.25rem auto;
44
+ object-fit: contain;
45
+ border-radius: var(--radius-sm);
46
+ background: repeating-conic-gradient(var(--surface-muted) 0% 25%, var(--surface-subtle) 0% 50%) 0
47
+ 0 / 16px 16px;
48
+ `;
49
+ const previewCaption = css`
50
+ /* design-preview-caption */
51
+ padding: 0 0.6rem 0.25rem;
52
+ color: var(--text-secondary);
53
+ font-size: var(--text-xs);
54
+ `;
55
+ /** Cached design render shown as a reference pane under the diff stage. */
56
+ const DesignReference = ({ src, name, href, providerName }) => /* @__PURE__ */ jsxs("figure", {
57
+ class: referenceRoot,
58
+ children: [/* @__PURE__ */ jsxs("figcaption", {
59
+ class: "reference__label",
60
+ children: [/* @__PURE__ */ jsxs("span", { children: [
61
+ providerName,
62
+ " reference",
63
+ name ? ` · ${name}` : ""
64
+ ] }), /* @__PURE__ */ jsxs("a", {
65
+ href,
66
+ target: "_blank",
67
+ rel: "noopener noreferrer",
68
+ children: [
69
+ /* @__PURE__ */ jsx(Icon, {
70
+ name: "external-link",
71
+ size: "sm"
72
+ }),
73
+ " Open in ",
74
+ providerName
75
+ ]
76
+ })]
77
+ }), /* @__PURE__ */ jsx("img", {
78
+ class: "reference__img",
79
+ src,
80
+ alt: name ? `${providerName} design: ${name}` : `${providerName} design`,
81
+ loading: "lazy"
82
+ })]
83
+ });
84
+ /** Small cached render for popovers, with an optional caption (the design name). */
85
+ const DesignPreview = ({ src, name, providerName }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("img", {
86
+ class: previewImg,
87
+ src,
88
+ alt: name ? `${providerName} design: ${name}` : `${providerName} design`,
89
+ loading: "lazy"
90
+ }), name ? /* @__PURE__ */ jsx("div", {
91
+ class: previewCaption,
92
+ children: name
93
+ }) : null] });
94
+ //#endregion
95
+ export { DesignPreview, DesignReference };
96
+
97
+ //# sourceMappingURL=design-reference.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"design-reference.mjs","names":[],"sources":["../../src/ui/design-reference.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Icon } from \"./icons/icon.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\nconst referenceRoot = css`\n /* design-reference */\n display: grid;\n background: var(--surface-card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n overflow: hidden;\n .reference__label {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-3);\n padding: 0.35rem 0.6rem;\n background: var(--surface-subtle);\n border-bottom: 1px solid var(--border);\n color: var(--text-secondary);\n font-size: var(--text-xs);\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n }\n .reference__img {\n display: block;\n max-width: 100%;\n max-height: 60vh;\n margin: 0 auto;\n object-fit: contain;\n }\n`;\n\nconst previewImg = css`\n /* design-preview */\n display: block;\n width: 16rem;\n max-width: 100%;\n max-height: 14rem;\n margin: 0.25rem auto;\n object-fit: contain;\n border-radius: var(--radius-sm);\n background: repeating-conic-gradient(var(--surface-muted) 0% 25%, var(--surface-subtle) 0% 50%) 0\n 0 / 16px 16px;\n`;\n\nconst previewCaption = css`\n /* design-preview-caption */\n padding: 0 0.6rem 0.25rem;\n color: var(--text-secondary);\n font-size: var(--text-xs);\n`;\n\n/** Cached design render shown as a reference pane under the diff stage. */\nexport const DesignReference: FC<{\n src: string;\n name?: string | null;\n href: string;\n providerName: string;\n}> = ({ src, name, href, providerName }) => (\n <figure class={referenceRoot}>\n <figcaption class=\"reference__label\">\n <span>\n {providerName} reference{name ? ` · ${name}` : \"\"}\n </span>\n <a href={href} target=\"_blank\" rel=\"noopener noreferrer\">\n <Icon name=\"external-link\" size=\"sm\" /> Open in {providerName}\n </a>\n </figcaption>\n <img\n class=\"reference__img\"\n src={src}\n alt={name ? `${providerName} design: ${name}` : `${providerName} design`}\n loading=\"lazy\"\n />\n </figure>\n);\n\n/** Small cached render for popovers, with an optional caption (the design name). */\nexport const DesignPreview: FC<{ src: string; name?: string | null; providerName: string }> = ({\n src,\n name,\n providerName,\n}) => (\n <>\n <img\n class={previewImg}\n src={src}\n alt={name ? `${providerName} design: ${name}` : `${providerName} design`}\n loading=\"lazy\"\n />\n {name ? <div class={previewCaption}>{name}</div> : null}\n </>\n);\n"],"mappings":";;;;;;;AAMA,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BzB,MAAM,aAAa,GAAG;;;;;;;;;;;;AAatB,MAAM,iBAAiB,GAAG;;;;;;;AAQ1B,MAAa,mBAKP,EAAE,KAAK,MAAM,MAAM,mBACvB,qBAAC,UAAD;CAAQ,OAAO;CAAf,UAAA,CACE,qBAAC,cAAD;EAAY,OAAM;EAAlB,UAAA,CACE,qBAAC,QAAD,EAAA,UAAA;GACG;GAAa;GAAW,OAAO,MAAM,SAAS;EAC3C,EAAA,CAAA,GACN,qBAAC,KAAD;GAAS;GAAM,QAAO;GAAS,KAAI;GAAnC,UAAA;IACE,oBAAC,MAAD;KAAM,MAAK;KAAgB,MAAK;IAAM,CAAA;IAAC;IAAU;GAChD;EACO,CAAA,CAAA;CACZ,CAAA,GAAA,oBAAC,OAAD;EACE,OAAM;EACD;EACL,KAAK,OAAO,GAAG,aAAa,WAAW,SAAS,GAAG,aAAa;EAChE,SAAQ;CACT,CAAA,CACK;;;AAIV,MAAa,iBAAkF,EAC7F,KACA,MACA,mBAEA,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,OAAD;CACE,OAAO;CACF;CACL,KAAK,OAAO,GAAG,aAAa,WAAW,SAAS,GAAG,aAAa;CAChE,SAAQ;AACT,CAAA,GACA,OAAO,oBAAC,OAAD;CAAK,OAAO;CAAiB,UAAA;AAAU,CAAA,IAAI,IACnD,EAAA,CAAA"}
@@ -7,7 +7,6 @@ import { Style, css } from "./css.mjs";
7
7
  import { assetManifest } from "../asset-manifest.mjs";
8
8
  import { BareShell } from "./shell/bare.mjs";
9
9
  import { bootScript } from "./shell/boot-script.mjs";
10
- import { clientScript } from "./shell/client-script.mjs";
11
10
  import { Sidebar } from "./shell/sidebar.mjs";
12
11
  import { ToastRegion, TopBar } from "./shell/topbar.mjs";
13
12
  import { baseStyle } from "./styles.mjs";
@@ -120,7 +119,7 @@ const DocumentLayout = ({ title, nav, layout = "default", children }) => {
120
119
  })] }),
121
120
  /* @__PURE__ */ jsx(ToastRegion, {}),
122
121
  /* @__PURE__ */ jsx("script", { src: assetManifest.htmx.href }),
123
- /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: clientScript() } })
122
+ /* @__PURE__ */ jsx("script", { src: assetManifest.client.href })
124
123
  ] })]
125
124
  })] });
126
125
  };
@@ -1 +1 @@
1
- {"version":3,"file":"document.mjs","names":[],"sources":["../../src/ui/document.tsx"],"sourcesContent":["import { raw } from \"hono/html\";\nimport type { FC } from \"hono/jsx\";\nimport { assetManifest } from \"../asset-manifest.ts\";\nimport { getCsrfToken } from \"../middleware/csrf.ts\";\nimport { getStore } from \"../store.ts\";\nimport { Style, css } from \"./css.ts\";\nimport { BareShell } from \"./shell/bare.tsx\";\nimport { bootScript } from \"./shell/boot-script.ts\";\nimport { clientScript } from \"./shell/client-script.ts\";\nimport type { NavConfig } from \"./shell/nav.ts\";\nimport { Sidebar } from \"./shell/sidebar.tsx\";\nimport { ToastRegion, TopBar } from \"./shell/topbar.tsx\";\nimport { baseStyle } from \"./styles.ts\";\nimport { DARK_THEME, LIGHT_THEME } from \"./theme.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Active section, project, and breadcrumb trail passed to the shell. */\nexport type { NavConfig };\n\n/** Rendered HTML content returned by page components. */\nexport type RenderedContent = string | Promise<string>;\n\n/**\n * Page frame: `default` is a readable column, `wide` uses the full viewport,\n * `bare` drops the app chrome (sign-in, invites).\n */\ntype PageLayout = \"default\" | \"wide\" | \"bare\";\n\nconst shellRoot = css`\n /* shell */\n display: grid;\n grid-template-columns: var(--sidebar-width) minmax(0, 1fr);\n min-height: 100vh;\n @media (max-width: 880px) {\n & {\n grid-template-columns: minmax(0, 1fr);\n }\n }\n`;\n\nconst shellContent = css`\n /* shell-content */\n width: 100%;\n max-width: 1200px;\n margin: 0 auto;\n padding: var(--space-6) var(--space-6) var(--space-8);\n min-width: 0;\n @media (max-width: 880px) {\n & {\n padding: var(--space-4);\n }\n }\n`;\n\nconst shellContentWide = css`\n /* shell-content-wide */\n ${shellContent}\n max-width: none;\n`;\n\nconst shellBackdrop = css`\n /* shell-backdrop */\n position: fixed;\n inset: 0;\n z-index: 55;\n background: rgb(0 0 0 / 0.4);\n &[hidden] {\n display: none;\n }\n`;\n\n// Hono's JSX.Element is typed as `HtmlEscapedString | Promise<...>`, so JSX-returning\n// Functions can legitimately return a promise; the rule is a false positive here.\n/** Full HTML document shell with sidebar, top bar, theme, toasts, and HTMX. */\nexport const DocumentLayout: FC<{\n title: string;\n nav?: NavConfig;\n layout?: PageLayout;\n children?: unknown;\n}> = ({ title, nav, layout = \"default\", children }) => {\n const { ui, config, sessionId } = getStore();\n const name = ui.name ?? \"StoryShelf\";\n const logo = ui.logo ?? assetManifest.mark.href;\n const light = ui.lightTheme ?? LIGHT_THEME;\n const dark = ui.darkTheme ?? DARK_THEME;\n\n return (\n <>\n {/* Without a doctype browsers use quirks mode (form margins, line-box struts, …). */}\n {raw(\"<!DOCTYPE html>\")}\n <html lang=\"en\" data-theme=\"system\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta name=\"color-scheme\" content=\"light dark\" />\n <meta name=\"csrf-token\" content={getCsrfToken(config.secret, sessionId)} />\n {/* Favicon is the product's own mark, not brandable: it identifies\n StoryShelf in the tab strip. `ui.logo` stays overridable. */}\n <link rel=\"icon\" type=\"image/svg+xml\" href={assetManifest.mark.href} />\n <title>\n {title} · {name}\n </title>\n <style dangerouslySetInnerHTML={{ __html: baseStyle(light, dark) }} />\n <Style />\n <script dangerouslySetInnerHTML={{ __html: bootScript() }} />\n </head>\n <body>\n <a class=\"skip-link\" href=\"#main-content\">\n Skip to content\n </a>\n {layout === \"bare\" ? (\n <BareShell name={name} logo={logo}>\n {children}\n </BareShell>\n ) : (\n <>\n <div class={shellRoot}>\n <Sidebar nav={nav} name={name} logo={logo} />\n <div>\n <TopBar nav={nav} />\n <main\n id=\"main-content\"\n class={layout === \"wide\" ? shellContentWide : shellContent}\n tabindex={-1}\n >\n {children}\n </main>\n </div>\n </div>\n <div class={shellBackdrop} data-sidebar-backdrop hidden />\n </>\n )}\n <ToastRegion />\n <script src={assetManifest.htmx.href} />\n <script dangerouslySetInnerHTML={{ __html: clientScript() }} />\n </body>\n </html>\n </>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA6BA,MAAM,YAAY,GAAG;;;;;;;;;;;AAYrB,MAAM,eAAe,GAAG;;;;;;;;;;;;;AAcxB,MAAM,mBAAmB,GAAG;;IAExB,aAAa;;;AAIjB,MAAM,gBAAgB,GAAG;;;;;;;;;;;AAczB,MAAa,kBAKP,EAAE,OAAO,KAAK,SAAS,WAAW,eAAe;CACrD,MAAM,EAAE,IAAI,QAAQ,cAAc,SAAS;CAC3C,MAAM,OAAO,GAAG,QAAQ;CACxB,MAAM,OAAO,GAAG,QAAQ,cAAc,KAAK;CAC3C,MAAM,QAAQ,GAAG,cAAc;CAC/B,MAAM,OAAO,GAAG,aAAa;CAE7B,OACE,qBAAA,UAAA,EAAA,UAAA,CAEG,IAAI,iBAAiB,GACtB,qBAAC,QAAD;EAAM,MAAK;EAAK,cAAW;EAA3B,UAAA,CACE,qBAAC,QAAD,EAAA,UAAA;GACE,oBAAC,QAAD,EAAM,SAAQ,QAAS,CAAA;GACvB,oBAAC,QAAD;IAAM,MAAK;IAAW,SAAQ;GAAuC,CAAA;GACrE,oBAAC,QAAD;IAAM,MAAK;IAAe,SAAQ;GAAc,CAAA;GAChD,oBAAC,QAAD;IAAM,MAAK;IAAa,SAAS,aAAa,OAAO,QAAQ,SAAS;GAAI,CAAA;GAG1E,oBAAC,QAAD;IAAM,KAAI;IAAO,MAAK;IAAgB,MAAM,cAAc,KAAK;GAAO,CAAA;GACtE,qBAAC,SAAD,EAAA,UAAA;IACG;IAAM;IAAI;GACN,EAAA,CAAA;GACP,oBAAC,SAAD,EAAO,yBAAyB,EAAE,QAAQ,UAAU,OAAO,IAAI,EAAE,EAAI,CAAA;GACrE,oBAAC,OAAD,CAAQ,CAAA;GACR,oBAAC,UAAD,EAAQ,yBAAyB,EAAE,QAAQ,WAAW,EAAE,EAAI,CAAA;EACxD,EAAA,CAAA,GACN,qBAAC,QAAD,EAAA,UAAA;GACE,oBAAC,KAAD;IAAG,OAAM;IAAY,MAAK;IAAgB,UAAA;GAEvC,CAAA;GACF,WAAW,SACV,oBAAC,WAAD;IAAiB;IAAY;IAC1B;GACQ,CAAA,IAEX,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,OAAD;IAAK,OAAO;IAAZ,UAAA,CACE,oBAAC,SAAD;KAAc;KAAW;KAAY;IAAO,CAAA,GAC5C,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,QAAD,EAAa,IAAM,CAAA,GACnB,oBAAC,QAAD;KACE,IAAG;KACH,OAAO,WAAW,SAAS,mBAAmB;KAC9C,UAAU;KAET;IACG,CAAA,CACH,EAAA,CAAA,CACF;GACL,CAAA,GAAA,oBAAC,OAAD;IAAK,OAAO;IAAe,yBAAA;IAAsB,QAAA;GAAQ,CAAA,CACzD,EAAA,CAAA;GAEJ,oBAAC,aAAD,CAAc,CAAA;GACd,oBAAC,UAAD,EAAQ,KAAK,cAAc,KAAK,KAAO,CAAA;GACvC,oBAAC,UAAD,EAAQ,yBAAyB,EAAE,QAAQ,aAAa,EAAE,EAAI,CAAA;EAC1D,EAAA,CAAA,CACF;CACN,CAAA,CAAA,EAAA,CAAA;AAEN"}
1
+ {"version":3,"file":"document.mjs","names":[],"sources":["../../src/ui/document.tsx"],"sourcesContent":["import { raw } from \"hono/html\";\nimport type { FC } from \"hono/jsx\";\nimport { assetManifest } from \"../asset-manifest.ts\";\nimport { getCsrfToken } from \"../middleware/csrf.ts\";\nimport { getStore } from \"../store.ts\";\nimport { Style, css } from \"./css.ts\";\nimport { BareShell } from \"./shell/bare.tsx\";\nimport { bootScript } from \"./shell/boot-script.ts\";\nimport type { NavConfig } from \"./shell/nav.ts\";\nimport { Sidebar } from \"./shell/sidebar.tsx\";\nimport { ToastRegion, TopBar } from \"./shell/topbar.tsx\";\nimport { baseStyle } from \"./styles.ts\";\nimport { DARK_THEME, LIGHT_THEME } from \"./theme.ts\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/** Active section, project, and breadcrumb trail passed to the shell. */\nexport type { NavConfig };\n\n/** Rendered HTML content returned by page components. */\nexport type RenderedContent = string | Promise<string>;\n\n/**\n * Page frame: `default` is a readable column, `wide` uses the full viewport,\n * `bare` drops the app chrome (sign-in, invites).\n */\ntype PageLayout = \"default\" | \"wide\" | \"bare\";\n\nconst shellRoot = css`\n /* shell */\n display: grid;\n grid-template-columns: var(--sidebar-width) minmax(0, 1fr);\n min-height: 100vh;\n @media (max-width: 880px) {\n & {\n grid-template-columns: minmax(0, 1fr);\n }\n }\n`;\n\nconst shellContent = css`\n /* shell-content */\n width: 100%;\n max-width: 1200px;\n margin: 0 auto;\n padding: var(--space-6) var(--space-6) var(--space-8);\n min-width: 0;\n @media (max-width: 880px) {\n & {\n padding: var(--space-4);\n }\n }\n`;\n\nconst shellContentWide = css`\n /* shell-content-wide */\n ${shellContent}\n max-width: none;\n`;\n\nconst shellBackdrop = css`\n /* shell-backdrop */\n position: fixed;\n inset: 0;\n z-index: 55;\n background: rgb(0 0 0 / 0.4);\n &[hidden] {\n display: none;\n }\n`;\n\n// Hono's JSX.Element is typed as `HtmlEscapedString | Promise<...>`, so JSX-returning\n// Functions can legitimately return a promise; the rule is a false positive here.\n/** Full HTML document shell with sidebar, top bar, theme, toasts, and HTMX. */\nexport const DocumentLayout: FC<{\n title: string;\n nav?: NavConfig;\n layout?: PageLayout;\n children?: unknown;\n}> = ({ title, nav, layout = \"default\", children }) => {\n const { ui, config, sessionId } = getStore();\n const name = ui.name ?? \"StoryShelf\";\n const logo = ui.logo ?? assetManifest.mark.href;\n const light = ui.lightTheme ?? LIGHT_THEME;\n const dark = ui.darkTheme ?? DARK_THEME;\n\n return (\n <>\n {/* Without a doctype browsers use quirks mode (form margins, line-box struts, …). */}\n {raw(\"<!DOCTYPE html>\")}\n <html lang=\"en\" data-theme=\"system\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <meta name=\"color-scheme\" content=\"light dark\" />\n <meta name=\"csrf-token\" content={getCsrfToken(config.secret, sessionId)} />\n {/* Favicon is the product's own mark, not brandable: it identifies\n StoryShelf in the tab strip. `ui.logo` stays overridable. */}\n <link rel=\"icon\" type=\"image/svg+xml\" href={assetManifest.mark.href} />\n <title>\n {title} · {name}\n </title>\n <style dangerouslySetInnerHTML={{ __html: baseStyle(light, dark) }} />\n <Style />\n <script dangerouslySetInnerHTML={{ __html: bootScript() }} />\n </head>\n <body>\n <a class=\"skip-link\" href=\"#main-content\">\n Skip to content\n </a>\n {layout === \"bare\" ? (\n <BareShell name={name} logo={logo}>\n {children}\n </BareShell>\n ) : (\n <>\n <div class={shellRoot}>\n <Sidebar nav={nav} name={name} logo={logo} />\n <div>\n <TopBar nav={nav} />\n <main\n id=\"main-content\"\n class={layout === \"wide\" ? shellContentWide : shellContent}\n tabindex={-1}\n >\n {children}\n </main>\n </div>\n </div>\n <div class={shellBackdrop} data-sidebar-backdrop hidden />\n </>\n )}\n <ToastRegion />\n <script src={assetManifest.htmx.href} />\n <script src={assetManifest.client.href} />\n </body>\n </html>\n </>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;AA4BA,MAAM,YAAY,GAAG;;;;;;;;;;;AAYrB,MAAM,eAAe,GAAG;;;;;;;;;;;;;AAcxB,MAAM,mBAAmB,GAAG;;IAExB,aAAa;;;AAIjB,MAAM,gBAAgB,GAAG;;;;;;;;;;;AAczB,MAAa,kBAKP,EAAE,OAAO,KAAK,SAAS,WAAW,eAAe;CACrD,MAAM,EAAE,IAAI,QAAQ,cAAc,SAAS;CAC3C,MAAM,OAAO,GAAG,QAAQ;CACxB,MAAM,OAAO,GAAG,QAAQ,cAAc,KAAK;CAC3C,MAAM,QAAQ,GAAG,cAAc;CAC/B,MAAM,OAAO,GAAG,aAAa;CAE7B,OACE,qBAAA,UAAA,EAAA,UAAA,CAEG,IAAI,iBAAiB,GACtB,qBAAC,QAAD;EAAM,MAAK;EAAK,cAAW;EAA3B,UAAA,CACE,qBAAC,QAAD,EAAA,UAAA;GACE,oBAAC,QAAD,EAAM,SAAQ,QAAS,CAAA;GACvB,oBAAC,QAAD;IAAM,MAAK;IAAW,SAAQ;GAAuC,CAAA;GACrE,oBAAC,QAAD;IAAM,MAAK;IAAe,SAAQ;GAAc,CAAA;GAChD,oBAAC,QAAD;IAAM,MAAK;IAAa,SAAS,aAAa,OAAO,QAAQ,SAAS;GAAI,CAAA;GAG1E,oBAAC,QAAD;IAAM,KAAI;IAAO,MAAK;IAAgB,MAAM,cAAc,KAAK;GAAO,CAAA;GACtE,qBAAC,SAAD,EAAA,UAAA;IACG;IAAM;IAAI;GACN,EAAA,CAAA;GACP,oBAAC,SAAD,EAAO,yBAAyB,EAAE,QAAQ,UAAU,OAAO,IAAI,EAAE,EAAI,CAAA;GACrE,oBAAC,OAAD,CAAQ,CAAA;GACR,oBAAC,UAAD,EAAQ,yBAAyB,EAAE,QAAQ,WAAW,EAAE,EAAI,CAAA;EACxD,EAAA,CAAA,GACN,qBAAC,QAAD,EAAA,UAAA;GACE,oBAAC,KAAD;IAAG,OAAM;IAAY,MAAK;IAAgB,UAAA;GAEvC,CAAA;GACF,WAAW,SACV,oBAAC,WAAD;IAAiB;IAAY;IAC1B;GACQ,CAAA,IAEX,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,OAAD;IAAK,OAAO;IAAZ,UAAA,CACE,oBAAC,SAAD;KAAc;KAAW;KAAY;IAAO,CAAA,GAC5C,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,QAAD,EAAa,IAAM,CAAA,GACnB,oBAAC,QAAD;KACE,IAAG;KACH,OAAO,WAAW,SAAS,mBAAmB;KAC9C,UAAU;KAET;IACG,CAAA,CACH,EAAA,CAAA,CACF;GACL,CAAA,GAAA,oBAAC,OAAD;IAAK,OAAO;IAAe,yBAAA;IAAsB,QAAA;GAAQ,CAAA,CACzD,EAAA,CAAA;GAEJ,oBAAC,aAAD,CAAc,CAAA;GACd,oBAAC,UAAD,EAAQ,KAAK,cAAc,KAAK,KAAO,CAAA;GACvC,oBAAC,UAAD,EAAQ,KAAK,cAAc,OAAO,KAAO,CAAA;EACrC,EAAA,CAAA,CACF;CACN,CAAA,CAAA,EAAA,CAAA;AAEN"}
package/dist/ui/forms.mjs CHANGED
@@ -226,28 +226,29 @@ const CheckField = ({ label, name, value, checked, disabled, hint }) => {
226
226
  });
227
227
  };
228
228
  /** Labeled textarea with error and hint states. */
229
- const TextareaField = ({ label, name, value, placeholder, rows = 3, required, hint, error }) => {
229
+ const TextareaField = ({ label, name, id, value, placeholder, rows = 3, required, hint, error }) => {
230
+ const domId = id ?? name;
230
231
  return /* @__PURE__ */ jsxs("div", {
231
232
  class: fieldWrap,
232
233
  children: [
233
234
  /* @__PURE__ */ jsx("label", {
234
235
  class: fieldLabel,
235
- for: name,
236
+ for: domId,
236
237
  children: label
237
238
  }),
238
239
  /* @__PURE__ */ jsx("textarea", {
239
240
  class: error ? fieldTextareaError : fieldTextarea,
240
- id: name,
241
+ id: domId,
241
242
  name,
242
243
  placeholder,
243
244
  rows,
244
245
  required,
245
246
  "aria-invalid": error ? "true" : void 0,
246
- "aria-describedby": fieldDescribedBy(name, error, hint),
247
+ "aria-describedby": fieldDescribedBy(domId, error, hint),
247
248
  children: value
248
249
  }),
249
250
  /* @__PURE__ */ jsx(FieldAssistant, {
250
- name,
251
+ name: domId,
251
252
  error,
252
253
  hint
253
254
  })