@storyshelf/core 0.1.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 (189) hide show
  1. package/README.md +127 -0
  2. package/dist/adapter/auth.d.mts +50 -0
  3. package/dist/adapter/auth.mjs +10 -0
  4. package/dist/adapter/auth.mjs.map +1 -0
  5. package/dist/adapter/capture-runner.d.mts +70 -0
  6. package/dist/adapter/capture-runner.mjs +4 -0
  7. package/dist/adapter/database.d.mts +43 -0
  8. package/dist/adapter/database.mjs +4 -0
  9. package/dist/adapter/git-host/helpers.d.mts +12 -0
  10. package/dist/adapter/git-host/helpers.mjs +29 -0
  11. package/dist/adapter/git-host/helpers.mjs.map +1 -0
  12. package/dist/adapter/git-host.d.mts +46 -0
  13. package/dist/adapter/git-host.mjs +4 -0
  14. package/dist/adapter/storage.d.mts +22 -0
  15. package/dist/adapter/storage.mjs +4 -0
  16. package/dist/adapters/capture-queue.d.mts +49 -0
  17. package/dist/adapters/metadata.d.mts +24 -0
  18. package/dist/adapters/webhook-events.mjs +39 -0
  19. package/dist/adapters/webhook-events.mjs.map +1 -0
  20. package/dist/capture/adapter.d.mts +29 -0
  21. package/dist/capture/adapter.mjs +13 -0
  22. package/dist/capture/adapter.mjs.map +1 -0
  23. package/dist/capture/orchestrator.d.mts +23 -0
  24. package/dist/capture/orchestrator.mjs +103 -0
  25. package/dist/capture/orchestrator.mjs.map +1 -0
  26. package/dist/capture/pipeline.d.mts +41 -0
  27. package/dist/capture/pipeline.mjs +119 -0
  28. package/dist/capture/pipeline.mjs.map +1 -0
  29. package/dist/capture/queue.d.mts +35 -0
  30. package/dist/capture/queue.mjs +96 -0
  31. package/dist/capture/queue.mjs.map +1 -0
  32. package/dist/capture/status-fanout.mjs +66 -0
  33. package/dist/capture/status-fanout.mjs.map +1 -0
  34. package/dist/capture/storybook.d.mts +14 -0
  35. package/dist/capture/storybook.mjs +40 -0
  36. package/dist/capture/storybook.mjs.map +1 -0
  37. package/dist/config.d.mts +68 -0
  38. package/dist/config.mjs +69 -0
  39. package/dist/config.mjs.map +1 -0
  40. package/dist/ddl.d.mts +7 -0
  41. package/dist/ddl.mjs +155 -0
  42. package/dist/ddl.mjs.map +1 -0
  43. package/dist/diff/engine.d.mts +8 -0
  44. package/dist/diff/engine.mjs +79 -0
  45. package/dist/diff/engine.mjs.map +1 -0
  46. package/dist/diff/options.d.mts +40 -0
  47. package/dist/diff/options.mjs +15 -0
  48. package/dist/diff/options.mjs.map +1 -0
  49. package/dist/index.d.mts +48 -0
  50. package/dist/index.mjs +327 -0
  51. package/dist/index.mjs.map +1 -0
  52. package/dist/logger.d.mts +39 -0
  53. package/dist/logger.mjs +35 -0
  54. package/dist/logger.mjs.map +1 -0
  55. package/dist/middleware/csrf.mjs +39 -0
  56. package/dist/middleware/csrf.mjs.map +1 -0
  57. package/dist/middleware/rate-limit.mjs +38 -0
  58. package/dist/middleware/rate-limit.mjs.map +1 -0
  59. package/dist/models/baseline.d.mts +20 -0
  60. package/dist/models/baseline.mjs +89 -0
  61. package/dist/models/baseline.mjs.map +1 -0
  62. package/dist/models/build.d.mts +35 -0
  63. package/dist/models/build.mjs +77 -0
  64. package/dist/models/build.mjs.map +1 -0
  65. package/dist/models/comment.d.mts +20 -0
  66. package/dist/models/comment.mjs +42 -0
  67. package/dist/models/comment.mjs.map +1 -0
  68. package/dist/models/label.d.mts +43 -0
  69. package/dist/models/label.mjs +107 -0
  70. package/dist/models/label.mjs.map +1 -0
  71. package/dist/models/member.d.mts +27 -0
  72. package/dist/models/member.mjs +54 -0
  73. package/dist/models/member.mjs.map +1 -0
  74. package/dist/models/project.d.mts +42 -0
  75. package/dist/models/project.mjs +78 -0
  76. package/dist/models/project.mjs.map +1 -0
  77. package/dist/models/snapshot.d.mts +46 -0
  78. package/dist/models/snapshot.mjs +75 -0
  79. package/dist/models/snapshot.mjs.map +1 -0
  80. package/dist/models/status-config.mjs +61 -0
  81. package/dist/models/status-config.mjs.map +1 -0
  82. package/dist/models/token.d.mts +33 -0
  83. package/dist/models/token.mjs +60 -0
  84. package/dist/models/token.mjs.map +1 -0
  85. package/dist/models/webhook.d.mts +37 -0
  86. package/dist/models/webhook.mjs +67 -0
  87. package/dist/models/webhook.mjs.map +1 -0
  88. package/dist/pages/build-detail.mjs +333 -0
  89. package/dist/pages/build-detail.mjs.map +1 -0
  90. package/dist/pages/build-diff.mjs +437 -0
  91. package/dist/pages/build-diff.mjs.map +1 -0
  92. package/dist/pages/compute-jobs.mjs +196 -0
  93. package/dist/pages/compute-jobs.mjs.map +1 -0
  94. package/dist/pages/login.mjs +77 -0
  95. package/dist/pages/login.mjs.map +1 -0
  96. package/dist/pages/project-builds.mjs +228 -0
  97. package/dist/pages/project-builds.mjs.map +1 -0
  98. package/dist/pages/project-create.mjs +168 -0
  99. package/dist/pages/project-create.mjs.map +1 -0
  100. package/dist/pages/project-settings.mjs +111 -0
  101. package/dist/pages/project-settings.mjs.map +1 -0
  102. package/dist/pages/projects.mjs +178 -0
  103. package/dist/pages/projects.mjs.map +1 -0
  104. package/dist/pages/root.mjs +90 -0
  105. package/dist/pages/root.mjs.map +1 -0
  106. package/dist/pages/settings-general.mjs +191 -0
  107. package/dist/pages/settings-general.mjs.map +1 -0
  108. package/dist/pages/settings-labels.mjs +149 -0
  109. package/dist/pages/settings-labels.mjs.map +1 -0
  110. package/dist/pages/settings-members.mjs +124 -0
  111. package/dist/pages/settings-members.mjs.map +1 -0
  112. package/dist/pages/settings-status.mjs +150 -0
  113. package/dist/pages/settings-status.mjs.map +1 -0
  114. package/dist/pages/settings-tokens.mjs +95 -0
  115. package/dist/pages/settings-tokens.mjs.map +1 -0
  116. package/dist/pages/settings-webhooks.mjs +149 -0
  117. package/dist/pages/settings-webhooks.mjs.map +1 -0
  118. package/dist/retention/purge.d.mts +28 -0
  119. package/dist/retention/purge.mjs +66 -0
  120. package/dist/retention/purge.mjs.map +1 -0
  121. package/dist/routers/admin.mjs +41 -0
  122. package/dist/routers/admin.mjs.map +1 -0
  123. package/dist/routers/assets.mjs +24 -0
  124. package/dist/routers/assets.mjs.map +1 -0
  125. package/dist/routers/auth.mjs +77 -0
  126. package/dist/routers/auth.mjs.map +1 -0
  127. package/dist/routers/builds.handlers.mjs +87 -0
  128. package/dist/routers/builds.handlers.mjs.map +1 -0
  129. package/dist/routers/builds.mjs +162 -0
  130. package/dist/routers/builds.mjs.map +1 -0
  131. package/dist/routers/comments.mjs +89 -0
  132. package/dist/routers/comments.mjs.map +1 -0
  133. package/dist/routers/helpers.mjs +76 -0
  134. package/dist/routers/helpers.mjs.map +1 -0
  135. package/dist/routers/htmx.mjs +18 -0
  136. package/dist/routers/htmx.mjs.map +1 -0
  137. package/dist/routers/labels.mjs +77 -0
  138. package/dist/routers/labels.mjs.map +1 -0
  139. package/dist/routers/media.mjs +54 -0
  140. package/dist/routers/media.mjs.map +1 -0
  141. package/dist/routers/members.mjs +101 -0
  142. package/dist/routers/members.mjs.map +1 -0
  143. package/dist/routers/projects.mjs +121 -0
  144. package/dist/routers/projects.mjs.map +1 -0
  145. package/dist/routers/schemas.mjs +188 -0
  146. package/dist/routers/schemas.mjs.map +1 -0
  147. package/dist/routers/settings.mjs +235 -0
  148. package/dist/routers/settings.mjs.map +1 -0
  149. package/dist/routers/snapshots.mjs +143 -0
  150. package/dist/routers/snapshots.mjs.map +1 -0
  151. package/dist/routers/status-configs.mjs +100 -0
  152. package/dist/routers/status-configs.mjs.map +1 -0
  153. package/dist/routers/tokens.mjs +80 -0
  154. package/dist/routers/tokens.mjs.map +1 -0
  155. package/dist/routers/ui.mjs +142 -0
  156. package/dist/routers/ui.mjs.map +1 -0
  157. package/dist/routers/webhooks.mjs +89 -0
  158. package/dist/routers/webhooks.mjs.map +1 -0
  159. package/dist/schema.d.mts +4372 -0
  160. package/dist/schema.mjs +158 -0
  161. package/dist/schema.mjs.map +1 -0
  162. package/dist/store.mjs +18 -0
  163. package/dist/store.mjs.map +1 -0
  164. package/dist/types.d.mts +18 -0
  165. package/dist/types.mjs +49 -0
  166. package/dist/types.mjs.map +1 -0
  167. package/dist/ui/components.mjs +127 -0
  168. package/dist/ui/components.mjs.map +1 -0
  169. package/dist/ui/document.d.mts +8 -0
  170. package/dist/ui/document.mjs +338 -0
  171. package/dist/ui/document.mjs.map +1 -0
  172. package/dist/ui/styles.mjs +173 -0
  173. package/dist/ui/styles.mjs.map +1 -0
  174. package/dist/ui/theme.mjs +42 -0
  175. package/dist/ui/theme.mjs.map +1 -0
  176. package/dist/urls.d.mts +19 -0
  177. package/dist/urls.mjs +23 -0
  178. package/dist/urls.mjs.map +1 -0
  179. package/dist/utils/encrypt.mjs +49 -0
  180. package/dist/utils/encrypt.mjs.map +1 -0
  181. package/dist/utils/hash.mjs +22 -0
  182. package/dist/utils/hash.mjs.map +1 -0
  183. package/dist/utils/paths.d.mts +11 -0
  184. package/dist/utils/paths.mjs +23 -0
  185. package/dist/utils/paths.mjs.map +1 -0
  186. package/dist/utils/ulid.d.mts +8 -0
  187. package/dist/utils/ulid.mjs +41 -0
  188. package/dist/utils/ulid.mjs.map +1 -0
  189. package/package.json +162 -0
@@ -0,0 +1,338 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ import { getStore } from "../store.mjs";
5
+ import { baseStyle } from "./styles.mjs";
6
+ import { DARK_THEME, LIGHT_THEME } from "./theme.mjs";
7
+ import { Fragment, jsx, jsxs } from "hono/jsx/jsx-runtime";
8
+ //#region src/ui/document.tsx
9
+ const DocumentLayout = ({ title, nav, children }) => {
10
+ const { ui } = getStore();
11
+ const name = ui.name ?? "StoryShelf";
12
+ const light = ui.lightTheme ?? LIGHT_THEME;
13
+ const dark = ui.darkTheme ?? DARK_THEME;
14
+ return /* @__PURE__ */ jsxs("html", {
15
+ lang: "en",
16
+ "data-theme": "system",
17
+ children: [/* @__PURE__ */ jsxs("head", { children: [
18
+ /* @__PURE__ */ jsx("meta", { charset: "utf-8" }),
19
+ /* @__PURE__ */ jsx("meta", {
20
+ name: "viewport",
21
+ content: "width=device-width, initial-scale=1"
22
+ }),
23
+ /* @__PURE__ */ jsx("meta", {
24
+ name: "color-scheme",
25
+ content: "light dark"
26
+ }),
27
+ ui.favicon ? /* @__PURE__ */ jsx("link", {
28
+ rel: "icon",
29
+ href: ui.favicon
30
+ }) : null,
31
+ /* @__PURE__ */ jsxs("title", { children: [
32
+ title,
33
+ " · ",
34
+ name
35
+ ] }),
36
+ /* @__PURE__ */ jsx("style", { children: baseStyle(light, dark) }),
37
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: themeScript() } })
38
+ ] }), /* @__PURE__ */ jsxs("body", { children: [
39
+ /* @__PURE__ */ jsx("a", {
40
+ class: "skip-link",
41
+ href: "#main-content",
42
+ children: "Skip to content"
43
+ }),
44
+ /* @__PURE__ */ jsx(TopBar, {
45
+ name,
46
+ logo: ui.logo,
47
+ nav
48
+ }),
49
+ /* @__PURE__ */ jsxs("div", {
50
+ class: "shell",
51
+ children: [/* @__PURE__ */ jsx(Sidebar, { nav }), /* @__PURE__ */ jsx("main", {
52
+ id: "main-content",
53
+ class: "content",
54
+ tabindex: -1,
55
+ children
56
+ })]
57
+ }),
58
+ /* @__PURE__ */ jsx("script", { src: "/assets/htmx.js" }),
59
+ /* @__PURE__ */ jsx("script", { dangerouslySetInnerHTML: { __html: clientScript() } })
60
+ ] })]
61
+ });
62
+ };
63
+ const TopBar = ({ name, logo, nav }) => {
64
+ return /* @__PURE__ */ jsx("header", {
65
+ class: "topbar",
66
+ role: "banner",
67
+ children: /* @__PURE__ */ jsxs("div", {
68
+ class: "topbar__inner",
69
+ children: [/* @__PURE__ */ jsxs("div", {
70
+ class: "topbar__left",
71
+ children: [
72
+ /* @__PURE__ */ jsx("button", {
73
+ class: "topbar__menu",
74
+ type: "button",
75
+ "aria-label": "Toggle navigation",
76
+ "aria-expanded": "false",
77
+ "aria-controls": "sidebar",
78
+ "data-sidebar-toggle": true,
79
+ children: /* @__PURE__ */ jsx("span", {
80
+ "aria-hidden": "true",
81
+ children: "☰"
82
+ })
83
+ }),
84
+ /* @__PURE__ */ jsxs("a", {
85
+ class: "brand",
86
+ href: "/",
87
+ children: [logo ? /* @__PURE__ */ jsx("img", {
88
+ class: "logo",
89
+ src: logo,
90
+ alt: name,
91
+ width: "28",
92
+ height: "28"
93
+ }) : /* @__PURE__ */ jsx("span", {
94
+ class: "brand__mark",
95
+ "aria-hidden": "true",
96
+ children: "◆"
97
+ }), /* @__PURE__ */ jsx("span", {
98
+ class: "brand__name",
99
+ children: name
100
+ })]
101
+ }),
102
+ nav?.projectSlug ? /* @__PURE__ */ jsxs("nav", {
103
+ class: "project-crumb",
104
+ "aria-label": "Project",
105
+ children: [/* @__PURE__ */ jsx("span", {
106
+ class: "project-crumb__sep",
107
+ "aria-hidden": "true",
108
+ children: "/"
109
+ }), /* @__PURE__ */ jsx("a", {
110
+ href: `/projects/${nav.projectSlug}/builds`,
111
+ children: nav.projectName ?? nav.projectSlug
112
+ })]
113
+ }) : null
114
+ ]
115
+ }), /* @__PURE__ */ jsxs("div", {
116
+ class: "topbar__right",
117
+ children: [/* @__PURE__ */ jsx(ThemeToggle, {}), /* @__PURE__ */ jsx(AuthMenu, {})]
118
+ })]
119
+ })
120
+ });
121
+ };
122
+ const ThemeToggle = () => {
123
+ return /* @__PURE__ */ jsxs("button", {
124
+ class: "topbar__theme",
125
+ type: "button",
126
+ "aria-label": "Toggle theme",
127
+ "data-theme-toggle": true,
128
+ children: [
129
+ /* @__PURE__ */ jsx("span", {
130
+ "data-theme-icon": "light",
131
+ "aria-hidden": "true",
132
+ children: "☀"
133
+ }),
134
+ /* @__PURE__ */ jsx("span", {
135
+ "data-theme-icon": "dark",
136
+ "aria-hidden": "true",
137
+ children: "☾"
138
+ }),
139
+ /* @__PURE__ */ jsx("span", {
140
+ "data-theme-icon": "system",
141
+ "aria-hidden": "true",
142
+ children: "◐"
143
+ })
144
+ ]
145
+ });
146
+ };
147
+ const AuthMenu = () => {
148
+ const { user, authEnabled } = getStore();
149
+ if (!authEnabled) return null;
150
+ if (!user) return /* @__PURE__ */ jsx("a", {
151
+ class: "topbar__login",
152
+ href: "/auth/login",
153
+ children: "Sign in"
154
+ });
155
+ return /* @__PURE__ */ jsxs("div", {
156
+ class: "user-menu",
157
+ children: [
158
+ user.avatarUrl ? /* @__PURE__ */ jsx("img", {
159
+ class: "user-menu__avatar",
160
+ src: user.avatarUrl,
161
+ alt: "",
162
+ width: "28",
163
+ height: "28"
164
+ }) : /* @__PURE__ */ jsx("span", {
165
+ class: "user-menu__avatar user-menu__avatar--fallback",
166
+ "aria-hidden": "true",
167
+ children: user.name.slice(0, 1).toUpperCase()
168
+ }),
169
+ /* @__PURE__ */ jsx("span", {
170
+ class: "user-menu__name",
171
+ children: user.name
172
+ }),
173
+ /* @__PURE__ */ jsx("span", {
174
+ class: `user-menu__role user-menu__role--${user.role}`,
175
+ children: user.role
176
+ }),
177
+ /* @__PURE__ */ jsx("form", {
178
+ method: "post",
179
+ action: "/auth/logout",
180
+ children: /* @__PURE__ */ jsx("button", {
181
+ class: "user-menu__logout",
182
+ type: "submit",
183
+ children: "Sign out"
184
+ })
185
+ })
186
+ ]
187
+ });
188
+ };
189
+ const Sidebar = ({ nav }) => {
190
+ return /* @__PURE__ */ jsx("aside", {
191
+ id: "sidebar",
192
+ class: "sidebar",
193
+ "aria-label": "Primary",
194
+ children: /* @__PURE__ */ jsxs("nav", {
195
+ class: "sidebar__nav",
196
+ children: [
197
+ /* @__PURE__ */ jsxs("a", {
198
+ class: `sidebar__link ${nav?.active === "projects" ? "sidebar__link--active" : ""}`,
199
+ href: "/projects",
200
+ "aria-current": nav?.active === "projects" ? "page" : void 0,
201
+ children: [/* @__PURE__ */ jsx("span", {
202
+ "aria-hidden": "true",
203
+ children: "▦"
204
+ }), " Projects"]
205
+ }),
206
+ nav?.projectSlug ? /* @__PURE__ */ jsxs(Fragment, { children: [
207
+ /* @__PURE__ */ jsx("div", {
208
+ class: "sidebar__section",
209
+ children: "Project"
210
+ }),
211
+ /* @__PURE__ */ jsxs("a", {
212
+ class: `sidebar__link ${nav.active === "builds" ? "sidebar__link--active" : ""}`,
213
+ href: `/projects/${nav.projectSlug}/builds`,
214
+ "aria-current": nav.active === "builds" ? "page" : void 0,
215
+ children: [/* @__PURE__ */ jsx("span", {
216
+ "aria-hidden": "true",
217
+ children: "◧"
218
+ }), " Builds"]
219
+ }),
220
+ /* @__PURE__ */ jsxs("a", {
221
+ class: `sidebar__link ${nav.active === "jobs" ? "sidebar__link--active" : ""}`,
222
+ href: `/projects/${nav.projectSlug}/jobs`,
223
+ "aria-current": nav.active === "jobs" ? "page" : void 0,
224
+ children: [/* @__PURE__ */ jsx("span", {
225
+ "aria-hidden": "true",
226
+ children: "◫"
227
+ }), " Jobs"]
228
+ }),
229
+ /* @__PURE__ */ jsxs("a", {
230
+ class: `sidebar__link ${nav.active === "settings" ? "sidebar__link--active" : ""}`,
231
+ href: `/projects/${nav.projectSlug}/settings`,
232
+ "aria-current": nav.active === "settings" ? "page" : void 0,
233
+ children: [/* @__PURE__ */ jsx("span", {
234
+ "aria-hidden": "true",
235
+ children: "⚙"
236
+ }), " Settings"]
237
+ })
238
+ ] }) : null,
239
+ /* @__PURE__ */ jsx("div", {
240
+ class: "sidebar__section",
241
+ children: "Resources"
242
+ }),
243
+ /* @__PURE__ */ jsxs("a", {
244
+ class: "sidebar__link",
245
+ href: "/api/v1/projects",
246
+ "aria-label": "API (JSON)",
247
+ children: [/* @__PURE__ */ jsx("span", {
248
+ "aria-hidden": "true",
249
+ children: "{} "
250
+ }), " API"]
251
+ })
252
+ ]
253
+ })
254
+ });
255
+ };
256
+ function themeScript() {
257
+ return `
258
+ (function(){
259
+ var cookieName='storyshelf_theme';
260
+ function readCookie(n){
261
+ var m=document.cookie.match(new RegExp('(?:^|; )'+n.replace(/([.$?*|{}\\[\\]\\\\/\\+^])/gu,'\\\\$1')+'=([^;]*)'));
262
+ return m?decodeURIComponent(m[1]):null;
263
+ }
264
+ function writeCookie(n,v){
265
+ document.cookie=n+'='+encodeURIComponent(v)+'; Path=/; Max-Age=31536000; SameSite=Lax';
266
+ }
267
+ function applyTheme(v){
268
+ var html=document.documentElement;
269
+ if(v==='light'||v==='dark'||v==='system'){ html.setAttribute('data-theme',v); }
270
+ else { html.setAttribute('data-theme','system'); }
271
+ }
272
+ var initial=readCookie(cookieName);
273
+ if(initial){ applyTheme(initial); }
274
+ window.__storyshelfTheme={readCookie:readCookie,writeCookie:writeCookie,applyTheme:applyTheme,cookieName:cookieName};
275
+ })();
276
+ `.trim();
277
+ }
278
+ function clientScript() {
279
+ return `
280
+ (function(){
281
+ var theme=window.__storyshelfTheme;
282
+ var toggle=document.querySelector('[data-theme-toggle]');
283
+ if(toggle&&theme){
284
+ toggle.addEventListener('click',function(){
285
+ var cur=document.documentElement.getAttribute('data-theme')||'system';
286
+ var next=cur==='system'?'light':cur==='light'?'dark':'system';
287
+ theme.applyTheme(next);
288
+ theme.writeCookie(theme.cookieName,next);
289
+ });
290
+ }
291
+ var menuBtn=document.querySelector('[data-sidebar-toggle]');
292
+ var sidebar=document.getElementById('sidebar');
293
+ if(menuBtn&&sidebar){
294
+ menuBtn.addEventListener('click',function(){
295
+ var open=sidebar.classList.toggle('sidebar--open');
296
+ menuBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
297
+ });
298
+ }
299
+ document.addEventListener('keydown',function(e){
300
+ if(e.key==='Escape'&&sidebar&&sidebar.classList.contains('sidebar--open')){
301
+ sidebar.classList.remove('sidebar--open');
302
+ if(menuBtn) menuBtn.setAttribute('aria-expanded','false');
303
+ menuBtn && menuBtn.focus();
304
+ }
305
+ });
306
+ // HTMX after swap focus management
307
+ document.body.addEventListener('htmx:afterSwap',function(e){
308
+ var target=e.detail && e.detail.target;
309
+ if(target&&target.querySelector){
310
+ var focusable=target.querySelector('[autofocus], input, select, textarea, button');
311
+ if(focusable) try{ focusable.focus(); }catch(_){}
312
+ }
313
+ });
314
+ // Diff keyboard shortcuts
315
+ document.addEventListener('keydown',function(e){
316
+ if(e.target instanceof HTMLElement && (e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA'||e.target.isContentEditable)) return;
317
+ var diffRoot=document.querySelector('[data-diff-nav]');
318
+ if(!diffRoot) return;
319
+ if(e.key==='ArrowLeft' || e.key==='ArrowRight'){
320
+ var links=Array.from(diffRoot.querySelectorAll('[data-snapshot-link]'));
321
+ if(links.length===0) return;
322
+ var active=document.activeElement;
323
+ var idx=links.indexOf(active);
324
+ if(idx===-1){
325
+ var current=diffRoot.getAttribute('data-current');
326
+ idx=links.findIndex(function(a){ return a.getAttribute('data-snapshot-id')===current; });
327
+ }
328
+ var nextIdx=e.key==='ArrowRight' ? Math.min(links.length-1, idx+1) : Math.max(0, idx-1);
329
+ if(links[nextIdx]){ e.preventDefault(); links[nextIdx].focus(); links[nextIdx].click(); }
330
+ }
331
+ });
332
+ })();
333
+ `.trim();
334
+ }
335
+ //#endregion
336
+ export { DocumentLayout };
337
+
338
+ //# sourceMappingURL=document.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.mjs","names":[],"sources":["../../src/ui/document.tsx"],"sourcesContent":["import type { FC } from \"hono/jsx\";\n\nimport { getStore } from \"../store.ts\";\nimport { baseStyle } from \"./styles.ts\";\nimport { DARK_THEME, LIGHT_THEME } from \"./theme.ts\";\n\nexport type RenderedContent = string | Promise<string>;\n\ninterface NavConfig {\n active?: string;\n projectSlug?: string;\n projectName?: string;\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// eslint-disable-next-line promise-function-async -- JSX component return type\nexport const DocumentLayout: FC<{ title: string; nav?: NavConfig; children?: unknown }> = ({ title, nav, children }) => {\n const { ui } = getStore();\n const name = ui.name ?? \"StoryShelf\";\n const light = ui.lightTheme ?? LIGHT_THEME;\n const dark = ui.darkTheme ?? DARK_THEME;\n\n return (\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 {ui.favicon ? <link rel=\"icon\" href={ui.favicon} /> : null}\n <title>\n {title} · {name}\n </title>\n <style>{baseStyle(light, dark)}</style>\n <script dangerouslySetInnerHTML={{ __html: themeScript() }} />\n </head>\n <body>\n <a class=\"skip-link\" href=\"#main-content\">\n Skip to content\n </a>\n <TopBar name={name} logo={ui.logo} nav={nav} />\n <div class=\"shell\">\n <Sidebar nav={nav} />\n <main id=\"main-content\" class=\"content\" tabindex={-1}>\n {children}\n </main>\n </div>\n <script src=\"/assets/htmx.js\" />\n <script dangerouslySetInnerHTML={{ __html: clientScript() }} />\n </body>\n </html>\n );\n};\n\n// eslint-disable-next-line promise-function-async -- JSX component return type\nconst TopBar: FC<{ name: string; logo?: string; nav?: NavConfig }> = ({ name, logo, nav }) => {\n return (\n <header class=\"topbar\" role=\"banner\">\n <div class=\"topbar__inner\">\n <div class=\"topbar__left\">\n <button class=\"topbar__menu\" type=\"button\" aria-label=\"Toggle navigation\" aria-expanded=\"false\" aria-controls=\"sidebar\" data-sidebar-toggle>\n <span aria-hidden=\"true\">☰</span>\n </button>\n <a class=\"brand\" href=\"/\">\n {logo ? <img class=\"logo\" src={logo} alt={name} width=\"28\" height=\"28\" /> : <span class=\"brand__mark\" aria-hidden=\"true\">◆</span>}\n <span class=\"brand__name\">{name}</span>\n </a>\n {nav?.projectSlug ? (\n <nav class=\"project-crumb\" aria-label=\"Project\">\n <span class=\"project-crumb__sep\" aria-hidden=\"true\">\n /\n </span>\n <a href={`/projects/${nav.projectSlug}/builds`}>{nav.projectName ?? nav.projectSlug}</a>\n </nav>\n ) : null}\n </div>\n <div class=\"topbar__right\">\n <ThemeToggle />\n <AuthMenu />\n </div>\n </div>\n </header>\n );\n};\n\n// eslint-disable-next-line promise-function-async -- JSX component return type\nconst ThemeToggle: FC = () => {\n return (\n <button class=\"topbar__theme\" type=\"button\" aria-label=\"Toggle theme\" data-theme-toggle>\n <span data-theme-icon=\"light\" aria-hidden=\"true\">\n ☀\n </span>\n <span data-theme-icon=\"dark\" aria-hidden=\"true\">\n ☾\n </span>\n <span data-theme-icon=\"system\" aria-hidden=\"true\">\n ◐\n </span>\n </button>\n );\n};\n\n// eslint-disable-next-line promise-function-async -- JSX component return type\nconst AuthMenu: FC = () => {\n const { user, authEnabled } = getStore();\n if (!authEnabled) {\n return null;\n }\n if (!user) {\n return (\n <a class=\"topbar__login\" href=\"/auth/login\">\n Sign in\n </a>\n );\n }\n return (\n <div class=\"user-menu\">\n {user.avatarUrl ? (\n <img class=\"user-menu__avatar\" src={user.avatarUrl} alt=\"\" width=\"28\" height=\"28\" />\n ) : (\n <span class=\"user-menu__avatar user-menu__avatar--fallback\" aria-hidden=\"true\">\n {user.name.slice(0, 1).toUpperCase()}\n </span>\n )}\n <span class=\"user-menu__name\">{user.name}</span>\n <span class={`user-menu__role user-menu__role--${user.role}`}>{user.role}</span>\n <form method=\"post\" action=\"/auth/logout\">\n <button class=\"user-menu__logout\" type=\"submit\">\n Sign out\n </button>\n </form>\n </div>\n );\n};\n\n// eslint-disable-next-line promise-function-async -- JSX component return type\nconst Sidebar: FC<{ nav?: NavConfig }> = ({ nav }) => {\n return (\n <aside id=\"sidebar\" class=\"sidebar\" aria-label=\"Primary\">\n <nav class=\"sidebar__nav\">\n <a class={`sidebar__link ${nav?.active === \"projects\" ? \"sidebar__link--active\" : \"\"}`} href=\"/projects\" aria-current={nav?.active === \"projects\" ? \"page\" : undefined}>\n <span aria-hidden=\"true\">▦</span> Projects\n </a>\n {nav?.projectSlug ? (\n <>\n <div class=\"sidebar__section\">Project</div>\n <a class={`sidebar__link ${nav.active === \"builds\" ? \"sidebar__link--active\" : \"\"}`} href={`/projects/${nav.projectSlug}/builds`} aria-current={nav.active === \"builds\" ? \"page\" : undefined}>\n <span aria-hidden=\"true\">◧</span> Builds\n </a>\n <a class={`sidebar__link ${nav.active === \"jobs\" ? \"sidebar__link--active\" : \"\"}`} href={`/projects/${nav.projectSlug}/jobs`} aria-current={nav.active === \"jobs\" ? \"page\" : undefined}>\n <span aria-hidden=\"true\">◫</span> Jobs\n </a>\n <a class={`sidebar__link ${nav.active === \"settings\" ? \"sidebar__link--active\" : \"\"}`} href={`/projects/${nav.projectSlug}/settings`} aria-current={nav.active === \"settings\" ? \"page\" : undefined}>\n <span aria-hidden=\"true\">⚙</span> Settings\n </a>\n </>\n ) : null}\n <div class=\"sidebar__section\">Resources</div>\n <a class=\"sidebar__link\" href=\"/api/v1/projects\" aria-label=\"API (JSON)\">\n <span aria-hidden=\"true\">{\"{} \"}</span> API\n </a>\n </nav>\n </aside>\n );\n};\n\nfunction themeScript(): string {\n return `\n (function(){\n var cookieName='storyshelf_theme';\n function readCookie(n){\n var m=document.cookie.match(new RegExp('(?:^|; )'+n.replace(/([.$?*|{}\\\\[\\\\]\\\\\\\\/\\\\+^])/gu,'\\\\\\\\$1')+'=([^;]*)'));\n return m?decodeURIComponent(m[1]):null;\n }\n function writeCookie(n,v){\n document.cookie=n+'='+encodeURIComponent(v)+'; Path=/; Max-Age=31536000; SameSite=Lax';\n }\n function applyTheme(v){\n var html=document.documentElement;\n if(v==='light'||v==='dark'||v==='system'){ html.setAttribute('data-theme',v); }\n else { html.setAttribute('data-theme','system'); }\n }\n var initial=readCookie(cookieName);\n if(initial){ applyTheme(initial); }\n window.__storyshelfTheme={readCookie:readCookie,writeCookie:writeCookie,applyTheme:applyTheme,cookieName:cookieName};\n })();\n `.trim();\n}\n\nfunction clientScript(): string {\n return `\n (function(){\n var theme=window.__storyshelfTheme;\n var toggle=document.querySelector('[data-theme-toggle]');\n if(toggle&&theme){\n toggle.addEventListener('click',function(){\n var cur=document.documentElement.getAttribute('data-theme')||'system';\n var next=cur==='system'?'light':cur==='light'?'dark':'system';\n theme.applyTheme(next);\n theme.writeCookie(theme.cookieName,next);\n });\n }\n var menuBtn=document.querySelector('[data-sidebar-toggle]');\n var sidebar=document.getElementById('sidebar');\n if(menuBtn&&sidebar){\n menuBtn.addEventListener('click',function(){\n var open=sidebar.classList.toggle('sidebar--open');\n menuBtn.setAttribute('aria-expanded', open ? 'true' : 'false');\n });\n }\n document.addEventListener('keydown',function(e){\n if(e.key==='Escape'&&sidebar&&sidebar.classList.contains('sidebar--open')){\n sidebar.classList.remove('sidebar--open');\n if(menuBtn) menuBtn.setAttribute('aria-expanded','false');\n menuBtn && menuBtn.focus();\n }\n });\n // HTMX after swap focus management\n document.body.addEventListener('htmx:afterSwap',function(e){\n var target=e.detail && e.detail.target;\n if(target&&target.querySelector){\n var focusable=target.querySelector('[autofocus], input, select, textarea, button');\n if(focusable) try{ focusable.focus(); }catch(_){}\n }\n });\n // Diff keyboard shortcuts\n document.addEventListener('keydown',function(e){\n if(e.target instanceof HTMLElement && (e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA'||e.target.isContentEditable)) return;\n var diffRoot=document.querySelector('[data-diff-nav]');\n if(!diffRoot) return;\n if(e.key==='ArrowLeft' || e.key==='ArrowRight'){\n var links=Array.from(diffRoot.querySelectorAll('[data-snapshot-link]'));\n if(links.length===0) return;\n var active=document.activeElement;\n var idx=links.indexOf(active);\n if(idx===-1){\n var current=diffRoot.getAttribute('data-current');\n idx=links.findIndex(function(a){ return a.getAttribute('data-snapshot-id')===current; });\n }\n var nextIdx=e.key==='ArrowRight' ? Math.min(links.length-1, idx+1) : Math.max(0, idx-1);\n if(links[nextIdx]){ e.preventDefault(); links[nextIdx].focus(); links[nextIdx].click(); }\n }\n });\n })();\n `.trim();\n}\n"],"mappings":"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"}
@@ -0,0 +1,173 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/ui/styles.ts
5
+ function baseStyle(light, dark) {
6
+ return `
7
+ :root {
8
+ --accent: ${light.accent};
9
+ --accent-contrast: #fff;
10
+ --surface-base: ${light.surface.base};
11
+ --surface-card: ${light.surface.card};
12
+ --surface-muted: #f4f4f5;
13
+ --text-primary: ${light.text.primary};
14
+ --text-secondary: ${light.text.secondary};
15
+ --border: ${light.border};
16
+ --radius: 10px;
17
+ --radius-sm: 6px;
18
+ --shadow: 0 1px 2px rgba(0,0,0,.06), 0 4px 12px rgba(0,0,0,.04);
19
+ --status-approved: ${light.status.approved};
20
+ --status-new: ${light.status.new};
21
+ --status-rejected: ${light.status.rejected};
22
+ --topbar-bg: ${light.accent};
23
+ --sidebar-bg: #ffffff;
24
+ --sidebar-width: 240px;
25
+ }
26
+ [data-theme="dark"] {
27
+ --accent: ${dark.accent};
28
+ --surface-base: ${dark.surface.base};
29
+ --surface-card: ${dark.surface.card};
30
+ --surface-muted: #1e1e22;
31
+ --text-primary: ${dark.text.primary};
32
+ --text-secondary: ${dark.text.secondary};
33
+ --border: ${dark.border};
34
+ --status-approved: ${dark.status.approved};
35
+ --status-new: ${dark.status.new};
36
+ --status-rejected: ${dark.status.rejected};
37
+ --topbar-bg: #0f172a;
38
+ --sidebar-bg: #111113;
39
+ }
40
+ @media (prefers-color-scheme: dark) {
41
+ [data-theme="system"] {
42
+ --accent: ${dark.accent};
43
+ --surface-base: ${dark.surface.base};
44
+ --surface-card: ${dark.surface.card};
45
+ --surface-muted: #1e1e22;
46
+ --text-primary: ${dark.text.primary};
47
+ --text-secondary: ${dark.text.secondary};
48
+ --border: ${dark.border};
49
+ --status-approved: ${dark.status.approved};
50
+ --status-new: ${dark.status.new};
51
+ --status-rejected: ${dark.status.rejected};
52
+ --topbar-bg: #0f172a;
53
+ --sidebar-bg: #111113;
54
+ }
55
+ }
56
+ * { box-sizing: border-box; }
57
+ html { scroll-behavior: smooth; }
58
+ body { margin: 0; font-family: ui-sans-system, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; background: var(--surface-base); color: var(--text-primary); line-height: 1.5; }
59
+ a { color: var(--accent); text-decoration: none; }
60
+ a:hover { text-decoration: underline; }
61
+ a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
62
+ .skip-link { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }
63
+ .skip-link:focus { left: 1rem; top: 1rem; width: auto; height: auto; background: var(--surface-card); color: var(--text-primary); padding: .5rem .75rem; border-radius: var(--radius-sm); box-shadow: var(--shadow); z-index: 100; }
64
+ .topbar { position: sticky; top: 0; z-index: 40; background: var(--topbar-bg); color: #fff; border-bottom: 1px solid rgba(255,255,255,.12); }
65
+ .topbar__inner { max-width: 1440px; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .6rem 1rem; }
66
+ .topbar__left, .topbar__right { display: flex; align-items: center; gap: .75rem; min-width: 0; }
67
+ .topbar__menu { display: none; background: transparent; border: 1px solid rgba(255,255,255,.3); color: #fff; border-radius: 6px; padding: .35rem .5rem; cursor: pointer; }
68
+ .brand { display: inline-flex; align-items: center; gap: .5rem; color: #fff; font-weight: 650; text-decoration: none; }
69
+ .brand__mark { width: 28px; height: 28px; display: inline-grid; place-items: center; background: rgba(255,255,255,.18); border-radius: 8px; }
70
+ .logo { border-radius: 6px; display: block; }
71
+ .project-crumb { display: inline-flex; align-items: center; gap: .35rem; color: rgba(255,255,255,.9); }
72
+ .project-crumb a { color: #fff; text-decoration: underline; text-underline-offset: 3px; }
73
+ .project-crumb__sep { opacity: .7; }
74
+ .topbar__theme { background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.22); color: #fff; border-radius: 999px; padding: .35rem .6rem; cursor: pointer; display: inline-flex; gap: .25rem; }
75
+ .topbar__theme [data-theme-icon] { display: none; }
76
+ [data-theme="light"] [data-theme-icon="light"], [data-theme="dark"] [data-theme-icon="dark"], [data-theme="system"] [data-theme-icon="system"] { display: inline; }
77
+ .topbar__login { background: #fff; color: var(--accent); padding: .4rem .75rem; border-radius: 999px; font-weight: 600; text-decoration: none; }
78
+ .user-menu { display: inline-flex; align-items: center; gap: .5rem; background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.2); padding: .25rem .5rem .25rem .35rem; border-radius: 999px; }
79
+ .user-menu__avatar { width: 28px; height: 28px; border-radius: 999px; object-fit: cover; background: rgba(255,255,255,.2); }
80
+ .user-menu__avatar--fallback { display: inline-grid; place-items: center; font-weight: 700; color: #fff; }
81
+ .user-menu__name { color: #fff; font-weight: 600; max-width: 12ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
82
+ .user-menu__role { font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; background: rgba(255,255,255,.2); color: #fff; padding: .15rem .4rem; border-radius: 999px; }
83
+ .user-menu__logout { color: #fff; font-size: .85rem; text-decoration: underline; text-underline-offset: 2px; margin-left: .25rem; }
84
+ .shell { max-width: 1440px; margin: 0 auto; display: grid; grid-template-columns: var(--sidebar-width) 1fr; min-height: calc(100vh - 56px); }
85
+ .sidebar { background: var(--sidebar-bg); border-right: 1px solid var(--border); padding: 1rem .75rem; position: sticky; top: 56px; height: calc(100vh - 56px); overflow: auto; }
86
+ .sidebar__nav { display: flex; flex-direction: column; gap: .25rem; }
87
+ .sidebar__section { margin: 1rem 0 .35rem; font-size: .7rem; text-transform: uppercase; letter-spacing: .08em; color: var(--text-secondary); padding: 0 .5rem; }
88
+ .sidebar__link { display: flex; align-items: center; gap: .5rem; padding: .5rem .6rem; border-radius: 8px; color: var(--text-primary); text-decoration: none; }
89
+ .sidebar__link:hover { background: var(--surface-muted); text-decoration: none; }
90
+ .sidebar__link--active { background: var(--surface-muted); font-weight: 600; }
91
+ .content { padding: 1.25rem 1.5rem 2rem; min-width: 0; max-width: 1100px; }
92
+ .page-header { margin-bottom: 1.25rem; }
93
+ .page-header__row { display: flex; gap: 1rem; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; }
94
+ .page-header__title { margin: 0; font-size: 1.6rem; line-height: 1.2; letter-spacing: -0.02em; }
95
+ .page-header__desc { margin: .35rem 0 0; color: var(--text-secondary); max-width: 60ch; }
96
+ .page-header__actions { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }
97
+ .breadcrumbs ol { list-style: none; padding: 0; margin: 0 0 .5rem; display: flex; gap: .4rem; flex-wrap: wrap; color: var(--text-secondary); font-size: .85rem; }
98
+ .breadcrumbs li + li::before { content: "/"; margin-right: .4rem; color: var(--text-secondary); }
99
+ .card { background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }
100
+ .card--padded { padding: 1rem; }
101
+ .btn { appearance: none; border: 1px solid transparent; border-radius: 8px; padding: .55rem .85rem; font-weight: 600; font-size: .9rem; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: .4rem; line-height: 1; }
102
+ .btn--primary { background: var(--accent); color: var(--accent-contrast); border-color: var(--accent); }
103
+ .btn--primary:hover { filter: brightness(1.05); text-decoration: none; }
104
+ .btn--secondary { background: var(--surface-card); color: var(--text-primary); border-color: var(--border); }
105
+ .btn--ghost { background: transparent; color: var(--text-primary); border-color: transparent; }
106
+ .btn--danger { background: var(--status-rejected); color: #fff; border-color: var(--status-rejected); }
107
+ .btn:disabled { opacity: .6; cursor: not-allowed; }
108
+ .badge { display: inline-flex; align-items: center; padding: .15rem .5rem; border-radius: 999px; font-size: .75rem; font-weight: 650; letter-spacing: .02em; border: 1px solid var(--border); background: var(--surface-muted); color: var(--text-secondary); }
109
+ .badge--success { background: color-mix(in srgb, var(--status-approved) 14%, var(--surface-card)); color: var(--status-approved); border-color: color-mix(in srgb, var(--status-approved) 30%, var(--border)); }
110
+ .badge--warning { background: color-mix(in srgb, var(--status-new) 14%, var(--surface-card)); color: var(--status-new); border-color: color-mix(in srgb, var(--status-new) 30%, var(--border)); }
111
+ .badge--danger { background: color-mix(in srgb, var(--status-rejected) 14%, var(--surface-card)); color: var(--status-rejected); border-color: color-mix(in srgb, var(--status-rejected) 30%, var(--border)); }
112
+ .badge--info { background: color-mix(in srgb, var(--accent) 10%, var(--surface-card)); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 25%, var(--border)); }
113
+ .badge--neutral { background: var(--surface-muted); color: var(--text-secondary); }
114
+ .field { display: grid; gap: .35rem; margin-bottom: .9rem; }
115
+ .field__label { font-weight: 600; font-size: .9rem; }
116
+ .field__input { width: 100%; padding: .6rem .7rem; border-radius: 8px; border: 1px solid var(--border); background: var(--surface-card); color: var(--text-primary); font: inherit; }
117
+ .field__input:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }
118
+ .field__input--error { border-color: var(--status-rejected); }
119
+ .field__input--textarea { resize: vertical; }
120
+ .field__hint { margin: 0; color: var(--text-secondary); font-size: .85rem; }
121
+ .field__error { margin: 0; color: var(--status-rejected); font-size: .85rem; }
122
+ .alert { border-radius: 8px; padding: .75rem .85rem; border: 1px solid var(--border); background: var(--surface-card); }
123
+ .alert--info { border-color: color-mix(in srgb, var(--accent) 22%, var(--border)); background: color-mix(in srgb, var(--accent) 7%, var(--surface-card)); }
124
+ .alert--success { border-color: color-mix(in srgb, var(--status-approved) 22%, var(--border)); background: color-mix(in srgb, var(--status-approved) 7%, var(--surface-card)); }
125
+ .alert--warning { border-color: color-mix(in srgb, var(--status-new) 22%, var(--border)); background: color-mix(in srgb, var(--status-new) 7%, var(--surface-card)); }
126
+ .alert--danger { border-color: color-mix(in srgb, var(--status-rejected) 22%, var(--border)); background: color-mix(in srgb, var(--status-rejected) 7%, var(--surface-card)); }
127
+ .alert__title { display: block; margin-bottom: .2rem; }
128
+ .tabs { display: flex; gap: .25rem; border-bottom: 1px solid var(--border); margin-bottom: 1rem; overflow-x: auto; }
129
+ .tabs__link { padding: .6rem .75rem; border-bottom: 2px solid transparent; color: var(--text-secondary); text-decoration: none; white-space: nowrap; font-weight: 600; }
130
+ .tabs__link:hover { color: var(--text-primary); text-decoration: none; }
131
+ .tabs__link--active { color: var(--text-primary); border-bottom-color: var(--accent); }
132
+ .empty { text-align: center; padding: 2rem 1rem; border: 1px dashed var(--border); border-radius: var(--radius); background: var(--surface-card); }
133
+ .empty__title { margin: 0 0 .3rem; font-size: 1.1rem; }
134
+ .empty__desc { margin: 0 auto; color: var(--text-secondary); max-width: 50ch; }
135
+ .empty__action { margin-top: 1rem; }
136
+ .grid { display: grid; gap: 1rem; }
137
+ .grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
138
+ .grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
139
+ @media (max-width: 880px) { .grid--2, .grid--3 { grid-template-columns: 1fr; } }
140
+ .table-wrap { overflow: auto; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-card); }
141
+ table { width: 100%; border-collapse: collapse; font-size: .9rem; }
142
+ th, td { text-align: left; padding: .65rem .75rem; border-bottom: 1px solid var(--border); vertical-align: top; }
143
+ th { background: var(--surface-muted); font-weight: 650; color: var(--text-secondary); font-size: .8rem; text-transform: uppercase; letter-spacing: .06em; }
144
+ tr:last-child td { border-bottom: 0; }
145
+ .stat { text-align: center; padding: .5rem; }
146
+ .stat__value { font-size: 1.4rem; font-weight: 750; line-height: 1; }
147
+ .stat__label { color: var(--text-secondary); font-size: .8rem; text-transform: uppercase; letter-spacing: .06em; }
148
+ .snapshot-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
149
+ .snapshot-card { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--surface-card); display: flex; flex-direction: column; }
150
+ .snapshot-card__head { padding: .6rem .75rem; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; gap: .5rem; align-items: center; }
151
+ .snapshot-card__meta { font-size: .8rem; color: var(--text-secondary); }
152
+ .snapshot-card__body { padding: .5rem; display: grid; gap: .5rem; }
153
+ .diff-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .75rem; }
154
+ @media (max-width: 900px) { .diff-grid { grid-template-columns: 1fr; } }
155
+ .diff-pane { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--surface-card); }
156
+ .diff-pane__label { padding: .4rem .6rem; font-weight: 650; font-size: .8rem; text-transform: uppercase; letter-spacing: .06em; color: var(--text-secondary); background: var(--surface-muted); border-bottom: 1px solid var(--border); }
157
+ .diff-pane__img { display: block; width: 100%; height: auto; background: repeating-conic-gradient(#e5e7eb 0% 25%, #f9fafb 0% 50%) 0 0 / 16px 16px; }
158
+ .comment { border: 1px solid var(--border); border-radius: var(--radius); padding: .75rem; background: var(--surface-card); }
159
+ .comment__head { display: flex; gap: .5rem; align-items: center; font-size: .85rem; color: var(--text-secondary); }
160
+ .comment__body { margin: .5rem 0 0; white-space: pre-wrap; word-break: break-word; }
161
+ @media (max-width: 880px) {
162
+ .topbar__menu { display: inline-flex; }
163
+ .shell { grid-template-columns: 1fr; }
164
+ .sidebar { display: none; position: fixed; inset: 56px 0 0 0; z-index: 30; height: auto; }
165
+ .sidebar--open { display: block; }
166
+ .content { padding: 1rem; }
167
+ }
168
+ `;
169
+ }
170
+ //#endregion
171
+ export { baseStyle };
172
+
173
+ //# sourceMappingURL=styles.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.mjs","names":[],"sources":["../../src/ui/styles.ts"],"sourcesContent":["import type { BrandTheme } from \"./theme.ts\";\n\nexport function baseStyle(light: BrandTheme, dark: BrandTheme): string {\n return `\n :root {\n --accent: ${light.accent};\n --accent-contrast: #fff;\n --surface-base: ${light.surface.base};\n --surface-card: ${light.surface.card};\n --surface-muted: #f4f4f5;\n --text-primary: ${light.text.primary};\n --text-secondary: ${light.text.secondary};\n --border: ${light.border};\n --radius: 10px;\n --radius-sm: 6px;\n --shadow: 0 1px 2px rgba(0,0,0,.06), 0 4px 12px rgba(0,0,0,.04);\n --status-approved: ${light.status.approved};\n --status-new: ${light.status.new};\n --status-rejected: ${light.status.rejected};\n --topbar-bg: ${light.accent};\n --sidebar-bg: #ffffff;\n --sidebar-width: 240px;\n }\n [data-theme=\"dark\"] {\n --accent: ${dark.accent};\n --surface-base: ${dark.surface.base};\n --surface-card: ${dark.surface.card};\n --surface-muted: #1e1e22;\n --text-primary: ${dark.text.primary};\n --text-secondary: ${dark.text.secondary};\n --border: ${dark.border};\n --status-approved: ${dark.status.approved};\n --status-new: ${dark.status.new};\n --status-rejected: ${dark.status.rejected};\n --topbar-bg: #0f172a;\n --sidebar-bg: #111113;\n }\n @media (prefers-color-scheme: dark) {\n [data-theme=\"system\"] {\n --accent: ${dark.accent};\n --surface-base: ${dark.surface.base};\n --surface-card: ${dark.surface.card};\n --surface-muted: #1e1e22;\n --text-primary: ${dark.text.primary};\n --text-secondary: ${dark.text.secondary};\n --border: ${dark.border};\n --status-approved: ${dark.status.approved};\n --status-new: ${dark.status.new};\n --status-rejected: ${dark.status.rejected};\n --topbar-bg: #0f172a;\n --sidebar-bg: #111113;\n }\n }\n * { box-sizing: border-box; }\n html { scroll-behavior: smooth; }\n body { margin: 0; font-family: ui-sans-system, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; background: var(--surface-base); color: var(--text-primary); line-height: 1.5; }\n a { color: var(--accent); text-decoration: none; }\n a:hover { text-decoration: underline; }\n a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n .skip-link { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }\n .skip-link:focus { left: 1rem; top: 1rem; width: auto; height: auto; background: var(--surface-card); color: var(--text-primary); padding: .5rem .75rem; border-radius: var(--radius-sm); box-shadow: var(--shadow); z-index: 100; }\n .topbar { position: sticky; top: 0; z-index: 40; background: var(--topbar-bg); color: #fff; border-bottom: 1px solid rgba(255,255,255,.12); }\n .topbar__inner { max-width: 1440px; margin: 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: .6rem 1rem; }\n .topbar__left, .topbar__right { display: flex; align-items: center; gap: .75rem; min-width: 0; }\n .topbar__menu { display: none; background: transparent; border: 1px solid rgba(255,255,255,.3); color: #fff; border-radius: 6px; padding: .35rem .5rem; cursor: pointer; }\n .brand { display: inline-flex; align-items: center; gap: .5rem; color: #fff; font-weight: 650; text-decoration: none; }\n .brand__mark { width: 28px; height: 28px; display: inline-grid; place-items: center; background: rgba(255,255,255,.18); border-radius: 8px; }\n .logo { border-radius: 6px; display: block; }\n .project-crumb { display: inline-flex; align-items: center; gap: .35rem; color: rgba(255,255,255,.9); }\n .project-crumb a { color: #fff; text-decoration: underline; text-underline-offset: 3px; }\n .project-crumb__sep { opacity: .7; }\n .topbar__theme { background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.22); color: #fff; border-radius: 999px; padding: .35rem .6rem; cursor: pointer; display: inline-flex; gap: .25rem; }\n .topbar__theme [data-theme-icon] { display: none; }\n [data-theme=\"light\"] [data-theme-icon=\"light\"], [data-theme=\"dark\"] [data-theme-icon=\"dark\"], [data-theme=\"system\"] [data-theme-icon=\"system\"] { display: inline; }\n .topbar__login { background: #fff; color: var(--accent); padding: .4rem .75rem; border-radius: 999px; font-weight: 600; text-decoration: none; }\n .user-menu { display: inline-flex; align-items: center; gap: .5rem; background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.2); padding: .25rem .5rem .25rem .35rem; border-radius: 999px; }\n .user-menu__avatar { width: 28px; height: 28px; border-radius: 999px; object-fit: cover; background: rgba(255,255,255,.2); }\n .user-menu__avatar--fallback { display: inline-grid; place-items: center; font-weight: 700; color: #fff; }\n .user-menu__name { color: #fff; font-weight: 600; max-width: 12ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .user-menu__role { font-size: .7rem; text-transform: uppercase; letter-spacing: .06em; background: rgba(255,255,255,.2); color: #fff; padding: .15rem .4rem; border-radius: 999px; }\n .user-menu__logout { color: #fff; font-size: .85rem; text-decoration: underline; text-underline-offset: 2px; margin-left: .25rem; }\n .shell { max-width: 1440px; margin: 0 auto; display: grid; grid-template-columns: var(--sidebar-width) 1fr; min-height: calc(100vh - 56px); }\n .sidebar { background: var(--sidebar-bg); border-right: 1px solid var(--border); padding: 1rem .75rem; position: sticky; top: 56px; height: calc(100vh - 56px); overflow: auto; }\n .sidebar__nav { display: flex; flex-direction: column; gap: .25rem; }\n .sidebar__section { margin: 1rem 0 .35rem; font-size: .7rem; text-transform: uppercase; letter-spacing: .08em; color: var(--text-secondary); padding: 0 .5rem; }\n .sidebar__link { display: flex; align-items: center; gap: .5rem; padding: .5rem .6rem; border-radius: 8px; color: var(--text-primary); text-decoration: none; }\n .sidebar__link:hover { background: var(--surface-muted); text-decoration: none; }\n .sidebar__link--active { background: var(--surface-muted); font-weight: 600; }\n .content { padding: 1.25rem 1.5rem 2rem; min-width: 0; max-width: 1100px; }\n .page-header { margin-bottom: 1.25rem; }\n .page-header__row { display: flex; gap: 1rem; align-items: flex-start; justify-content: space-between; flex-wrap: wrap; }\n .page-header__title { margin: 0; font-size: 1.6rem; line-height: 1.2; letter-spacing: -0.02em; }\n .page-header__desc { margin: .35rem 0 0; color: var(--text-secondary); max-width: 60ch; }\n .page-header__actions { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }\n .breadcrumbs ol { list-style: none; padding: 0; margin: 0 0 .5rem; display: flex; gap: .4rem; flex-wrap: wrap; color: var(--text-secondary); font-size: .85rem; }\n .breadcrumbs li + li::before { content: \"/\"; margin-right: .4rem; color: var(--text-secondary); }\n .card { background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); }\n .card--padded { padding: 1rem; }\n .btn { appearance: none; border: 1px solid transparent; border-radius: 8px; padding: .55rem .85rem; font-weight: 600; font-size: .9rem; cursor: pointer; text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: .4rem; line-height: 1; }\n .btn--primary { background: var(--accent); color: var(--accent-contrast); border-color: var(--accent); }\n .btn--primary:hover { filter: brightness(1.05); text-decoration: none; }\n .btn--secondary { background: var(--surface-card); color: var(--text-primary); border-color: var(--border); }\n .btn--ghost { background: transparent; color: var(--text-primary); border-color: transparent; }\n .btn--danger { background: var(--status-rejected); color: #fff; border-color: var(--status-rejected); }\n .btn:disabled { opacity: .6; cursor: not-allowed; }\n .badge { display: inline-flex; align-items: center; padding: .15rem .5rem; border-radius: 999px; font-size: .75rem; font-weight: 650; letter-spacing: .02em; border: 1px solid var(--border); background: var(--surface-muted); color: var(--text-secondary); }\n .badge--success { background: color-mix(in srgb, var(--status-approved) 14%, var(--surface-card)); color: var(--status-approved); border-color: color-mix(in srgb, var(--status-approved) 30%, var(--border)); }\n .badge--warning { background: color-mix(in srgb, var(--status-new) 14%, var(--surface-card)); color: var(--status-new); border-color: color-mix(in srgb, var(--status-new) 30%, var(--border)); }\n .badge--danger { background: color-mix(in srgb, var(--status-rejected) 14%, var(--surface-card)); color: var(--status-rejected); border-color: color-mix(in srgb, var(--status-rejected) 30%, var(--border)); }\n .badge--info { background: color-mix(in srgb, var(--accent) 10%, var(--surface-card)); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 25%, var(--border)); }\n .badge--neutral { background: var(--surface-muted); color: var(--text-secondary); }\n .field { display: grid; gap: .35rem; margin-bottom: .9rem; }\n .field__label { font-weight: 600; font-size: .9rem; }\n .field__input { width: 100%; padding: .6rem .7rem; border-radius: 8px; border: 1px solid var(--border); background: var(--surface-card); color: var(--text-primary); font: inherit; }\n .field__input:focus { outline: 2px solid var(--accent); outline-offset: 0; border-color: var(--accent); }\n .field__input--error { border-color: var(--status-rejected); }\n .field__input--textarea { resize: vertical; }\n .field__hint { margin: 0; color: var(--text-secondary); font-size: .85rem; }\n .field__error { margin: 0; color: var(--status-rejected); font-size: .85rem; }\n .alert { border-radius: 8px; padding: .75rem .85rem; border: 1px solid var(--border); background: var(--surface-card); }\n .alert--info { border-color: color-mix(in srgb, var(--accent) 22%, var(--border)); background: color-mix(in srgb, var(--accent) 7%, var(--surface-card)); }\n .alert--success { border-color: color-mix(in srgb, var(--status-approved) 22%, var(--border)); background: color-mix(in srgb, var(--status-approved) 7%, var(--surface-card)); }\n .alert--warning { border-color: color-mix(in srgb, var(--status-new) 22%, var(--border)); background: color-mix(in srgb, var(--status-new) 7%, var(--surface-card)); }\n .alert--danger { border-color: color-mix(in srgb, var(--status-rejected) 22%, var(--border)); background: color-mix(in srgb, var(--status-rejected) 7%, var(--surface-card)); }\n .alert__title { display: block; margin-bottom: .2rem; }\n .tabs { display: flex; gap: .25rem; border-bottom: 1px solid var(--border); margin-bottom: 1rem; overflow-x: auto; }\n .tabs__link { padding: .6rem .75rem; border-bottom: 2px solid transparent; color: var(--text-secondary); text-decoration: none; white-space: nowrap; font-weight: 600; }\n .tabs__link:hover { color: var(--text-primary); text-decoration: none; }\n .tabs__link--active { color: var(--text-primary); border-bottom-color: var(--accent); }\n .empty { text-align: center; padding: 2rem 1rem; border: 1px dashed var(--border); border-radius: var(--radius); background: var(--surface-card); }\n .empty__title { margin: 0 0 .3rem; font-size: 1.1rem; }\n .empty__desc { margin: 0 auto; color: var(--text-secondary); max-width: 50ch; }\n .empty__action { margin-top: 1rem; }\n .grid { display: grid; gap: 1rem; }\n .grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n .grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }\n @media (max-width: 880px) { .grid--2, .grid--3 { grid-template-columns: 1fr; } }\n .table-wrap { overflow: auto; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface-card); }\n table { width: 100%; border-collapse: collapse; font-size: .9rem; }\n th, td { text-align: left; padding: .65rem .75rem; border-bottom: 1px solid var(--border); vertical-align: top; }\n th { background: var(--surface-muted); font-weight: 650; color: var(--text-secondary); font-size: .8rem; text-transform: uppercase; letter-spacing: .06em; }\n tr:last-child td { border-bottom: 0; }\n .stat { text-align: center; padding: .5rem; }\n .stat__value { font-size: 1.4rem; font-weight: 750; line-height: 1; }\n .stat__label { color: var(--text-secondary); font-size: .8rem; text-transform: uppercase; letter-spacing: .06em; }\n .snapshot-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }\n .snapshot-card { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--surface-card); display: flex; flex-direction: column; }\n .snapshot-card__head { padding: .6rem .75rem; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; gap: .5rem; align-items: center; }\n .snapshot-card__meta { font-size: .8rem; color: var(--text-secondary); }\n .snapshot-card__body { padding: .5rem; display: grid; gap: .5rem; }\n .diff-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .75rem; }\n @media (max-width: 900px) { .diff-grid { grid-template-columns: 1fr; } }\n .diff-pane { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; background: var(--surface-card); }\n .diff-pane__label { padding: .4rem .6rem; font-weight: 650; font-size: .8rem; text-transform: uppercase; letter-spacing: .06em; color: var(--text-secondary); background: var(--surface-muted); border-bottom: 1px solid var(--border); }\n .diff-pane__img { display: block; width: 100%; height: auto; background: repeating-conic-gradient(#e5e7eb 0% 25%, #f9fafb 0% 50%) 0 0 / 16px 16px; }\n .comment { border: 1px solid var(--border); border-radius: var(--radius); padding: .75rem; background: var(--surface-card); }\n .comment__head { display: flex; gap: .5rem; align-items: center; font-size: .85rem; color: var(--text-secondary); }\n .comment__body { margin: .5rem 0 0; white-space: pre-wrap; word-break: break-word; }\n @media (max-width: 880px) {\n .topbar__menu { display: inline-flex; }\n .shell { grid-template-columns: 1fr; }\n .sidebar { display: none; position: fixed; inset: 56px 0 0 0; z-index: 30; height: auto; }\n .sidebar--open { display: block; }\n .content { padding: 1rem; }\n }\n `;\n}"],"mappings":";;;;AAEA,SAAgB,UAAU,OAAmB,MAA0B;CACrE,OAAO;;kBAES,MAAM,OAAO;;wBAEP,MAAM,QAAQ,KAAK;wBACnB,MAAM,QAAQ,KAAK;;wBAEnB,MAAM,KAAK,QAAQ;0BACjB,MAAM,KAAK,UAAU;kBAC7B,MAAM,OAAO;;;;2BAIJ,MAAM,OAAO,SAAS;sBAC3B,MAAM,OAAO,IAAI;2BACZ,MAAM,OAAO,SAAS;qBAC5B,MAAM,OAAO;;;;;kBAKhB,KAAK,OAAO;wBACN,KAAK,QAAQ,KAAK;wBAClB,KAAK,QAAQ,KAAK;;wBAElB,KAAK,KAAK,QAAQ;0BAChB,KAAK,KAAK,UAAU;kBAC5B,KAAK,OAAO;2BACH,KAAK,OAAO,SAAS;sBAC1B,KAAK,OAAO,IAAI;2BACX,KAAK,OAAO,SAAS;;;;;;oBAM5B,KAAK,OAAO;0BACN,KAAK,QAAQ,KAAK;0BAClB,KAAK,QAAQ,KAAK;;0BAElB,KAAK,KAAK,QAAQ;4BAChB,KAAK,KAAK,UAAU;oBAC5B,KAAK,OAAO;6BACH,KAAK,OAAO,SAAS;wBAC1B,KAAK,OAAO,IAAI;6BACX,KAAK,OAAO,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsHlD"}
@@ -0,0 +1,42 @@
1
+ import "node:path";
2
+ import "node:url";
3
+ import.meta.url;
4
+ //#region src/ui/theme.ts
5
+ const LIGHT_THEME = {
6
+ accent: "#2b7fff",
7
+ surface: {
8
+ base: "#f6f6f7",
9
+ card: "#ffffff"
10
+ },
11
+ text: {
12
+ primary: "#09090b",
13
+ secondary: "#71717b"
14
+ },
15
+ border: "#e4e4e7",
16
+ status: {
17
+ approved: "#16a34a",
18
+ new: "#d97706",
19
+ rejected: "#dc2626"
20
+ }
21
+ };
22
+ const DARK_THEME = {
23
+ accent: "#4f8fff",
24
+ surface: {
25
+ base: "#09090b",
26
+ card: "#18181b"
27
+ },
28
+ text: {
29
+ primary: "#fafafa",
30
+ secondary: "#9f9fa9"
31
+ },
32
+ border: "#ffffff1a",
33
+ status: {
34
+ approved: "#22c55e",
35
+ new: "#f59e0b",
36
+ rejected: "#ef4444"
37
+ }
38
+ };
39
+ //#endregion
40
+ export { DARK_THEME, LIGHT_THEME };
41
+
42
+ //# sourceMappingURL=theme.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.mjs","names":[],"sources":["../../src/ui/theme.ts"],"sourcesContent":["export interface BrandTheme {\n accent: string;\n surface: { base: string; card: string };\n text: { primary: string; secondary: string };\n border: string;\n status: { approved: string; new: string; rejected: string };\n}\n\nexport const LIGHT_THEME: BrandTheme = {\n accent: \"#2b7fff\",\n surface: { base: \"#f6f6f7\", card: \"#ffffff\" },\n text: { primary: \"#09090b\", secondary: \"#71717b\" },\n border: \"#e4e4e7\",\n status: { approved: \"#16a34a\", new: \"#d97706\", rejected: \"#dc2626\" },\n};\n\nexport const DARK_THEME: BrandTheme = {\n accent: \"#4f8fff\",\n surface: { base: \"#09090b\", card: \"#18181b\" },\n text: { primary: \"#fafafa\", secondary: \"#9f9fa9\" },\n border: \"#ffffff1a\",\n status: { approved: \"#22c55e\", new: \"#f59e0b\", rejected: \"#ef4444\" },\n};\n"],"mappings":";;;;AAQA,MAAa,cAA0B;CACrC,QAAQ;CACR,SAAS;EAAE,MAAM;EAAW,MAAM;CAAU;CAC5C,MAAM;EAAE,SAAS;EAAW,WAAW;CAAU;CACjD,QAAQ;CACR,QAAQ;EAAE,UAAU;EAAW,KAAK;EAAW,UAAU;CAAU;AACrE;AAEA,MAAa,aAAyB;CACpC,QAAQ;CACR,SAAS;EAAE,MAAM;EAAW,MAAM;CAAU;CAC5C,MAAM;EAAE,SAAS;EAAW,WAAW;CAAU;CACjD,QAAQ;CACR,QAAQ;EAAE,UAAU;EAAW,KAAK;EAAW,UAAU;CAAU;AACrE"}
@@ -0,0 +1,19 @@
1
+ import __tsdown_shims_path from 'node:path';
2
+ import __tsdown_shims_url from 'node:url';
3
+ //#region src/urls.d.ts
4
+ interface UrlBuilder {
5
+ projectsList(): string;
6
+ project(slug: string): string;
7
+ buildsList(slug: string): string;
8
+ build(slug: string, buildId: string): string;
9
+ labels(slug: string): string;
10
+ label(slug: string, key: string, value: string): string;
11
+ storybook(slug: string): string;
12
+ storybookLabel(slug: string, key: string, value: string): string;
13
+ storybookBuild(slug: string, buildId: string): string;
14
+ settings(slug: string): string;
15
+ }
16
+ declare function createUrlBuilder(baseUrl: string, publishedBaseDomain?: string): UrlBuilder;
17
+ //#endregion
18
+ export { UrlBuilder, createUrlBuilder };
19
+ //# sourceMappingURL=urls.d.mts.map