@storyshelf/app 0.7.0 → 0.9.0-next-d2cc082

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 (270) hide show
  1. package/dist/app-types.d.mts +1 -3
  2. package/dist/asset-manifest.mjs +1 -2
  3. package/dist/assets/htmx.mjs +1 -2
  4. package/dist/assets/mark.mjs +1 -2
  5. package/dist/boot-tasks.mjs +1 -2
  6. package/dist/build-state-flags.mjs +1 -2
  7. package/dist/build-state-setup.mjs +1 -2
  8. package/dist/capture-alerts.mjs +1 -2
  9. package/dist/capture-process.mjs +1 -2
  10. package/dist/capture-setup.mjs +1 -2
  11. package/dist/connections.mjs +1 -2
  12. package/dist/consumer-setup.mjs +1 -2
  13. package/dist/credential-keys.mjs +1 -2
  14. package/dist/credentials.mjs +1 -2
  15. package/dist/design-events.mjs +71 -0
  16. package/dist/design-events.mjs.map +1 -0
  17. package/dist/git-status.mjs +1 -2
  18. package/dist/hidden-data.mjs +1 -2
  19. package/dist/index.d.mts +0 -2
  20. package/dist/index.mjs +2 -3
  21. package/dist/insights/budget.mjs +1 -2
  22. package/dist/insights/deps.mjs +1 -2
  23. package/dist/insights/errors.mjs +1 -2
  24. package/dist/insights/health-panel.mjs +0 -1
  25. package/dist/insights/job.mjs +1 -2
  26. package/dist/insights/load-health.mjs +1 -2
  27. package/dist/insights/load-triage.mjs +1 -2
  28. package/dist/insights/panel.mjs +1 -2
  29. package/dist/insights/request.mjs +1 -2
  30. package/dist/insights/stale.mjs +1 -2
  31. package/dist/insights/usage-report.mjs +1 -2
  32. package/dist/insights/view.mjs +1 -2
  33. package/dist/job-canceller.mjs +1 -2
  34. package/dist/job-queue.mjs +1 -2
  35. package/dist/jobs-setup.mjs +1 -2
  36. package/dist/lifecycle.mjs +2 -3
  37. package/dist/lifecycle.mjs.map +1 -1
  38. package/dist/middleware/auth-gate.mjs +1 -2
  39. package/dist/middleware/csrf.mjs +1 -2
  40. package/dist/middleware/rate-limit.mjs +1 -2
  41. package/dist/middleware/request-log.mjs +1 -2
  42. package/dist/middleware/require-review.mjs +1 -2
  43. package/dist/middleware/require-testing.mjs +1 -2
  44. package/dist/middleware/server-timing.mjs +1 -2
  45. package/dist/middleware/setup-gate.mjs +1 -2
  46. package/dist/middleware/store-scope.mjs +1 -2
  47. package/dist/notifications-pause.mjs +1 -2
  48. package/dist/notify.d.mts +1 -3
  49. package/dist/notify.mjs +5 -3
  50. package/dist/notify.mjs.map +1 -1
  51. package/dist/pages/a11y-violations.mjs +1 -2
  52. package/dist/pages/admin-ai.mjs +1 -2
  53. package/dist/pages/admin-system-origin.mjs +19 -10
  54. package/dist/pages/admin-system-origin.mjs.map +1 -1
  55. package/dist/pages/admin-system-targets.mjs +1 -2
  56. package/dist/pages/admin-system.mjs +1 -2
  57. package/dist/pages/build-detail.mjs +1 -2
  58. package/dist/pages/build-diff-actions.mjs +1 -2
  59. package/dist/pages/build-diff-comments.mjs +1 -2
  60. package/dist/pages/build-diff-default.mjs +53 -0
  61. package/dist/pages/build-diff-default.mjs.map +1 -0
  62. package/dist/pages/build-diff-environment.mjs +34 -5
  63. package/dist/pages/build-diff-environment.mjs.map +1 -1
  64. package/dist/pages/build-diff-header.mjs +1 -2
  65. package/dist/pages/build-diff-insight.mjs +1 -2
  66. package/dist/pages/build-diff-model.mjs +20 -3
  67. package/dist/pages/build-diff-model.mjs.map +1 -1
  68. package/dist/pages/build-diff-nav.mjs +1 -2
  69. package/dist/pages/build-diff-reference.mjs +84 -0
  70. package/dist/pages/build-diff-reference.mjs.map +1 -0
  71. package/dist/pages/build-diff-shortcuts.mjs +1 -2
  72. package/dist/pages/build-diff-stale.mjs +1 -2
  73. package/dist/pages/build-diff-usage.mjs +1 -2
  74. package/dist/pages/build-diff-viewer.mjs +38 -12
  75. package/dist/pages/build-diff-viewer.mjs.map +1 -1
  76. package/dist/pages/build-diff.mjs +6 -12
  77. package/dist/pages/build-diff.mjs.map +1 -1
  78. package/dist/pages/build-tests.mjs +1 -2
  79. package/dist/pages/component-detail.mjs +1 -2
  80. package/dist/pages/component-stories.mjs +1 -2
  81. package/dist/pages/compute-jobs.mjs +1 -2
  82. package/dist/pages/design-links.mjs +1 -2
  83. package/dist/pages/environment-change.mjs +16 -4
  84. package/dist/pages/environment-change.mjs.map +1 -1
  85. package/dist/pages/health-panel.mjs +1 -2
  86. package/dist/pages/invite.mjs +1 -2
  87. package/dist/pages/label-detail.mjs +1 -2
  88. package/dist/pages/library-grid.mjs +1 -2
  89. package/dist/pages/library-model.mjs +1 -2
  90. package/dist/pages/library-snapshots.mjs +1 -2
  91. package/dist/pages/library.mjs +1 -2
  92. package/dist/pages/login.mjs +1 -2
  93. package/dist/pages/notifications-paused.mjs +1 -2
  94. package/dist/pages/passkey-ceremony.mjs +1 -2
  95. package/dist/pages/profile.mjs +1 -2
  96. package/dist/pages/project-builds-table.mjs +1 -2
  97. package/dist/pages/project-builds.mjs +1 -2
  98. package/dist/pages/project-create.mjs +1 -2
  99. package/dist/pages/project-settings.mjs +1 -2
  100. package/dist/pages/projects-card.mjs +1 -2
  101. package/dist/pages/projects.mjs +1 -2
  102. package/dist/pages/review-changeset.mjs +1 -2
  103. package/dist/pages/review-comments.mjs +1 -2
  104. package/dist/pages/review-verdict.mjs +1 -2
  105. package/dist/pages/root.mjs +1 -2
  106. package/dist/pages/settings-ai.mjs +1 -2
  107. package/dist/pages/settings-capture.mjs +1 -2
  108. package/dist/pages/settings-design-credential.mjs +1 -2
  109. package/dist/pages/settings-design-links.mjs +1 -2
  110. package/dist/pages/settings-design.mjs +1 -2
  111. package/dist/pages/settings-general.mjs +1 -2
  112. package/dist/pages/settings-labels.mjs +1 -2
  113. package/dist/pages/settings-links.mjs +1 -2
  114. package/dist/pages/settings-members.mjs +1 -2
  115. package/dist/pages/settings-notifications-git.mjs +1 -2
  116. package/dist/pages/settings-notifications.mjs +1 -2
  117. package/dist/pages/settings-tests.mjs +1 -2
  118. package/dist/pages/settings-tokens.mjs +1 -2
  119. package/dist/pages/settings-webhooks.mjs +1 -2
  120. package/dist/pages/storybook.mjs +1 -2
  121. package/dist/pages/target-choices.mjs +1 -2
  122. package/dist/publish-setup.mjs +1 -2
  123. package/dist/retention-timer.mjs +10 -11
  124. package/dist/retention-timer.mjs.map +1 -1
  125. package/dist/review-events.mjs +1 -2
  126. package/dist/routers/admin-pages.mjs +1 -2
  127. package/dist/routers/admin-public-origin.mjs +10 -3
  128. package/dist/routers/admin-public-origin.mjs.map +1 -1
  129. package/dist/routers/admin.mjs +8 -4
  130. package/dist/routers/admin.mjs.map +1 -1
  131. package/dist/routers/assets.mjs +1 -2
  132. package/dist/routers/attempts.mjs +1 -2
  133. package/dist/routers/auth-sync.mjs +1 -2
  134. package/dist/routers/auth.mjs +1 -2
  135. package/dist/routers/baseline-guard.mjs +2 -3
  136. package/dist/routers/baseline-guard.mjs.map +1 -1
  137. package/dist/routers/baseline-reference.mjs +72 -0
  138. package/dist/routers/baseline-reference.mjs.map +1 -0
  139. package/dist/routers/baselines.handlers.mjs +1 -2
  140. package/dist/routers/baselines.mjs +1 -2
  141. package/dist/routers/build-cancel.mjs +1 -2
  142. package/dist/routers/build-changeset.mjs +1 -2
  143. package/dist/routers/build-reviews.handlers.mjs +1 -2
  144. package/dist/routers/build-reviews.mjs +1 -2
  145. package/dist/routers/builds.handlers.mjs +23 -9
  146. package/dist/routers/builds.handlers.mjs.map +1 -1
  147. package/dist/routers/builds.mjs +7 -3
  148. package/dist/routers/builds.mjs.map +1 -1
  149. package/dist/routers/capture-cache.mjs +29 -0
  150. package/dist/routers/capture-cache.mjs.map +1 -0
  151. package/dist/routers/capture-config.mjs +1 -2
  152. package/dist/routers/comments.mjs +1 -2
  153. package/dist/routers/comparison-image.mjs +29 -0
  154. package/dist/routers/comparison-image.mjs.map +1 -0
  155. package/dist/routers/component-catalog.handlers.mjs +1 -2
  156. package/dist/routers/component.handlers.mjs +1 -2
  157. package/dist/routers/connections.mjs +1 -2
  158. package/dist/routers/design-connect.handlers.mjs +1 -2
  159. package/dist/routers/design.handlers.mjs +8 -2
  160. package/dist/routers/design.handlers.mjs.map +1 -1
  161. package/dist/routers/flash.mjs +1 -2
  162. package/dist/routers/health-report.mjs +1 -2
  163. package/dist/routers/health.mjs +1 -2
  164. package/dist/routers/helpers.mjs +1 -2
  165. package/dist/routers/htmx.mjs +1 -2
  166. package/dist/routers/insights-ui.mjs +1 -2
  167. package/dist/routers/insights.handlers.mjs +1 -2
  168. package/dist/routers/insights.mjs +1 -2
  169. package/dist/routers/labels.mjs +1 -2
  170. package/dist/routers/links.handlers.mjs +1 -2
  171. package/dist/routers/links.mjs +1 -2
  172. package/dist/routers/media.mjs +14 -5
  173. package/dist/routers/media.mjs.map +1 -1
  174. package/dist/routers/members.mjs +1 -2
  175. package/dist/routers/notification-channels.mjs +1 -2
  176. package/dist/routers/notification-subscriptions.mjs +1 -2
  177. package/dist/routers/oauth-state.mjs +1 -2
  178. package/dist/routers/preview-base.mjs +1 -2
  179. package/dist/routers/preview-token.mjs +1 -2
  180. package/dist/routers/profile.mjs +1 -2
  181. package/dist/routers/projects.mjs +1 -2
  182. package/dist/routers/publish-upload.mjs +19 -12
  183. package/dist/routers/publish-upload.mjs.map +1 -1
  184. package/dist/routers/published-access.mjs +1 -2
  185. package/dist/routers/published-compression.mjs +109 -0
  186. package/dist/routers/published-compression.mjs.map +1 -0
  187. package/dist/routers/published-files.mjs +26 -9
  188. package/dist/routers/published-files.mjs.map +1 -1
  189. package/dist/routers/review-forms.mjs +1 -2
  190. package/dist/routers/review-next.mjs +1 -2
  191. package/dist/routers/review-section.handlers.mjs +1 -2
  192. package/dist/routers/review-switch-off.mjs +1 -2
  193. package/dist/routers/schemas.mjs +1 -2
  194. package/dist/routers/scoped-models.mjs +1 -2
  195. package/dist/routers/settings-ai.mjs +1 -2
  196. package/dist/routers/settings-capture.mjs +1 -2
  197. package/dist/routers/settings-design.mjs +1 -2
  198. package/dist/routers/settings-general.mjs +1 -2
  199. package/dist/routers/settings-git-channel.mjs +1 -2
  200. package/dist/routers/settings-labels.mjs +1 -2
  201. package/dist/routers/settings-links.mjs +1 -2
  202. package/dist/routers/settings-members.mjs +1 -2
  203. package/dist/routers/settings-notifications.mjs +1 -2
  204. package/dist/routers/settings-tokens.mjs +1 -2
  205. package/dist/routers/settings-webhooks.mjs +1 -2
  206. package/dist/routers/settings.handlers.mjs +1 -2
  207. package/dist/routers/settings.mjs +1 -2
  208. package/dist/routers/signed-storybook.mjs +5 -3
  209. package/dist/routers/signed-storybook.mjs.map +1 -1
  210. package/dist/routers/snapshot-groups.mjs +1 -2
  211. package/dist/routers/snapshots.mjs +1 -2
  212. package/dist/routers/storybook.mjs +21 -17
  213. package/dist/routers/storybook.mjs.map +1 -1
  214. package/dist/routers/tokens.mjs +1 -2
  215. package/dist/routers/ui.mjs +4 -2
  216. package/dist/routers/ui.mjs.map +1 -1
  217. package/dist/routers/webhooks.mjs +1 -2
  218. package/dist/routers/well-known.mjs +1 -2
  219. package/dist/routes/api-routes.mjs +1 -2
  220. package/dist/routes/classification.mjs +1 -2
  221. package/dist/routes/openapi-classes.mjs +1 -2
  222. package/dist/routes/page-routes.mjs +2 -3
  223. package/dist/routes/page-routes.mjs.map +1 -1
  224. package/dist/runtime.mjs +1 -2
  225. package/dist/serverless.mjs +1 -2
  226. package/dist/store.mjs +1 -2
  227. package/dist/targets.mjs +1 -2
  228. package/dist/ui/avatar.mjs +1 -2
  229. package/dist/ui/buttons.mjs +1 -2
  230. package/dist/ui/code-block.mjs +1 -2
  231. package/dist/ui/compare-stage.mjs +86 -54
  232. package/dist/ui/compare-stage.mjs.map +1 -1
  233. package/dist/ui/csrf-field.mjs +1 -2
  234. package/dist/ui/css.mjs +1 -2
  235. package/dist/ui/design-reference.mjs +1 -2
  236. package/dist/ui/document.mjs +1 -2
  237. package/dist/ui/dropdown.mjs +1 -2
  238. package/dist/ui/feedback.mjs +1 -2
  239. package/dist/ui/filter-input.mjs +1 -2
  240. package/dist/ui/forms.mjs +1 -2
  241. package/dist/ui/icons/icon.mjs +1 -2
  242. package/dist/ui/icons/paths.mjs +1 -2
  243. package/dist/ui/icons/sprite.mjs +1 -2
  244. package/dist/ui/kbd.mjs +1 -2
  245. package/dist/ui/layout.mjs +1 -2
  246. package/dist/ui/preview-embed.mjs +1 -2
  247. package/dist/ui/progress.mjs +1 -2
  248. package/dist/ui/segmented.mjs +1 -2
  249. package/dist/ui/shell/account.mjs +1 -2
  250. package/dist/ui/shell/bare.mjs +1 -2
  251. package/dist/ui/shell/boot-script.mjs +1 -2
  252. package/dist/ui/shell/client-script.mjs +1 -2
  253. package/dist/ui/shell/nav.mjs +1 -2
  254. package/dist/ui/shell/review-script.mjs +17 -3
  255. package/dist/ui/shell/review-script.mjs.map +1 -1
  256. package/dist/ui/shell/sidebar.mjs +1 -2
  257. package/dist/ui/shell/topbar.mjs +1 -2
  258. package/dist/ui/stacks.mjs +1 -2
  259. package/dist/ui/styles/base.mjs +1 -2
  260. package/dist/ui/styles/behavior.mjs +1 -2
  261. package/dist/ui/styles/review-layout.mjs +1 -2
  262. package/dist/ui/styles/review.mjs +1 -2
  263. package/dist/ui/styles/tokens.mjs +1 -2
  264. package/dist/ui/styles.mjs +1 -2
  265. package/dist/ui/subnav.mjs +1 -2
  266. package/dist/ui/table.mjs +1 -2
  267. package/dist/ui/theme.mjs +1 -2
  268. package/dist/ui/thumbnail.mjs +1 -2
  269. package/dist/ui/time.mjs +1 -2
  270. package/package.json +9 -18
@@ -2,7 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { getStore } from "../store.mjs";
5
- //#region src/routers/well-known.ts
5
+ // src/routers/well-known.ts
6
6
  /** SSO-capable login methods (oauth buttons + SSO) with IdP reply URLs. */
7
7
  function ssoProviders(auth, origin) {
8
8
  return auth.loginMethods().filter((method) => method.kind === "oauth" || method.kind === "sso").map((method) => ({
@@ -44,7 +44,6 @@ function registerWellKnown(app, auth) {
44
44
  });
45
45
  app.get("/.well-known/change-password", (c) => c.redirect("/profile", 302));
46
46
  }
47
- //#endregion
48
47
  export { registerWellKnown };
49
48
 
50
49
  //# sourceMappingURL=well-known.mjs.map
@@ -1,7 +1,7 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- //#region src/routes/api-routes.ts
4
+ // src/routes/api-routes.ts
5
5
  /** JSON API routes, keyed by `METHOD /hono-pattern`. */
6
6
  const API_ROUTE_CLASSES = {
7
7
  "GET /api/v1/projects": "hosting",
@@ -87,7 +87,6 @@ const API_ROUTE_CLASSES = {
87
87
  "POST /api/v1/projects/:slug/builds/:buildId/reviews": "review",
88
88
  "GET /api/v1/projects/:slug/builds/:buildId/changeset": "review"
89
89
  };
90
- //#endregion
91
90
  export { API_ROUTE_CLASSES };
92
91
 
93
92
  //# sourceMappingURL=api-routes.mjs.map
@@ -3,7 +3,7 @@ import "node:url";
3
3
  import.meta.url;
4
4
  import { API_ROUTE_CLASSES } from "./api-routes.mjs";
5
5
  import { PAGE_ROUTE_CLASSES } from "./page-routes.mjs";
6
- //#region src/routes/classification.ts
6
+ // src/routes/classification.ts
7
7
  /**
8
8
  * Route classification (ADR 0006, decision 8): every route the app registers declares whether it
9
9
  * belongs to hosting, testing, review, or both. `classification.test.ts` fails on any registered
@@ -32,7 +32,6 @@ function classOfRoute(method, path) {
32
32
  if (exact !== void 0) return exact;
33
33
  return ROUTE_PREFIX_CLASSES.find((rule) => path.startsWith(rule.prefix))?.routeClass;
34
34
  }
35
- //#endregion
36
35
  export { ROUTE_CLASSES, ROUTE_PREFIX_CLASSES, classOfRoute };
37
36
 
38
37
  //# sourceMappingURL=classification.mjs.map
@@ -2,7 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { classOfRoute } from "./classification.mjs";
5
- //#region src/routes/openapi-classes.ts
5
+ // src/routes/openapi-classes.ts
6
6
  /** Tags every operation of an OpenAPI document with the class of its route (`x-storyshelf-class`). */
7
7
  /** OpenAPI path templates (`{slug}`) as Hono patterns (`:slug`), the form the route table uses. */
8
8
  function honoPattern(path) {
@@ -16,7 +16,6 @@ function markRouteClasses(document) {
16
16
  }
17
17
  return document;
18
18
  }
19
- //#endregion
20
19
  export { markRouteClasses };
21
20
 
22
21
  //# sourceMappingURL=openapi-classes.mjs.map
@@ -1,7 +1,7 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- //#region src/routes/page-routes.ts
4
+ // src/routes/page-routes.ts
5
5
  /** HTML and auth routes, keyed by `METHOD /hono-pattern`. */
6
6
  const PAGE_ROUTE_CLASSES = {
7
7
  "GET /": "hosting",
@@ -53,7 +53,7 @@ const PAGE_ROUTE_CLASSES = {
53
53
  "GET /_/:id": "hosting",
54
54
  "GET /_/:id/*": "hosting",
55
55
  "GET /projects/:slug/storybook": "hosting",
56
- "GET /projects/:slug/storybook/:key/:value": "hosting",
56
+ "GET /projects/:slug/storybook/:key/*": "hosting",
57
57
  "GET /projects/:slug/storybook/build/:buildId/*": "hosting",
58
58
  "GET /_s/:token/*": "testing",
59
59
  "GET /auth/login": "hosting",
@@ -98,7 +98,6 @@ const PAGE_ROUTE_CLASSES = {
98
98
  "GET /projects/:slug/library": "both",
99
99
  "GET /projects/:slug/components/*": "both"
100
100
  };
101
- //#endregion
102
101
  export { PAGE_ROUTE_CLASSES };
103
102
 
104
103
  //# sourceMappingURL=page-routes.mjs.map
@@ -1 +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"}
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/*\": \"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,wCAAwC;CACxC,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
@@ -4,7 +4,7 @@ import.meta.url;
4
4
  import { validateConfig, validateUiConfig } from "@storyshelf/core/config";
5
5
  import { createPublicOriginResolver } from "@storyshelf/core/models";
6
6
  import { createShelfLogger } from "@storyshelf/core/logger";
7
- //#region src/runtime.ts
7
+ // src/runtime.ts
8
8
  function addOptionalSnapshots(snap, options) {
9
9
  if (options.testing?.runner) snap["captureRunner"] = options.testing.runner.metadata;
10
10
  }
@@ -51,7 +51,6 @@ function resolveRuntime(options) {
51
51
  })
52
52
  };
53
53
  }
54
- //#endregion
55
54
  export { buildAdapterSnapshot, resolveRuntime };
56
55
 
57
56
  //# sourceMappingURL=runtime.mjs.map
@@ -1,5 +1,4 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { isServerlessHost as isServerlessHost$1 } from "@storyshelf/core/config";
5
- export { isServerlessHost$1 as isServerlessHost };
4
+ export { isServerlessHost } from "@storyshelf/core/config";
package/dist/store.mjs CHANGED
@@ -3,7 +3,7 @@ import "node:url";
3
3
  import.meta.url;
4
4
  import { createCatalogLoader } from "@storyshelf/core/capture";
5
5
  import { AsyncLocalStorage } from "node:async_hooks";
6
- //#region src/store.ts
6
+ // src/store.ts
7
7
  const storage = new AsyncLocalStorage();
8
8
  /** Run a function with the given request store in scope. */
9
9
  function runWithStore(store, fn) {
@@ -32,7 +32,6 @@ function catalogOf(store = getStore()) {
32
32
  logger: store.logger
33
33
  });
34
34
  }
35
- //#endregion
36
35
  export { catalogOf, getStore, runWithStore, testingEnabled };
37
36
 
38
37
  //# sourceMappingURL=store.mjs.map
package/dist/targets.mjs CHANGED
@@ -5,7 +5,7 @@ import { getStore } from "./store.mjs";
5
5
  import { MAX_BROWSERS, assertSupportedBrowsers, offeredTargets } from "@storyshelf/core/capture";
6
6
  import { HTTPException } from "hono/http-exception";
7
7
  import { isCaptureTarget, parseTarget } from "@storyshelf/core/adapter/capture-runner";
8
- //#region src/targets.ts
8
+ // src/targets.ts
9
9
  /** Which capture targets this instance offers right now, and the checks a settings save runs (ADR 0007). */
10
10
  /**
11
11
  * The targets the runner offers, with the ones that need a URL marked `unavailable` while there is
@@ -40,7 +40,6 @@ async function assertTargetsSelectable(targets, current = []) {
40
40
  throw new HTTPException(400, { message: error instanceof Error ? error.message : "Refused" });
41
41
  }
42
42
  }
43
- //#endregion
44
43
  export { assertTargetsSelectable, runnerTargets, targetLabel };
45
44
 
46
45
  //# sourceMappingURL=targets.mjs.map
@@ -3,7 +3,7 @@ import "node:url";
3
3
  import.meta.url;
4
4
  import { css } from "./css.mjs";
5
5
  import { jsx } from "hono/jsx/jsx-runtime";
6
- //#region src/ui/avatar.tsx
6
+ // src/ui/avatar.tsx
7
7
  const avatarBase = css`
8
8
  /* avatar */
9
9
  flex: none;
@@ -70,7 +70,6 @@ const Avatar = ({ name, src, size = "md" }) => {
70
70
  children: initials(name) || "?"
71
71
  });
72
72
  };
73
- //#endregion
74
73
  export { Avatar, initials };
75
74
 
76
75
  //# sourceMappingURL=avatar.mjs.map
@@ -4,7 +4,7 @@ import.meta.url;
4
4
  import { css } from "./css.mjs";
5
5
  import { Icon } from "./icons/icon.mjs";
6
6
  import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
7
- //#region src/ui/buttons.tsx
7
+ // src/ui/buttons.tsx
8
8
  const btnBase = css`
9
9
  /* btn */
10
10
  appearance: none;
@@ -212,7 +212,6 @@ const Tabs = ({ tabs, label = "Sections" }) => {
212
212
  }))
213
213
  });
214
214
  };
215
- //#endregion
216
215
  export { Button, Tabs };
217
216
 
218
217
  //# sourceMappingURL=buttons.mjs.map
@@ -4,7 +4,7 @@ import.meta.url;
4
4
  import { css } from "./css.mjs";
5
5
  import { Button } from "./buttons.mjs";
6
6
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
7
- //#region src/ui/code-block.tsx
7
+ // src/ui/code-block.tsx
8
8
  const codeWrap = css`
9
9
  /* code-block */
10
10
  display: flex;
@@ -41,7 +41,6 @@ const CodeBlock = ({ code, label = "Copy" }) => {
41
41
  })]
42
42
  });
43
43
  };
44
- //#endregion
45
44
  export { CodeBlock };
46
45
 
47
46
  //# sourceMappingURL=code-block.mjs.map
@@ -4,7 +4,7 @@ import.meta.url;
4
4
  import { css } from "./css.mjs";
5
5
  import { Segmented } from "./segmented.mjs";
6
6
  import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
7
- //#region src/ui/compare-stage.tsx
7
+ // src/ui/compare-stage.tsx
8
8
  /**
9
9
  * Screenshot comparison surface. All modes share one set of images; the
10
10
  * `data-view` / `data-zoom` / `data-flip` attributes on the root (driven by
@@ -70,6 +70,10 @@ const stageRoot = css`
70
70
  font-size: var(--text-sm);
71
71
  text-align: center;
72
72
  }
73
+ .stage__note {
74
+ color: var(--text-secondary);
75
+ font-size: var(--text-sm);
76
+ }
73
77
  .stage__divider,
74
78
  .stage__controls {
75
79
  display: none;
@@ -103,6 +107,10 @@ const stageRoot = css`
103
107
  &[data-zoom="400"] .stage__img {
104
108
  image-rendering: pixelated;
105
109
  }
110
+ /* Design variant: screenshot against a render, two panes and no diff. */
111
+ &[data-variant="design"][data-view="split"] .stage__grid {
112
+ grid-template-columns: repeat(2, minmax(0, 1fr));
113
+ }
106
114
  &[data-view="diff"] .stage__grid,
107
115
  &[data-view="flip"] .stage__grid,
108
116
  &[data-view="swipe"] .stage__grid,
@@ -218,6 +226,12 @@ const ZOOMS = [
218
226
  value: "400"
219
227
  }
220
228
  ];
229
+ /** Modes and zoom a design comparison offers: no pixel diff, and the render always fits the stage. */
230
+ const DESIGN_MODES = [
231
+ "split",
232
+ "swipe",
233
+ "onion"
234
+ ];
221
235
  function withActive(items, active) {
222
236
  return items.map((item) => ({
223
237
  ...item,
@@ -244,12 +258,12 @@ const Pane = ({ kind, label, src, alt, empty, meta }) => {
244
258
  });
245
259
  };
246
260
  /** Swipe and onion-skin sliders; each is shown only in its own mode (CSS). */
247
- const RangeControls = () => {
261
+ const RangeControls = ({ baselineLabel }) => {
248
262
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("label", {
249
263
  class: "stage__controls",
250
264
  "data-for": "swipe",
251
265
  children: [
252
- /* @__PURE__ */ jsx("span", { children: "Baseline" }),
266
+ /* @__PURE__ */ jsx("span", { children: baselineLabel }),
253
267
  /* @__PURE__ */ jsx("input", {
254
268
  type: "range",
255
269
  min: "0",
@@ -264,7 +278,7 @@ const RangeControls = () => {
264
278
  class: "stage__controls",
265
279
  "data-for": "onion",
266
280
  children: [
267
- /* @__PURE__ */ jsx("span", { children: "Baseline" }),
281
+ /* @__PURE__ */ jsx("span", { children: baselineLabel }),
268
282
  /* @__PURE__ */ jsx("input", {
269
283
  type: "range",
270
284
  min: "0",
@@ -277,68 +291,86 @@ const RangeControls = () => {
277
291
  ]
278
292
  })] });
279
293
  };
294
+ /** Mode switch, plus the zoom switch (a design comparison always fits, so it has none). */
295
+ const StageBar = ({ design }) => {
296
+ const modes = design ? MODES.filter((mode) => DESIGN_MODES.includes(mode.value)) : MODES;
297
+ return /* @__PURE__ */ jsxs("div", {
298
+ class: "stage__bar",
299
+ children: [/* @__PURE__ */ jsx(Segmented, {
300
+ label: "Comparison mode",
301
+ "data-compare-mode": true,
302
+ items: withActive(modes, "split")
303
+ }), design ? null : /* @__PURE__ */ jsx(Segmented, {
304
+ label: "Zoom",
305
+ "data-compare-zoom": true,
306
+ items: withActive(ZOOMS, "fit")
307
+ })]
308
+ });
309
+ };
310
+ /** The panes (baseline or design, current, diff) and the swipe divider. */
311
+ const StageGrid = ({ baselineSrc, currentSrc, diffSrc, subject, baselineEmpty = "New story — no baseline yet. Approve to set the baseline.", diffEmpty = "No diff", diffMeta, design, baselineLabel }) => /* @__PURE__ */ jsxs("div", {
312
+ class: "stage__grid",
313
+ children: [
314
+ /* @__PURE__ */ jsx(Pane, {
315
+ kind: "baseline",
316
+ label: baselineLabel,
317
+ src: baselineSrc,
318
+ alt: `${baselineLabel} for ${subject}`,
319
+ empty: baselineEmpty
320
+ }),
321
+ /* @__PURE__ */ jsx(Pane, {
322
+ kind: "current",
323
+ label: "Current",
324
+ src: currentSrc,
325
+ alt: `Current for ${subject}`,
326
+ empty: "No screenshot"
327
+ }),
328
+ design ? null : /* @__PURE__ */ jsx(Pane, {
329
+ kind: "diff",
330
+ label: "Diff",
331
+ src: diffSrc,
332
+ alt: `Diff for ${subject}`,
333
+ empty: diffEmpty,
334
+ meta: diffMeta
335
+ }),
336
+ /* @__PURE__ */ jsx("span", {
337
+ class: "stage__divider",
338
+ "aria-hidden": "true",
339
+ children: /* @__PURE__ */ jsx("span", {
340
+ class: "stage__knob",
341
+ "data-compare-knob": true,
342
+ children: "↔"
343
+ })
344
+ })
345
+ ]
346
+ });
280
347
  /** Baseline / current / diff comparison with swipe, onion-skin, flip, and zoom. */
281
- const CompareStage = ({ baselineSrc, currentSrc, diffSrc, subject, baselineEmpty = "New story — no baseline yet. Approve to set the baseline.", diffEmpty = "No diff", diffMeta }) => {
348
+ const CompareStage = ({ variant = "baseline", baselineLabel = "Baseline", note, ...images }) => {
349
+ const design = variant === "design";
282
350
  return /* @__PURE__ */ jsxs("div", {
283
351
  class: stageRoot,
284
352
  "data-compare": true,
285
353
  "data-view": "split",
286
354
  "data-zoom": "fit",
287
355
  "data-flip": "current",
356
+ "data-variant": variant,
357
+ "data-modes": design ? DESIGN_MODES.join(",") : void 0,
358
+ "data-zooms": design ? "fit" : void 0,
288
359
  children: [
289
- /* @__PURE__ */ jsxs("div", {
290
- class: "stage__bar",
291
- children: [/* @__PURE__ */ jsx(Segmented, {
292
- label: "Comparison mode",
293
- "data-compare-mode": true,
294
- items: withActive(MODES, "split")
295
- }), /* @__PURE__ */ jsx(Segmented, {
296
- label: "Zoom",
297
- "data-compare-zoom": true,
298
- items: withActive(ZOOMS, "fit")
299
- })]
300
- }),
301
- /* @__PURE__ */ jsxs("div", {
302
- class: "stage__grid",
303
- children: [
304
- /* @__PURE__ */ jsx(Pane, {
305
- kind: "baseline",
306
- label: "Baseline",
307
- src: baselineSrc,
308
- alt: `Baseline for ${subject}`,
309
- empty: baselineEmpty
310
- }),
311
- /* @__PURE__ */ jsx(Pane, {
312
- kind: "current",
313
- label: "Current",
314
- src: currentSrc,
315
- alt: `Current for ${subject}`,
316
- empty: "No screenshot"
317
- }),
318
- /* @__PURE__ */ jsx(Pane, {
319
- kind: "diff",
320
- label: "Diff",
321
- src: diffSrc,
322
- alt: `Diff for ${subject}`,
323
- empty: diffEmpty,
324
- meta: diffMeta
325
- }),
326
- /* @__PURE__ */ jsx("span", {
327
- class: "stage__divider",
328
- "aria-hidden": "true",
329
- children: /* @__PURE__ */ jsx("span", {
330
- class: "stage__knob",
331
- "data-compare-knob": true,
332
- children: "↔"
333
- })
334
- })
335
- ]
360
+ /* @__PURE__ */ jsx(StageBar, { design }),
361
+ note ? /* @__PURE__ */ jsx("p", {
362
+ class: "stage__note",
363
+ children: note
364
+ }) : null,
365
+ /* @__PURE__ */ jsx(StageGrid, {
366
+ ...images,
367
+ design,
368
+ baselineLabel
336
369
  }),
337
- /* @__PURE__ */ jsx(RangeControls, {})
370
+ /* @__PURE__ */ jsx(RangeControls, { baselineLabel })
338
371
  ]
339
372
  });
340
373
  };
341
- //#endregion
342
374
  export { CompareStage };
343
375
 
344
376
  //# sourceMappingURL=compare-stage.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"compare-stage.mjs","names":[],"sources":["../../src/ui/compare-stage.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Segmented } from \"./segmented.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/**\n * Screenshot comparison surface. All modes share one set of images; the\n * `data-view` / `data-zoom` / `data-flip` attributes on the root (driven by\n * the review script) decide what is shown. Without JS it stays side-by-side.\n */\nconst stageRoot = css`\n /* compare-stage */\n display: grid;\n gap: var(--space-3);\n .stage__bar {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-3);\n align-items: center;\n justify-content: space-between;\n }\n .stage__grid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: var(--space-3);\n align-items: start;\n }\n .stage__pane {\n position: relative;\n min-width: 0;\n max-height: 78vh;\n overflow: auto;\n background: var(--surface-card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n }\n .stage__label {\n position: sticky;\n top: 0;\n left: 0;\n z-index: 2;\n display: flex;\n align-items: center;\n justify-content: space-between;\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 .stage__img {\n display: block;\n width: 100%;\n height: auto;\n background: repeating-conic-gradient(var(--surface-muted) 0% 25%, var(--surface-subtle) 0% 50%)\n 0 0 / 16px 16px;\n }\n .stage__empty {\n display: grid;\n place-items: center;\n aspect-ratio: 16 / 9;\n padding: var(--space-4);\n background: var(--surface-subtle);\n color: var(--text-secondary);\n font-size: var(--text-sm);\n text-align: center;\n }\n .stage__divider,\n .stage__controls {\n display: none;\n }\n .stage__controls {\n align-items: center;\n gap: var(--space-3);\n color: var(--text-secondary);\n font-size: var(--text-sm);\n }\n .stage__controls input {\n flex: 1;\n accent-color: var(--accent);\n }\n &[data-zoom=\"100\"] .stage__img {\n width: auto;\n max-width: none;\n }\n &[data-zoom=\"200\"] .stage__img {\n width: auto;\n max-width: none;\n zoom: 2;\n }\n &[data-zoom=\"400\"] .stage__img {\n width: auto;\n max-width: none;\n zoom: 4;\n }\n /* Zoomed in, show real pixels instead of a smoothed blur. */\n &[data-zoom=\"200\"] .stage__img,\n &[data-zoom=\"400\"] .stage__img {\n image-rendering: pixelated;\n }\n &[data-view=\"diff\"] .stage__grid,\n &[data-view=\"flip\"] .stage__grid,\n &[data-view=\"swipe\"] .stage__grid,\n &[data-view=\"onion\"] .stage__grid {\n grid-template-columns: minmax(0, 1fr);\n }\n &[data-view=\"diff\"] .stage__pane:not([data-pane=\"diff\"]) {\n display: none;\n }\n &[data-view=\"flip\"][data-flip=\"baseline\"] .stage__pane:not([data-pane=\"baseline\"]),\n &[data-view=\"flip\"]:not([data-flip=\"baseline\"]) .stage__pane:not([data-pane=\"current\"]) {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane[data-pane=\"diff\"],\n &[data-view=\"onion\"] .stage__pane[data-pane=\"diff\"] {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane,\n &[data-view=\"onion\"] .stage__pane,\n &[data-view=\"swipe\"] .stage__divider {\n grid-area: 1 / 1;\n }\n &[data-view=\"swipe\"] .stage__label,\n &[data-view=\"onion\"] .stage__label {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane[data-pane=\"current\"] {\n clip-path: inset(0 0 0 var(--swipe, 50%));\n background: transparent;\n }\n &[data-view=\"onion\"] .stage__pane[data-pane=\"current\"] {\n opacity: var(--onion, 0.5);\n background: transparent;\n }\n &[data-view=\"swipe\"] .stage__divider {\n position: relative;\n display: block;\n z-index: 3;\n justify-self: start;\n width: 2px;\n height: 100%;\n margin-left: var(--swipe, 50%);\n background: var(--accent);\n pointer-events: none;\n }\n /* The only part of the divider that takes pointer input: drag it to wipe. */\n .stage__knob {\n position: absolute;\n top: 50%;\n left: 50%;\n display: grid;\n place-items: center;\n width: 32px;\n height: 32px;\n transform: translate(-50%, -50%);\n background: var(--accent);\n color: var(--accent-contrast);\n border: 2px solid var(--surface-card);\n border-radius: var(--radius-pill);\n box-shadow: var(--shadow-2);\n font-size: var(--text-lg);\n line-height: 1;\n cursor: ew-resize;\n pointer-events: auto;\n touch-action: none;\n user-select: none;\n }\n &[data-view=\"flip\"] .stage__grid {\n cursor: pointer;\n }\n &[data-view=\"swipe\"] .stage__controls[data-for=\"swipe\"],\n &[data-view=\"onion\"] .stage__controls[data-for=\"onion\"] {\n display: flex;\n }\n`;\n\nconst MODES = [\n { label: \"Side by side\", value: \"split\" },\n { label: \"Swipe\", value: \"swipe\" },\n { label: \"Onion skin\", value: \"onion\" },\n { label: \"Diff\", value: \"diff\" },\n { label: \"Flip\", value: \"flip\" },\n];\n\nconst ZOOMS = [\n { label: \"Fit\", value: \"fit\" },\n { label: \"100%\", value: \"100\" },\n { label: \"200%\", value: \"200\" },\n { label: \"400%\", value: \"400\" },\n];\n\nfunction withActive(\n items: { label: string; value: string }[],\n active: string,\n): { label: string; value: string; active: boolean }[] {\n return items.map((item) => ({ ...item, active: item.value === active }));\n}\n\ninterface PaneProps {\n kind: \"baseline\" | \"current\" | \"diff\";\n label: string;\n src: string | null;\n alt: string;\n empty: unknown;\n meta?: string;\n}\n\nconst Pane: FC<PaneProps> = ({ kind, label, src, alt, empty, meta }) => {\n return (\n <div class=\"stage__pane\" data-pane={kind}>\n <div class=\"stage__label\">\n <span>{label}</span>\n {meta ? <span>{meta}</span> : null}\n </div>\n {src ? (\n <img class=\"stage__img\" src={src} alt={alt} loading=\"lazy\" decoding=\"async\" />\n ) : (\n <div class=\"stage__empty\">{empty}</div>\n )}\n </div>\n );\n};\n\n/** Swipe and onion-skin sliders; each is shown only in its own mode (CSS). */\nconst RangeControls: FC = () => {\n return (\n <>\n <label class=\"stage__controls\" data-for=\"swipe\">\n <span>Baseline</span>\n <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" data-compare-swipe aria-label=\"Swipe\" />\n <span>Current</span>\n </label>\n <label class=\"stage__controls\" data-for=\"onion\">\n <span>Baseline</span>\n <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" data-compare-onion aria-label=\"Opacity\" />\n <span>Current</span>\n </label>\n </>\n );\n};\n\n/** Props for the screenshot comparison surface. */\nexport interface CompareStageProps {\n /** Baseline image URL, or null when the story has no baseline yet. */\n baselineSrc: string | null;\n currentSrc: string;\n /** Diff overlay URL, or null when none was produced. */\n diffSrc: string | null;\n /** Accessible description shared by the images, e.g. \"Button / Primary\". */\n subject: string;\n baselineEmpty?: unknown;\n diffEmpty?: unknown;\n diffMeta?: string;\n}\n\n/** Baseline / current / diff comparison with swipe, onion-skin, flip, and zoom. */\nexport const CompareStage: FC<CompareStageProps> = ({\n baselineSrc,\n currentSrc,\n diffSrc,\n subject,\n baselineEmpty = \"New story — no baseline yet. Approve to set the baseline.\",\n diffEmpty = \"No diff\",\n diffMeta,\n}) => {\n return (\n <div class={stageRoot} data-compare data-view=\"split\" data-zoom=\"fit\" data-flip=\"current\">\n <div class=\"stage__bar\">\n <Segmented label=\"Comparison mode\" data-compare-mode items={withActive(MODES, \"split\")} />\n <Segmented label=\"Zoom\" data-compare-zoom items={withActive(ZOOMS, \"fit\")} />\n </div>\n <div class=\"stage__grid\">\n <Pane\n kind=\"baseline\"\n label=\"Baseline\"\n src={baselineSrc}\n alt={`Baseline for ${subject}`}\n empty={baselineEmpty}\n />\n <Pane\n kind=\"current\"\n label=\"Current\"\n src={currentSrc}\n alt={`Current for ${subject}`}\n empty=\"No screenshot\"\n />\n <Pane\n kind=\"diff\"\n label=\"Diff\"\n src={diffSrc}\n alt={`Diff for ${subject}`}\n empty={diffEmpty}\n meta={diffMeta}\n />\n <span class=\"stage__divider\" aria-hidden=\"true\">\n <span class=\"stage__knob\" data-compare-knob>\n ↔\n </span>\n </span>\n </div>\n <RangeControls />\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"compare-stage.mjs","names":[],"sources":["../../src/ui/compare-stage.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\nimport { css } from \"./css.ts\";\nimport { Segmented } from \"./segmented.tsx\";\n\n/* eslint-disable promise-function-async -- Hono JSX components return HtmlEscapedString | Promise<HtmlEscapedString> */\n\n/**\n * Screenshot comparison surface. All modes share one set of images; the\n * `data-view` / `data-zoom` / `data-flip` attributes on the root (driven by\n * the review script) decide what is shown. Without JS it stays side-by-side.\n */\nconst stageRoot = css`\n /* compare-stage */\n display: grid;\n gap: var(--space-3);\n .stage__bar {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-3);\n align-items: center;\n justify-content: space-between;\n }\n .stage__grid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: var(--space-3);\n align-items: start;\n }\n .stage__pane {\n position: relative;\n min-width: 0;\n max-height: 78vh;\n overflow: auto;\n background: var(--surface-card);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n }\n .stage__label {\n position: sticky;\n top: 0;\n left: 0;\n z-index: 2;\n display: flex;\n align-items: center;\n justify-content: space-between;\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 .stage__img {\n display: block;\n width: 100%;\n height: auto;\n background: repeating-conic-gradient(var(--surface-muted) 0% 25%, var(--surface-subtle) 0% 50%)\n 0 0 / 16px 16px;\n }\n .stage__empty {\n display: grid;\n place-items: center;\n aspect-ratio: 16 / 9;\n padding: var(--space-4);\n background: var(--surface-subtle);\n color: var(--text-secondary);\n font-size: var(--text-sm);\n text-align: center;\n }\n .stage__note {\n color: var(--text-secondary);\n font-size: var(--text-sm);\n }\n .stage__divider,\n .stage__controls {\n display: none;\n }\n .stage__controls {\n align-items: center;\n gap: var(--space-3);\n color: var(--text-secondary);\n font-size: var(--text-sm);\n }\n .stage__controls input {\n flex: 1;\n accent-color: var(--accent);\n }\n &[data-zoom=\"100\"] .stage__img {\n width: auto;\n max-width: none;\n }\n &[data-zoom=\"200\"] .stage__img {\n width: auto;\n max-width: none;\n zoom: 2;\n }\n &[data-zoom=\"400\"] .stage__img {\n width: auto;\n max-width: none;\n zoom: 4;\n }\n /* Zoomed in, show real pixels instead of a smoothed blur. */\n &[data-zoom=\"200\"] .stage__img,\n &[data-zoom=\"400\"] .stage__img {\n image-rendering: pixelated;\n }\n /* Design variant: screenshot against a render, two panes and no diff. */\n &[data-variant=\"design\"][data-view=\"split\"] .stage__grid {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n &[data-view=\"diff\"] .stage__grid,\n &[data-view=\"flip\"] .stage__grid,\n &[data-view=\"swipe\"] .stage__grid,\n &[data-view=\"onion\"] .stage__grid {\n grid-template-columns: minmax(0, 1fr);\n }\n &[data-view=\"diff\"] .stage__pane:not([data-pane=\"diff\"]) {\n display: none;\n }\n &[data-view=\"flip\"][data-flip=\"baseline\"] .stage__pane:not([data-pane=\"baseline\"]),\n &[data-view=\"flip\"]:not([data-flip=\"baseline\"]) .stage__pane:not([data-pane=\"current\"]) {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane[data-pane=\"diff\"],\n &[data-view=\"onion\"] .stage__pane[data-pane=\"diff\"] {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane,\n &[data-view=\"onion\"] .stage__pane,\n &[data-view=\"swipe\"] .stage__divider {\n grid-area: 1 / 1;\n }\n &[data-view=\"swipe\"] .stage__label,\n &[data-view=\"onion\"] .stage__label {\n display: none;\n }\n &[data-view=\"swipe\"] .stage__pane[data-pane=\"current\"] {\n clip-path: inset(0 0 0 var(--swipe, 50%));\n background: transparent;\n }\n &[data-view=\"onion\"] .stage__pane[data-pane=\"current\"] {\n opacity: var(--onion, 0.5);\n background: transparent;\n }\n &[data-view=\"swipe\"] .stage__divider {\n position: relative;\n display: block;\n z-index: 3;\n justify-self: start;\n width: 2px;\n height: 100%;\n margin-left: var(--swipe, 50%);\n background: var(--accent);\n pointer-events: none;\n }\n /* The only part of the divider that takes pointer input: drag it to wipe. */\n .stage__knob {\n position: absolute;\n top: 50%;\n left: 50%;\n display: grid;\n place-items: center;\n width: 32px;\n height: 32px;\n transform: translate(-50%, -50%);\n background: var(--accent);\n color: var(--accent-contrast);\n border: 2px solid var(--surface-card);\n border-radius: var(--radius-pill);\n box-shadow: var(--shadow-2);\n font-size: var(--text-lg);\n line-height: 1;\n cursor: ew-resize;\n pointer-events: auto;\n touch-action: none;\n user-select: none;\n }\n &[data-view=\"flip\"] .stage__grid {\n cursor: pointer;\n }\n &[data-view=\"swipe\"] .stage__controls[data-for=\"swipe\"],\n &[data-view=\"onion\"] .stage__controls[data-for=\"onion\"] {\n display: flex;\n }\n`;\n\nconst MODES = [\n { label: \"Side by side\", value: \"split\" },\n { label: \"Swipe\", value: \"swipe\" },\n { label: \"Onion skin\", value: \"onion\" },\n { label: \"Diff\", value: \"diff\" },\n { label: \"Flip\", value: \"flip\" },\n];\n\nconst ZOOMS = [\n { label: \"Fit\", value: \"fit\" },\n { label: \"100%\", value: \"100\" },\n { label: \"200%\", value: \"200\" },\n { label: \"400%\", value: \"400\" },\n];\n\n/** Modes and zoom a design comparison offers: no pixel diff, and the render always fits the stage. */\nconst DESIGN_MODES = [\"split\", \"swipe\", \"onion\"];\n\nfunction withActive(\n items: { label: string; value: string }[],\n active: string,\n): { label: string; value: string; active: boolean }[] {\n return items.map((item) => ({ ...item, active: item.value === active }));\n}\n\ninterface PaneProps {\n kind: \"baseline\" | \"current\" | \"diff\";\n label: string;\n src: string | null;\n alt: string;\n empty: unknown;\n meta?: string;\n}\n\nconst Pane: FC<PaneProps> = ({ kind, label, src, alt, empty, meta }) => {\n return (\n <div class=\"stage__pane\" data-pane={kind}>\n <div class=\"stage__label\">\n <span>{label}</span>\n {meta ? <span>{meta}</span> : null}\n </div>\n {src ? (\n <img class=\"stage__img\" src={src} alt={alt} loading=\"lazy\" decoding=\"async\" />\n ) : (\n <div class=\"stage__empty\">{empty}</div>\n )}\n </div>\n );\n};\n\n/** Swipe and onion-skin sliders; each is shown only in its own mode (CSS). */\nconst RangeControls: FC<{ baselineLabel: string }> = ({ baselineLabel }) => {\n return (\n <>\n <label class=\"stage__controls\" data-for=\"swipe\">\n <span>{baselineLabel}</span>\n <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" data-compare-swipe aria-label=\"Swipe\" />\n <span>Current</span>\n </label>\n <label class=\"stage__controls\" data-for=\"onion\">\n <span>{baselineLabel}</span>\n <input type=\"range\" min=\"0\" max=\"100\" value=\"50\" data-compare-onion aria-label=\"Opacity\" />\n <span>Current</span>\n </label>\n </>\n );\n};\n\n/** Props for the screenshot comparison surface. */\nexport interface CompareStageProps {\n /** Baseline image URL, or null when the story has no baseline yet. */\n baselineSrc: string | null;\n currentSrc: string;\n /** Diff overlay URL, or null when none was produced. */\n diffSrc: string | null;\n /** Accessible description shared by the images, e.g. \"Button / Primary\". */\n subject: string;\n baselineEmpty?: unknown;\n diffEmpty?: unknown;\n diffMeta?: string;\n /**\n * `design` compares the screenshot with a design render instead of a baseline: side by side,\n * swipe and onion only (no diff, no flip), fit zoom only.\n */\n variant?: \"baseline\" | \"design\";\n /** Name of the left-hand image in labels and sliders (default \"Baseline\"). */\n baselineLabel?: string;\n /** A line under the controls, e.g. what the left-hand image is. */\n note?: string;\n}\n\n/** Mode switch, plus the zoom switch (a design comparison always fits, so it has none). */\nconst StageBar: FC<{ design: boolean }> = ({ design }) => {\n const modes = design ? MODES.filter((mode) => DESIGN_MODES.includes(mode.value)) : MODES;\n return (\n <div class=\"stage__bar\">\n <Segmented label=\"Comparison mode\" data-compare-mode items={withActive(modes, \"split\")} />\n {design ? null : (\n <Segmented label=\"Zoom\" data-compare-zoom items={withActive(ZOOMS, \"fit\")} />\n )}\n </div>\n );\n};\n\n/** The panes (baseline or design, current, diff) and the swipe divider. */\nconst StageGrid: FC<CompareStageProps & { design: boolean; baselineLabel: string }> = ({\n baselineSrc,\n currentSrc,\n diffSrc,\n subject,\n baselineEmpty = \"New story — no baseline yet. Approve to set the baseline.\",\n diffEmpty = \"No diff\",\n diffMeta,\n design,\n baselineLabel,\n}) => (\n <div class=\"stage__grid\">\n <Pane\n kind=\"baseline\"\n label={baselineLabel}\n src={baselineSrc}\n alt={`${baselineLabel} for ${subject}`}\n empty={baselineEmpty}\n />\n <Pane\n kind=\"current\"\n label=\"Current\"\n src={currentSrc}\n alt={`Current for ${subject}`}\n empty=\"No screenshot\"\n />\n {design ? null : (\n <Pane\n kind=\"diff\"\n label=\"Diff\"\n src={diffSrc}\n alt={`Diff for ${subject}`}\n empty={diffEmpty}\n meta={diffMeta}\n />\n )}\n <span class=\"stage__divider\" aria-hidden=\"true\">\n <span class=\"stage__knob\" data-compare-knob>\n ↔\n </span>\n </span>\n </div>\n);\n\n/** Baseline / current / diff comparison with swipe, onion-skin, flip, and zoom. */\nexport const CompareStage: FC<CompareStageProps> = ({\n variant = \"baseline\",\n baselineLabel = \"Baseline\",\n note,\n ...images\n}) => {\n const design = variant === \"design\";\n return (\n <div\n class={stageRoot}\n data-compare\n data-view=\"split\"\n data-zoom=\"fit\"\n data-flip=\"current\"\n data-variant={variant}\n data-modes={design ? DESIGN_MODES.join(\",\") : undefined}\n data-zooms={design ? \"fit\" : undefined}\n >\n <StageBar design={design} />\n {note ? <p class=\"stage__note\">{note}</p> : null}\n <StageGrid {...images} design={design} baselineLabel={baselineLabel} />\n <RangeControls baselineLabel={baselineLabel} />\n </div>\n );\n};\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ import.meta.url;
4
4
  import { getStore } from "../store.mjs";
5
5
  import { getCsrfToken } from "../middleware/csrf.mjs";
6
6
  import { jsx } from "hono/jsx/jsx-runtime";
7
- //#region src/ui/csrf-field.tsx
7
+ // src/ui/csrf-field.tsx
8
8
  /** Hidden CSRF token so native (non-HTMX) form posts pass the csrf middleware. */
9
9
  function csrfField() {
10
10
  const { config, sessionId } = getStore();
@@ -14,7 +14,6 @@ function csrfField() {
14
14
  value: getCsrfToken(config.secret, sessionId)
15
15
  });
16
16
  }
17
- //#endregion
18
17
  export { csrfField };
19
18
 
20
19
  //# sourceMappingURL=csrf-field.mjs.map
package/dist/ui/css.mjs CHANGED
@@ -2,7 +2,7 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  import { createCssContext } from "hono/css";
5
- //#region src/ui/css.ts
5
+ // src/ui/css.ts
6
6
  /**
7
7
  * hono/css building blocks for the server-rendered UI.
8
8
  *
@@ -30,7 +30,6 @@ const context = createCssContext({
30
30
  const css = (strings, ...values) => context.css(strings, ...values);
31
31
  /** Collects every used class into `<style id="storyshelf-css">`. */
32
32
  const Style = (args) => context.Style(args);
33
- //#endregion
34
33
  export { Style, css };
35
34
 
36
35
  //# sourceMappingURL=css.mjs.map
@@ -4,7 +4,7 @@ import.meta.url;
4
4
  import { css } from "./css.mjs";
5
5
  import { Icon } from "./icons/icon.mjs";
6
6
  import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
7
- //#region src/ui/design-reference.tsx
7
+ // src/ui/design-reference.tsx
8
8
  const referenceRoot = css`
9
9
  /* design-reference */
10
10
  display: grid;
@@ -91,7 +91,6 @@ const DesignPreview = ({ src, name, providerName }) => /* @__PURE__ */ jsxs(Frag
91
91
  class: previewCaption,
92
92
  children: name
93
93
  }) : null] });
94
- //#endregion
95
94
  export { DesignPreview, DesignReference };
96
95
 
97
96
  //# sourceMappingURL=design-reference.mjs.map
@@ -13,7 +13,7 @@ import { baseStyle } from "./styles.mjs";
13
13
  import { DARK_THEME, LIGHT_THEME } from "./theme.mjs";
14
14
  import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
15
15
  import { raw } from "hono/html";
16
- //#region src/ui/document.tsx
16
+ // src/ui/document.tsx
17
17
  const shellRoot = css`
18
18
  /* shell */
19
19
  display: grid;
@@ -123,7 +123,6 @@ const DocumentLayout = ({ title, nav, layout = "default", children }) => {
123
123
  ] })]
124
124
  })] });
125
125
  };
126
- //#endregion
127
126
  export { DocumentLayout };
128
127
 
129
128
  //# sourceMappingURL=document.mjs.map
@@ -4,7 +4,7 @@ import.meta.url;
4
4
  import { css } from "./css.mjs";
5
5
  import { Icon } from "./icons/icon.mjs";
6
6
  import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
7
- //#region src/ui/dropdown.tsx
7
+ // src/ui/dropdown.tsx
8
8
  const dropdownRoot = css`
9
9
  /* dropdown */
10
10
  position: relative;
@@ -175,7 +175,6 @@ const DropdownDivider = () => {
175
175
  role: "separator"
176
176
  });
177
177
  };
178
- //#endregion
179
178
  export { Dropdown, DropdownDivider, DropdownItem };
180
179
 
181
180
  //# sourceMappingURL=dropdown.mjs.map
@@ -4,7 +4,7 @@ import.meta.url;
4
4
  import { css } from "./css.mjs";
5
5
  import { Icon } from "./icons/icon.mjs";
6
6
  import { jsx, jsxs } from "hono/jsx/jsx-runtime";
7
- //#region src/ui/feedback.tsx
7
+ // src/ui/feedback.tsx
8
8
  const badgeBase = css`
9
9
  /* badge */
10
10
  display: inline-flex;
@@ -335,7 +335,6 @@ const Meta = ({ as = "p", mono = false, tone = "neutral", center = false, childr
335
335
  children
336
336
  });
337
337
  };
338
- //#endregion
339
338
  export { Alert, Badge, EmptyState, Meta, Stat, statusTone };
340
339
 
341
340
  //# sourceMappingURL=feedback.mjs.map